[{"data":1,"prerenderedAt":1682},["ShallowReactive",2],{"doc:\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fhandle-daylight-saving-time-in-excel-timestamps":3,"surround:\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fhandle-daylight-saving-time-in-excel-timestamps":1648,"ancestors:\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fhandle-daylight-saving-time-in-excel-timestamps":1657,"children:\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fhandle-daylight-saving-time-in-excel-timestamps":1662,"related:\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fhandle-daylight-saving-time-in-excel-timestamps":1663},{"id":4,"title":5,"body":6,"dateModified":1615,"datePublished":1615,"description":1616,"extension":1617,"faq":1618,"keyword":1627,"kind":1628,"meta":1629,"navigation":111,"path":1637,"seo":1638,"slug":1641,"stem":1642,"topics":1643,"type":1646,"__hash__":1647},"docs\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fhandle-daylight-saving-time-in-excel-timestamps\u002Findex.md","Handle Daylight Saving Time in Excel Timestamps",{"type":7,"value":8,"toc":1602},"minimark",[9,39,44,72,79,83,360,381,388,538,559,715,719,722,945,968,1059,1063,1066,1310,1328,1477,1481,1484,1488,1525,1529,1539,1543,1549,1555,1561,1567,1571,1598],[10,11,12,13,17,18,21,22,24,25,28,29,32,33,38],"p",{},"A spreadsheet timestamp is a wall-clock reading: ",[14,15,16],"code",{},"2026-10-25 01:30",". It carries no time zone and no hint of whether daylight saving time was in force. Most of the year that does not matter. Twice a year it does. When the clocks go back, the hour from 01:00 to 02:00 happens twice, so a meter that logs every half hour writes ",[14,19,20],{},"01:30"," two times; when they go forward, an hour is skipped, so ",[14,23,20],{}," never happens at all — unless someone typed it. Subtracting two such timestamps gives durations an hour out, grouping by day puts 25 or 23 hours into one day without saying so, and pandas refuses to attach a time zone at all, raising an error that tells you to use its ",[14,26,27],{},"ambiguous"," or ",[14,30,31],{},"nonexistent"," arguments. This page shows each problem on real clock-change dates for the UK, localises the timestamps safely — inferring the repeated hour from the order of readings — computes true elapsed time, aggregates by day correctly, and writes results back for Excel. It belongs to ",[34,35,37],"a",{"href":36},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002F","Working with Dates and Times in Excel Data",".",[40,41,43],"h2",{"id":42},"prerequisites","Prerequisites",[45,46,51],"pre",{"className":47,"code":48,"language":49,"meta":50,"style":50},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pandas openpyxl\n","bash","",[14,52,53],{"__ignoreMap":50},[54,55,58,62,66,69],"span",{"class":56,"line":57},"line",1,[54,59,61],{"class":60},"sMTad","pip",[54,63,65],{"class":64},"srMev"," install",[54,67,68],{"class":64}," pandas",[54,70,71],{"class":64}," openpyxl\n",[10,73,74,75,78],{},"The examples use the ",[14,76,77],{},"Europe\u002FLondon"," time zone, whose clocks went forward at 01:00 on 29 March 2026 and go back at 02:00 on 25 October 2026; the same code works for any zone in the IANA database. The data is a half-hourly meter export across the October change and a shift log across the March change.",[40,80,82],{"id":81},"step-1-the-repeated-hour","Step 1 — The repeated hour",[45,84,88],{"className":85,"code":86,"language":87,"meta":50,"style":50},"language-python shiki shiki-themes github-light github-dark-high-contrast","import pandas as pd\n\ntimes = pd.date_range(\"2026-10-24 23:00\", periods=10, freq=\"30min\").tolist()\ntimes = times[:6] + [pd.Timestamp(\"2026-10-25 01:00\"), pd.Timestamp(\"2026-10-25 01:30\")] + [pd.Timestamp(\"2026-10-25 02:00\"), pd.Timestamp(\"2026-10-25 02:30\")]\nmeter = pd.DataFrame({\"Reading time\": times, \"kWh\": [1.2, 1.1, 1.0, 0.9, 0.9, 0.8, 0.8, 0.9, 1.0, 1.1]})\nprint(meter.to_string(index=False))\ntry:\n    meter[\"Reading time\"].dt.tz_localize(\"Europe\u002FLondon\")\nexcept Exception as e:\n    print(type(e).__name__ + \":\", e)\n","python",[14,89,90,106,113,154,203,272,292,301,318,333],{"__ignoreMap":50},[54,91,92,96,100,103],{"class":56,"line":57},[54,93,95],{"class":94},"s-kum","import",[54,97,99],{"class":98},"skGVy"," pandas ",[54,101,102],{"class":94},"as",[54,104,105],{"class":98}," pd\n",[54,107,109],{"class":56,"line":108},2,[54,110,112],{"emptyLinePlaceholder":111},true,"\n",[54,114,116,119,122,125,128,131,135,137,141,143,146,148,151],{"class":56,"line":115},3,[54,117,118],{"class":98},"times ",[54,120,121],{"class":94},"=",[54,123,124],{"class":98}," pd.date_range(",[54,126,127],{"class":64},"\"2026-10-24 23:00\"",[54,129,130],{"class":98},", ",[54,132,134],{"class":133},"sa561","periods",[54,136,121],{"class":94},[54,138,140],{"class":139},"sP0c6","10",[54,142,130],{"class":98},[54,144,145],{"class":133},"freq",[54,147,121],{"class":94},[54,149,150],{"class":64},"\"30min\"",[54,152,153],{"class":98},").tolist()\n",[54,155,157,159,161,164,167,170,173,176,179,182,185,188,190,192,195,197,200],{"class":56,"line":156},4,[54,158,118],{"class":98},[54,160,121],{"class":94},[54,162,163],{"class":98}," times[:",[54,165,166],{"class":139},"6",[54,168,169],{"class":98},"] ",[54,171,172],{"class":94},"+",[54,174,175],{"class":98}," [pd.Timestamp(",[54,177,178],{"class":64},"\"2026-10-25 01:00\"",[54,180,181],{"class":98},"), pd.Timestamp(",[54,183,184],{"class":64},"\"2026-10-25 01:30\"",[54,186,187],{"class":98},")] ",[54,189,172],{"class":94},[54,191,175],{"class":98},[54,193,194],{"class":64},"\"2026-10-25 02:00\"",[54,196,181],{"class":98},[54,198,199],{"class":64},"\"2026-10-25 02:30\"",[54,201,202],{"class":98},")]\n",[54,204,206,209,211,214,217,220,223,226,229,231,234,236,239,241,244,246,248,250,253,255,257,259,261,263,265,267,269],{"class":56,"line":205},5,[54,207,208],{"class":98},"meter ",[54,210,121],{"class":94},[54,212,213],{"class":98}," pd.DataFrame({",[54,215,216],{"class":64},"\"Reading time\"",[54,218,219],{"class":98},": times, ",[54,221,222],{"class":64},"\"kWh\"",[54,224,225],{"class":98},": [",[54,227,228],{"class":139},"1.2",[54,230,130],{"class":98},[54,232,233],{"class":139},"1.1",[54,235,130],{"class":98},[54,237,238],{"class":139},"1.0",[54,240,130],{"class":98},[54,242,243],{"class":139},"0.9",[54,245,130],{"class":98},[54,247,243],{"class":139},[54,249,130],{"class":98},[54,251,252],{"class":139},"0.8",[54,254,130],{"class":98},[54,256,252],{"class":139},[54,258,130],{"class":98},[54,260,243],{"class":139},[54,262,130],{"class":98},[54,264,238],{"class":139},[54,266,130],{"class":98},[54,268,233],{"class":139},[54,270,271],{"class":98},"]})\n",[54,273,275,278,281,284,286,289],{"class":56,"line":274},6,[54,276,277],{"class":139},"print",[54,279,280],{"class":98},"(meter.to_string(",[54,282,283],{"class":133},"index",[54,285,121],{"class":94},[54,287,288],{"class":139},"False",[54,290,291],{"class":98},"))\n",[54,293,295,298],{"class":56,"line":294},7,[54,296,297],{"class":94},"try",[54,299,300],{"class":98},":\n",[54,302,304,307,309,312,315],{"class":56,"line":303},8,[54,305,306],{"class":98},"    meter[",[54,308,216],{"class":64},[54,310,311],{"class":98},"].dt.tz_localize(",[54,313,314],{"class":64},"\"Europe\u002FLondon\"",[54,316,317],{"class":98},")\n",[54,319,321,324,327,330],{"class":56,"line":320},9,[54,322,323],{"class":94},"except",[54,325,326],{"class":139}," Exception",[54,328,329],{"class":94}," as",[54,331,332],{"class":98}," e:\n",[54,334,336,339,342,345,348,351,354,357],{"class":56,"line":335},10,[54,337,338],{"class":139},"    print",[54,340,341],{"class":98},"(",[54,343,344],{"class":139},"type",[54,346,347],{"class":98},"(e).",[54,349,350],{"class":139},"__name__",[54,352,353],{"class":94}," +",[54,355,356],{"class":64}," \":\"",[54,358,359],{"class":98},", e)\n",[10,361,362,363,366,367,369,370,373,374,376,377,380],{},"The export shows ",[14,364,365],{},"01:00"," and ",[14,368,20],{}," twice — the first pair still in British Summer Time, the second after the clocks went back to GMT. As naive timestamps they are simply duplicates, so a pivot by time would add the two readings together and a check for duplicate timestamps would flag perfectly good data. ",[14,371,372],{},"tz_localize"," refuses to guess which ",[14,375,365],{}," is which and raises ",[14,378,379],{},"ValueError: Cannot infer dst time from 2026-10-25 01:00:00, try using the 'ambiguous' argument",". The information needed to decide is there, though: the readings are in time order, so the first occurrence must be the summer one.",[40,382,384,385],{"id":383},"step-2-localise-with-ambiguousinfer","Step 2 — Localise with ",[14,386,387],{},"ambiguous=\"infer\"",[45,389,391],{"className":85,"code":390,"language":87,"meta":50,"style":50},"local = meter[\"Reading time\"].dt.tz_localize(\"Europe\u002FLondon\", ambiguous=\"infer\")\nmeter[\"Local\"] = local\nmeter[\"UTC\"] = local.dt.tz_convert(\"UTC\")\nmeter[\"Offset\"] = local.map(lambda t: t.strftime(\"%z\"))\nprint(meter[[\"Reading time\", \"Offset\", \"UTC\"]].to_string(index=False))\ngaps = meter[\"UTC\"].diff().dropna().dt.total_seconds().div(60).unique()\nprint(\"minutes between consecutive readings:\", gaps)\n",[14,392,393,420,435,453,478,506,526],{"__ignoreMap":50},[54,394,395,398,400,403,405,407,409,411,413,415,418],{"class":56,"line":57},[54,396,397],{"class":98},"local ",[54,399,121],{"class":94},[54,401,402],{"class":98}," meter[",[54,404,216],{"class":64},[54,406,311],{"class":98},[54,408,314],{"class":64},[54,410,130],{"class":98},[54,412,27],{"class":133},[54,414,121],{"class":94},[54,416,417],{"class":64},"\"infer\"",[54,419,317],{"class":98},[54,421,422,425,428,430,432],{"class":56,"line":108},[54,423,424],{"class":98},"meter[",[54,426,427],{"class":64},"\"Local\"",[54,429,169],{"class":98},[54,431,121],{"class":94},[54,433,434],{"class":98}," local\n",[54,436,437,439,442,444,446,449,451],{"class":56,"line":115},[54,438,424],{"class":98},[54,440,441],{"class":64},"\"UTC\"",[54,443,169],{"class":98},[54,445,121],{"class":94},[54,447,448],{"class":98}," local.dt.tz_convert(",[54,450,441],{"class":64},[54,452,317],{"class":98},[54,454,455,457,460,462,464,467,470,473,476],{"class":56,"line":156},[54,456,424],{"class":98},[54,458,459],{"class":64},"\"Offset\"",[54,461,169],{"class":98},[54,463,121],{"class":94},[54,465,466],{"class":98}," local.map(",[54,468,469],{"class":94},"lambda",[54,471,472],{"class":98}," t: t.strftime(",[54,474,475],{"class":64},"\"%z\"",[54,477,291],{"class":98},[54,479,480,482,485,487,489,491,493,495,498,500,502,504],{"class":56,"line":205},[54,481,277],{"class":139},[54,483,484],{"class":98},"(meter[[",[54,486,216],{"class":64},[54,488,130],{"class":98},[54,490,459],{"class":64},[54,492,130],{"class":98},[54,494,441],{"class":64},[54,496,497],{"class":98},"]].to_string(",[54,499,283],{"class":133},[54,501,121],{"class":94},[54,503,288],{"class":139},[54,505,291],{"class":98},[54,507,508,511,513,515,517,520,523],{"class":56,"line":274},[54,509,510],{"class":98},"gaps ",[54,512,121],{"class":94},[54,514,402],{"class":98},[54,516,441],{"class":64},[54,518,519],{"class":98},"].diff().dropna().dt.total_seconds().div(",[54,521,522],{"class":139},"60",[54,524,525],{"class":98},").unique()\n",[54,527,528,530,532,535],{"class":56,"line":294},[54,529,277],{"class":139},[54,531,341],{"class":98},[54,533,534],{"class":64},"\"minutes between consecutive readings:\"",[54,536,537],{"class":98},", gaps)\n",[10,539,540,542,543,546,547,550,551,554,555,558],{},[14,541,387],{}," uses the order of the timestamps to decide: where the wall clock jumps backwards, it assigns the earlier copies to summer time and the later ones to winter time. The offset column shows the switch from ",[14,544,545],{},"+0100"," to ",[14,548,549],{},"+0000",", and in UTC the readings are exactly thirty minutes apart all the way through — proof that nothing is duplicated and nothing is missing. ",[14,552,553],{},"infer"," only works on data sorted in time order; for unsorted data, or a single value, pass a boolean array instead (",[14,556,557],{},"True"," for daylight time) built from another column, such as a sequence number from the meter.",[560,561,569,570,569,574,569,578,569,586,569,593,569,603,569,610,569,615,569,619,569,623,569,629,569,635,569,639,569,641,569,643,569,646,569,650,569,653,569,655,569,657,569,662,569,668,569,672,569,676,569,678,569,680,569,683,569,688,569,692,569,694,569,696,569,698,569,703,569,708,569,711,569,713],"svg",{"viewBox":562,"role":563,"ariaLabelledBy":564,"xmlns":567,"style":568},"4 0 752 245","img",[565,566],"dst-back-t","dst-back-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  ",[571,572,573],"title",{"id":565},"The night the clocks go back",[575,576,577],"desc",{"id":566},"On 25 October 2026 in London the clock reads 00:30, 01:00 and 01:30 in summer time, then goes back from 02:00 to 01:00, so 01:00 and 01:30 occur again in winter time before 02:00. In UTC the readings are evenly spaced thirty minutes apart.",[579,580],"rect",{"x":581,"y":582,"width":583,"height":584,"fill":585},"4","0","752","245","#ffffff",[56,587],{"x1":588,"y1":589,"x2":590,"y2":589,"stroke":591,"style":592},"64.0","122.5","696.0","var(--muted,#5b6780)","stroke-width:2.5px",[579,594],{"x":595,"y":596,"width":597,"height":598,"rx":599,"fill":600,"stroke":601,"style":602},"20.0","16.0","112.0","74.5","12","#e7ebef","var(--line,#cdd5e6)","stroke-width:2px",[604,605,609],"text",{"x":606,"y":607,"style":608},"76.0","49.0","font-size:12.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","00:30 BST",[604,611,614],{"x":606,"y":612,"style":613},"67.5","font-size:11px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","23:30 UTC",[56,616],{"x1":606,"y1":617,"x2":606,"y2":589,"stroke":591,"style":618},"90.5","stroke-width:1.5px",[620,621],"circle",{"cx":606,"cy":589,"r":622,"fill":591,"stroke":585,"style":602},"7.0",[579,624],{"x":625,"y":626,"width":597,"height":598,"rx":599,"fill":627,"stroke":628,"style":602},"141.6","154.5","#f0f4ff","var(--brand,#5b5cf0)",[604,630,634],{"x":631,"y":632,"style":633},"197.6","187.5","font-size:12.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","01:00 BST",[604,636,638],{"x":631,"y":637,"style":613},"206.0","00:00 UTC",[56,640],{"x1":631,"y1":589,"x2":631,"y2":626,"stroke":591,"style":618},[620,642],{"cx":631,"cy":589,"r":622,"fill":628,"stroke":585,"style":602},[579,644],{"x":645,"y":596,"width":597,"height":598,"rx":599,"fill":627,"stroke":628,"style":602},"263.2",[604,647,649],{"x":648,"y":607,"style":633},"319.2","01:30 BST",[604,651,652],{"x":648,"y":612,"style":613},"00:30 UTC",[56,654],{"x1":648,"y1":617,"x2":648,"y2":589,"stroke":591,"style":618},[620,656],{"cx":648,"cy":589,"r":622,"fill":628,"stroke":585,"style":602},[579,658],{"x":659,"y":626,"width":597,"height":598,"rx":599,"fill":660,"stroke":661,"style":602},"384.8","#fdefd8","var(--gold,#b4740a)",[604,663,667],{"x":664,"y":665,"style":666},"440.8","179.0","font-size:12.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","01:00 GMT",[604,669,671],{"x":664,"y":670,"style":613},"197.5","01:00 UTC —",[604,673,675],{"x":664,"y":674,"style":613},"214.5","repeated",[56,677],{"x1":664,"y1":589,"x2":664,"y2":626,"stroke":591,"style":618},[620,679],{"cx":664,"cy":589,"r":622,"fill":661,"stroke":585,"style":602},[579,681],{"x":682,"y":596,"width":597,"height":598,"rx":599,"fill":660,"stroke":661,"style":602},"506.4",[604,684,687],{"x":685,"y":686,"style":666},"562.4","40.5","01:30 GMT",[604,689,691],{"x":685,"y":690,"style":613},"59.0","01:30 UTC —",[604,693,675],{"x":685,"y":606,"style":613},[56,695],{"x1":685,"y1":617,"x2":685,"y2":589,"stroke":591,"style":618},[620,697],{"cx":685,"cy":589,"r":622,"fill":661,"stroke":585,"style":602},[579,699],{"x":700,"y":626,"width":597,"height":598,"rx":599,"fill":701,"stroke":702,"style":602},"628.0","#d9f4f1","var(--teal,#0f9488)",[604,704,707],{"x":705,"y":632,"style":706},"684.0","font-size:12.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","02:00 GMT",[604,709,710],{"x":705,"y":637,"style":613},"02:00 UTC",[56,712],{"x1":705,"y1":589,"x2":705,"y2":626,"stroke":591,"style":618},[620,714],{"cx":705,"cy":589,"r":622,"fill":702,"stroke":585,"style":602},[40,716,718],{"id":717},"step-3-the-missing-hour-and-real-durations","Step 3 — The missing hour, and real durations",[10,720,721],{},"In spring the opposite happens. A shift log records start and end times on the wall clock; across the change, simple subtraction is an hour out, and a mistyped time inside the gap does not exist:",[45,723,725],{"className":85,"code":724,"language":87,"meta":50,"style":50},"shifts = pd.DataFrame({\"Shift\": [\"Night A\", \"Night B\", \"Early\"],\n                       \"Start\": pd.to_datetime([\"2026-03-28 22:00\", \"2026-03-29 00:30\", \"2026-03-29 01:30\"]),\n                       \"End\": pd.to_datetime([\"2026-03-29 06:00\", \"2026-03-29 08:30\", \"2026-03-29 09:30\"])})\nshifts[\"Wall-clock hours\"] = (shifts[\"End\"] - shifts[\"Start\"]).dt.total_seconds() \u002F 3600\nstart = shifts[\"Start\"].dt.tz_localize(\"Europe\u002FLondon\", nonexistent=\"NaT\")\nend = shifts[\"End\"].dt.tz_localize(\"Europe\u002FLondon\", nonexistent=\"NaT\")\nshifts[\"Actual hours\"] = (end - start).dt.total_seconds() \u002F 3600\nshifts[\"Start is valid\"] = start.notna()\nprint(shifts.to_string(index=False))\n",[14,726,727,757,781,804,842,868,893,916,930],{"__ignoreMap":50},[54,728,729,732,734,736,739,741,744,746,749,751,754],{"class":56,"line":57},[54,730,731],{"class":98},"shifts ",[54,733,121],{"class":94},[54,735,213],{"class":98},[54,737,738],{"class":64},"\"Shift\"",[54,740,225],{"class":98},[54,742,743],{"class":64},"\"Night A\"",[54,745,130],{"class":98},[54,747,748],{"class":64},"\"Night B\"",[54,750,130],{"class":98},[54,752,753],{"class":64},"\"Early\"",[54,755,756],{"class":98},"],\n",[54,758,759,762,765,768,770,773,775,778],{"class":56,"line":108},[54,760,761],{"class":64},"                       \"Start\"",[54,763,764],{"class":98},": pd.to_datetime([",[54,766,767],{"class":64},"\"2026-03-28 22:00\"",[54,769,130],{"class":98},[54,771,772],{"class":64},"\"2026-03-29 00:30\"",[54,774,130],{"class":98},[54,776,777],{"class":64},"\"2026-03-29 01:30\"",[54,779,780],{"class":98},"]),\n",[54,782,783,786,788,791,793,796,798,801],{"class":56,"line":115},[54,784,785],{"class":64},"                       \"End\"",[54,787,764],{"class":98},[54,789,790],{"class":64},"\"2026-03-29 06:00\"",[54,792,130],{"class":98},[54,794,795],{"class":64},"\"2026-03-29 08:30\"",[54,797,130],{"class":98},[54,799,800],{"class":64},"\"2026-03-29 09:30\"",[54,802,803],{"class":98},"])})\n",[54,805,806,809,812,814,816,819,822,824,827,830,833,836,839],{"class":56,"line":156},[54,807,808],{"class":98},"shifts[",[54,810,811],{"class":64},"\"Wall-clock hours\"",[54,813,169],{"class":98},[54,815,121],{"class":94},[54,817,818],{"class":98}," (shifts[",[54,820,821],{"class":64},"\"End\"",[54,823,169],{"class":98},[54,825,826],{"class":94},"-",[54,828,829],{"class":98}," shifts[",[54,831,832],{"class":64},"\"Start\"",[54,834,835],{"class":98},"]).dt.total_seconds() ",[54,837,838],{"class":94},"\u002F",[54,840,841],{"class":139}," 3600\n",[54,843,844,847,849,851,853,855,857,859,861,863,866],{"class":56,"line":205},[54,845,846],{"class":98},"start ",[54,848,121],{"class":94},[54,850,829],{"class":98},[54,852,832],{"class":64},[54,854,311],{"class":98},[54,856,314],{"class":64},[54,858,130],{"class":98},[54,860,31],{"class":133},[54,862,121],{"class":94},[54,864,865],{"class":64},"\"NaT\"",[54,867,317],{"class":98},[54,869,870,873,875,877,879,881,883,885,887,889,891],{"class":56,"line":274},[54,871,872],{"class":98},"end ",[54,874,121],{"class":94},[54,876,829],{"class":98},[54,878,821],{"class":64},[54,880,311],{"class":98},[54,882,314],{"class":64},[54,884,130],{"class":98},[54,886,31],{"class":133},[54,888,121],{"class":94},[54,890,865],{"class":64},[54,892,317],{"class":98},[54,894,895,897,900,902,904,907,909,912,914],{"class":56,"line":294},[54,896,808],{"class":98},[54,898,899],{"class":64},"\"Actual hours\"",[54,901,169],{"class":98},[54,903,121],{"class":94},[54,905,906],{"class":98}," (end ",[54,908,826],{"class":94},[54,910,911],{"class":98}," start).dt.total_seconds() ",[54,913,838],{"class":94},[54,915,841],{"class":139},[54,917,918,920,923,925,927],{"class":56,"line":303},[54,919,808],{"class":98},[54,921,922],{"class":64},"\"Start is valid\"",[54,924,169],{"class":98},[54,926,121],{"class":94},[54,928,929],{"class":98}," start.notna()\n",[54,931,932,934,937,939,941,943],{"class":56,"line":320},[54,933,277],{"class":139},[54,935,936],{"class":98},"(shifts.to_string(",[54,938,283],{"class":133},[54,940,121],{"class":94},[54,942,288],{"class":139},[54,944,291],{"class":98},[10,946,947,948,952,953,956,957,366,960,963,964,38],{},"The night shift from 22:00 to 06:00 looks like eight hours but lasts seven, because the clocks went forward in the middle; paying it as eight, or measuring it against an eight-hour target, is wrong by an hour. Subtracting ",[949,950,951],"em",{},"time-zone-aware"," timestamps gives the real elapsed time. The early shift claims to start at 01:30 on 29 March, a time that did not exist in London that night. ",[14,954,955],{},"nonexistent=\"NaT\""," turns it into a missing value so it is flagged rather than silently moved; the alternatives ",[14,958,959],{},"\"shift_forward\"",[14,961,962],{},"\"shift_backward\""," move it to the edge of the gap, which is right when you know the device clock skipped the hour and wrong when a person mistyped. Durations over 24 hours and shift arithmetic are covered in ",[34,965,967],{"href":966},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fcalculate-working-hours-between-two-timestamps\u002F","Calculate Working Hours Between Two Timestamps",[560,969,569,974,569,977,569,980,569,983,569,988,569,999,569,1005,569,1011,569,1016,569,1020,569,1024,569,1028,569,1031,569,1038,569,1043,569,1047,569,1050,569,1053,569,1056],{"viewBox":970,"role":563,"ariaLabelledBy":971,"xmlns":567,"style":568},"4 0 752 233",[972,973],"dst-duration-t","dst-duration-d",[571,975,976],{"id":972},"Wall-clock and real durations across the spring change",[575,978,979],{"id":973},"A night shift from 22:00 on 28 March to 06:00 on 29 March 2026 shows eight hours of wall-clock time but lasts seven real hours, because the clocks went forward at 01:00. A start time of 01:30 on 29 March did not exist and is flagged.",[579,981],{"x":581,"y":582,"width":583,"height":982,"fill":585},"233",[579,984],{"x":595,"y":596,"width":985,"height":986,"rx":987,"fill":660,"stroke":661,"style":602},"351.0","201.0","14",[989,990,992],"g",{"transform":991},"translate(182.5,29.0) scale(1.083)",[993,994],"path",{"d":995,"fill":996,"stroke":997,"style":998},"M12 3a9 9 0 1 0 .01 0z M12 7v5l3.5 2","none","var(--gold-ink,#7a4e06)","stroke-width:1.66px;stroke-linecap:round;stroke-linejoin:round",[604,1000,1004],{"x":1001,"y":1002,"style":1003},"195.5","78.0","font-size:14px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Naive subtraction",[56,1006],{"x1":1007,"y1":1008,"x2":1009,"y2":1008,"stroke":661,"style":1010},"36.0","91.0","355.0","stroke-width:1px",[604,1012,1015],{"x":1001,"y":1013,"style":1014},"111.0","font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","22:00 → 06:00 = 8 h",[604,1017,1019],{"x":1001,"y":1018,"style":1014},"137.0","ignores the clock change",[604,1021,1023],{"x":1001,"y":1022,"style":1014},"163.0","01:30 accepted",[604,1025,1027],{"x":1001,"y":1026,"style":1014},"189.0","pay and targets wrong",[579,1029],{"x":1030,"y":596,"width":985,"height":986,"rx":987,"fill":701,"stroke":702,"style":602},"389.0",[989,1032,1034],{"transform":1033},"translate(551.5,29.0) scale(1.083)",[993,1035],{"d":1036,"fill":996,"stroke":1037,"style":998},"M5 12.5l4.5 4.5L19 7","var(--teal-ink,#0b6157)",[604,1039,1042],{"x":1040,"y":1002,"style":1041},"564.5","font-size:14px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Time-zone-aware",[56,1044],{"x1":1045,"y1":1008,"x2":1046,"y2":1008,"stroke":702,"style":1010},"405.0","724.0",[604,1048,1049],{"x":1040,"y":1013,"style":1014},"22:00 → 06:00 = 7 h",[604,1051,1052],{"x":1040,"y":1018,"style":1014},"true elapsed time",[604,1054,1055],{"x":1040,"y":1022,"style":1014},"01:30 → NaT, flagged",[604,1057,1058],{"x":1040,"y":1026,"style":1014},"correct for pay and SLAs",[40,1060,1062],{"id":1061},"step-4-daily-totals-and-writing-back-to-excel","Step 4 — Daily totals and writing back to Excel",[10,1064,1065],{},"Grouping by local calendar day is still right — a day is a day to the people reading the report — but the day of a clock change has 23 or 25 hours, which matters for averages and for checks on reading counts:",[45,1067,1069],{"className":85,"code":1068,"language":87,"meta":50,"style":50},"day = meter.set_index(\"Local\")[\"kWh\"].groupby(lambda t: t.date()).agg([\"sum\", \"count\"])\nprint(day)\nprint(\"hours on 25 Oct 2026:\", (pd.Timestamp(\"2026-10-26\", tz=\"Europe\u002FLondon\") - pd.Timestamp(\"2026-10-25\", tz=\"Europe\u002FLondon\")) \u002F pd.Timedelta(hours=1))\n\nout = meter.assign(**{\"Local time\": meter[\"Local\"].dt.tz_localize(None), \"UTC time\": meter[\"UTC\"].dt.tz_localize(None),\n                      \"Zone\": meter[\"Offset\"].map({\"+0100\": \"BST\", \"+0000\": \"GMT\"})})[[\"Local time\", \"Zone\", \"UTC time\", \"kWh\"]]\nout.to_excel(\"meter_dst.xlsx\", index=False)\nprint(out.to_string(index=False))\n",[14,1070,1071,1107,1114,1175,1179,1225,1277,1295],{"__ignoreMap":50},[54,1072,1073,1076,1078,1081,1083,1086,1088,1091,1093,1096,1099,1101,1104],{"class":56,"line":57},[54,1074,1075],{"class":98},"day ",[54,1077,121],{"class":94},[54,1079,1080],{"class":98}," meter.set_index(",[54,1082,427],{"class":64},[54,1084,1085],{"class":98},")[",[54,1087,222],{"class":64},[54,1089,1090],{"class":98},"].groupby(",[54,1092,469],{"class":94},[54,1094,1095],{"class":98}," t: t.date()).agg([",[54,1097,1098],{"class":64},"\"sum\"",[54,1100,130],{"class":98},[54,1102,1103],{"class":64},"\"count\"",[54,1105,1106],{"class":98},"])\n",[54,1108,1109,1111],{"class":56,"line":108},[54,1110,277],{"class":139},[54,1112,1113],{"class":98},"(day)\n",[54,1115,1116,1118,1120,1123,1126,1129,1131,1134,1136,1138,1141,1143,1146,1149,1151,1153,1155,1157,1160,1162,1165,1168,1170,1173],{"class":56,"line":115},[54,1117,277],{"class":139},[54,1119,341],{"class":98},[54,1121,1122],{"class":64},"\"hours on 25 Oct 2026:\"",[54,1124,1125],{"class":98},", (pd.Timestamp(",[54,1127,1128],{"class":64},"\"2026-10-26\"",[54,1130,130],{"class":98},[54,1132,1133],{"class":133},"tz",[54,1135,121],{"class":94},[54,1137,314],{"class":64},[54,1139,1140],{"class":98},") ",[54,1142,826],{"class":94},[54,1144,1145],{"class":98}," pd.Timestamp(",[54,1147,1148],{"class":64},"\"2026-10-25\"",[54,1150,130],{"class":98},[54,1152,1133],{"class":133},[54,1154,121],{"class":94},[54,1156,314],{"class":64},[54,1158,1159],{"class":98},")) ",[54,1161,838],{"class":94},[54,1163,1164],{"class":98}," pd.Timedelta(",[54,1166,1167],{"class":133},"hours",[54,1169,121],{"class":94},[54,1171,1172],{"class":139},"1",[54,1174,291],{"class":98},[54,1176,1177],{"class":56,"line":156},[54,1178,112],{"emptyLinePlaceholder":111},[54,1180,1181,1184,1186,1189,1192,1195,1198,1201,1203,1205,1208,1211,1214,1216,1218,1220,1222],{"class":56,"line":205},[54,1182,1183],{"class":98},"out ",[54,1185,121],{"class":94},[54,1187,1188],{"class":98}," meter.assign(",[54,1190,1191],{"class":94},"**",[54,1193,1194],{"class":98},"{",[54,1196,1197],{"class":64},"\"Local time\"",[54,1199,1200],{"class":98},": meter[",[54,1202,427],{"class":64},[54,1204,311],{"class":98},[54,1206,1207],{"class":139},"None",[54,1209,1210],{"class":98},"), ",[54,1212,1213],{"class":64},"\"UTC time\"",[54,1215,1200],{"class":98},[54,1217,441],{"class":64},[54,1219,311],{"class":98},[54,1221,1207],{"class":139},[54,1223,1224],{"class":98},"),\n",[54,1226,1227,1230,1232,1234,1237,1240,1243,1246,1248,1251,1253,1256,1259,1261,1263,1266,1268,1270,1272,1274],{"class":56,"line":274},[54,1228,1229],{"class":64},"                      \"Zone\"",[54,1231,1200],{"class":98},[54,1233,459],{"class":64},[54,1235,1236],{"class":98},"].map({",[54,1238,1239],{"class":64},"\"+0100\"",[54,1241,1242],{"class":98},": ",[54,1244,1245],{"class":64},"\"BST\"",[54,1247,130],{"class":98},[54,1249,1250],{"class":64},"\"+0000\"",[54,1252,1242],{"class":98},[54,1254,1255],{"class":64},"\"GMT\"",[54,1257,1258],{"class":98},"})})[[",[54,1260,1197],{"class":64},[54,1262,130],{"class":98},[54,1264,1265],{"class":64},"\"Zone\"",[54,1267,130],{"class":98},[54,1269,1213],{"class":64},[54,1271,130],{"class":98},[54,1273,222],{"class":64},[54,1275,1276],{"class":98},"]]\n",[54,1278,1279,1282,1285,1287,1289,1291,1293],{"class":56,"line":294},[54,1280,1281],{"class":98},"out.to_excel(",[54,1283,1284],{"class":64},"\"meter_dst.xlsx\"",[54,1286,130],{"class":98},[54,1288,283],{"class":133},[54,1290,121],{"class":94},[54,1292,288],{"class":139},[54,1294,317],{"class":98},[54,1296,1297,1299,1302,1304,1306,1308],{"class":56,"line":303},[54,1298,277],{"class":139},[54,1300,1301],{"class":98},"(out.to_string(",[54,1303,283],{"class":133},[54,1305,121],{"class":94},[54,1307,288],{"class":139},[54,1309,291],{"class":98},[10,1311,1312,1313,1316,1317,1320,1321,1323,1324,38],{},"Subtracting midnight from the next midnight in the London zone gives 25 hours for 25 October 2026, so a check that expects 48 half-hourly readings per day should expect 50 that day and 46 on the day in March. Excel cannot store time-zone-aware values, so before writing, remove the zone with ",[14,1314,1315],{},"tz_localize(None)"," — keeping local time for people and a UTC column for anything that needs to sort, subtract or join reliably — and add a column saying which offset applied. With ",[949,1318,1319],{},"Zone"," next to it, the repeated ",[14,1322,365],{}," is no longer a mystery to someone reading the sheet. Converting between zones more generally is covered in ",[34,1325,1327],{"href":1326},"\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",[560,1329,569,1334,569,1337,569,1340,569,1343,569,1347,569,1355,569,1361,569,1365,569,1370,569,1375,569,1380,569,1383,569,1389,569,1394,569,1397,569,1400,569,1404,569,1407,569,1410,569,1416,569,1422,569,1426,569,1430,569,1433,569,1436,569,1442,569,1447,569,1450,569,1454,569,1457,569,1460,569,1466,569,1470,569,1474],{"viewBox":1330,"role":563,"ariaLabelledBy":1331,"xmlns":567,"style":568},"4 0 752 142",[1332,1333],"dst-recipe-t","dst-recipe-d",[571,1335,1336],{"id":1332},"A DST-safe pipeline for Excel timestamps",[575,1338,1339],{"id":1333},"Read the naive timestamps, sort them, attach the local zone with ambiguous infer and nonexistent NaT, flag any missing values, do arithmetic and joins in UTC, and write local time, UTC time and the zone back to Excel as naive columns.",[579,1341],{"x":581,"y":582,"width":583,"height":1342,"fill":585},"142",[579,1344],{"x":595,"y":596,"width":1345,"height":1346,"rx":599,"fill":600,"stroke":601,"style":602},"115.2","110.5",[989,1348,1350],{"transform":1349},"translate(65.6,29.0) scale(1.000)",[993,1351],{"d":1352,"fill":996,"stroke":1353,"style":1354},"M3 5h18v14H3z M3 10h18 M3 15h18 M9 5v14","var(--text,#172033)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[604,1356,1360],{"x":1357,"y":1358,"style":1359},"77.6","75.5","font-size:13.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","Read naive",[604,1362,1364],{"x":1357,"y":1363,"style":1359},"93.0","times",[604,1366,1369],{"x":1357,"y":1367,"style":1368},"112.5","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","from Excel",[56,1371],{"x1":1372,"y1":1373,"x2":1374,"y2":1373,"stroke":628,"style":602},"140.2","71.2","162.2",[1376,1377],"polygon",{"points":1378,"fill":1379},"166.2,71.2 157.2,76.2 157.2,66.2","#5b5cf0",[579,1381],{"x":1382,"y":596,"width":1345,"height":1346,"rx":599,"fill":627,"stroke":628,"style":602},"171.2",[989,1384,1386],{"transform":1385},"translate(216.8,29.0) scale(1.000)",[993,1387],{"d":995,"fill":996,"stroke":1388,"style":1354},"var(--brand-strong,#4338ca)",[604,1390,1393],{"x":1391,"y":1358,"style":1392},"228.8","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Sort +",[604,1395,1396],{"x":1391,"y":1363,"style":1392},"localise",[604,1398,1399],{"x":1391,"y":1367,"style":1368},"ambiguous='infer'",[56,1401],{"x1":1402,"y1":1373,"x2":1403,"y2":1373,"stroke":628,"style":602},"291.4","313.4",[1376,1405],{"points":1406,"fill":1379},"317.4,71.2 308.4,76.2 308.4,66.2",[579,1408],{"x":1409,"y":596,"width":1345,"height":1346,"rx":599,"fill":660,"stroke":661,"style":602},"322.4",[989,1411,1413],{"transform":1412},"translate(368.0,37.8) scale(1.000)",[993,1414],{"d":1415,"fill":996,"stroke":997,"style":1354},"M12 3.5l9.5 17h-19z M12 10v5 M12 17.8v.4",[604,1417,1421],{"x":1418,"y":1419,"style":1420},"380.0","84.2","font-size:13.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Flag gaps",[604,1423,1425],{"x":1418,"y":1424,"style":1368},"103.8","nonexistent='NaT'",[56,1427],{"x1":1428,"y1":1373,"x2":1429,"y2":1373,"stroke":628,"style":602},"442.6","464.6",[1376,1431],{"points":1432,"fill":1379},"468.6,71.2 459.6,76.2 459.6,66.2",[579,1434],{"x":1435,"y":596,"width":1345,"height":1346,"rx":599,"fill":701,"stroke":702,"style":602},"473.6",[989,1437,1439],{"transform":1438},"translate(519.2,37.8) scale(1.000)",[993,1440],{"d":1441,"fill":996,"stroke":1037,"style":1354},"M20 11a8 8 0 1 0-2.3 5.7 M20 4v7h-7",[604,1443,1446],{"x":1444,"y":1419,"style":1445},"531.2","font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Work in UTC",[604,1448,1449],{"x":1444,"y":1424,"style":1368},"durations, joins",[56,1451],{"x1":1452,"y1":1373,"x2":1453,"y2":1373,"stroke":628,"style":602},"593.8","615.8",[1376,1455],{"points":1456,"fill":1379},"619.8,71.2 610.8,76.2 610.8,66.2",[579,1458],{"x":1459,"y":596,"width":1345,"height":1346,"rx":599,"fill":600,"stroke":601,"style":602},"624.8",[989,1461,1463],{"transform":1462},"translate(670.4,29.0) scale(1.000)",[993,1464],{"d":1465,"fill":996,"stroke":1353,"style":1354},"M6 3h9l4 4v14H6z M15 3v4h4",[604,1467,1469],{"x":1468,"y":1358,"style":1359},"682.4","Write back",[604,1471,1473],{"x":1468,"y":1472,"style":1368},"95.0","local, UTC,",[604,1475,1476],{"x":1468,"y":1367,"style":1368},"zone",[40,1478,1480],{"id":1479},"where-the-clock-change-problems-hide","Where the clock-change problems hide",[10,1482,1483],{},"Most data never touches the two awkward hours, which is why these bugs survive for years. They tend to appear in three places. Overnight operations — shifts, batch jobs, meters, call centres — have data inside the gap or the repeat every year. Durations and rates computed from naive timestamps are an hour out for anything spanning the change, which shows up as one strangely short or long shift, or a missed service-level target, twice a year. And joins on timestamps between systems where one stores UTC and the other local time drift by an hour for half the year and match for the other half, which is the hardest of all to spot. The defence is the same in each case: attach the zone as soon as the data is read, do arithmetic and joins in UTC, and convert back to local time only for display.",[40,1485,1487],{"id":1486},"common-pitfalls","Common pitfalls",[1489,1490,1491,1499,1505,1513,1519],"ul",{},[1492,1493,1494,1498],"li",{},[1495,1496,1497],"strong",{},"Treating repeated times as duplicates."," On the night the clocks go back they are distinct readings.",[1492,1500,1501,1504],{},[1495,1502,1503],{},"Naive subtraction across a change."," Localise first; real elapsed time differs by an hour.",[1492,1506,1507,1512],{},[1495,1508,1509,1511],{},[14,1510,387],{}," on unsorted data."," Sort first, or pass an explicit boolean array.",[1492,1514,1515,1518],{},[1495,1516,1517],{},"Shifting nonexistent times silently."," Flag them unless you know the device skipped the hour.",[1492,1520,1521,1524],{},[1495,1522,1523],{},"Writing aware timestamps to Excel."," Remove the zone and add an offset or zone column.",[40,1526,1528],{"id":1527},"conclusion","Conclusion",[10,1530,1531,1532,1535,1536,1538],{},"Excel timestamps are naive wall-clock times, so the repeated autumn hour and the missing spring hour must be resolved when the data is read. Localise with ",[14,1533,1534],{},"tz_localize(zone, ambiguous=\"infer\")"," on time-ordered data, flag nonexistent times with ",[14,1537,955],{},", compute durations from aware timestamps or in UTC, remember that clock-change days have 23 or 25 hours, and write local time, UTC time and the zone back to Excel so readers can see what happened.",[40,1540,1542],{"id":1541},"frequently-asked-questions","Frequently asked questions",[10,1544,1545,1548],{},[1495,1546,1547],{},"How do I fix \"Cannot infer dst time\" when localising Excel timestamps?","\nSort the timestamps and use tz_localize(zone, ambiguous=\"infer\"), or pass a boolean array that says which ambiguous values are in daylight saving time.",[10,1550,1551,1554],{},[1495,1552,1553],{},"How do I handle times that do not exist because the clocks went forward?","\nPass nonexistent=\"NaT\" to flag them, or \"shift_forward\" or \"shift_backward\" to move them to the edge of the gap when you know the clock skipped the hour.",[10,1556,1557,1560],{},[1495,1558,1559],{},"How do I calculate the real length of a shift across a clock change?","\nLocalise both start and end to the time zone and subtract; the result is the true elapsed time, for example seven hours instead of eight.",[10,1562,1563,1566],{},[1495,1564,1565],{},"Can Excel store time zones?","\nNo. Store local time and UTC as separate naive columns, with a column for the zone or offset, and convert in Python when reading.",[40,1568,1570],{"id":1569},"related","Related",[1489,1572,1573,1579,1584,1591],{},[1492,1574,1575,1576,1578],{},"Up one level: ",[34,1577,37],{"href":36}," — dates and times in pandas.",[1492,1580,1581,1583],{},[34,1582,1327],{"href":1326}," — converting between zones.",[1492,1585,1586,1590],{},[34,1587,1589],{"href":1588},"\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-excel-does-not-support-timezones-in-datetimes\u002F","Fix \"Excel does not support datetimes with timezones\" in pandas"," — writing aware values.",[1492,1592,1593,1597],{},[34,1594,1596],{"href":1595},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fresample-irregular-readings-to-regular-intervals\u002F","Resample Irregular Readings to Regular Intervals"," — meter data in general.",[1599,1600,1601],"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}",{"title":50,"searchDepth":108,"depth":108,"links":1603},[1604,1605,1606,1608,1609,1610,1611,1612,1613,1614],{"id":42,"depth":108,"text":43},{"id":81,"depth":108,"text":82},{"id":383,"depth":108,"text":1607},"Step 2 — Localise with ambiguous=\"infer\"",{"id":717,"depth":108,"text":718},{"id":1061,"depth":108,"text":1062},{"id":1479,"depth":108,"text":1480},{"id":1486,"depth":108,"text":1487},{"id":1527,"depth":108,"text":1528},{"id":1541,"depth":108,"text":1542},{"id":1569,"depth":108,"text":1570},"2026-10-02","Excel stores local clock times with no time zone, so the hour that repeats when clocks go back and the hour that never happens when they go forward cause wrong durations, double-counted readings and errors in pandas. Localise naive timestamps safely with ambiguous and nonexistent options, infer the repeated hour from the order of readings, compute true elapsed durations, aggregate by day without 23- and 25-hour surprises, and write results back in a form Excel users understand.","md",[1619,1621,1623,1625],{"q":1547,"a":1620},"Sort the timestamps and use tz_localize(zone, ambiguous=\"infer\"), or pass a boolean array that says which ambiguous values are in daylight saving time.",{"q":1553,"a":1622},"Pass nonexistent=\"NaT\" to flag them, or \"shift_forward\" or \"shift_backward\" to move them to the edge of the gap when you know the clock skipped the hour.",{"q":1559,"a":1624},"Localise both start and end to the time zone and subtract; the result is the true elapsed time, for example seven hours instead of eight.",{"q":1565,"a":1626},"No. Store local time and UTC as separate naive columns, with a column for the zone or offset, and convert in Python when reading.","daylight saving time excel timestamps pandas",null,{"breadcrumb":1630},[1631,1633,1636],{"name":1632,"item":838},"Home",{"name":1634,"item":1635},"Advanced Data Transformation and Cleaning for Excel Automation","\u002Fadvanced-data-transformation-and-cleaning\u002F",{"name":37,"item":36},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fhandle-daylight-saving-time-in-excel-timestamps",{"title":1639,"description":1640},"Handle Daylight Saving Time in Excel Timestamps with pandas","DST-safe pandas for Excel timestamps: tz_localize with ambiguous=\"infer\" and nonexistent, the \"Cannot infer dst time\" error, real durations across clock changes, 23 and 25 hour days, UTC storage and writing naive local times back.","handle-daylight-saving-time-in-excel-timestamps","advanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fhandle-daylight-saving-time-in-excel-timestamps\u002Findex",[1644,1645],"dates-and-times","troubleshooting","how-to","aN_22WMgjmrinSgYi-aMMbl5WegvNcpP0qXjXDWp_AU",[1649,1653],{"title":1650,"path":1651,"stem":1652,"children":-1},"Group Excel Rows by Month and Quarter with pandas","\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fgroup-excel-rows-by-month-and-quarter-with-pandas","advanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fgroup-excel-rows-by-month-and-quarter-with-pandas\u002Findex",{"title":1654,"path":1655,"stem":1656,"children":-1},"Handle the 1904 Date System in Excel Files with Python","\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fhandle-the-1904-date-system-in-excel-files","advanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fhandle-the-1904-date-system-in-excel-files\u002Findex",[1658,1660],{"path":1659,"title":1634},"\u002Fadvanced-data-transformation-and-cleaning",{"path":1661,"title":37},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data",[],[1664,1667,1670,1673,1676,1679],{"path":1665,"title":1666,"kind":1646},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-overdue-dates-with-openpyxl","Highlight Overdue Dates in Excel with openpyxl",{"path":1668,"title":1669,"kind":1646},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-weekends-and-holidays-in-a-date-column","Highlight Weekends and Holidays in a Date Column",{"path":1671,"title":1672,"kind":1646},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fkeep-the-latest-record-per-key-with-pandas","Keep the Latest Record per Key When Deduplicating with pandas",{"path":1674,"title":1675,"kind":1646},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fcalculate-moving-averages-with-pandas-rolling","Calculate Moving Averages with pandas rolling",{"path":1677,"title":1678,"kind":1646},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Feomonth-and-edate-equivalents-in-pandas","EOMONTH and EDATE Equivalents in pandas",{"path":1680,"title":1681,"kind":1646},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fyearfrac-and-days360-equivalents-in-python","YEARFRAC and DAYS360 Equivalents in Python",1790972106446]