[{"data":1,"prerenderedAt":1957},["ShallowReactive",2],{"doc:\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-excel-does-not-support-timezones-in-datetimes":3,"surround:\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-excel-does-not-support-timezones-in-datetimes":1921,"ancestors:\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-excel-does-not-support-timezones-in-datetimes":1930,"children:\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-excel-does-not-support-timezones-in-datetimes":1935,"related:\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-excel-does-not-support-timezones-in-datetimes":1936},{"id":4,"title":5,"body":6,"dateModified":1886,"datePublished":1886,"description":1887,"extension":1888,"faq":1889,"keyword":1898,"kind":1899,"meta":1900,"navigation":273,"path":1909,"seo":1910,"slug":1913,"stem":1914,"topics":1915,"type":1919,"__hash__":1920},"docs\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-excel-does-not-support-timezones-in-datetimes\u002Findex.md","Fix \"Excel does not support datetimes with timezones\" in pandas",{"type":7,"value":8,"toc":1872},"minimark",[9,23,33,42,215,220,245,397,401,404,421,563,578,582,589,689,696,760,773,777,780,935,942,946,960,1018,1040,1109,1113,1116,1315,1326,1330,1340,1495,1506,1510,1517,1729,1744,1748,1797,1801,1807,1811,1817,1823,1829,1835,1839,1868],[10,11,12,13,17,18,22],"p",{},"Timestamps from APIs, databases and log files usually arrive ",[14,15,16],"em",{},"timezone-aware"," — pandas knows they are in UTC or in ",[19,20,21],"code",{},"Europe\u002FBerlin",". Write such a frame to Excel and pandas stops:",[24,25,31],"pre",{"className":26,"code":28,"language":29,"meta":30},[27],"language-text","ValueError: Excel does not support datetimes with timezones. Please ensure that datetimes are timezone unaware before writing to Excel.\n","text","",[19,32,28],{"__ignoreMap":30},[10,34,35,36,41],{},"The message is accurate. An Excel date is a number of days since 1900 with no room for a zone, so any zone information would be lost silently — pandas refuses rather than guess which clock time you wanted. The fix is to make that decision explicitly. This guide, part of ",[37,38,40],"a",{"href":39},"\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002F","Troubleshooting Common Python Excel Errors",", shows how, including the places the error hides after the obvious column is fixed.",[43,44,52,53,52,57,52,61,52,69,52,78,52,82,52,88,52,92,52,97,52,100,52,104,52,107,52,111,52,115,52,119,52,122,52,126,52,128,52,131,52,134,52,138,52,141,52,143,52,146,52,148,52,151,52,153,52,155,52,159,52,163,52,165,52,167,52,169,52,172,52,178,52,182,52,190,52,195,52,203,52,209],"svg",{"viewBox":45,"role":46,"ariaLabelledBy":47,"xmlns":50,"style":51},"4 0 752 190","img",[48,49],"tz-convert-t","tz-convert-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  ",[54,55,56],"title",{"id":48},"Converting UTC timestamps to the reader's local time before writing",[58,59,60],"desc",{"id":49},"Before, the timestamps are timezone-aware in UTC, 00:30 and 01:30 on 29 March 2026. Converting to Europe\u002FLondon and dropping the zone gives 00:30 and 02:30 local time, because British clocks moved forward that night, and these naive values write to Excel without error.",[62,63],"rect",{"x":64,"y":65,"width":66,"height":67,"fill":68},"4","0","752","190","#ffffff",[62,70],{"x":71,"y":72,"width":73,"height":74,"rx":65,"fill":75,"stroke":76,"style":77},"20.0","40.0","32.0","24.0","#e7ebef","var(--line,#cdd5e6)","stroke-width:1px",[62,79],{"x":80,"y":72,"width":81,"height":74,"rx":65,"fill":75,"stroke":76,"style":77},"52.0","181.9",[29,83,87],{"x":84,"y":85,"style":86},"142.9","57.0","font-size:11px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","A",[62,89],{"x":71,"y":90,"width":73,"height":91,"rx":65,"fill":75,"stroke":76,"style":77},"64.0","28.0",[29,93,96],{"x":94,"y":95,"style":86},"36.0","83.0","1",[62,98],{"x":80,"y":90,"width":81,"height":91,"rx":65,"fill":75,"stroke":76,"style":99},"stroke-width:1.5px",[29,101,103],{"x":84,"y":95,"style":102},"font-size:12px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","ts",[62,105],{"x":71,"y":106,"width":73,"height":91,"rx":65,"fill":75,"stroke":76,"style":77},"92.0",[29,108,110],{"x":94,"y":109,"style":86},"111.0","2",[62,112],{"x":80,"y":106,"width":81,"height":91,"rx":65,"fill":113,"stroke":114,"style":99},"#fee8f2","var(--accent,#f43f8f)",[29,116,118],{"x":84,"y":109,"style":117},"font-size:12px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","2026-03-29 00:30+00:00",[62,120],{"x":71,"y":121,"width":73,"height":91,"rx":65,"fill":75,"stroke":76,"style":77},"120.0",[29,123,125],{"x":94,"y":124,"style":86},"139.0","3",[62,127],{"x":80,"y":121,"width":81,"height":91,"rx":65,"fill":113,"stroke":114,"style":99},[29,129,130],{"x":84,"y":124,"style":117},"2026-03-29 01:30+00:00",[62,132],{"x":133,"y":72,"width":73,"height":74,"rx":65,"fill":75,"stroke":76,"style":77},"571.4",[62,135],{"x":136,"y":72,"width":137,"height":74,"rx":65,"fill":75,"stroke":76,"style":77},"603.4","136.6",[29,139,87],{"x":140,"y":85,"style":86},"671.7",[62,142],{"x":133,"y":90,"width":73,"height":91,"rx":65,"fill":75,"stroke":76,"style":77},[29,144,96],{"x":145,"y":95,"style":86},"587.4",[62,147],{"x":136,"y":90,"width":137,"height":91,"rx":65,"fill":75,"stroke":76,"style":99},[29,149,150],{"x":140,"y":95,"style":102},"ts (London)",[62,152],{"x":133,"y":106,"width":73,"height":91,"rx":65,"fill":75,"stroke":76,"style":77},[29,154,110],{"x":145,"y":109,"style":86},[62,156],{"x":136,"y":106,"width":137,"height":91,"rx":65,"fill":157,"stroke":158,"style":99},"#d9f4f1","var(--teal,#0f9488)",[29,160,162],{"x":140,"y":109,"style":161},"font-size:12px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","2026-03-29 00:30",[62,164],{"x":133,"y":121,"width":73,"height":91,"rx":65,"fill":75,"stroke":76,"style":77},[29,166,125],{"x":145,"y":124,"style":86},[62,168],{"x":136,"y":121,"width":137,"height":91,"rx":65,"fill":157,"stroke":158,"style":99},[29,170,171],{"x":140,"y":124,"style":161},"2026-03-29 02:30",[29,173,177],{"x":174,"y":175,"style":176},"126.9","29.0","font-size:12.5px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:middle","aware, UTC",[29,179,181],{"x":180,"y":175,"style":176},"655.7","naive, Europe\u002FLondon",[183,184],"line",{"x1":185,"y1":186,"x2":187,"y2":186,"stroke":188,"style":189},"243.9","94.0","557.4","var(--brand,#5b5cf0)","stroke-width:2px",[191,192],"polygon",{"points":193,"fill":194},"561.4,94.0 552.4,99.0 552.4,89.0","#5b5cf0",[62,196],{"x":197,"y":198,"width":199,"height":200,"rx":201,"fill":68,"stroke":76,"style":202},"283.6","61.0","238.1","22.0","11","stroke-width:1.2px",[29,204,208],{"x":205,"y":206,"style":207},"402.6","76.0","font-size:11.5px;font-weight:400;fill:var(--brand-strong,#4338ca);text-anchor:middle;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace","tz_convert + tz_localize(None)",[29,210,214],{"x":211,"y":212,"style":213},"380.0","170.0","font-size:12.5px;font-weight:400;fill:var(--muted,#5b6780);text-anchor:middle","the clocks went forward that night — converting through the zone gets the local times right",[216,217,219],"h2",{"id":218},"prerequisites","Prerequisites",[24,221,225],{"className":222,"code":223,"language":224,"meta":30,"style":30},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pandas openpyxl\n","bash",[19,226,227],{"__ignoreMap":30},[228,229,231,235,239,242],"span",{"class":183,"line":230},1,[228,232,234],{"class":233},"sMTad","pip",[228,236,238],{"class":237},"srMev"," install",[228,240,241],{"class":237}," pandas",[228,243,244],{"class":237}," openpyxl\n",[24,246,250],{"className":247,"code":248,"language":249,"meta":30,"style":30},"language-python shiki shiki-themes github-light github-dark-high-contrast","import pandas as pd\n\nevents = pd.DataFrame({\n    \"event\": [\"deploy\", \"incident\"],\n    \"ts\": pd.to_datetime([\"2026-03-29 00:30\", \"2026-03-29 01:30\"]).tz_localize(\"UTC\"),\n})\ntry:\n    events.to_excel(\"events.xlsx\", index=False)\nexcept ValueError as exc:\n    print(exc)\n","python",[19,251,252,268,275,287,308,334,340,349,373,388],{"__ignoreMap":30},[228,253,254,258,262,265],{"class":183,"line":230},[228,255,257],{"class":256},"s-kum","import",[228,259,261],{"class":260},"skGVy"," pandas ",[228,263,264],{"class":256},"as",[228,266,267],{"class":260}," pd\n",[228,269,271],{"class":183,"line":270},2,[228,272,274],{"emptyLinePlaceholder":273},true,"\n",[228,276,278,281,284],{"class":183,"line":277},3,[228,279,280],{"class":260},"events ",[228,282,283],{"class":256},"=",[228,285,286],{"class":260}," pd.DataFrame({\n",[228,288,290,293,296,299,302,305],{"class":183,"line":289},4,[228,291,292],{"class":237},"    \"event\"",[228,294,295],{"class":260},": [",[228,297,298],{"class":237},"\"deploy\"",[228,300,301],{"class":260},", ",[228,303,304],{"class":237},"\"incident\"",[228,306,307],{"class":260},"],\n",[228,309,311,314,317,320,322,325,328,331],{"class":183,"line":310},5,[228,312,313],{"class":237},"    \"ts\"",[228,315,316],{"class":260},": pd.to_datetime([",[228,318,319],{"class":237},"\"2026-03-29 00:30\"",[228,321,301],{"class":260},[228,323,324],{"class":237},"\"2026-03-29 01:30\"",[228,326,327],{"class":260},"]).tz_localize(",[228,329,330],{"class":237},"\"UTC\"",[228,332,333],{"class":260},"),\n",[228,335,337],{"class":183,"line":336},6,[228,338,339],{"class":260},"})\n",[228,341,343,346],{"class":183,"line":342},7,[228,344,345],{"class":256},"try",[228,347,348],{"class":260},":\n",[228,350,352,355,358,360,364,366,370],{"class":183,"line":351},8,[228,353,354],{"class":260},"    events.to_excel(",[228,356,357],{"class":237},"\"events.xlsx\"",[228,359,301],{"class":260},[228,361,363],{"class":362},"sa561","index",[228,365,283],{"class":256},[228,367,369],{"class":368},"sP0c6","False",[228,371,372],{"class":260},")\n",[228,374,376,379,382,385],{"class":183,"line":375},9,[228,377,378],{"class":256},"except",[228,380,381],{"class":368}," ValueError",[228,383,384],{"class":256}," as",[228,386,387],{"class":260}," exc:\n",[228,389,391,394],{"class":183,"line":390},10,[228,392,393],{"class":368},"    print",[228,395,396],{"class":260},"(exc)\n",[216,398,400],{"id":399},"step-1-decide-which-clock-the-reader-needs","Step 1 — Decide which clock the reader needs",[10,402,403],{},"Before touching code, answer one question: when a reader looks at the cell, which time should they see? There are two sensible answers, and they lead to different fixes:",[405,406,407,415],"ul",{},[408,409,410,414],"li",{},[411,412,413],"strong",{},"Local time"," — for people in one place reading about their own day: shift schedules, delivery times, anything tied to office hours.",[408,416,417,420],{},[411,418,419],{},"UTC"," — for systems, logs and anything compared across regions, where one consistent clock matters more than familiarity.",[43,422,52,427,52,430,52,433,52,436,52,444,52,455,52,459,52,463,52,467,52,470,52,473,52,480,52,485,52,490,52,497,52,501,52,506,52,508,52,511,52,515,52,518,52,521,52,527,52,529,52,532,52,534,52,537,52,541,52,544,52,549,52,556,52,560],{"viewBox":423,"role":46,"ariaLabelledBy":424,"xmlns":50,"style":51},"4 0 752 272",[425,426],"tz-choose-t","tz-choose-d",[54,428,429],{"id":425},"Choosing which time to write to Excel",[58,431,432],{"id":426},"If the readers are in one time zone and think in local time, convert to that zone and drop it. If the data spans regions or feeds other systems, convert to UTC and drop the zone, labelling the column as UTC. If readers need both, write two columns.",[62,434],{"x":64,"y":65,"width":66,"height":435,"fill":68},"272",[62,437],{"x":438,"y":439,"width":440,"height":441,"rx":442,"fill":443,"stroke":188,"style":189},"240.0","16.0","280.0","75.5","12","#f0f4ff",[445,446,448],"g",{"transform":447},"translate(368.0,29.0) scale(1.000)",[449,450],"path",{"d":451,"fill":452,"stroke":453,"style":454},"M9 11a3.5 3.5 0 1 0 0-7 3.5 3.5 0 0 0 0 7z M2.5 20c0-3.6 2.9-6 6.5-6s6.5 2.4 6.5 6 M16 4.5a3.3 3.3 0 0 1 0 6.3 M18 14.3c2.2.7 3.5 2.8 3.5 5.7","none","var(--brand-strong,#4338ca)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[29,456,458],{"x":211,"y":441,"style":457},"font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Who reads the timestamps?",[183,460],{"x1":211,"y1":461,"x2":211,"y2":462,"stroke":188,"style":189},"91.5","111.5",[183,464],{"x1":465,"y1":462,"x2":466,"y2":462,"stroke":188,"style":189},"134.7","625.3",[183,468],{"x1":465,"y1":462,"x2":465,"y2":469,"stroke":188,"style":189},"157.5",[191,471],{"points":472,"fill":194},"134.7,161.5 129.7,152.5 139.7,152.5",[62,474],{"x":475,"y":476,"width":477,"height":478,"rx":479,"fill":68,"stroke":76,"style":202},"68.7","121.5","132.0","21.0","10",[29,481,484],{"x":465,"y":482,"style":483},"136.0","font-size:11.5px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:middle","people in one place",[62,486],{"x":71,"y":487,"width":488,"height":489,"rx":442,"fill":157,"stroke":158,"style":189},"163.5","229.3","93.0",[445,491,493],{"transform":492},"translate(122.7,176.5) scale(1.000)",[449,494],{"d":495,"fill":452,"stroke":496,"style":454},"M12 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8z M4 21c0-4 3.6-7 8-7s8 3 8 7","var(--teal-ink,#0b6157)",[29,498,413],{"x":465,"y":499,"style":500},"223.0","font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle",[29,502,505],{"x":465,"y":503,"style":504},"242.5","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","tz_convert(zone), then drop",[183,507],{"x1":211,"y1":462,"x2":211,"y2":469,"stroke":188,"style":189},[191,509],{"points":510,"fill":194},"380.0,161.5 375.0,152.5 385.0,152.5",[62,512],{"x":513,"y":476,"width":514,"height":478,"rx":479,"fill":68,"stroke":76,"style":202},"296.7","166.6",[29,516,517],{"x":211,"y":482,"style":483},"systems or many regions",[62,519],{"x":520,"y":487,"width":488,"height":489,"rx":442,"fill":443,"stroke":188,"style":189},"265.3",[445,522,524],{"transform":523},"translate(368.0,176.5) scale(1.000)",[449,525],{"d":526,"fill":452,"stroke":453,"style":454},"M12 3a9 9 0 1 0 .01 0z M3 12h18 M12 3c3.2 3 3.2 15 0 18 M12 3c-3.2 3-3.2 15 0 18",[29,528,419],{"x":211,"y":499,"style":457},[29,530,531],{"x":211,"y":503,"style":504},"label the header (UTC)",[183,533],{"x1":466,"y1":462,"x2":466,"y2":469,"stroke":188,"style":189},[191,535],{"points":536,"fill":194},"625.3,161.5 620.3,152.5 630.3,152.5",[62,538],{"x":539,"y":476,"width":540,"height":478,"rx":479,"fill":68,"stroke":76,"style":202},"602.9","45.0",[29,542,543],{"x":466,"y":482,"style":483},"both",[62,545],{"x":546,"y":487,"width":488,"height":489,"rx":442,"fill":547,"stroke":548,"style":189},"510.7","#fdefd8","var(--gold,#b4740a)",[445,550,552],{"transform":551},"translate(613.3,176.5) scale(1.000)",[449,553],{"d":554,"fill":452,"stroke":555,"style":454},"M12 3l9 5-9 5-9-5z M3 13l9 5 9-5 M3 17l9 5 9-5","var(--gold-ink,#7a4e06)",[29,557,559],{"x":466,"y":499,"style":558},"font-size:13.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Two columns",[29,561,562],{"x":466,"y":503,"style":504},"local and UTC",[10,564,565,566,569,570,573,574,577],{},"Whichever you choose, say so in the header. A cell reading ",[19,567,568],{},"02:30"," means nothing trustworthy unless the column is called ",[14,571,572],{},"Time (London)"," or ",[14,575,576],{},"Time (UTC)",". The header is the only place the zone survives once the file leaves Python, so treat it as part of the data, not decoration.",[216,579,581],{"id":580},"step-2-convert-then-drop-the-zone","Step 2 — Convert, then drop the zone",[10,583,584,585,588],{},"For local time, convert to the zone first, then remove the zone with ",[19,586,587],{},"tz_localize(None)",", which keeps the converted wall-clock time:",[24,590,592],{"className":247,"code":591,"language":249,"meta":30,"style":30},"local = events.copy()\nlocal[\"ts\"] = local[\"ts\"].dt.tz_convert(\"Europe\u002FLondon\").dt.tz_localize(None)\nlocal = local.rename(columns={\"ts\": \"Time (London)\"})\nlocal.to_excel(\"events_local.xlsx\", index=False)\nprint(local)\n",[19,593,594,604,636,663,681],{"__ignoreMap":30},[228,595,596,599,601],{"class":183,"line":230},[228,597,598],{"class":260},"local ",[228,600,283],{"class":256},[228,602,603],{"class":260}," events.copy()\n",[228,605,606,609,612,615,617,620,622,625,628,631,634],{"class":183,"line":270},[228,607,608],{"class":260},"local[",[228,610,611],{"class":237},"\"ts\"",[228,613,614],{"class":260},"] ",[228,616,283],{"class":256},[228,618,619],{"class":260}," local[",[228,621,611],{"class":237},[228,623,624],{"class":260},"].dt.tz_convert(",[228,626,627],{"class":237},"\"Europe\u002FLondon\"",[228,629,630],{"class":260},").dt.tz_localize(",[228,632,633],{"class":368},"None",[228,635,372],{"class":260},[228,637,638,640,642,645,648,650,653,655,658,661],{"class":183,"line":277},[228,639,598],{"class":260},[228,641,283],{"class":256},[228,643,644],{"class":260}," local.rename(",[228,646,647],{"class":362},"columns",[228,649,283],{"class":256},[228,651,652],{"class":260},"{",[228,654,611],{"class":237},[228,656,657],{"class":260},": ",[228,659,660],{"class":237},"\"Time (London)\"",[228,662,339],{"class":260},[228,664,665,668,671,673,675,677,679],{"class":183,"line":289},[228,666,667],{"class":260},"local.to_excel(",[228,669,670],{"class":237},"\"events_local.xlsx\"",[228,672,301],{"class":260},[228,674,363],{"class":362},[228,676,283],{"class":256},[228,678,369],{"class":368},[228,680,372],{"class":260},[228,682,683,686],{"class":183,"line":310},[228,684,685],{"class":368},"print",[228,687,688],{"class":260},"(local)\n",[10,690,691,692,695],{},"For UTC, ",[19,693,694],{},"tz_convert(None)"," converts to UTC and drops the zone in one step:",[24,697,699],{"className":247,"code":698,"language":249,"meta":30,"style":30},"utc = events.assign(ts=events[\"ts\"].dt.tz_convert(None)).rename(columns={\"ts\": \"Time (UTC)\"})\nutc.to_excel(\"events_utc.xlsx\", index=False)\n",[19,700,701,742],{"__ignoreMap":30},[228,702,703,706,708,711,713,715,718,720,722,724,727,729,731,733,735,737,740],{"class":183,"line":230},[228,704,705],{"class":260},"utc ",[228,707,283],{"class":256},[228,709,710],{"class":260}," events.assign(",[228,712,103],{"class":362},[228,714,283],{"class":256},[228,716,717],{"class":260},"events[",[228,719,611],{"class":237},[228,721,624],{"class":260},[228,723,633],{"class":368},[228,725,726],{"class":260},")).rename(",[228,728,647],{"class":362},[228,730,283],{"class":256},[228,732,652],{"class":260},[228,734,611],{"class":237},[228,736,657],{"class":260},[228,738,739],{"class":237},"\"Time (UTC)\"",[228,741,339],{"class":260},[228,743,744,747,750,752,754,756,758],{"class":183,"line":270},[228,745,746],{"class":260},"utc.to_excel(",[228,748,749],{"class":237},"\"events_utc.xlsx\"",[228,751,301],{"class":260},[228,753,363],{"class":362},[228,755,283],{"class":256},[228,757,369],{"class":368},[228,759,372],{"class":260},[10,761,762,763,765,766,768,769,772],{},"The two look similar and are easy to confuse. ",[19,764,587],{}," on a UTC column keeps the UTC clock time; on a London column it keeps the London time. ",[19,767,694],{}," always produces UTC. Use the pattern ",[14,770,771],{},"convert to the zone you want, then localize to None",", and there is nothing to remember.",[216,774,776],{"id":775},"step-3-fix-every-aware-column-at-once","Step 3 — Fix every aware column at once",[10,778,779],{},"Real frames often have several timestamp columns. Select them all by dtype and convert them in one pass, so a new column added next month does not reintroduce the error:",[24,781,783],{"className":247,"code":782,"language":249,"meta":30,"style":30},"def naive_in(frame: pd.DataFrame, zone: str) -> pd.DataFrame:\n    out = frame.copy()\n    for col in out.select_dtypes(include=[\"datetimetz\"]).columns:\n        out[col] = out[col].dt.tz_convert(zone).dt.tz_localize(None)\n    return out\n\nevents[\"acknowledged\"] = events[\"ts\"] + pd.Timedelta(minutes=12)\nnaive_in(events, \"Europe\u002FLondon\").to_excel(\"events_all.xlsx\", index=False)\nprint(pd.read_excel(\"events_all.xlsx\").dtypes.to_dict())\n",[19,784,785,803,813,841,855,863,867,900,923],{"__ignoreMap":30},[228,786,787,790,794,797,800],{"class":183,"line":230},[228,788,789],{"class":256},"def",[228,791,793],{"class":792},"s_Opv"," naive_in",[228,795,796],{"class":260},"(frame: pd.DataFrame, zone: ",[228,798,799],{"class":368},"str",[228,801,802],{"class":260},") -> pd.DataFrame:\n",[228,804,805,808,810],{"class":183,"line":270},[228,806,807],{"class":260},"    out ",[228,809,283],{"class":256},[228,811,812],{"class":260}," frame.copy()\n",[228,814,815,818,821,824,827,830,832,835,838],{"class":183,"line":277},[228,816,817],{"class":256},"    for",[228,819,820],{"class":260}," col ",[228,822,823],{"class":256},"in",[228,825,826],{"class":260}," out.select_dtypes(",[228,828,829],{"class":362},"include",[228,831,283],{"class":256},[228,833,834],{"class":260},"[",[228,836,837],{"class":237},"\"datetimetz\"",[228,839,840],{"class":260},"]).columns:\n",[228,842,843,846,848,851,853],{"class":183,"line":289},[228,844,845],{"class":260},"        out[col] ",[228,847,283],{"class":256},[228,849,850],{"class":260}," out[col].dt.tz_convert(zone).dt.tz_localize(",[228,852,633],{"class":368},[228,854,372],{"class":260},[228,856,857,860],{"class":183,"line":310},[228,858,859],{"class":256},"    return",[228,861,862],{"class":260}," out\n",[228,864,865],{"class":183,"line":336},[228,866,274],{"emptyLinePlaceholder":273},[228,868,869,871,874,876,878,881,883,885,888,891,894,896,898],{"class":183,"line":342},[228,870,717],{"class":260},[228,872,873],{"class":237},"\"acknowledged\"",[228,875,614],{"class":260},[228,877,283],{"class":256},[228,879,880],{"class":260}," events[",[228,882,611],{"class":237},[228,884,614],{"class":260},[228,886,887],{"class":256},"+",[228,889,890],{"class":260}," pd.Timedelta(",[228,892,893],{"class":362},"minutes",[228,895,283],{"class":256},[228,897,442],{"class":368},[228,899,372],{"class":260},[228,901,902,905,907,910,913,915,917,919,921],{"class":183,"line":351},[228,903,904],{"class":260},"naive_in(events, ",[228,906,627],{"class":237},[228,908,909],{"class":260},").to_excel(",[228,911,912],{"class":237},"\"events_all.xlsx\"",[228,914,301],{"class":260},[228,916,363],{"class":362},[228,918,283],{"class":256},[228,920,369],{"class":368},[228,922,372],{"class":260},[228,924,925,927,930,932],{"class":183,"line":375},[228,926,685],{"class":368},[228,928,929],{"class":260},"(pd.read_excel(",[228,931,912],{"class":237},[228,933,934],{"class":260},").dtypes.to_dict())\n",[10,936,937,938,941],{},"The read-back shows ordinary ",[19,939,940],{},"datetime64"," columns — Excel dates with no zone, exactly what the file can hold.",[216,943,945],{"id":944},"step-4-check-the-index-and-object-columns","Step 4 — Check the index and object columns",[10,947,948,949,951,952,955,956,959],{},"If the error survives Step 3, it is hiding in one of two places. A timezone-aware ",[14,950,363],{}," — common after ",[19,953,954],{},"set_index(\"ts\")"," or resampling — is written with the data unless you pass ",[19,957,958],{},"index=False",", and the column helper does not touch it. Convert the columns and the index separately:",[24,961,963],{"className":247,"code":962,"language":249,"meta":30,"style":30},"by_time = naive_in(events.set_index(\"ts\"), \"Europe\u002FLondon\")   # the remaining columns\nby_time.index = by_time.index.tz_convert(\"Europe\u002FLondon\").tz_localize(None)\nby_time.to_excel(\"events_indexed.xlsx\")\n",[19,964,965,989,1008],{"__ignoreMap":30},[228,966,967,970,972,975,977,980,982,985],{"class":183,"line":230},[228,968,969],{"class":260},"by_time ",[228,971,283],{"class":256},[228,973,974],{"class":260}," naive_in(events.set_index(",[228,976,611],{"class":237},[228,978,979],{"class":260},"), ",[228,981,627],{"class":237},[228,983,984],{"class":260},")   ",[228,986,988],{"class":987},"s-wDw","# the remaining columns\n",[228,990,991,994,996,999,1001,1004,1006],{"class":183,"line":270},[228,992,993],{"class":260},"by_time.index ",[228,995,283],{"class":256},[228,997,998],{"class":260}," by_time.index.tz_convert(",[228,1000,627],{"class":237},[228,1002,1003],{"class":260},").tz_localize(",[228,1005,633],{"class":368},[228,1007,372],{"class":260},[228,1009,1010,1013,1016],{"class":183,"line":277},[228,1011,1012],{"class":260},"by_time.to_excel(",[228,1014,1015],{"class":237},"\"events_indexed.xlsx\"",[228,1017,372],{"class":260},[10,1019,1020,1021,1024,1025,1028,1029,1032,1033,1035,1036,1039],{},"The other hiding place is an ",[19,1022,1023],{},"object"," column that holds individual ",[19,1026,1027],{},"Timestamp"," values with zones — typical when data came from JSON or a list of Python objects. ",[19,1030,1031],{},"select_dtypes(include=\"datetimetz\")"," does not see those, because the column's dtype is ",[19,1034,1023],{},". Convert first with ",[19,1037,1038],{},"pd.to_datetime(col, utc=True)",", which produces a proper UTC column, and then apply the conversion above.",[43,1041,52,1047,52,1050,52,1053,52,1058,52,1062,52,1067,52,1070,52,1074,52,1077,52,1082,52,1086,52,1090,52,1093,52,1100,52,1105],{"viewBox":1042,"role":46,"ariaLabelledBy":1043,"xmlns":50,"style":1046},"64 0 632 331",[1044,1045],"tz-places-t","tz-places-d","width:100%;max-width:632px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[54,1048,1049],{"id":1044},"Where timezone-aware datetimes hide in a DataFrame",[58,1051,1052],{"id":1045},"The error can come from datetime columns with a time zone dtype, from a timezone-aware index, or from object columns that hold individual zoned Timestamp values. Each needs converting before to_excel.",[62,1054],{"x":1055,"y":65,"width":1056,"height":1057,"fill":68},"64","632","331",[62,1059],{"x":1060,"y":439,"width":1061,"height":489,"rx":442,"fill":443,"stroke":188,"style":189},"80.0","600.0",[445,1063,1064],{"transform":447},[449,1065],{"d":1066,"fill":452,"stroke":453,"style":454},"M3 5h18v14H3z M3 10h18 M3 15h18 M9 5v14",[29,1068,1069],{"x":211,"y":441,"style":457},"datetime64[ns, UTC] columns",[29,1071,1073],{"x":211,"y":1072,"style":504},"95.0","select_dtypes(include='datetimetz')",[62,1075],{"x":1060,"y":1076,"width":1061,"height":489,"rx":442,"fill":547,"stroke":548,"style":189},"119.0",[445,1078,1080],{"transform":1079},"translate(368.0,132.0) scale(1.000)",[449,1081],{"d":554,"fill":452,"stroke":555,"style":454},[29,1083,1085],{"x":211,"y":1084,"style":558},"178.5","A timezone-aware index",[29,1087,1089],{"x":211,"y":1088,"style":504},"198.0","df.index.tz is not None",[62,1091],{"x":1060,"y":1092,"width":1061,"height":489,"rx":442,"fill":113,"stroke":114,"style":189},"222.0",[445,1094,1096],{"transform":1095},"translate(368.0,235.0) scale(1.000)",[449,1097],{"d":1098,"fill":452,"stroke":1099,"style":454},"M9 8l-4 4 4 4 M15 8l4 4-4 4","var(--accent-ink,#be185d)",[29,1101,1104],{"x":211,"y":1102,"style":1103},"281.5","font-size:13.5px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","Object columns of Timestamps",[29,1106,1108],{"x":211,"y":1107,"style":504},"301.0","pd.to_datetime(col, utc=True) first",[216,1110,1112],{"id":1111},"step-5-keep-the-zone-where-the-reader-can-see-it","Step 5 — Keep the zone where the reader can see it",[10,1114,1115],{},"Stripping the zone from the cells does not mean losing it. Put it in the column header, as above, or add a small text column when rows come from several zones — for example, a report of deliveries across regions, each shown in its own local time:",[24,1117,1119],{"className":247,"code":1118,"language":249,"meta":30,"style":30},"deliveries = pd.DataFrame({\n    \"region\": [\"London\", \"New York\"],\n    \"zone\": [\"Europe\u002FLondon\", \"America\u002FNew_York\"],\n    \"ts\": pd.to_datetime([\"2026-06-01 14:00\", \"2026-06-01 14:00\"]).tz_localize(\"UTC\"),\n})\ndeliveries[\"local_time\"] = [ts.tz_convert(z).tz_localize(None) for ts, z in zip(deliveries[\"ts\"], deliveries[\"zone\"])]\ndeliveries[\"utc_offset\"] = [ts.tz_convert(z).strftime(\"%z\") for ts, z in zip(deliveries[\"ts\"], deliveries[\"zone\"])]\ndeliveries.drop(columns=\"ts\").to_excel(\"deliveries.xlsx\", index=False)\nprint(deliveries[[\"region\", \"local_time\", \"utc_offset\"]])\n",[19,1120,1121,1130,1147,1163,1182,1186,1231,1268,1294],{"__ignoreMap":30},[228,1122,1123,1126,1128],{"class":183,"line":230},[228,1124,1125],{"class":260},"deliveries ",[228,1127,283],{"class":256},[228,1129,286],{"class":260},[228,1131,1132,1135,1137,1140,1142,1145],{"class":183,"line":270},[228,1133,1134],{"class":237},"    \"region\"",[228,1136,295],{"class":260},[228,1138,1139],{"class":237},"\"London\"",[228,1141,301],{"class":260},[228,1143,1144],{"class":237},"\"New York\"",[228,1146,307],{"class":260},[228,1148,1149,1152,1154,1156,1158,1161],{"class":183,"line":277},[228,1150,1151],{"class":237},"    \"zone\"",[228,1153,295],{"class":260},[228,1155,627],{"class":237},[228,1157,301],{"class":260},[228,1159,1160],{"class":237},"\"America\u002FNew_York\"",[228,1162,307],{"class":260},[228,1164,1165,1167,1169,1172,1174,1176,1178,1180],{"class":183,"line":289},[228,1166,313],{"class":237},[228,1168,316],{"class":260},[228,1170,1171],{"class":237},"\"2026-06-01 14:00\"",[228,1173,301],{"class":260},[228,1175,1171],{"class":237},[228,1177,327],{"class":260},[228,1179,330],{"class":237},[228,1181,333],{"class":260},[228,1183,1184],{"class":183,"line":310},[228,1185,339],{"class":260},[228,1187,1188,1191,1194,1196,1198,1201,1203,1206,1209,1212,1214,1217,1220,1222,1225,1228],{"class":183,"line":336},[228,1189,1190],{"class":260},"deliveries[",[228,1192,1193],{"class":237},"\"local_time\"",[228,1195,614],{"class":260},[228,1197,283],{"class":256},[228,1199,1200],{"class":260}," [ts.tz_convert(z).tz_localize(",[228,1202,633],{"class":368},[228,1204,1205],{"class":260},") ",[228,1207,1208],{"class":256},"for",[228,1210,1211],{"class":260}," ts, z ",[228,1213,823],{"class":256},[228,1215,1216],{"class":368}," zip",[228,1218,1219],{"class":260},"(deliveries[",[228,1221,611],{"class":237},[228,1223,1224],{"class":260},"], deliveries[",[228,1226,1227],{"class":237},"\"zone\"",[228,1229,1230],{"class":260},"])]\n",[228,1232,1233,1235,1238,1240,1242,1245,1248,1250,1252,1254,1256,1258,1260,1262,1264,1266],{"class":183,"line":342},[228,1234,1190],{"class":260},[228,1236,1237],{"class":237},"\"utc_offset\"",[228,1239,614],{"class":260},[228,1241,283],{"class":256},[228,1243,1244],{"class":260}," [ts.tz_convert(z).strftime(",[228,1246,1247],{"class":237},"\"%z\"",[228,1249,1205],{"class":260},[228,1251,1208],{"class":256},[228,1253,1211],{"class":260},[228,1255,823],{"class":256},[228,1257,1216],{"class":368},[228,1259,1219],{"class":260},[228,1261,611],{"class":237},[228,1263,1224],{"class":260},[228,1265,1227],{"class":237},[228,1267,1230],{"class":260},[228,1269,1270,1273,1275,1277,1279,1281,1284,1286,1288,1290,1292],{"class":183,"line":351},[228,1271,1272],{"class":260},"deliveries.drop(",[228,1274,647],{"class":362},[228,1276,283],{"class":256},[228,1278,611],{"class":237},[228,1280,909],{"class":260},[228,1282,1283],{"class":237},"\"deliveries.xlsx\"",[228,1285,301],{"class":260},[228,1287,363],{"class":362},[228,1289,283],{"class":256},[228,1291,369],{"class":368},[228,1293,372],{"class":260},[228,1295,1296,1298,1301,1304,1306,1308,1310,1312],{"class":183,"line":375},[228,1297,685],{"class":368},[228,1299,1300],{"class":260},"(deliveries[[",[228,1302,1303],{"class":237},"\"region\"",[228,1305,301],{"class":260},[228,1307,1193],{"class":237},[228,1309,301],{"class":260},[228,1311,1237],{"class":237},[228,1313,1314],{"class":260},"]])\n",[10,1316,1317,1318,1321,1322,1325],{},"The same instant appears as 15:00 in London (",[19,1319,1320],{},"+0100",", summer time) and 10:00 in New York (",[19,1323,1324],{},"-0400","). With the offset in its own column, a reader can compare rows correctly even though each shows a local clock.",[216,1327,1329],{"id":1328},"step-6-read-the-times-back-into-aware-timestamps","Step 6 — Read the times back into aware timestamps",[10,1331,1332,1333,1335,1336,1339],{},"The reverse trip needs the same care. A workbook column labelled ",[14,1334,572],{}," reads back as naive datetimes; to compare it with UTC data again, attach the zone it was written in. Local clocks are awkward twice a year: in spring an hour does not exist, in autumn an hour happens twice. ",[19,1337,1338],{},"tz_localize"," refuses to guess about either unless you tell it how:",[24,1341,1343],{"className":247,"code":1342,"language":249,"meta":30,"style":30},"back = pd.read_excel(\"events_local.xlsx\")\ntimes = back[\"Time (London)\"]\ntry:\n    times.dt.tz_localize(\"Europe\u002FLondon\")\n    print(\"localised without trouble\")\nexcept Exception as exc:\n    print(type(exc).__name__)\n\nodd = pd.Series(pd.to_datetime([\"2026-03-29 01:30\", \"2026-10-25 01:30\"]))\naware = odd.dt.tz_localize(\"Europe\u002FLondon\", nonexistent=\"shift_forward\", ambiguous=\"NaT\")\nprint(aware.tolist())\n",[19,1344,1345,1359,1374,1380,1389,1401,1412,1429,1433,1453,1487],{"__ignoreMap":30},[228,1346,1347,1350,1352,1355,1357],{"class":183,"line":230},[228,1348,1349],{"class":260},"back ",[228,1351,283],{"class":256},[228,1353,1354],{"class":260}," pd.read_excel(",[228,1356,670],{"class":237},[228,1358,372],{"class":260},[228,1360,1361,1364,1366,1369,1371],{"class":183,"line":270},[228,1362,1363],{"class":260},"times ",[228,1365,283],{"class":256},[228,1367,1368],{"class":260}," back[",[228,1370,660],{"class":237},[228,1372,1373],{"class":260},"]\n",[228,1375,1376,1378],{"class":183,"line":277},[228,1377,345],{"class":256},[228,1379,348],{"class":260},[228,1381,1382,1385,1387],{"class":183,"line":289},[228,1383,1384],{"class":260},"    times.dt.tz_localize(",[228,1386,627],{"class":237},[228,1388,372],{"class":260},[228,1390,1391,1393,1396,1399],{"class":183,"line":310},[228,1392,393],{"class":368},[228,1394,1395],{"class":260},"(",[228,1397,1398],{"class":237},"\"localised without trouble\"",[228,1400,372],{"class":260},[228,1402,1403,1405,1408,1410],{"class":183,"line":336},[228,1404,378],{"class":256},[228,1406,1407],{"class":368}," Exception",[228,1409,384],{"class":256},[228,1411,387],{"class":260},[228,1413,1414,1416,1418,1421,1424,1427],{"class":183,"line":342},[228,1415,393],{"class":368},[228,1417,1395],{"class":260},[228,1419,1420],{"class":368},"type",[228,1422,1423],{"class":260},"(exc).",[228,1425,1426],{"class":368},"__name__",[228,1428,372],{"class":260},[228,1430,1431],{"class":183,"line":351},[228,1432,274],{"emptyLinePlaceholder":273},[228,1434,1435,1438,1440,1443,1445,1447,1450],{"class":183,"line":375},[228,1436,1437],{"class":260},"odd ",[228,1439,283],{"class":256},[228,1441,1442],{"class":260}," pd.Series(pd.to_datetime([",[228,1444,324],{"class":237},[228,1446,301],{"class":260},[228,1448,1449],{"class":237},"\"2026-10-25 01:30\"",[228,1451,1452],{"class":260},"]))\n",[228,1454,1455,1458,1460,1463,1465,1467,1470,1472,1475,1477,1480,1482,1485],{"class":183,"line":390},[228,1456,1457],{"class":260},"aware ",[228,1459,283],{"class":256},[228,1461,1462],{"class":260}," odd.dt.tz_localize(",[228,1464,627],{"class":237},[228,1466,301],{"class":260},[228,1468,1469],{"class":362},"nonexistent",[228,1471,283],{"class":256},[228,1473,1474],{"class":237},"\"shift_forward\"",[228,1476,301],{"class":260},[228,1478,1479],{"class":362},"ambiguous",[228,1481,283],{"class":256},[228,1483,1484],{"class":237},"\"NaT\"",[228,1486,372],{"class":260},[228,1488,1490,1492],{"class":183,"line":1489},11,[228,1491,685],{"class":368},[228,1493,1494],{"class":260},"(aware.tolist())\n",[10,1496,1497,1498,1501,1502,1505],{},"The values written in Step 2 localise cleanly, because they came from real instants. The second series shows the edge cases: 01:30 on 29 March 2026 never happened in London, so ",[19,1499,1500],{},"nonexistent=\"shift_forward\""," moves it to 02:00; 01:30 on 25 October happened twice, so ",[19,1503,1504],{},"ambiguous=\"NaT\""," marks it missing rather than picking one. Choosing those policies explicitly — and reporting any NaT they produce — is safer than letting a default hide an hour of data. If readers edit timestamps by hand in the workbook, expect these cases to appear. Keeping a UTC column alongside the local one, as Step 1 suggested for mixed audiences, avoids the question entirely when the file comes back.",[216,1507,1509],{"id":1508},"writing-with-openpyxl-directly","Writing with openpyxl directly",[10,1511,1512,1513,1516],{},"openpyxl enforces the same rule with its own message when you assign a zoned ",[19,1514,1515],{},"datetime"," to a cell:",[24,1518,1520],{"className":247,"code":1519,"language":249,"meta":30,"style":30},"from datetime import datetime\nfrom zoneinfo import ZoneInfo\n\nfrom openpyxl import Workbook\n\nwb = Workbook()\ntry:\n    wb.active[\"A1\"] = datetime(2026, 3, 29, 1, 30, tzinfo=ZoneInfo(\"UTC\"))\n    wb.save(\"direct.xlsx\")\nexcept TypeError as exc:\n    print(exc)\nwb.active[\"A1\"] = datetime(2026, 3, 29, 1, 30, tzinfo=ZoneInfo(\"UTC\")).astimezone(ZoneInfo(\"Europe\u002FLondon\")).replace(tzinfo=None)\nwb.save(\"direct.xlsx\")\n",[19,1521,1522,1535,1547,1551,1563,1567,1577,1583,1634,1644,1655,1661,1719],{"__ignoreMap":30},[228,1523,1524,1527,1530,1532],{"class":183,"line":230},[228,1525,1526],{"class":256},"from",[228,1528,1529],{"class":260}," datetime ",[228,1531,257],{"class":256},[228,1533,1534],{"class":260}," datetime\n",[228,1536,1537,1539,1542,1544],{"class":183,"line":270},[228,1538,1526],{"class":256},[228,1540,1541],{"class":260}," zoneinfo ",[228,1543,257],{"class":256},[228,1545,1546],{"class":260}," ZoneInfo\n",[228,1548,1549],{"class":183,"line":277},[228,1550,274],{"emptyLinePlaceholder":273},[228,1552,1553,1555,1558,1560],{"class":183,"line":289},[228,1554,1526],{"class":256},[228,1556,1557],{"class":260}," openpyxl ",[228,1559,257],{"class":256},[228,1561,1562],{"class":260}," Workbook\n",[228,1564,1565],{"class":183,"line":310},[228,1566,274],{"emptyLinePlaceholder":273},[228,1568,1569,1572,1574],{"class":183,"line":336},[228,1570,1571],{"class":260},"wb ",[228,1573,283],{"class":256},[228,1575,1576],{"class":260}," Workbook()\n",[228,1578,1579,1581],{"class":183,"line":342},[228,1580,345],{"class":256},[228,1582,348],{"class":260},[228,1584,1585,1588,1591,1593,1595,1598,1601,1603,1605,1607,1610,1612,1614,1616,1619,1621,1624,1626,1629,1631],{"class":183,"line":351},[228,1586,1587],{"class":260},"    wb.active[",[228,1589,1590],{"class":237},"\"A1\"",[228,1592,614],{"class":260},[228,1594,283],{"class":256},[228,1596,1597],{"class":260}," datetime(",[228,1599,1600],{"class":368},"2026",[228,1602,301],{"class":260},[228,1604,125],{"class":368},[228,1606,301],{"class":260},[228,1608,1609],{"class":368},"29",[228,1611,301],{"class":260},[228,1613,96],{"class":368},[228,1615,301],{"class":260},[228,1617,1618],{"class":368},"30",[228,1620,301],{"class":260},[228,1622,1623],{"class":362},"tzinfo",[228,1625,283],{"class":256},[228,1627,1628],{"class":260},"ZoneInfo(",[228,1630,330],{"class":237},[228,1632,1633],{"class":260},"))\n",[228,1635,1636,1639,1642],{"class":183,"line":375},[228,1637,1638],{"class":260},"    wb.save(",[228,1640,1641],{"class":237},"\"direct.xlsx\"",[228,1643,372],{"class":260},[228,1645,1646,1648,1651,1653],{"class":183,"line":390},[228,1647,378],{"class":256},[228,1649,1650],{"class":368}," TypeError",[228,1652,384],{"class":256},[228,1654,387],{"class":260},[228,1656,1657,1659],{"class":183,"line":1489},[228,1658,393],{"class":368},[228,1660,396],{"class":260},[228,1662,1664,1667,1669,1671,1673,1675,1677,1679,1681,1683,1685,1687,1689,1691,1693,1695,1697,1699,1701,1703,1706,1708,1711,1713,1715,1717],{"class":183,"line":1663},12,[228,1665,1666],{"class":260},"wb.active[",[228,1668,1590],{"class":237},[228,1670,614],{"class":260},[228,1672,283],{"class":256},[228,1674,1597],{"class":260},[228,1676,1600],{"class":368},[228,1678,301],{"class":260},[228,1680,125],{"class":368},[228,1682,301],{"class":260},[228,1684,1609],{"class":368},[228,1686,301],{"class":260},[228,1688,96],{"class":368},[228,1690,301],{"class":260},[228,1692,1618],{"class":368},[228,1694,301],{"class":260},[228,1696,1623],{"class":362},[228,1698,283],{"class":256},[228,1700,1628],{"class":260},[228,1702,330],{"class":237},[228,1704,1705],{"class":260},")).astimezone(ZoneInfo(",[228,1707,627],{"class":237},[228,1709,1710],{"class":260},")).replace(",[228,1712,1623],{"class":362},[228,1714,283],{"class":256},[228,1716,633],{"class":368},[228,1718,372],{"class":260},[228,1720,1722,1725,1727],{"class":183,"line":1721},13,[228,1723,1724],{"class":260},"wb.save(",[228,1726,1641],{"class":237},[228,1728,372],{"class":260},[10,1730,1731,1732,1734,1735,1738,1739,1743],{},"The fix is the same: convert to the wanted zone, then remove ",[19,1733,1623],{}," with ",[19,1736,1737],{},"replace(tzinfo=None)",". Formatting the resulting dates for display is covered in ",[37,1740,1742],{"href":1741},"\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",".",[216,1745,1747],{"id":1746},"common-pitfalls","Common pitfalls",[405,1749,1750,1759,1765,1775,1791],{},[408,1751,1752,1755,1756,1758],{},[411,1753,1754],{},"Dropping the zone without converting."," ",[19,1757,587],{}," on a UTC column writes UTC times; readers in another zone will read them as local. Convert first.",[408,1760,1761,1764],{},[411,1762,1763],{},"Daylight saving."," Local times skip or repeat an hour on changeover nights. Converting through the zone handles this correctly; adding a fixed offset does not.",[408,1766,1767,1770,1771,1774],{},[411,1768,1769],{},"Reading the file back and re-localising."," A naive local time read from Excel must be localised with the zone it was written in — ",[19,1772,1773],{},"tz_localize(\"Europe\u002FLondon\")"," — before any further conversion.",[408,1776,1777,1780,1781,1783,1784,1786,1787,1790],{},[411,1778,1779],{},"Mixed zones in one column."," A column built from several sources can contain timestamps in different zones, which pandas stores as ",[19,1782,1023],{},". ",[19,1785,1038],{}," normalises them to one UTC column before any conversion; skipping that step leaves the ",[19,1788,1789],{},".dt"," accessor unavailable and the error in place.",[408,1792,1793,1796],{},[411,1794,1795],{},"Unlabelled columns."," Without \"(UTC)\" or a zone in the header, nobody can tell which clock a column uses a month later.",[216,1798,1800],{"id":1799},"conclusion","Conclusion",[10,1802,1803,1804,1806],{},"Excel dates cannot carry a time zone, so pandas refuses to write aware timestamps. Decide which clock readers need, convert each aware column, index and object column to that zone, drop the zone with ",[19,1805,587],{},", and record the zone in the header or an offset column. Use the same convert-then-strip rule when writing cells with openpyxl.",[216,1808,1810],{"id":1809},"frequently-asked-questions","Frequently asked questions",[10,1812,1813,1816],{},[411,1814,1815],{},"What is the quickest fix?","\nConvert each timezone-aware column to the time zone your readers expect and then drop the zone — call .dt.tz_convert(\"Europe\u002FLondon\") on the column, then .dt.tz_localize(None). The values then write as ordinary Excel dates.",[10,1818,1819,1822],{},[411,1820,1821],{},"Should I use tz_localize(None) or tz_convert(None)?","\nBoth remove the zone but give different clock times. tz_convert(None) converts to UTC first and then drops the zone; tz_localize(None) keeps the local wall-clock time of the current zone. Choose tz_convert to the zone you want, then tz_localize(None).",[10,1824,1825,1828],{},[411,1826,1827],{},"Why do I still get the error after fixing all columns?","\nThe index can be timezone-aware too, and so can an object column holding Timestamp values with a zone. Check df.index and convert object columns with pd.to_datetime before stripping the zone.",[10,1830,1831,1834],{},[411,1832,1833],{},"How do I keep the time zone information for the reader?","\nPut it in the header — \"Created (UTC)\" or \"Created (Europe\u002FLondon)\" — or add a text column with the UTC offset. The cells themselves cannot hold it.",[216,1836,1838],{"id":1837},"related","Related",[405,1840,1841,1847,1854,1861],{},[408,1842,1843,1844,1846],{},"Up one level: ",[37,1845,40],{"href":39}," — every common error explained.",[408,1848,1849,1853],{},[37,1850,1852],{"href":1851},"\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"," — time zones in depth, including reading.",[408,1855,1856,1860],{},[37,1857,1859],{"href":1858},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002F","Working with Dates and Times in Excel Data"," — the date model behind the error.",[408,1862,1863,1867],{},[37,1864,1866],{"href":1865},"\u002Fadvanced-data-transformation-and-cleaning\u002Fmoving-data-between-excel-and-databases\u002Fexport-sql-query-results-to-excel-with-python\u002F","Export SQL Query Results to Excel with Python"," — where aware timestamps usually come from.",[1869,1870,1871],"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 .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}html pre.shiki code .sP0c6, html code.shiki .sP0c6{--shiki-default:#005CC5;--shiki-dark:#91CBFF}html pre.shiki code .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}html pre.shiki code .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}",{"title":30,"searchDepth":270,"depth":270,"links":1873},[1874,1875,1876,1877,1878,1879,1880,1881,1882,1883,1884,1885],{"id":218,"depth":270,"text":219},{"id":399,"depth":270,"text":400},{"id":580,"depth":270,"text":581},{"id":775,"depth":270,"text":776},{"id":944,"depth":270,"text":945},{"id":1111,"depth":270,"text":1112},{"id":1328,"depth":270,"text":1329},{"id":1508,"depth":270,"text":1509},{"id":1746,"depth":270,"text":1747},{"id":1799,"depth":270,"text":1800},{"id":1809,"depth":270,"text":1810},{"id":1837,"depth":270,"text":1838},"2026-09-23","Excel cells cannot store a time zone, so pandas refuses to write timezone-aware timestamps. Decide which clock the reader needs, convert to it, drop the zone, and label the column — for columns, indexes and object columns alike.","md",[1890,1892,1894,1896],{"q":1815,"a":1891},"Convert each timezone-aware column to the time zone your readers expect and then drop the zone — call .dt.tz_convert(\"Europe\u002FLondon\") on the column, then .dt.tz_localize(None). The values then write as ordinary Excel dates.",{"q":1821,"a":1893},"Both remove the zone but give different clock times. tz_convert(None) converts to UTC first and then drops the zone; tz_localize(None) keeps the local wall-clock time of the current zone. Choose tz_convert to the zone you want, then tz_localize(None).",{"q":1827,"a":1895},"The index can be timezone-aware too, and so can an object column holding Timestamp values with a zone. Check df.index and convert object columns with pd.to_datetime before stripping the zone.",{"q":1833,"a":1897},"Put it in the header — \"Created (UTC)\" or \"Created (Europe\u002FLondon)\" — or add a text column with the UTC offset. The cells themselves cannot hold it.","fix excel does not support datetimes with timezones in pandas",null,{"breadcrumb":1901},[1902,1905,1908],{"name":1903,"item":1904},"Home","\u002F",{"name":1906,"item":1907},"Getting Started with Python Excel Automation","\u002Fgetting-started-with-python-excel-automation\u002F",{"name":40,"item":39},"\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-excel-does-not-support-timezones-in-datetimes",{"title":1911,"description":1912},"Fix ValueError: Excel does not support datetimes with timezones","Fix pandas to_excel ValueError \"Excel does not support datetimes with timezones\" — convert to the reader's local time or UTC, drop tzinfo with tz_localize(None), handle indexes and object columns, keep the zone in a label, and daylight saving.","fix-excel-does-not-support-timezones-in-datetimes","getting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-excel-does-not-support-timezones-in-datetimes\u002Findex",[1916,1917,1918],"troubleshooting","dates-and-times","pandas","how-to","G31WjDu6sPpBowCnrvlwfo_qUIeYl2W1ByzCJqt-jgk",[1922,1926],{"title":1923,"path":1924,"stem":1925,"children":-1},"Fix Duplicate Column Names When Reading Excel with pandas","\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-duplicate-column-names-when-reading-excel","getting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-duplicate-column-names-when-reading-excel\u002Findex",{"title":1927,"path":1928,"stem":1929,"children":-1},"Fix \"Excel File Format Cannot Be Determined\" in pandas","\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-excel-file-format-cannot-be-determined-in-pandas","getting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-excel-file-format-cannot-be-determined-in-pandas\u002Findex",[1931,1933],{"path":1932,"title":1906},"\u002Fgetting-started-with-python-excel-automation",{"path":1934,"title":40},"\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors",[],[1937,1941,1944,1947,1950,1953],{"path":1938,"title":1939,"kind":1940},"\u002Fautomating-reporting-workflows\u002Ferror-handling-and-logging-in-excel-automation\u002Fhandle-permission-denied-when-writing-excel-in-python","Handle Permission Denied When Writing Excel in Python","fix",{"path":1942,"title":1943,"kind":1919},"\u002Fgetting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation\u002Fwhat-openpyxl-loses-when-saving-an-existing-workbook","What openpyxl Loses When You Save an Existing Workbook",{"path":1945,"title":1946,"kind":1940},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Ffix-excel-serial-numbers-showing-instead-of-dates","Fix Excel Serial Numbers Showing Instead of Dates",{"path":1948,"title":1949,"kind":1919},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fhandle-daylight-saving-time-in-excel-timestamps","Handle Daylight Saving Time in Excel Timestamps",{"path":1951,"title":1952,"kind":1919},"\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fhandle-microsoft-graph-throttling-for-excel","Handle Microsoft Graph Throttling in Excel Automation",{"path":1954,"title":1955,"kind":1956},"\u002Fautomating-reporting-workflows\u002Ferror-handling-and-logging-in-excel-automation","Error Handling and Logging in Excel Automation","guide",1790972065591]