[{"data":1,"prerenderedAt":781},["ShallowReactive",2],{"guide-timezone-conversion-scheduled-jobs":3},{"id":4,"title":5,"body":6,"date":773,"description":774,"extension":775,"meta":776,"navigation":88,"path":777,"readingTime":294,"seo":778,"stem":779,"__hash__":780},"guides\u002Fguides\u002Ftimezone-conversion-scheduled-jobs.md","Timezone Conversion in Scheduled Jobs",{"type":7,"value":8,"toc":751},"minimark",[9,14,18,21,25,28,33,46,120,123,127,131,134,178,181,185,188,425,429,432,440,447,451,454,460,465,469,473,484,524,528,531,537,540,544,547,551,554,604,607,611,683,687,710,714,735,739,747],[10,11,13],"h2",{"id":12},"why-scheduled-jobs-are-timezone-minefields","Why Scheduled Jobs Are Timezone Minefields",[15,16,17],"p",{},"A cron job that runs \"at 2 AM every day\" seems straightforward. But 2 AM where? The server? The user? UTC? And what happens when Daylight Saving Time shifts the clock — does the job run twice, skip entirely, or drift an hour?",[15,19,20],{},"Scheduled jobs fail due to timezone bugs more often than logic bugs. The consequences are real: missed backups, duplicate email sends, financial calculations off by an hour, or batch jobs running during peak traffic instead of off-peak.",[10,22,24],{"id":23},"the-utc-first-rule","The UTC-First Rule",[15,26,27],{},"Every scheduled job should be defined and stored in UTC. No exceptions.",[29,30,32],"h3",{"id":31},"why-utc","Why UTC?",[34,35,36,40,43],"ul",{},[37,38,39],"li",{},"UTC does not observe DST — no ambiguous or missing hours",[37,41,42],{},"UTC has no regional variation — it means the same thing on every server",[37,44,45],{},"UTC is what most job schedulers assume when no timezone is specified",[47,48,53],"pre",{"className":49,"code":50,"language":51,"meta":52,"style":52},"language-yaml shiki shiki-themes github-light github-dark","# WRONG — ambiguous without specifying timezone\nschedule: \"0 2 * * *\"          # 2 AM... but in which timezone?\n\n# CORRECT — explicit UTC\nschedule: \"0 6 * * *\"          # 6 AM UTC = 2 AM America\u002FNew_York (during EDT)\ntimezone: \"UTC\"\n","yaml","",[54,55,56,65,83,90,96,109],"code",{"__ignoreMap":52},[57,58,61],"span",{"class":59,"line":60},"line",1,[57,62,64],{"class":63},"sJ8bj","# WRONG — ambiguous without specifying timezone\n",[57,66,68,72,76,80],{"class":59,"line":67},2,[57,69,71],{"class":70},"s9eBZ","schedule",[57,73,75],{"class":74},"sVt8B",": ",[57,77,79],{"class":78},"sZZnC","\"0 2 * * *\"",[57,81,82],{"class":63},"          # 2 AM... but in which timezone?\n",[57,84,86],{"class":59,"line":85},3,[57,87,89],{"emptyLinePlaceholder":88},true,"\n",[57,91,93],{"class":59,"line":92},4,[57,94,95],{"class":63},"# CORRECT — explicit UTC\n",[57,97,99,101,103,106],{"class":59,"line":98},5,[57,100,71],{"class":70},[57,102,75],{"class":74},[57,104,105],{"class":78},"\"0 6 * * *\"",[57,107,108],{"class":63},"          # 6 AM UTC = 2 AM America\u002FNew_York (during EDT)\n",[57,110,112,115,117],{"class":59,"line":111},6,[57,113,114],{"class":70},"timezone",[57,116,75],{"class":74},[57,118,119],{"class":78},"\"UTC\"\n",[15,121,122],{},"When a user requests a job at their local 2 AM, convert it to UTC at the time of creation. Store the UTC schedule and the user's IANA timezone. Display the local time for the user, schedule in UTC.",[10,124,126],{"id":125},"handling-daylight-saving-time-in-jobs","Handling Daylight Saving Time in Jobs",[29,128,130],{"id":129},"fixed-utc-schedules-simplest-safest","Fixed UTC schedules (simplest, safest)",[15,132,133],{},"If the job should run at the same UTC time every day regardless of DST, just set it in UTC and forget about it. The local time shifts by an hour when DST changes — if that is acceptable, you are done.",[135,136,137,153],"table",{},[138,139,140],"thead",{},[141,142,143,147,150],"tr",{},[144,145,146],"th",{},"Season",[144,148,149],{},"UTC schedule",[144,151,152],{},"New York local",[154,155,156,168],"tbody",{},[141,157,158,162,165],{},[159,160,161],"td",{},"Summer (EDT)",[159,163,164],{},"06:00 UTC",[159,166,167],{},"02:00 AM",[141,169,170,173,175],{},[159,171,172],{},"Winter (EST)",[159,174,164],{},[159,176,177],{},"01:00 AM",[15,179,180],{},"If business requirements demand the local time stay constant (e.g., \"always at 2 AM Eastern\"), you need a different approach.",[29,182,184],{"id":183},"fixed-local-time-schedules","Fixed local time schedules",[15,186,187],{},"When the local time must stay constant across DST transitions, store the user's timezone and recalculate the UTC equivalent after each transition.",[47,189,193],{"className":190,"code":191,"language":192,"meta":52,"style":52},"language-javascript shiki shiki-themes github-light github-dark","\u002F\u002F After a DST transition, recalculate the next run time\nfunction getNextRun(localHour, timezone) {\n  const now = new Date()\n  const tomorrow = new Date(now)\n  tomorrow.setDate(tomorrow.getDate() + 1)\n\n  \u002F\u002F Create the desired local time tomorrow\n  const localTime = new Date(\n    tomorrow.getFullYear(),\n    tomorrow.getMonth(),\n    tomorrow.getDate(),\n    localHour, 0, 0\n  )\n\n  \u002F\u002F Convert to UTC using the IANA timezone\n  const utcString = localTime.toLocaleString('en-US', { timeZone: timezone })\n  return new Date(utcString).toUTCString()\n}\n","javascript",[54,194,195,200,225,246,262,288,292,298,315,327,337,346,360,366,371,377,401,419],{"__ignoreMap":52},[57,196,197],{"class":59,"line":60},[57,198,199],{"class":63},"\u002F\u002F After a DST transition, recalculate the next run time\n",[57,201,202,206,210,213,217,220,222],{"class":59,"line":67},[57,203,205],{"class":204},"szBVR","function",[57,207,209],{"class":208},"sScJk"," getNextRun",[57,211,212],{"class":74},"(",[57,214,216],{"class":215},"s4XuR","localHour",[57,218,219],{"class":74},", ",[57,221,114],{"class":215},[57,223,224],{"class":74},") {\n",[57,226,227,230,234,237,240,243],{"class":59,"line":85},[57,228,229],{"class":204},"  const",[57,231,233],{"class":232},"sj4cs"," now",[57,235,236],{"class":204}," =",[57,238,239],{"class":204}," new",[57,241,242],{"class":208}," Date",[57,244,245],{"class":74},"()\n",[57,247,248,250,253,255,257,259],{"class":59,"line":92},[57,249,229],{"class":204},[57,251,252],{"class":232}," tomorrow",[57,254,236],{"class":204},[57,256,239],{"class":204},[57,258,242],{"class":208},[57,260,261],{"class":74},"(now)\n",[57,263,264,267,270,273,276,279,282,285],{"class":59,"line":98},[57,265,266],{"class":74},"  tomorrow.",[57,268,269],{"class":208},"setDate",[57,271,272],{"class":74},"(tomorrow.",[57,274,275],{"class":208},"getDate",[57,277,278],{"class":74},"() ",[57,280,281],{"class":204},"+",[57,283,284],{"class":232}," 1",[57,286,287],{"class":74},")\n",[57,289,290],{"class":59,"line":111},[57,291,89],{"emptyLinePlaceholder":88},[57,293,295],{"class":59,"line":294},7,[57,296,297],{"class":63},"  \u002F\u002F Create the desired local time tomorrow\n",[57,299,301,303,306,308,310,312],{"class":59,"line":300},8,[57,302,229],{"class":204},[57,304,305],{"class":232}," localTime",[57,307,236],{"class":204},[57,309,239],{"class":204},[57,311,242],{"class":208},[57,313,314],{"class":74},"(\n",[57,316,318,321,324],{"class":59,"line":317},9,[57,319,320],{"class":74},"    tomorrow.",[57,322,323],{"class":208},"getFullYear",[57,325,326],{"class":74},"(),\n",[57,328,330,332,335],{"class":59,"line":329},10,[57,331,320],{"class":74},[57,333,334],{"class":208},"getMonth",[57,336,326],{"class":74},[57,338,340,342,344],{"class":59,"line":339},11,[57,341,320],{"class":74},[57,343,275],{"class":208},[57,345,326],{"class":74},[57,347,349,352,355,357],{"class":59,"line":348},12,[57,350,351],{"class":74},"    localHour, ",[57,353,354],{"class":232},"0",[57,356,219],{"class":74},[57,358,359],{"class":232},"0\n",[57,361,363],{"class":59,"line":362},13,[57,364,365],{"class":74},"  )\n",[57,367,369],{"class":59,"line":368},14,[57,370,89],{"emptyLinePlaceholder":88},[57,372,374],{"class":59,"line":373},15,[57,375,376],{"class":63},"  \u002F\u002F Convert to UTC using the IANA timezone\n",[57,378,380,382,385,387,390,393,395,398],{"class":59,"line":379},16,[57,381,229],{"class":204},[57,383,384],{"class":232}," utcString",[57,386,236],{"class":204},[57,388,389],{"class":74}," localTime.",[57,391,392],{"class":208},"toLocaleString",[57,394,212],{"class":74},[57,396,397],{"class":78},"'en-US'",[57,399,400],{"class":74},", { timeZone: timezone })\n",[57,402,404,407,409,411,414,417],{"class":59,"line":403},17,[57,405,406],{"class":204},"  return",[57,408,239],{"class":204},[57,410,242],{"class":208},[57,412,413],{"class":74},"(utcString).",[57,415,416],{"class":208},"toUTCString",[57,418,245],{"class":74},[57,420,422],{"class":59,"line":421},18,[57,423,424],{"class":74},"}\n",[29,426,428],{"id":427},"the-fall-back-duplicate-run","The fall-back duplicate run",[15,430,431],{},"If a job runs every hour, it executes twice at 1 AM on fall-back day:",[47,433,438],{"className":434,"code":436,"language":437},[435],"language-text","01:00 EDT → runs job\n01:00 EST → runs job again (same wall-clock time, different UTC time)\n","text",[54,439,436],{"__ignoreMap":52},[15,441,442,446],{},[443,444,445],"strong",{},"Fix:"," Track the last UTC run timestamp. Before executing, check if the job already ran at this UTC time.",[29,448,450],{"id":449},"the-spring-forward-skip","The spring-forward skip",[15,452,453],{},"If a job is scheduled for 2:30 AM local time, it never runs on spring-forward day:",[47,455,458],{"className":456,"code":457,"language":437},[435],"01:59 EST → 03:00 EDT — 02:30 does not exist\n",[54,459,457],{"__ignoreMap":52},[15,461,462,464],{},[443,463,445],{}," Detect the gap using the IANA database. If the scheduled time falls in a DST gap, advance to the next valid time (e.g., 03:00 AM) and log a warning.",[10,466,468],{"id":467},"distributed-system-patterns","Distributed System Patterns",[29,470,472],{"id":471},"server-timezone-independence","Server timezone independence",[15,474,475,476,479,480,483],{},"Never depend on the server's local timezone. A server in ",[54,477,478],{},"America\u002FNew_York"," moved to ",[54,481,482],{},"Europe\u002FLondon"," changes every schedule. Always specify timezones explicitly.",[47,485,489],{"className":486,"code":487,"language":488,"meta":52,"style":52},"language-python shiki shiki-themes github-light github-dark","# WRONG — depends on server timezone\nimport schedule\nschedule.every().day.at(\"02:00\").do(job)\n\n# CORRECT — explicit UTC\nfrom datetime import datetime, timezone\nnext_run = datetime(2026, 1, 15, 6, 0, tzinfo=timezone.utc)\n","python",[54,490,491,496,501,506,510,514,519],{"__ignoreMap":52},[57,492,493],{"class":59,"line":60},[57,494,495],{},"# WRONG — depends on server timezone\n",[57,497,498],{"class":59,"line":67},[57,499,500],{},"import schedule\n",[57,502,503],{"class":59,"line":85},[57,504,505],{},"schedule.every().day.at(\"02:00\").do(job)\n",[57,507,508],{"class":59,"line":92},[57,509,89],{"emptyLinePlaceholder":88},[57,511,512],{"class":59,"line":98},[57,513,95],{},[57,515,516],{"class":59,"line":111},[57,517,518],{},"from datetime import datetime, timezone\n",[57,520,521],{"class":59,"line":294},[57,522,523],{},"next_run = datetime(2026, 1, 15, 6, 0, tzinfo=timezone.utc)\n",[29,525,527],{"id":526},"clock-skew-between-servers","Clock skew between servers",[15,529,530],{},"In distributed systems, servers may have clock skew of several milliseconds. For jobs that must not run twice (e.g., financial batch processing), use a distributed lock:",[47,532,535],{"className":533,"code":534,"language":437},[435],"1. Acquire lock with TTL slightly longer than job duration\n2. Execute job\n3. Release lock\n",[54,536,534],{"__ignoreMap":52},[15,538,539],{},"Redis, ZooKeeper, or database-level advisory locks all work. Without locking, two servers may both decide it is time to run the job.",[29,541,543],{"id":542},"idempotency","Idempotency",[15,545,546],{},"Design every scheduled job to be safely re-runnable. If a job runs twice due to a DST ambiguity or a retry, the result should be identical to a single run. This eliminates an entire class of timezone-related bugs.",[10,548,550],{"id":549},"scheduling-across-timezones-for-users","Scheduling Across Timezones for Users",[15,552,553],{},"When users in multiple timezones need jobs at \"their\" 2 AM:",[135,555,556,569],{},[138,557,558],{},[141,559,560,563,566],{},[144,561,562],{},"Pattern",[144,564,565],{},"Implementation",[144,567,568],{},"Trade-off",[154,570,571,582,593],{},[141,572,573,576,579],{},[159,574,575],{},"Per-user UTC conversion",[159,577,578],{},"Convert each user's 2 AM to UTC, schedule individually",[159,580,581],{},"Most accurate, complex to manage",[141,583,584,587,590],{},[159,585,586],{},"Regional batches",[159,588,589],{},"Group users by timezone, schedule batch per region",[159,591,592],{},"Simpler, less granular",[141,594,595,598,601],{},[159,596,597],{},"Fixed UTC with offset display",[159,599,600],{},"Run at one UTC time, show local equivalent per user",[159,602,603],{},"Simplest, local times shift with DST",[15,605,606],{},"Choose based on how critical the exact local time is. Most batch jobs tolerate a one-hour shift — user-facing notifications often do not.",[10,608,610],{"id":609},"common-bugs-and-fixes","Common Bugs and Fixes",[135,612,613,626],{},[138,614,615],{},[141,616,617,620,623],{},[144,618,619],{},"Bug",[144,621,622],{},"Root cause",[144,624,625],{},"Fix",[154,627,628,639,650,661,672],{},[141,629,630,633,636],{},[159,631,632],{},"Job runs an hour late after DST ends",[159,634,635],{},"Cron uses server local time",[159,637,638],{},"Schedule in UTC explicitly",[141,640,641,644,647],{},[159,642,643],{},"Job runs twice on fall-back day",[159,645,646],{},"Hourly schedule hits ambiguous time",[159,648,649],{},"Track last UTC run timestamp",[141,651,652,655,658],{},[159,653,654],{},"Job skips on spring-forward day",[159,656,657],{},"Fixed local time falls in gap",[159,659,660],{},"Detect gap, advance to next valid time",[141,662,663,666,669],{},[159,664,665],{},"Batch processes wrong day's data",[159,667,668],{},"Date boundary shifts with DST",[159,670,671],{},"Use UTC date boundaries for data selection",[141,673,674,677,680],{},[159,675,676],{},"drift over months",[159,678,679],{},"Server timezone mismatch",[159,681,682],{},"Never depend on server timezone",[10,684,686],{"id":685},"key-takeaways","Key Takeaways",[34,688,689,692,695,698,701,704,707],{},[37,690,691],{},"Define and store every scheduled job in UTC — no exceptions",[37,693,694],{},"When local time must stay constant across DST, store the user's IANA timezone and recalculate UTC after each transition",[37,696,697],{},"Track last-run timestamps to prevent duplicate execution during fall-back",[37,699,700],{},"Detect spring-forward gaps and advance skipping jobs to the next valid time",[37,702,703],{},"Never depend on server local timezone — specify explicitly",[37,705,706],{},"Use distributed locks for jobs that must not run twice across servers",[37,708,709],{},"Design jobs to be idempotent — safe re-runs eliminate an entire class of bugs",[10,711,713],{"id":712},"related-guides","Related Guides",[34,715,716,723,729],{},[37,717,718],{},[719,720,722],"a",{"href":721},"\u002Fguides\u002Ftimezone-converter-guide","Timezone Converter Guide",[37,724,725],{},[719,726,728],{"href":727},"\u002Fguides\u002Fdaylight-saving-time-edge-cases","Daylight Saving Time Edge Cases",[37,730,731],{},[719,732,734],{"href":733},"\u002Fguides\u002Fworking-with-timezones","Working with Timezones",[10,736,738],{"id":737},"try-it-yourself","Try It Yourself",[15,740,741,742,746],{},"Verify your timezone conversions with our free ",[719,743,745],{"href":744},"\u002Ftools\u002Ftimezone-converter","Timezone Converter",". Enter your UTC schedule and check the local equivalent across DST boundaries — all calculations use IANA data with automatic DST adjustment.",[748,749,750],"style",{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}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}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 .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}",{"title":52,"searchDepth":67,"depth":67,"links":752},[753,754,757,763,768,769,770,771,772],{"id":12,"depth":67,"text":13},{"id":23,"depth":67,"text":24,"children":755},[756],{"id":31,"depth":85,"text":32},{"id":125,"depth":67,"text":126,"children":758},[759,760,761,762],{"id":129,"depth":85,"text":130},{"id":183,"depth":85,"text":184},{"id":427,"depth":85,"text":428},{"id":449,"depth":85,"text":450},{"id":467,"depth":67,"text":468,"children":764},[765,766,767],{"id":471,"depth":85,"text":472},{"id":526,"depth":85,"text":527},{"id":542,"depth":85,"text":543},{"id":549,"depth":67,"text":550},{"id":609,"depth":67,"text":610},{"id":685,"depth":67,"text":686},{"id":712,"depth":67,"text":713},{"id":737,"depth":67,"text":738},"2026-05-28","Avoid timezone bugs in cron jobs, scheduled tasks, and distributed systems — UTC-first strategies, DST-safe scheduling, and practical patterns.","md",{"immutable":88},"\u002Fguides\u002Ftimezone-conversion-scheduled-jobs",{"title":5,"description":774},"guides\u002Ftimezone-conversion-scheduled-jobs","vVWjeDaDPxuu9YyioPzjbmmt9a3dCeMj4Id_IxEcPpM",1780401337542]