[{"data":1,"prerenderedAt":515},["ShallowReactive",2],{"guide-dst-transition-handling":3},{"id":4,"title":5,"body":6,"date":507,"description":508,"extension":509,"meta":510,"navigation":55,"path":511,"readingTime":71,"seo":512,"stem":513,"__hash__":514},"guides\u002Fguides\u002Fdst-transition-handling.md","Handling Daylight Saving Time Transitions",{"type":7,"value":8,"toc":492},"minimark",[9,13,18,23,26,81,85,92,117,121,125,128,225,228,232,235,261,299,303,306,335,339,425,429,432,446,449,453,470,474,488],[10,11,12],"p",{},"Twice a year, many regions shift their clocks for daylight saving time (DST). These transitions create two recurring headaches for developers: a missing hour in spring and a repeated hour in fall. Both situations break assumptions that time moves forward at a constant rate, and both cause real bugs — missed scheduled jobs, duplicate log entries, incorrect billing intervals, and broken time-range queries.",[14,15,17],"h2",{"id":16},"the-two-transition-problems","The Two Transition Problems",[19,20,22],"h3",{"id":21},"spring-forward-the-missing-hour","Spring Forward: The Missing Hour",[10,24,25],{},"When clocks jump from 2:00 AM to 3:00 AM, the hour between 2:00 and 3:00 never exists. A scheduled task set for 2:30 AM simply does not run.",[27,28,33],"pre",{"className":29,"code":30,"language":31,"meta":32,"style":32},"language-python shiki shiki-themes github-light github-dark","from datetime import datetime\nimport pytz\n\neastern = pytz.timezone('US\u002FEastern')\n# March 12, 2026 — spring forward\nt = eastern.localize(datetime(2026, 3, 12, 2, 30), is_dst=None)\n# AmbiguousTimeError: 2:30 AM does not exist\n","python","",[34,35,36,44,50,57,63,69,75],"code",{"__ignoreMap":32},[37,38,41],"span",{"class":39,"line":40},"line",1,[37,42,43],{},"from datetime import datetime\n",[37,45,47],{"class":39,"line":46},2,[37,48,49],{},"import pytz\n",[37,51,53],{"class":39,"line":52},3,[37,54,56],{"emptyLinePlaceholder":55},true,"\n",[37,58,60],{"class":39,"line":59},4,[37,61,62],{},"eastern = pytz.timezone('US\u002FEastern')\n",[37,64,66],{"class":39,"line":65},5,[37,67,68],{},"# March 12, 2026 — spring forward\n",[37,70,72],{"class":39,"line":71},6,[37,73,74],{},"t = eastern.localize(datetime(2026, 3, 12, 2, 30), is_dst=None)\n",[37,76,78],{"class":39,"line":77},7,[37,79,80],{},"# AmbiguousTimeError: 2:30 AM does not exist\n",[19,82,84],{"id":83},"fall-back-the-repeated-hour","Fall Back: The Repeated Hour",[10,86,87,88,91],{},"When clocks fall from 2:00 AM back to 1:00 AM, the hour between 1:00 and 2:00 occurs twice. Without disambiguation, a timestamp like ",[34,89,90],{},"2026-11-01 01:30:00"," is ambiguous — is it the first occurrence (EDT) or the second (EST)?",[27,93,95],{"className":29,"code":94,"language":31,"meta":32,"style":32},"# November 1, 2026 — fall back\nt1 = eastern.localize(datetime(2026, 11, 1, 1, 30), is_dst=True)   # EDT\nt2 = eastern.localize(datetime(2026, 11, 1, 1, 30), is_dst=False)  # EST\n# t1 and t2 are 1 hour apart in UTC, but display the same local time\n",[34,96,97,102,107,112],{"__ignoreMap":32},[37,98,99],{"class":39,"line":40},[37,100,101],{},"# November 1, 2026 — fall back\n",[37,103,104],{"class":39,"line":46},[37,105,106],{},"t1 = eastern.localize(datetime(2026, 11, 1, 1, 30), is_dst=True)   # EDT\n",[37,108,109],{"class":39,"line":52},[37,110,111],{},"t2 = eastern.localize(datetime(2026, 11, 1, 1, 30), is_dst=False)  # EST\n",[37,113,114],{"class":39,"line":59},[37,115,116],{},"# t1 and t2 are 1 hour apart in UTC, but display the same local time\n",[14,118,120],{"id":119},"strategies-for-handling-transitions","Strategies for Handling Transitions",[19,122,124],{"id":123},"store-utc-display-local","Store UTC, Display Local",[10,126,127],{},"The single most effective strategy: always store and compute timestamps in UTC. UTC has no DST transitions — it is a continuous, monotonically increasing timeline. Convert to local time only at the display layer.",[27,129,133],{"className":130,"code":131,"language":132,"meta":32,"style":32},"language-javascript shiki shiki-themes github-light github-dark","\u002F\u002F Store in UTC\nconst utcTimestamp = new Date().toISOString(); \u002F\u002F \"2026-05-28T14:30:00.000Z\"\n\n\u002F\u002F Display in local timezone\nconst localString = new Date(utcTimestamp).toLocaleString('en-US', {\n  timeZone: 'America\u002FNew_York'\n});\n","javascript",[34,134,135,141,174,178,183,212,220],{"__ignoreMap":32},[37,136,137],{"class":39,"line":40},[37,138,140],{"class":139},"sJ8bj","\u002F\u002F Store in UTC\n",[37,142,143,147,151,154,157,161,165,168,171],{"class":39,"line":46},[37,144,146],{"class":145},"szBVR","const",[37,148,150],{"class":149},"sj4cs"," utcTimestamp",[37,152,153],{"class":145}," =",[37,155,156],{"class":145}," new",[37,158,160],{"class":159},"sScJk"," Date",[37,162,164],{"class":163},"sVt8B","().",[37,166,167],{"class":159},"toISOString",[37,169,170],{"class":163},"(); ",[37,172,173],{"class":139},"\u002F\u002F \"2026-05-28T14:30:00.000Z\"\n",[37,175,176],{"class":39,"line":52},[37,177,56],{"emptyLinePlaceholder":55},[37,179,180],{"class":39,"line":59},[37,181,182],{"class":139},"\u002F\u002F Display in local timezone\n",[37,184,185,187,190,192,194,196,199,202,205,209],{"class":39,"line":65},[37,186,146],{"class":145},[37,188,189],{"class":149}," localString",[37,191,153],{"class":145},[37,193,156],{"class":145},[37,195,160],{"class":159},[37,197,198],{"class":163},"(utcTimestamp).",[37,200,201],{"class":159},"toLocaleString",[37,203,204],{"class":163},"(",[37,206,208],{"class":207},"sZZnC","'en-US'",[37,210,211],{"class":163},", {\n",[37,213,214,217],{"class":39,"line":71},[37,215,216],{"class":163},"  timeZone: ",[37,218,219],{"class":207},"'America\u002FNew_York'\n",[37,221,222],{"class":39,"line":77},[37,223,224],{"class":163},"});\n",[10,226,227],{},"This eliminates the ambiguous-timestamp problem entirely. If someone asks \"what happened at 1:30 AM on November 1?\" you answer by querying UTC ranges, not local strings.",[19,229,231],{"id":230},"handle-scheduling-with-utc-or-dst-aware-libraries","Handle Scheduling with UTC or DST-Aware Libraries",[10,233,234],{},"For scheduled jobs, do not use local-time cron expressions if your system runs in a DST-affected timezone. Instead:",[236,237,238,246],"ul",{},[239,240,241,242,245],"li",{},"Schedule in UTC (",[34,243,244],{},"0 7 * * *"," for 7:00 AM UTC, every day)",[239,247,248,249,252,253,256,257,260],{},"Or use a DST-aware scheduler (like ",[34,250,251],{},"java-time","'s ",[34,254,255],{},"ZonedDateTime"," in Java or Python's ",[34,258,259],{},"zoneinfo",") that adjusts execution time automatically",[27,262,264],{"className":29,"code":263,"language":31,"meta":32,"style":32},"from zoneinfo import ZoneInfo\nfrom datetime import datetime\n\ntz = ZoneInfo('US\u002FEastern')\n# This correctly handles spring forward — job runs at 3:00 AM EDT\nnext_run = datetime(2026, 3, 12, 2, 30, tzinfo=tz)\nprint(next_run)  # 2026-03-12 03:30:00-04:00 (skipped to 3:30 AM EDT)\n",[34,265,266,271,275,279,284,289,294],{"__ignoreMap":32},[37,267,268],{"class":39,"line":40},[37,269,270],{},"from zoneinfo import ZoneInfo\n",[37,272,273],{"class":39,"line":46},[37,274,43],{},[37,276,277],{"class":39,"line":52},[37,278,56],{"emptyLinePlaceholder":55},[37,280,281],{"class":39,"line":59},[37,282,283],{},"tz = ZoneInfo('US\u002FEastern')\n",[37,285,286],{"class":39,"line":65},[37,287,288],{},"# This correctly handles spring forward — job runs at 3:00 AM EDT\n",[37,290,291],{"class":39,"line":71},[37,292,293],{},"next_run = datetime(2026, 3, 12, 2, 30, tzinfo=tz)\n",[37,295,296],{"class":39,"line":77},[37,297,298],{},"print(next_run)  # 2026-03-12 03:30:00-04:00 (skipped to 3:30 AM EDT)\n",[19,300,302],{"id":301},"use-interval-arithmetic-not-wall-clock-differences","Use Interval Arithmetic, Not Wall-Clock Differences",[10,304,305],{},"When calculating the duration between two events, never subtract local timestamps. DST transitions make wall-clock differences incorrect.",[27,307,309],{"className":29,"code":308,"language":31,"meta":32,"style":32},"# Wrong: subtracts local times (misses the spring-forward gap)\nduration = local_end - local_start  # May be off by 1 hour\n\n# Right: subtract UTC times or use aware datetimes\nduration = utc_end - utc_start  # Always correct\n",[34,310,311,316,321,325,330],{"__ignoreMap":32},[37,312,313],{"class":39,"line":40},[37,314,315],{},"# Wrong: subtracts local times (misses the spring-forward gap)\n",[37,317,318],{"class":39,"line":46},[37,319,320],{},"duration = local_end - local_start  # May be off by 1 hour\n",[37,322,323],{"class":39,"line":52},[37,324,56],{"emptyLinePlaceholder":55},[37,326,327],{"class":39,"line":59},[37,328,329],{},"# Right: subtract UTC times or use aware datetimes\n",[37,331,332],{"class":39,"line":65},[37,333,334],{},"duration = utc_end - utc_start  # Always correct\n",[14,336,338],{"id":337},"common-bugs-and-fixes","Common Bugs and Fixes",[340,341,342,358],"table",{},[343,344,345],"thead",{},[346,347,348,352,355],"tr",{},[349,350,351],"th",{},"Bug",[349,353,354],{},"Cause",[349,356,357],{},"Fix",[359,360,361,373,384,395,406],"tbody",{},[346,362,363,367,370],{},[364,365,366],"td",{},"Daily report skips a day",[364,368,369],{},"Report scheduled at 2:30 AM local — skipped during spring forward",[364,371,372],{},"Schedule in UTC or shift to 3:00 AM local",[346,374,375,378,381],{},[364,376,377],{},"Duplicate entries in logs",[364,379,380],{},"Fall-back creates two 1:00 AM hours; both written with same local string",[364,382,383],{},"Use UTC timestamps in logs",[346,385,386,389,392],{},[364,387,388],{},"Hourly rate overcharges",[364,390,391],{},"3-hour window spans fall back; naive counting treats 2 AM as a new hour",[364,393,394],{},"Use UTC intervals for billing",[346,396,397,400,403],{},[364,398,399],{},"Time-range query returns duplicates",[364,401,402],{},"BETWEEN on local timestamps matches the repeated hour twice",[364,404,405],{},"Query on UTC column instead",[346,407,408,411,418],{},[364,409,410],{},"Countdown timer jumps",[364,412,413,414,417],{},"Timer computes ",[34,415,416],{},"target - Date.now()"," using local Date objects",[364,419,420,421,424],{},"Use ",[34,422,423],{},"Date.UTC()"," or epoch milliseconds",[14,426,428],{"id":427},"dst-rules-vary-globally","DST Rules Vary Globally",[10,430,431],{},"Not all regions observe DST, and those that do change their rules periodically:",[236,433,434,437,440,443],{},[239,435,436],{},"United States: second Sunday in March to first Sunday in November",[239,438,439],{},"European Union: last Sunday in March to last Sunday in October",[239,441,442],{},"Australia (southern hemisphere): first Sunday in October to first Sunday in April (opposite season)",[239,444,445],{},"Japan, India, China: no DST",[10,447,448],{},"Never hardcode transition dates. Always use a timezone database (IANA tzdata) that is regularly updated. Most operating systems and language runtimes ship with tzdata and update it periodically.",[14,450,452],{"id":451},"key-takeaways","Key Takeaways",[236,454,455,458,461,464,467],{},[239,456,457],{},"Spring forward creates a missing hour; fall back creates a repeated hour — both break naive time arithmetic.",[239,459,460],{},"Store and compute in UTC; convert to local time only for display.",[239,462,463],{},"Schedule jobs in UTC or with DST-aware libraries to guarantee execution.",[239,465,466],{},"Use UTC intervals for billing, logging, and time-range queries to avoid duplicate or missing records.",[239,468,469],{},"Never hardcode DST transition dates — rely on the IANA timezone database.",[14,471,473],{"id":472},"try-it-yourself","Try It Yourself",[10,475,476,477,482,483,487],{},"Working with timestamps across timezones? The ",[478,479,481],"a",{"href":480},"\u002Ftools\u002Ftimestamp-converter","Timestamp Converter"," and ",[478,484,486],{"href":485},"\u002Ftools\u002Ftimezone-converter","Timezone Converter"," help you visualize transitions and convert between UTC and local times accurately.",[489,490,491],"style",{},"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 .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}",{"title":32,"searchDepth":46,"depth":46,"links":493},[494,498,503,504,505,506],{"id":16,"depth":46,"text":17,"children":495},[496,497],{"id":21,"depth":52,"text":22},{"id":83,"depth":52,"text":84},{"id":119,"depth":46,"text":120,"children":499},[500,501,502],{"id":123,"depth":52,"text":124},{"id":230,"depth":52,"text":231},{"id":301,"depth":52,"text":302},{"id":337,"depth":46,"text":338},{"id":427,"depth":46,"text":428},{"id":451,"depth":46,"text":452},{"id":472,"depth":46,"text":473},"2026-05-28","Prevent bugs during spring forward and fall back with these timezone coding strategies.","md",{"immutable":55},"\u002Fguides\u002Fdst-transition-handling",{"title":5,"description":508},"guides\u002Fdst-transition-handling","JGYvcGIqGkZdtSO-7ktT8b0v4nzRT9caZoS863sFfL0",1780401333514]