[{"data":1,"prerenderedAt":2240},["ShallowReactive",2],{"doc:\u002Fgetting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation\u002Fhow-openpyxl-stores-python-values-in-cells":3,"surround:\u002Fgetting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation\u002Fhow-openpyxl-stores-python-values-in-cells":2205,"ancestors:\u002Fgetting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation\u002Fhow-openpyxl-stores-python-values-in-cells":2214,"children:\u002Fgetting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation\u002Fhow-openpyxl-stores-python-values-in-cells":2219,"related:\u002Fgetting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation\u002Fhow-openpyxl-stores-python-values-in-cells":2220},{"id":4,"title":5,"body":6,"dateModified":2170,"datePublished":2170,"description":2171,"extension":2172,"faq":2173,"keyword":2182,"kind":2183,"meta":2184,"navigation":392,"path":2193,"seo":2194,"slug":2197,"stem":2198,"topics":2199,"type":2203,"__hash__":2204},"docs\u002Fgetting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation\u002Fhow-openpyxl-stores-python-values-in-cells\u002Findex.md","How openpyxl Stores Python Values: Types, Dates and None",{"type":7,"value":8,"toc":2157},"minimark",[9,32,314,319,350,573,579,583,586,646,676,680,683,791,821,845,978,982,985,1037,1077,1083,1187,1199,1203,1210,1446,1475,1601,1605,1608,1975,1983,1987,2046,2050,2070,2074,2092,2096,2102,2108,2114,2120,2124,2153],[10,11,12,16,17,21,22,25,26,31],"p",{},[13,14,15],"code",{},"ws[\"B2\"] = value"," looks like it simply puts a value in a cell, but underneath openpyxl decides three things: what ",[18,19,20],"em",{},"kind"," of cell it is (number, text, boolean, date, formula), which number format it gets, and how it will be serialised into the file. Most of the time the decisions are right. A few are surprising, and a couple only surface later — when the file is reopened, or when ",[13,23,24],{},"save()"," fails far from the line that caused it. This page, part of ",[27,28,30],"a",{"href":29},"\u002Fgetting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation\u002F","Using openpyxl for Excel File Manipulation",", maps every common Python value to what openpyxl does with it, as tested with openpyxl 3.1.",[33,34,42,43,42,47,42,51,42,59,42,67,42,74,42,81,42,84,42,88,42,91,42,95,42,100,42,106,42,108,42,113,42,115,42,118,42,120,42,122,42,125,42,129,42,131,42,134,42,136,42,138,42,140,42,143,42,146,42,150,42,152,42,156,42,158,42,160,42,162,42,164,42,167,42,171,42,173,42,176,42,178,42,180,42,182,42,184,42,187,42,191,42,193,42,197,42,199,42,201,42,203,42,206,42,209,42,213,42,215,42,219,42,221,42,224,42,226,42,228,42,231,42,234,42,236,42,239,42,241,42,244,42,246,42,249,42,252,42,256,42,258,42,261,42,263,42,266,42,268,42,270,42,273,42,277,42,279,42,282,42,284,42,287,42,289,42,291,42,294,42,298,42,300,42,304,42,306,42,309,42,311],"svg",{"viewBox":35,"role":36,"ariaLabelledBy":37,"xmlns":40,"style":41},"4 0 752 412","img",[38,39],"opv-map-t","opv-map-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[44,45,46],"title",{"id":38},"Python values and the cells they become",[48,49,50],"desc",{"id":39},"A table of Python types. int, float and Decimal become number cells with the General format and read back as int or float. bool becomes a boolean cell. str becomes text, unless it starts with an equals sign, which makes it a formula. datetime, date and time become date cells with yyyy-mm-dd h:mm:ss, yyyy-mm-dd and h:mm:ss formats; dates read back as datetimes. timedelta gets the [hh]:mm:ss format. None and NaN leave the cell empty.",[52,53],"rect",{"x":54,"y":55,"width":56,"height":57,"fill":58},"4","0","752","412","#ffffff",[52,60],{"x":61,"y":62,"width":63,"height":64,"rx":55,"fill":58,"stroke":65,"style":66},"20.0","16.0","125.1","40.0","var(--line,#cdd5e6)","stroke-width:1px",[52,68],{"x":69,"y":62,"width":70,"height":64,"rx":55,"fill":71,"stroke":72,"style":73},"145.1","198.3","#f0f4ff","var(--brand,#5b5cf0)","stroke-width:1.2px",[75,76,80],"text",{"x":77,"y":78,"style":79},"244.2","41.0","font-size:13px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Cell type",[52,82],{"x":83,"y":62,"width":70,"height":64,"rx":55,"fill":71,"stroke":72,"style":73},"343.4",[75,85,87],{"x":86,"y":78,"style":79},"442.5","Default format",[52,89],{"x":90,"y":62,"width":70,"height":64,"rx":55,"fill":71,"stroke":72,"style":73},"541.7",[75,92,94],{"x":93,"y":78,"style":79},"640.8","Read back as",[52,96],{"x":61,"y":97,"width":63,"height":98,"rx":55,"fill":99,"stroke":65,"style":66},"56.0","34.0","#e7ebef",[75,101,105],{"x":102,"y":103,"style":104},"32.0","78.0","font-size:12.5px;font-weight:700;fill:var(--text,#172033);text-anchor:start","int, float",[52,107],{"x":69,"y":97,"width":70,"height":98,"rx":55,"fill":58,"stroke":65,"style":66},[75,109,112],{"x":77,"y":110,"style":111},"77.0","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","number",[52,114],{"x":83,"y":97,"width":70,"height":98,"rx":55,"fill":58,"stroke":65,"style":66},[75,116,117],{"x":86,"y":110,"style":111},"General",[52,119],{"x":90,"y":97,"width":70,"height":98,"rx":55,"fill":58,"stroke":65,"style":66},[75,121,105],{"x":93,"y":110,"style":111},[52,123],{"x":61,"y":124,"width":63,"height":98,"rx":55,"fill":99,"stroke":65,"style":66},"90.0",[75,126,128],{"x":102,"y":127,"style":104},"112.0","Decimal",[52,130],{"x":69,"y":124,"width":70,"height":98,"rx":55,"fill":58,"stroke":65,"style":66},[75,132,112],{"x":77,"y":133,"style":111},"111.0",[52,135],{"x":83,"y":124,"width":70,"height":98,"rx":55,"fill":58,"stroke":65,"style":66},[75,137,117],{"x":86,"y":133,"style":111},[52,139],{"x":90,"y":124,"width":70,"height":98,"rx":55,"fill":58,"stroke":65,"style":66},[75,141,142],{"x":93,"y":133,"style":111},"float",[52,144],{"x":61,"y":145,"width":63,"height":98,"rx":55,"fill":99,"stroke":65,"style":66},"124.0",[75,147,149],{"x":102,"y":148,"style":104},"146.0","bool",[52,151],{"x":69,"y":145,"width":70,"height":98,"rx":55,"fill":58,"stroke":65,"style":66},[75,153,155],{"x":77,"y":154,"style":111},"145.0","boolean",[52,157],{"x":83,"y":145,"width":70,"height":98,"rx":55,"fill":58,"stroke":65,"style":66},[75,159,117],{"x":86,"y":154,"style":111},[52,161],{"x":90,"y":145,"width":70,"height":98,"rx":55,"fill":58,"stroke":65,"style":66},[75,163,149],{"x":93,"y":154,"style":111},[52,165],{"x":61,"y":166,"width":63,"height":98,"rx":55,"fill":99,"stroke":65,"style":66},"158.0",[75,168,170],{"x":102,"y":169,"style":104},"180.0","str",[52,172],{"x":69,"y":166,"width":70,"height":98,"rx":55,"fill":58,"stroke":65,"style":66},[75,174,75],{"x":77,"y":175,"style":111},"179.0",[52,177],{"x":83,"y":166,"width":70,"height":98,"rx":55,"fill":58,"stroke":65,"style":66},[75,179,117],{"x":86,"y":175,"style":111},[52,181],{"x":90,"y":166,"width":70,"height":98,"rx":55,"fill":58,"stroke":65,"style":66},[75,183,170],{"x":93,"y":175,"style":111},[52,185],{"x":61,"y":186,"width":63,"height":98,"rx":55,"fill":99,"stroke":65,"style":66},"192.0",[75,188,190],{"x":102,"y":189,"style":104},"214.0","str starting \"=\"",[52,192],{"x":69,"y":186,"width":70,"height":98,"rx":55,"fill":58,"stroke":65,"style":66},[75,194,196],{"x":77,"y":195,"style":111},"213.0","formula",[52,198],{"x":83,"y":186,"width":70,"height":98,"rx":55,"fill":58,"stroke":65,"style":66},[75,200,117],{"x":86,"y":195,"style":111},[52,202],{"x":90,"y":186,"width":70,"height":98,"rx":55,"fill":58,"stroke":65,"style":66},[75,204,205],{"x":93,"y":195,"style":111},"formula text",[52,207],{"x":61,"y":208,"width":63,"height":98,"rx":55,"fill":99,"stroke":65,"style":66},"226.0",[75,210,212],{"x":102,"y":211,"style":104},"248.0","datetime",[52,214],{"x":69,"y":208,"width":70,"height":98,"rx":55,"fill":58,"stroke":65,"style":66},[75,216,218],{"x":77,"y":217,"style":111},"247.0","date",[52,220],{"x":83,"y":208,"width":70,"height":98,"rx":55,"fill":58,"stroke":65,"style":66},[75,222,223],{"x":86,"y":217,"style":111},"yyyy-mm-dd h:mm:ss",[52,225],{"x":90,"y":208,"width":70,"height":98,"rx":55,"fill":58,"stroke":65,"style":66},[75,227,212],{"x":93,"y":217,"style":111},[52,229],{"x":61,"y":230,"width":63,"height":98,"rx":55,"fill":99,"stroke":65,"style":66},"260.0",[75,232,218],{"x":102,"y":233,"style":104},"282.0",[52,235],{"x":69,"y":230,"width":70,"height":98,"rx":55,"fill":58,"stroke":65,"style":66},[75,237,218],{"x":77,"y":238,"style":111},"281.0",[52,240],{"x":83,"y":230,"width":70,"height":98,"rx":55,"fill":58,"stroke":65,"style":66},[75,242,243],{"x":86,"y":238,"style":111},"yyyy-mm-dd",[52,245],{"x":90,"y":230,"width":70,"height":98,"rx":55,"fill":58,"stroke":65,"style":66},[75,247,248],{"x":93,"y":238,"style":111},"datetime at 00:00",[52,250],{"x":61,"y":251,"width":63,"height":98,"rx":55,"fill":99,"stroke":65,"style":66},"294.0",[75,253,255],{"x":102,"y":254,"style":104},"316.0","time",[52,257],{"x":69,"y":251,"width":70,"height":98,"rx":55,"fill":58,"stroke":65,"style":66},[75,259,218],{"x":77,"y":260,"style":111},"315.0",[52,262],{"x":83,"y":251,"width":70,"height":98,"rx":55,"fill":58,"stroke":65,"style":66},[75,264,265],{"x":86,"y":260,"style":111},"h:mm:ss",[52,267],{"x":90,"y":251,"width":70,"height":98,"rx":55,"fill":58,"stroke":65,"style":66},[75,269,255],{"x":93,"y":260,"style":111},[52,271],{"x":61,"y":272,"width":63,"height":98,"rx":55,"fill":99,"stroke":65,"style":66},"328.0",[75,274,276],{"x":102,"y":275,"style":104},"350.0","timedelta",[52,278],{"x":69,"y":272,"width":70,"height":98,"rx":55,"fill":58,"stroke":65,"style":66},[75,280,218],{"x":77,"y":281,"style":111},"349.0",[52,283],{"x":83,"y":272,"width":70,"height":98,"rx":55,"fill":58,"stroke":65,"style":66},[75,285,286],{"x":86,"y":281,"style":111},"[hh]:mm:ss",[52,288],{"x":90,"y":272,"width":70,"height":98,"rx":55,"fill":58,"stroke":65,"style":66},[75,290,276],{"x":93,"y":281,"style":111},[52,292],{"x":61,"y":293,"width":63,"height":98,"rx":55,"fill":99,"stroke":65,"style":66},"362.0",[75,295,297],{"x":102,"y":296,"style":104},"384.0","None, NaN",[52,299],{"x":69,"y":293,"width":70,"height":98,"rx":55,"fill":58,"stroke":65,"style":66},[75,301,303],{"x":77,"y":302,"style":111},"383.0","empty",[52,305],{"x":83,"y":293,"width":70,"height":98,"rx":55,"fill":58,"stroke":65,"style":66},[75,307,308],{"x":86,"y":302,"style":111},"—",[52,310],{"x":90,"y":293,"width":70,"height":98,"rx":55,"fill":58,"stroke":65,"style":66},[75,312,313],{"x":93,"y":302,"style":111},"None",[315,316,318],"h2",{"id":317},"prerequisites","Prerequisites",[320,321,326],"pre",{"className":322,"code":323,"language":324,"meta":325,"style":325},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install openpyxl numpy pandas\n","bash","",[13,327,328],{"__ignoreMap":325},[329,330,333,337,341,344,347],"span",{"class":331,"line":332},"line",1,[329,334,336],{"class":335},"sMTad","pip",[329,338,340],{"class":339},"srMev"," install",[329,342,343],{"class":339}," openpyxl",[329,345,346],{"class":339}," numpy",[329,348,349],{"class":339}," pandas\n",[320,351,355],{"className":352,"code":353,"language":354,"meta":325,"style":325},"language-python shiki shiki-themes github-light github-dark-high-contrast","import datetime as dt\nfrom decimal import Decimal\n\nimport numpy as np\nimport pandas as pd\nfrom openpyxl import Workbook, load_workbook\n\ndef roundtrip(value):\n    wb = Workbook()\n    ws = wb.active\n    ws[\"A1\"] = value\n    cell = ws[\"A1\"]\n    written = (cell.data_type, cell.number_format)\n    wb.save(\"roundtrip.xlsx\")\n    back = load_workbook(\"roundtrip.xlsx\").active[\"A1\"].value\n    return written, type(back).__name__, back\n","python",[13,356,357,373,387,394,407,420,433,438,451,463,474,491,507,518,530,551],{"__ignoreMap":325},[329,358,359,363,367,370],{"class":331,"line":332},[329,360,362],{"class":361},"s-kum","import",[329,364,366],{"class":365},"skGVy"," datetime ",[329,368,369],{"class":361},"as",[329,371,372],{"class":365}," dt\n",[329,374,376,379,382,384],{"class":331,"line":375},2,[329,377,378],{"class":361},"from",[329,380,381],{"class":365}," decimal ",[329,383,362],{"class":361},[329,385,386],{"class":365}," Decimal\n",[329,388,390],{"class":331,"line":389},3,[329,391,393],{"emptyLinePlaceholder":392},true,"\n",[329,395,397,399,402,404],{"class":331,"line":396},4,[329,398,362],{"class":361},[329,400,401],{"class":365}," numpy ",[329,403,369],{"class":361},[329,405,406],{"class":365}," np\n",[329,408,410,412,415,417],{"class":331,"line":409},5,[329,411,362],{"class":361},[329,413,414],{"class":365}," pandas ",[329,416,369],{"class":361},[329,418,419],{"class":365}," pd\n",[329,421,423,425,428,430],{"class":331,"line":422},6,[329,424,378],{"class":361},[329,426,427],{"class":365}," openpyxl ",[329,429,362],{"class":361},[329,431,432],{"class":365}," Workbook, load_workbook\n",[329,434,436],{"class":331,"line":435},7,[329,437,393],{"emptyLinePlaceholder":392},[329,439,441,444,448],{"class":331,"line":440},8,[329,442,443],{"class":361},"def",[329,445,447],{"class":446},"s_Opv"," roundtrip",[329,449,450],{"class":365},"(value):\n",[329,452,454,457,460],{"class":331,"line":453},9,[329,455,456],{"class":365},"    wb ",[329,458,459],{"class":361},"=",[329,461,462],{"class":365}," Workbook()\n",[329,464,466,469,471],{"class":331,"line":465},10,[329,467,468],{"class":365},"    ws ",[329,470,459],{"class":361},[329,472,473],{"class":365}," wb.active\n",[329,475,477,480,483,486,488],{"class":331,"line":476},11,[329,478,479],{"class":365},"    ws[",[329,481,482],{"class":339},"\"A1\"",[329,484,485],{"class":365},"] ",[329,487,459],{"class":361},[329,489,490],{"class":365}," value\n",[329,492,494,497,499,502,504],{"class":331,"line":493},12,[329,495,496],{"class":365},"    cell ",[329,498,459],{"class":361},[329,500,501],{"class":365}," ws[",[329,503,482],{"class":339},[329,505,506],{"class":365},"]\n",[329,508,510,513,515],{"class":331,"line":509},13,[329,511,512],{"class":365},"    written ",[329,514,459],{"class":361},[329,516,517],{"class":365}," (cell.data_type, cell.number_format)\n",[329,519,521,524,527],{"class":331,"line":520},14,[329,522,523],{"class":365},"    wb.save(",[329,525,526],{"class":339},"\"roundtrip.xlsx\"",[329,528,529],{"class":365},")\n",[329,531,533,536,538,541,543,546,548],{"class":331,"line":532},15,[329,534,535],{"class":365},"    back ",[329,537,459],{"class":361},[329,539,540],{"class":365}," load_workbook(",[329,542,526],{"class":339},[329,544,545],{"class":365},").active[",[329,547,482],{"class":339},[329,549,550],{"class":365},"].value\n",[329,552,554,557,560,564,567,570],{"class":331,"line":553},16,[329,555,556],{"class":361},"    return",[329,558,559],{"class":365}," written, ",[329,561,563],{"class":562},"sP0c6","type",[329,565,566],{"class":365},"(back).",[329,568,569],{"class":562},"__name__",[329,571,572],{"class":365},", back\n",[10,574,575,578],{},[13,576,577],{},"roundtrip"," assigns a value, records the cell type and number format openpyxl chose, saves, reopens and returns what came back. Every claim below comes from running it.",[315,580,582],{"id":581},"numbers-text-and-booleans","Numbers, text and booleans",[10,584,585],{},"The plain types behave as you would expect:",[320,587,589],{"className":352,"code":588,"language":354,"meta":325,"style":325},"for value in (42, 3.14, True, \"hello\", \"00123\"):\n    print(repr(value), roundtrip(value))\n",[13,590,591,632],{"__ignoreMap":325},[329,592,593,596,599,602,605,608,611,614,616,619,621,624,626,629],{"class":331,"line":332},[329,594,595],{"class":361},"for",[329,597,598],{"class":365}," value ",[329,600,601],{"class":361},"in",[329,603,604],{"class":365}," (",[329,606,607],{"class":562},"42",[329,609,610],{"class":365},", ",[329,612,613],{"class":562},"3.14",[329,615,610],{"class":365},[329,617,618],{"class":562},"True",[329,620,610],{"class":365},[329,622,623],{"class":339},"\"hello\"",[329,625,610],{"class":365},[329,627,628],{"class":339},"\"00123\"",[329,630,631],{"class":365},"):\n",[329,633,634,637,640,643],{"class":331,"line":375},[329,635,636],{"class":562},"    print",[329,638,639],{"class":365},"(",[329,641,642],{"class":562},"repr",[329,644,645],{"class":365},"(value), roundtrip(value))\n",[10,647,648,651,652,655,656,659,660,663,664,666,667,669,670,672,673,675],{},[13,649,650],{},"data_type"," is ",[13,653,654],{},"'n'"," for numbers, ",[13,657,658],{},"'s'"," for strings and ",[13,661,662],{},"'b'"," for booleans, and all of them keep the ",[13,665,117],{}," format. A string that looks like a number, such as ",[13,668,628],{},", stays text — openpyxl never converts types for you, which is what keeps leading zeros intact. ",[13,671,128],{}," values are accepted and written as numbers, but come back as ",[13,674,142],{},": the file stores numbers, not decimal types.",[315,677,679],{"id":678},"dates-and-times-carry-their-type-in-the-format","Dates and times carry their type in the format",[10,681,682],{},"Excel has no date type. A date is a serial number — days since 30 December 1899 — with a number format that says \"show me as a date\". openpyxl writes the number and picks the format from the Python type:",[320,684,686],{"className":352,"code":685,"language":354,"meta":325,"style":325},"for value in (dt.datetime(2026, 3, 1, 14, 30), dt.date(2026, 3, 1), dt.time(9, 15),\n              dt.timedelta(hours=26, minutes=30), pd.Timestamp(\"2026-03-01 08:00\")):\n    print(repr(value), roundtrip(value))\n",[13,687,688,749,781],{"__ignoreMap":325},[329,689,690,692,694,696,699,702,704,707,709,712,714,717,719,722,725,727,729,731,733,735,738,741,743,746],{"class":331,"line":332},[329,691,595],{"class":361},[329,693,598],{"class":365},[329,695,601],{"class":361},[329,697,698],{"class":365}," (dt.datetime(",[329,700,701],{"class":562},"2026",[329,703,610],{"class":365},[329,705,706],{"class":562},"3",[329,708,610],{"class":365},[329,710,711],{"class":562},"1",[329,713,610],{"class":365},[329,715,716],{"class":562},"14",[329,718,610],{"class":365},[329,720,721],{"class":562},"30",[329,723,724],{"class":365},"), dt.date(",[329,726,701],{"class":562},[329,728,610],{"class":365},[329,730,706],{"class":562},[329,732,610],{"class":365},[329,734,711],{"class":562},[329,736,737],{"class":365},"), dt.time(",[329,739,740],{"class":562},"9",[329,742,610],{"class":365},[329,744,745],{"class":562},"15",[329,747,748],{"class":365},"),\n",[329,750,751,754,758,760,763,765,768,770,772,775,778],{"class":331,"line":375},[329,752,753],{"class":365},"              dt.timedelta(",[329,755,757],{"class":756},"sa561","hours",[329,759,459],{"class":361},[329,761,762],{"class":562},"26",[329,764,610],{"class":365},[329,766,767],{"class":756},"minutes",[329,769,459],{"class":361},[329,771,721],{"class":562},[329,773,774],{"class":365},"), pd.Timestamp(",[329,776,777],{"class":339},"\"2026-03-01 08:00\"",[329,779,780],{"class":365},")):\n",[329,782,783,785,787,789],{"class":331,"line":389},[329,784,636],{"class":562},[329,786,639],{"class":365},[329,788,642],{"class":562},[329,790,645],{"class":365},[10,792,793,794,796,797,799,800,796,802,799,804,796,806,808,809,796,811,813,814,817,818,820],{},"A ",[13,795,212],{}," gets ",[13,798,223],{},", a ",[13,801,218],{},[13,803,243],{},[13,805,255],{},[13,807,265],{},", and a ",[13,810,276],{},[13,812,286],{},", whose square brackets let hours run past 24. pandas ",[13,815,816],{},"Timestamp"," is a ",[13,819,212],{}," subclass and is handled the same way.",[10,822,823,824,826,827,829,830,832,833,835,836,839,840,844],{},"Reading back is where the asymmetry appears: a ",[13,825,218],{}," comes back as a ",[13,828,212],{}," at midnight. openpyxl sees a date-formatted number and cannot know it started life as a ",[13,831,218],{},". Code that compares a value read from a file with a ",[13,834,218],{}," object must convert first — ",[13,837,838],{},"value.date()"," — or the comparison is always false. ",[27,841,843],{"href":842},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fapplying-number-and-date-formats-in-excel\u002Fformat-dates-in-excel-cells-with-python\u002F","Format Dates in Excel Cells with Python"," covers changing the display format.",[33,846,42,851,42,854,42,857,42,860,42,866,42,877,42,883,42,887,42,892,42,897,42,900,42,907,42,912,42,915,42,919,42,922,42,925,42,931,42,936,42,939,42,942,42,945,42,949,42,952,42,957,42,964,42,970,42,974],{"viewBox":847,"role":36,"ariaLabelledBy":848,"xmlns":40,"style":41},"4 0 752 160",[849,850],"opv-date-t","opv-date-d",[44,852,853],{"id":849},"A date's round trip through an .xlsx file",[48,855,856],{"id":850},"A Python date for 1 March 2026 is assigned to a cell. openpyxl stores it as the serial number 46082 with the number format yyyy-mm-dd. When the file is reopened, the date format tells openpyxl to convert the number back, and it returns a datetime at midnight rather than a date.",[52,858],{"x":54,"y":55,"width":56,"height":859,"fill":58},"160",[52,861],{"x":61,"y":62,"width":862,"height":863,"rx":864,"fill":99,"stroke":65,"style":865},"153.0","128.0","12","stroke-width:2px",[867,868,870],"g",{"transform":869},"translate(84.5,46.5) scale(1.000)",[871,872],"path",{"d":873,"fill":874,"stroke":875,"style":876},"M4 5h16v15H4z M4 10h16 M8 3v4 M16 3v4","none","var(--text,#172033)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[75,878,882],{"x":879,"y":880,"style":881},"96.5","93.0","font-size:13.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","date(2026, 3, 1)",[75,884,886],{"x":879,"y":885,"style":111},"112.5","assigned to A1",[331,888],{"x1":889,"y1":890,"x2":891,"y2":890,"stroke":72,"style":865},"178.0","80.0","200.0",[893,894],"polygon",{"points":895,"fill":896},"204.0,80.0 195.0,85.0 195.0,75.0","#5b5cf0",[52,898],{"x":899,"y":62,"width":862,"height":863,"rx":864,"fill":71,"stroke":72,"style":865},"209.0",[867,901,903],{"transform":902},"translate(273.5,46.5) scale(1.000)",[871,904],{"d":905,"fill":874,"stroke":906,"style":876},"M5 9h14 M4 15h14 M10 4l-2 16 M16 4l-2 16","var(--brand-strong,#4338ca)",[75,908,911],{"x":909,"y":880,"style":910},"285.5","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Serial 46082",[75,913,914],{"x":909,"y":885,"style":111},"format yyyy-mm-dd",[331,916],{"x1":917,"y1":890,"x2":918,"y2":890,"stroke":72,"style":865},"367.0","389.0",[893,920],{"points":921,"fill":896},"393.0,80.0 384.0,85.0 384.0,75.0",[52,923],{"x":924,"y":62,"width":862,"height":863,"rx":864,"fill":99,"stroke":65,"style":865},"398.0",[867,926,928],{"transform":927},"translate(462.5,29.0) scale(1.000)",[871,929],{"d":930,"fill":874,"stroke":875,"style":876},"M6 3h9l4 4v14H6z M15 3v4h4",[75,932,935],{"x":933,"y":934,"style":881},"474.5","75.5","Saved and",[75,937,938],{"x":933,"y":880,"style":881},"reopened",[75,940,941],{"x":933,"y":885,"style":111},"format marks it as a",[75,943,218],{"x":933,"y":944,"style":111},"130.0",[331,946],{"x1":947,"y1":890,"x2":948,"y2":890,"stroke":72,"style":865},"556.0","578.0",[893,950],{"points":951,"fill":896},"582.0,80.0 573.0,85.0 573.0,75.0",[52,953],{"x":954,"y":62,"width":862,"height":863,"rx":864,"fill":955,"stroke":956,"style":865},"587.0","#fdefd8","var(--gold,#b4740a)",[867,958,960],{"transform":959},"translate(651.5,37.8) scale(1.000)",[871,961],{"d":962,"fill":874,"stroke":963,"style":876},"M12 3.5l9.5 17h-19z M12 10v5 M12 17.8v.4","var(--gold-ink,#7a4e06)",[75,965,969],{"x":966,"y":967,"style":968},"663.5","84.2","font-size:13.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","datetime(2026, 3,",[75,971,973],{"x":966,"y":972,"style":968},"101.8","1, 0, 0)",[75,975,977],{"x":966,"y":976,"style":111},"121.2","call .date() if needed",[315,979,981],{"id":980},"values-that-surprise-numpy-nan-and-strings-with","Values that surprise: NumPy, NaN and strings with \"=\"",[10,983,984],{},"Values from NumPy and pandas mostly work, because openpyxl registers NumPy's numeric types. One of them is handled differently from its Python equivalent:",[320,986,988],{"className":352,"code":987,"language":354,"meta":325,"style":325},"for value in (np.int64(7), np.float64(2.5), np.bool_(True), float(\"nan\")):\n    print(repr(value), roundtrip(value))\n",[13,989,990,1027],{"__ignoreMap":325},[329,991,992,994,996,998,1001,1004,1007,1010,1013,1015,1018,1020,1022,1025],{"class":331,"line":332},[329,993,595],{"class":361},[329,995,598],{"class":365},[329,997,601],{"class":361},[329,999,1000],{"class":365}," (np.int64(",[329,1002,1003],{"class":562},"7",[329,1005,1006],{"class":365},"), np.float64(",[329,1008,1009],{"class":562},"2.5",[329,1011,1012],{"class":365},"), np.bool_(",[329,1014,618],{"class":562},[329,1016,1017],{"class":365},"), ",[329,1019,142],{"class":562},[329,1021,639],{"class":365},[329,1023,1024],{"class":339},"\"nan\"",[329,1026,780],{"class":365},[329,1028,1029,1031,1033,1035],{"class":331,"line":375},[329,1030,636],{"class":562},[329,1032,639],{"class":365},[329,1034,642],{"class":562},[329,1036,645],{"class":365},[10,1038,1039,1042,1043,1046,1047,1042,1050,1052,1053,1056,1057,1059,1060,1062,1063,1066,1067,1070,1071,1073,1074,1076],{},[13,1040,1041],{},"np.int64"," and ",[13,1044,1045],{},"np.float64"," come back as plain ",[13,1048,1049],{},"int",[13,1051,142],{},". ",[13,1054,1055],{},"np.bool_"," does not become a boolean: openpyxl treats it as a number, so the cell holds ",[13,1058,711],{}," and reads back as the integer ",[13,1061,711],{}," — a flag column taken from a NumPy array turns into 0s and 1s. Convert with ",[13,1064,1065],{},"bool(value)"," before assigning. ",[13,1068,1069],{},"NaN"," produces an empty cell and reads back as ",[13,1072,313],{},", the same as assigning ",[13,1075,313],{},".",[10,1078,1079,1080,1082],{},"Strings that begin with ",[13,1081,459],{}," are stored as formulas, whatever follows:",[320,1084,1086],{"className":352,"code":1085,"language":354,"meta":325,"style":325},"print(roundtrip(\"= not a formula\"))\n\nwb = Workbook()\nws = wb.active\nws[\"A1\"] = \"= not a formula\"\nws[\"A1\"].data_type = \"s\"          # force text\nwb.save(\"text.xlsx\")\nprint(load_workbook(\"text.xlsx\").active[\"A1\"].data_type)\n# s\n",[13,1087,1088,1102,1106,1115,1124,1138,1156,1166,1182],{"__ignoreMap":325},[329,1089,1090,1093,1096,1099],{"class":331,"line":332},[329,1091,1092],{"class":562},"print",[329,1094,1095],{"class":365},"(roundtrip(",[329,1097,1098],{"class":339},"\"= not a formula\"",[329,1100,1101],{"class":365},"))\n",[329,1103,1104],{"class":331,"line":375},[329,1105,393],{"emptyLinePlaceholder":392},[329,1107,1108,1111,1113],{"class":331,"line":389},[329,1109,1110],{"class":365},"wb ",[329,1112,459],{"class":361},[329,1114,462],{"class":365},[329,1116,1117,1120,1122],{"class":331,"line":396},[329,1118,1119],{"class":365},"ws ",[329,1121,459],{"class":361},[329,1123,473],{"class":365},[329,1125,1126,1129,1131,1133,1135],{"class":331,"line":409},[329,1127,1128],{"class":365},"ws[",[329,1130,482],{"class":339},[329,1132,485],{"class":365},[329,1134,459],{"class":361},[329,1136,1137],{"class":339}," \"= not a formula\"\n",[329,1139,1140,1142,1144,1147,1149,1152],{"class":331,"line":422},[329,1141,1128],{"class":365},[329,1143,482],{"class":339},[329,1145,1146],{"class":365},"].data_type ",[329,1148,459],{"class":361},[329,1150,1151],{"class":339}," \"s\"",[329,1153,1155],{"class":1154},"s-wDw","          # force text\n",[329,1157,1158,1161,1164],{"class":331,"line":435},[329,1159,1160],{"class":365},"wb.save(",[329,1162,1163],{"class":339},"\"text.xlsx\"",[329,1165,529],{"class":365},[329,1167,1168,1170,1173,1175,1177,1179],{"class":331,"line":440},[329,1169,1092],{"class":562},[329,1171,1172],{"class":365},"(load_workbook(",[329,1174,1163],{"class":339},[329,1176,545],{"class":365},[329,1178,482],{"class":339},[329,1180,1181],{"class":365},"].data_type)\n",[329,1183,1184],{"class":331,"line":453},[329,1185,1186],{"class":1154},"# s\n",[10,1188,1189,1190,1193,1194,1198],{},"The first version writes a formula that Excel cannot parse. Setting ",[13,1191,1192],{},"data_type = \"s\""," after assigning keeps the value as text, which matters for any column holding user-entered notes; ",[27,1195,1197],{"href":1196},"\u002Fgetting-started-with-python-excel-automation\u002Fwriting-dataframes-to-excel-with-pandas\u002Fwrite-clickable-links-from-a-dataframe-to-excel\u002F","Write Clickable Links from a DataFrame Column to Excel"," discusses the security side of the same behaviour.",[315,1200,1202],{"id":1201},"values-openpyxl-rejects-now-or-later","Values openpyxl rejects — now or later",[10,1204,1205,1206,1209],{},"Some values fail immediately with ",[13,1207,1208],{},"ValueError: Cannot convert ... to Excel",". Others are accepted and fail only when the workbook is saved:",[320,1211,1213],{"className":352,"code":1212,"language":354,"meta":325,"style":325},"ws = Workbook().active\nfor value in ([1, 2], {\"a\": 1}, pd.NA, np.datetime64(\"2026-03-01\")):\n    try:\n        ws[\"A1\"] = value\n        print(\"accepted\", repr(value))\n    except ValueError as exc:\n        print(\"rejected\", exc)\n\nwb = Workbook()\nwb.active[\"A1\"] = dt.datetime(2026, 3, 1, 12, 0, tzinfo=dt.timezone.utc)\nprint(\"assignment accepted\")\ntry:\n    wb.save(\"tz.xlsx\")\nexcept TypeError as exc:\n    print(\"save failed:\", str(exc)[:48])\n",[13,1214,1215,1224,1267,1275,1288,1305,1319,1331,1335,1343,1385,1396,1403,1412,1424],{"__ignoreMap":325},[329,1216,1217,1219,1221],{"class":331,"line":332},[329,1218,1119],{"class":365},[329,1220,459],{"class":361},[329,1222,1223],{"class":365}," Workbook().active\n",[329,1225,1226,1228,1230,1232,1235,1237,1239,1242,1245,1248,1251,1253,1256,1259,1262,1265],{"class":331,"line":375},[329,1227,595],{"class":361},[329,1229,598],{"class":365},[329,1231,601],{"class":361},[329,1233,1234],{"class":365}," ([",[329,1236,711],{"class":562},[329,1238,610],{"class":365},[329,1240,1241],{"class":562},"2",[329,1243,1244],{"class":365},"], {",[329,1246,1247],{"class":339},"\"a\"",[329,1249,1250],{"class":365},": ",[329,1252,711],{"class":562},[329,1254,1255],{"class":365},"}, pd.",[329,1257,1258],{"class":562},"NA",[329,1260,1261],{"class":365},", np.datetime64(",[329,1263,1264],{"class":339},"\"2026-03-01\"",[329,1266,780],{"class":365},[329,1268,1269,1272],{"class":331,"line":389},[329,1270,1271],{"class":361},"    try",[329,1273,1274],{"class":365},":\n",[329,1276,1277,1280,1282,1284,1286],{"class":331,"line":396},[329,1278,1279],{"class":365},"        ws[",[329,1281,482],{"class":339},[329,1283,485],{"class":365},[329,1285,459],{"class":361},[329,1287,490],{"class":365},[329,1289,1290,1293,1295,1298,1300,1302],{"class":331,"line":409},[329,1291,1292],{"class":562},"        print",[329,1294,639],{"class":365},[329,1296,1297],{"class":339},"\"accepted\"",[329,1299,610],{"class":365},[329,1301,642],{"class":562},[329,1303,1304],{"class":365},"(value))\n",[329,1306,1307,1310,1313,1316],{"class":331,"line":422},[329,1308,1309],{"class":361},"    except",[329,1311,1312],{"class":562}," ValueError",[329,1314,1315],{"class":361}," as",[329,1317,1318],{"class":365}," exc:\n",[329,1320,1321,1323,1325,1328],{"class":331,"line":435},[329,1322,1292],{"class":562},[329,1324,639],{"class":365},[329,1326,1327],{"class":339},"\"rejected\"",[329,1329,1330],{"class":365},", exc)\n",[329,1332,1333],{"class":331,"line":440},[329,1334,393],{"emptyLinePlaceholder":392},[329,1336,1337,1339,1341],{"class":331,"line":453},[329,1338,1110],{"class":365},[329,1340,459],{"class":361},[329,1342,462],{"class":365},[329,1344,1345,1348,1350,1352,1354,1357,1359,1361,1363,1365,1367,1369,1371,1373,1375,1377,1380,1382],{"class":331,"line":465},[329,1346,1347],{"class":365},"wb.active[",[329,1349,482],{"class":339},[329,1351,485],{"class":365},[329,1353,459],{"class":361},[329,1355,1356],{"class":365}," dt.datetime(",[329,1358,701],{"class":562},[329,1360,610],{"class":365},[329,1362,706],{"class":562},[329,1364,610],{"class":365},[329,1366,711],{"class":562},[329,1368,610],{"class":365},[329,1370,864],{"class":562},[329,1372,610],{"class":365},[329,1374,55],{"class":562},[329,1376,610],{"class":365},[329,1378,1379],{"class":756},"tzinfo",[329,1381,459],{"class":361},[329,1383,1384],{"class":365},"dt.timezone.utc)\n",[329,1386,1387,1389,1391,1394],{"class":331,"line":476},[329,1388,1092],{"class":562},[329,1390,639],{"class":365},[329,1392,1393],{"class":339},"\"assignment accepted\"",[329,1395,529],{"class":365},[329,1397,1398,1401],{"class":331,"line":493},[329,1399,1400],{"class":361},"try",[329,1402,1274],{"class":365},[329,1404,1405,1407,1410],{"class":331,"line":509},[329,1406,523],{"class":365},[329,1408,1409],{"class":339},"\"tz.xlsx\"",[329,1411,529],{"class":365},[329,1413,1414,1417,1420,1422],{"class":331,"line":520},[329,1415,1416],{"class":361},"except",[329,1418,1419],{"class":562}," TypeError",[329,1421,1315],{"class":361},[329,1423,1318],{"class":365},[329,1425,1426,1428,1430,1433,1435,1437,1440,1443],{"class":331,"line":532},[329,1427,636],{"class":562},[329,1429,639],{"class":365},[329,1431,1432],{"class":339},"\"save failed:\"",[329,1434,610],{"class":365},[329,1436,170],{"class":562},[329,1438,1439],{"class":365},"(exc)[:",[329,1441,1442],{"class":562},"48",[329,1444,1445],{"class":365},"])\n",[10,1447,1448,1449,1042,1452,1455,1456,1458,1459,1462,1463,1465,1466,1469,1470,1474],{},"Lists, dictionaries, ",[13,1450,1451],{},"pd.NA",[13,1453,1454],{},"numpy.datetime64"," are rejected on assignment, which is easy to debug. A timezone-aware ",[13,1457,212],{}," is the awkward one: the assignment succeeds and the ",[13,1460,1461],{},"TypeError"," appears at ",[13,1464,24],{},", possibly thousands of lines and many cells later, with nothing pointing at the cell responsible. Convert to naive local time before assigning — ",[13,1467,1468],{},"value.astimezone(zone).replace(tzinfo=None)"," — and see ",[27,1471,1473],{"href":1472},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fhandle-timezones-in-excel-timestamps-with-python\u002F","Handle Timezones in Excel Timestamps with Python"," for choosing the zone.",[33,1476,42,1481,42,1484,42,1487,42,1490,42,1494,42,1501,42,1506,42,1511,42,1516,42,1522,42,1525,42,1530,42,1533,42,1538,42,1542,42,1549,42,1553,42,1559,42,1564,42,1568,42,1572,42,1577,42,1580,42,1585,42,1588,42,1593,42,1596],{"viewBox":1477,"role":36,"ariaLabelledBy":1478,"xmlns":40,"style":41},"4 0 752 233",[1479,1480],"opv-fail-t","opv-fail-d",[44,1482,1483],{"id":1479},"When a bad value is caught",[48,1485,1486],{"id":1480},"Two panels. Values rejected at assignment — lists, dictionaries, pd.NA and numpy datetime64 — raise ValueError on the line that assigns them. A timezone-aware datetime is accepted at assignment and raises TypeError only when the workbook is saved, far from the cause.",[52,1488],{"x":54,"y":55,"width":56,"height":1489,"fill":58},"233",[52,1491],{"x":61,"y":62,"width":1492,"height":1493,"rx":716,"fill":955,"stroke":956,"style":865},"351.0","201.0",[867,1495,1497],{"transform":1496},"translate(182.5,29.0) scale(1.083)",[871,1498],{"d":1499,"fill":874,"stroke":963,"style":1500},"M6 6l12 12 M18 6L6 18","stroke-width:1.66px;stroke-linecap:round;stroke-linejoin:round",[75,1502,1505],{"x":1503,"y":103,"style":1504},"195.5","font-size:14px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Rejected when assigned",[331,1507],{"x1":1508,"y1":1509,"x2":1510,"y2":1509,"stroke":956,"style":66},"36.0","91.0","355.0",[75,1512,1515],{"x":1513,"y":133,"style":1514},"60.0","font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:start","list or dict",[867,1517,1519],{"transform":1518},"translate(38.5,99.5) scale(0.625)",[871,1520],{"d":962,"fill":874,"stroke":956,"style":1521},"stroke-width:2.88px;stroke-linecap:round;stroke-linejoin:round",[75,1523,1451],{"x":1513,"y":1524,"style":1514},"137.0",[867,1526,1528],{"transform":1527},"translate(38.5,125.5) scale(0.625)",[871,1529],{"d":962,"fill":874,"stroke":956,"style":1521},[75,1531,1454],{"x":1513,"y":1532,"style":1514},"163.0",[867,1534,1536],{"transform":1535},"translate(38.5,151.5) scale(0.625)",[871,1537],{"d":962,"fill":874,"stroke":956,"style":1521},[75,1539,1541],{"x":1513,"y":1540,"style":1514},"189.0","error names the value",[867,1543,1545],{"transform":1544},"translate(38.5,177.5) scale(0.625)",[871,1546],{"d":1547,"fill":874,"stroke":1548,"style":1521},"M5 12.5l4.5 4.5L19 7","var(--teal,#0f9488)",[52,1550],{"x":918,"y":62,"width":1492,"height":1493,"rx":716,"fill":1551,"stroke":1552,"style":865},"#fee8f2","var(--accent,#f43f8f)",[867,1554,1556],{"transform":1555},"translate(551.5,29.0) scale(1.083)",[871,1557],{"d":962,"fill":874,"stroke":1558,"style":1500},"var(--accent-ink,#be185d)",[75,1560,1563],{"x":1561,"y":103,"style":1562},"564.5","font-size:14px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","Accepted, fails at save()",[331,1565],{"x1":1566,"y1":1509,"x2":1567,"y2":1509,"stroke":1552,"style":66},"405.0","724.0",[75,1569,1571],{"x":1570,"y":133,"style":1514},"429.0","timezone-aware datetime",[867,1573,1575],{"transform":1574},"translate(407.5,99.5) scale(0.625)",[871,1576],{"d":1499,"fill":874,"stroke":1552,"style":1521},[75,1578,1579],{"x":1570,"y":1524,"style":1514},"TypeError at wb.save()",[867,1581,1583],{"transform":1582},"translate(407.5,125.5) scale(0.625)",[871,1584],{"d":1499,"fill":874,"stroke":1552,"style":1521},[75,1586,1587],{"x":1570,"y":1532,"style":1514},"no cell address in the error",[867,1589,1591],{"transform":1590},"translate(407.5,151.5) scale(0.625)",[871,1592],{"d":1499,"fill":874,"stroke":1552,"style":1521},[75,1594,1595],{"x":1570,"y":1540,"style":1514},"fix: make it naive first",[867,1597,1599],{"transform":1598},"translate(407.5,177.5) scale(0.625)",[871,1600],{"d":1547,"fill":874,"stroke":1548,"style":1521},[315,1602,1604],{"id":1603},"normalise-values-before-they-reach-openpyxl","Normalise values before they reach openpyxl",[10,1606,1607],{},"The safest pattern for code that writes arbitrary data — rows from a database, values from a DataFrame — is one conversion function that every value passes through. It turns the surprising cases into the intended ones and makes the rejections explicit:",[320,1609,1611],{"className":352,"code":1610,"language":354,"meta":325,"style":325},"def to_cell(value, tz=None):\n    if value is None or value is pd.NaT or (isinstance(value, float) and value != value):\n        return None\n    if value is pd.NA:\n        return None\n    if isinstance(value, np.bool_):\n        return bool(value)\n    if isinstance(value, np.datetime64):\n        value = pd.Timestamp(value).to_pydatetime()\n    if isinstance(value, dt.datetime) and value.tzinfo is not None:\n        value = value.astimezone(tz).replace(tzinfo=None)\n    if isinstance(value, (list, tuple, dict, set)):\n        return str(value)\n    return value\n\nrow = [np.bool_(True), pd.NA, np.datetime64(\"2026-03-01T08:00\"),\n       dt.datetime(2026, 3, 1, 12, 0, tzinfo=dt.timezone.utc), [\"a\", \"b\"], 42]\nwb = Workbook()\nwb.active.append([to_cell(v, tz=dt.timezone.utc) for v in row])\nwb.save(\"normalised.xlsx\")\nprint([c.value for c in load_workbook(\"normalised.xlsx\").active[1]])\n",[13,1612,1613,1629,1679,1687,1702,1708,1718,1728,1737,1747,1770,1787,1816,1825,1831,1835,1859,1906,1915,1939,1949],{"__ignoreMap":325},[329,1614,1615,1617,1620,1623,1625,1627],{"class":331,"line":332},[329,1616,443],{"class":361},[329,1618,1619],{"class":446}," to_cell",[329,1621,1622],{"class":365},"(value, tz",[329,1624,459],{"class":361},[329,1626,313],{"class":562},[329,1628,631],{"class":365},[329,1630,1631,1634,1636,1639,1642,1645,1647,1649,1652,1655,1657,1660,1663,1665,1668,1671,1673,1676],{"class":331,"line":375},[329,1632,1633],{"class":361},"    if",[329,1635,598],{"class":365},[329,1637,1638],{"class":361},"is",[329,1640,1641],{"class":562}," None",[329,1643,1644],{"class":361}," or",[329,1646,598],{"class":365},[329,1648,1638],{"class":361},[329,1650,1651],{"class":365}," pd.NaT ",[329,1653,1654],{"class":361},"or",[329,1656,604],{"class":365},[329,1658,1659],{"class":562},"isinstance",[329,1661,1662],{"class":365},"(value, ",[329,1664,142],{"class":562},[329,1666,1667],{"class":365},") ",[329,1669,1670],{"class":361},"and",[329,1672,598],{"class":365},[329,1674,1675],{"class":361},"!=",[329,1677,1678],{"class":365}," value):\n",[329,1680,1681,1684],{"class":331,"line":389},[329,1682,1683],{"class":361},"        return",[329,1685,1686],{"class":562}," None\n",[329,1688,1689,1691,1693,1695,1698,1700],{"class":331,"line":396},[329,1690,1633],{"class":361},[329,1692,598],{"class":365},[329,1694,1638],{"class":361},[329,1696,1697],{"class":365}," pd.",[329,1699,1258],{"class":562},[329,1701,1274],{"class":365},[329,1703,1704,1706],{"class":331,"line":409},[329,1705,1683],{"class":361},[329,1707,1686],{"class":562},[329,1709,1710,1712,1715],{"class":331,"line":422},[329,1711,1633],{"class":361},[329,1713,1714],{"class":562}," isinstance",[329,1716,1717],{"class":365},"(value, np.bool_):\n",[329,1719,1720,1722,1725],{"class":331,"line":435},[329,1721,1683],{"class":361},[329,1723,1724],{"class":562}," bool",[329,1726,1727],{"class":365},"(value)\n",[329,1729,1730,1732,1734],{"class":331,"line":440},[329,1731,1633],{"class":361},[329,1733,1714],{"class":562},[329,1735,1736],{"class":365},"(value, np.datetime64):\n",[329,1738,1739,1742,1744],{"class":331,"line":453},[329,1740,1741],{"class":365},"        value ",[329,1743,459],{"class":361},[329,1745,1746],{"class":365}," pd.Timestamp(value).to_pydatetime()\n",[329,1748,1749,1751,1753,1756,1758,1761,1763,1766,1768],{"class":331,"line":465},[329,1750,1633],{"class":361},[329,1752,1714],{"class":562},[329,1754,1755],{"class":365},"(value, dt.datetime) ",[329,1757,1670],{"class":361},[329,1759,1760],{"class":365}," value.tzinfo ",[329,1762,1638],{"class":361},[329,1764,1765],{"class":361}," not",[329,1767,1641],{"class":562},[329,1769,1274],{"class":365},[329,1771,1772,1774,1776,1779,1781,1783,1785],{"class":331,"line":476},[329,1773,1741],{"class":365},[329,1775,459],{"class":361},[329,1777,1778],{"class":365}," value.astimezone(tz).replace(",[329,1780,1379],{"class":756},[329,1782,459],{"class":361},[329,1784,313],{"class":562},[329,1786,529],{"class":365},[329,1788,1789,1791,1793,1796,1799,1801,1804,1806,1809,1811,1814],{"class":331,"line":493},[329,1790,1633],{"class":361},[329,1792,1714],{"class":562},[329,1794,1795],{"class":365},"(value, (",[329,1797,1798],{"class":562},"list",[329,1800,610],{"class":365},[329,1802,1803],{"class":562},"tuple",[329,1805,610],{"class":365},[329,1807,1808],{"class":562},"dict",[329,1810,610],{"class":365},[329,1812,1813],{"class":562},"set",[329,1815,780],{"class":365},[329,1817,1818,1820,1823],{"class":331,"line":509},[329,1819,1683],{"class":361},[329,1821,1822],{"class":562}," str",[329,1824,1727],{"class":365},[329,1826,1827,1829],{"class":331,"line":520},[329,1828,556],{"class":361},[329,1830,490],{"class":365},[329,1832,1833],{"class":331,"line":532},[329,1834,393],{"emptyLinePlaceholder":392},[329,1836,1837,1840,1842,1845,1847,1850,1852,1854,1857],{"class":331,"line":553},[329,1838,1839],{"class":365},"row ",[329,1841,459],{"class":361},[329,1843,1844],{"class":365}," [np.bool_(",[329,1846,618],{"class":562},[329,1848,1849],{"class":365},"), pd.",[329,1851,1258],{"class":562},[329,1853,1261],{"class":365},[329,1855,1856],{"class":339},"\"2026-03-01T08:00\"",[329,1858,748],{"class":365},[329,1860,1862,1865,1867,1869,1871,1873,1875,1877,1879,1881,1883,1885,1887,1889,1892,1894,1896,1899,1902,1904],{"class":331,"line":1861},17,[329,1863,1864],{"class":365},"       dt.datetime(",[329,1866,701],{"class":562},[329,1868,610],{"class":365},[329,1870,706],{"class":562},[329,1872,610],{"class":365},[329,1874,711],{"class":562},[329,1876,610],{"class":365},[329,1878,864],{"class":562},[329,1880,610],{"class":365},[329,1882,55],{"class":562},[329,1884,610],{"class":365},[329,1886,1379],{"class":756},[329,1888,459],{"class":361},[329,1890,1891],{"class":365},"dt.timezone.utc), [",[329,1893,1247],{"class":339},[329,1895,610],{"class":365},[329,1897,1898],{"class":339},"\"b\"",[329,1900,1901],{"class":365},"], ",[329,1903,607],{"class":562},[329,1905,506],{"class":365},[329,1907,1909,1911,1913],{"class":331,"line":1908},18,[329,1910,1110],{"class":365},[329,1912,459],{"class":361},[329,1914,462],{"class":365},[329,1916,1918,1921,1924,1926,1929,1931,1934,1936],{"class":331,"line":1917},19,[329,1919,1920],{"class":365},"wb.active.append([to_cell(v, ",[329,1922,1923],{"class":756},"tz",[329,1925,459],{"class":361},[329,1927,1928],{"class":365},"dt.timezone.utc) ",[329,1930,595],{"class":361},[329,1932,1933],{"class":365}," v ",[329,1935,601],{"class":361},[329,1937,1938],{"class":365}," row])\n",[329,1940,1942,1944,1947],{"class":331,"line":1941},20,[329,1943,1160],{"class":365},[329,1945,1946],{"class":339},"\"normalised.xlsx\"",[329,1948,529],{"class":365},[329,1950,1952,1954,1957,1959,1962,1964,1966,1968,1970,1972],{"class":331,"line":1951},21,[329,1953,1092],{"class":562},[329,1955,1956],{"class":365},"([c.value ",[329,1958,595],{"class":361},[329,1960,1961],{"class":365}," c ",[329,1963,601],{"class":361},[329,1965,540],{"class":365},[329,1967,1946],{"class":339},[329,1969,545],{"class":365},[329,1971,711],{"class":562},[329,1973,1974],{"class":365},"]])\n",[10,1976,1977,1978,1982],{},"Every value now lands as something Excel understands: a real boolean, an empty cell, a naive datetime, text for the list. Converting the list to a string is a choice — raising an error instead would be equally reasonable for data that should never contain lists. ",[27,1979,1981],{"href":1980},"\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-cannot-convert-to-excel-valueerror\u002F","Fix \"ValueError: Cannot convert to Excel\" in openpyxl"," covers the rejection cases in more depth.",[315,1984,1986],{"id":1985},"common-pitfalls","Common pitfalls",[1988,1989,1990,2003,2009,2015,2021,2034],"ul",{},[1991,1992,1993,2000,2001,1076],"li",{},[1994,1995,1996,1997,1999],"strong",{},"Comparing reread dates with ",[13,1998,218],{}," objects."," They come back as datetimes; compare ",[13,2002,838],{},[1991,2004,2005,2008],{},[1994,2006,2007],{},"Writing NumPy booleans directly."," They become 0 and 1.",[1991,2010,2011,2014],{},[1994,2012,2013],{},"Assuming numeric-looking strings become numbers."," They stay text; convert explicitly if they are quantities.",[1991,2016,2017,2020],{},[1994,2018,2019],{},"Timezone-aware datetimes."," Accepted now, fatal at save.",[1991,2022,2023,2026,2027,2029,2030,2033],{},[1994,2024,2025],{},"Durations over a day."," A ",[13,2028,276],{}," of 26 hours displays as 26:30:00 in Excel because of the ",[13,2031,2032],{},"[hh]"," brackets in its default format; Excel documents that a format without them shows only the time of day, 02:30:00. LibreOffice happens to show 26:30:00 either way, so test duration formats in the application your readers use, and keep the brackets when you restyle duration columns.",[1991,2035,2036,2039,2040,2042,2043,1076],{},[1994,2037,2038],{},"Strings starting with \"=\"."," Stored as formulas unless ",[13,2041,650],{}," is set to ",[13,2044,2045],{},"\"s\"",[315,2047,2049],{"id":2048},"performance-notes","Performance notes",[10,2051,2052,2053,2055,2056,2059,2060,610,2062,2065,2066,2069],{},"Type detection happens on every assignment and is fast; the conversion function above adds a few ",[13,2054,1659],{}," checks per value, which is negligible next to the cost of creating cells. When writing large DataFrames, let pandas' ",[13,2057,2058],{},"to_excel"," do the conversion — it already handles ",[13,2061,1069],{},[13,2063,2064],{},"NaT"," and NumPy types — and use a function like ",[13,2067,2068],{},"to_cell"," only where your own code writes cells.",[315,2071,2073],{"id":2072},"conclusion","Conclusion",[10,2075,2076,2077,2079,2080,2083,2084,1042,2086,2088,2089,2091],{},"openpyxl stores numbers, text and booleans as you would expect, writes dates, times and durations as numbers with date formats — so dates come back as midnight datetimes — and turns any string beginning with ",[13,2078,459],{}," into a formula. ",[13,2081,2082],{},"numpy.bool_"," becomes 0 or 1, ",[13,2085,1451],{},[13,2087,1454],{}," are rejected, and timezone-aware datetimes fail only at ",[13,2090,24],{},". Route values through one normalising function before they reach cells, and these become deliberate choices rather than surprises.",[315,2093,2095],{"id":2094},"frequently-asked-questions","Frequently asked questions",[10,2097,2098,2101],{},[1994,2099,2100],{},"Why does a date I wrote come back as a datetime?","\nExcel has no separate date type: a date is a number with a date format. openpyxl writes a date with the yyyy-mm-dd format and reads every date-formatted number back as a datetime at midnight. Call .date() on it if you need a date.",[10,2103,2104,2107],{},[1994,2105,2106],{},"Why did True from a NumPy array become 0?","\nopenpyxl recognises Python's bool but treats numpy.bool_ as a number, so the cell is stored as 0 or 1 rather than TRUE or FALSE. Convert with bool(value) before assigning.",[10,2109,2110,2113],{},[1994,2111,2112],{},"Why does saving fail with \"Excel does not support timezones\"?","\nA timezone-aware datetime was assigned to a cell. openpyxl accepts it at assignment but refuses at save time, because the file format has no timezone. Convert to naive local time before assigning.",[10,2115,2116,2119],{},[1994,2117,2118],{},"How do I write text that starts with an equals sign?","\nAssign it and then set cell.data_type = \"s\". Without that, openpyxl stores any string beginning with = as a formula.",[315,2121,2123],{"id":2122},"related","Related",[1988,2125,2126,2132,2139,2146],{},[1991,2127,2128,2129,2131],{},"Up one level: ",[27,2130,30],{"href":29}," — reading and writing cells.",[1991,2133,2134,2138],{},[27,2135,2137],{"href":2136},"\u002Fgetting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation\u002Fread-cell-value-from-excel-with-openpyxl\u002F","Read a Cell Value From Excel With openpyxl"," — the reading side in detail.",[1991,2140,2141,2145],{},[27,2142,2144],{"href":2143},"\u002Fgetting-started-with-python-excel-automation\u002Fwriting-dataframes-to-excel-with-pandas\u002Fcontrol-how-nan-and-infinity-appear-in-excel-output\u002F","Control How NaN, None and Infinity Appear in Excel Output"," — the same questions through pandas.",[1991,2147,2148,2152],{},[27,2149,2151],{"href":2150},"\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-long-id-numbers-losing-digits-in-excel\u002F","Fix Long ID Numbers Losing Digits or Turning into 1.23E+18 in Excel"," — how large integers are written.",[2154,2155,2156],"style",{},"html pre.shiki code .sMTad, html code.shiki .sMTad{--shiki-default:#6F42C1;--shiki-dark:#FFB757}html pre.shiki code .srMev, html code.shiki .srMev{--shiki-default:#032F62;--shiki-dark:#ADDCFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s-kum, html code.shiki .s-kum{--shiki-default:#D73A49;--shiki-dark:#FF9492}html pre.shiki code .skGVy, html code.shiki .skGVy{--shiki-default:#24292E;--shiki-dark:#F0F3F6}html pre.shiki code .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}html pre.shiki code .sP0c6, html code.shiki .sP0c6{--shiki-default:#005CC5;--shiki-dark:#91CBFF}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}html pre.shiki code .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}",{"title":325,"searchDepth":375,"depth":375,"links":2158},[2159,2160,2161,2162,2163,2164,2165,2166,2167,2168,2169],{"id":317,"depth":375,"text":318},{"id":581,"depth":375,"text":582},{"id":678,"depth":375,"text":679},{"id":980,"depth":375,"text":981},{"id":1201,"depth":375,"text":1202},{"id":1603,"depth":375,"text":1604},{"id":1985,"depth":375,"text":1986},{"id":2048,"depth":375,"text":2049},{"id":2072,"depth":375,"text":2073},{"id":2094,"depth":375,"text":2095},{"id":2122,"depth":375,"text":2123},"2026-09-25","What happens to an int, a bool, a date, a Decimal, a NumPy value or a timezone-aware datetime when you assign it to an openpyxl cell — the cell type it becomes, the number format it gets, what comes back when the file is reopened, and which values fail only when you save.","md",[2174,2176,2178,2180],{"q":2100,"a":2175},"Excel has no separate date type: a date is a number with a date format. openpyxl writes a date with the yyyy-mm-dd format and reads every date-formatted number back as a datetime at midnight. Call .date() on it if you need a date.",{"q":2106,"a":2177},"openpyxl recognises Python's bool but treats numpy.bool_ as a number, so the cell is stored as 0 or 1 rather than TRUE or FALSE. Convert with bool(value) before assigning.",{"q":2112,"a":2179},"A timezone-aware datetime was assigned to a cell. openpyxl accepts it at assignment but refuses at save time, because the file format has no timezone. Convert to naive local time before assigning.",{"q":2118,"a":2181},"Assign it and then set cell.data_type = \"s\". Without that, openpyxl stores any string beginning with = as a formula.","how openpyxl maps python types to excel cell types",null,{"breadcrumb":2185},[2186,2189,2192],{"name":2187,"item":2188},"Home","\u002F",{"name":2190,"item":2191},"Getting Started with Python Excel Automation","\u002Fgetting-started-with-python-excel-automation\u002F",{"name":30,"item":29},"\u002Fgetting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation\u002Fhow-openpyxl-stores-python-values-in-cells",{"title":2195,"description":2196},"openpyxl Cell Data Types for Python Values (int, date, bool, NumPy)","A tested map of Python types to openpyxl cell data types and default number formats: dates read back as datetimes, numpy.bool_ becomes 0, pd.NA is rejected, timezone-aware datetimes fail on save, strings starting with = become formulas.","how-openpyxl-stores-python-values-in-cells","getting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation\u002Fhow-openpyxl-stores-python-values-in-cells\u002Findex",[2200,2201,2202],"openpyxl","writing-excel","dates-and-times","how-to","5_2cOUbSQixaR0hK-_bCJQSR1A4ifB16E1Pl8g_uf_U",[2206,2210],{"title":2207,"path":2208,"stem":2209,"children":-1},"Find the Last Row with Data in openpyxl","\u002Fgetting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation\u002Ffind-the-last-row-with-data-in-openpyxl","getting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation\u002Ffind-the-last-row-with-data-in-openpyxl\u002Findex",{"title":2211,"path":2212,"stem":2213,"children":-1},"Insert and Delete Rows and Columns with openpyxl","\u002Fgetting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation\u002Finsert-and-delete-rows-and-columns-with-openpyxl","getting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation\u002Finsert-and-delete-rows-and-columns-with-openpyxl\u002Findex",[2215,2217],{"path":2216,"title":2190},"\u002Fgetting-started-with-python-excel-automation",{"path":2218,"title":30},"\u002Fgetting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation",[],[2221,2225,2228,2231,2234,2237],{"path":2222,"title":2223,"kind":2224},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl","Applying Conditional Formatting With openpyxl","guide",{"path":2226,"title":2227,"kind":2203},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fadd-data-bars-and-colour-scales-with-openpyxl","Add Data Bars and Colour Scales with openpyxl",{"path":2229,"title":2230,"kind":2203},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fadd-icon-sets-with-openpyxl","Add Icon Sets to Excel Cells with openpyxl",{"path":2232,"title":2233,"kind":2203},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fapply-a-colour-scale-per-row-or-per-column-with-openpyxl","Apply a Colour Scale per Row or per Column with openpyxl",{"path":2235,"title":2236,"kind":2203},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fcontrol-conditional-formatting-rule-order-and-stop-if-true","Control Conditional Formatting Rule Order and Stop If True",{"path":2238,"title":2239,"kind":2203},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-an-entire-row-based-on-a-cell-value-with-openpyxl","Highlight an Entire Row Based on a Cell Value with openpyxl",1790972069897]