Daylight saving time and scheduled jobs: missed and doubled runs

Why scheduled automations skip or run twice when clocks change, how cron, AWS and Google Cloud schedulers differ, and how to schedule jobs safely.

AI Automation7 min read

In Melbourne, clocks jumped from 2:00 am straight to 3:00 am on Sunday 4 October 2026, according to timeanddate.com's Victorian DST page. If you had a backup, a report or a data sync scheduled for 2:30 am local time, there's a decent chance it simply didn't run that night. Back on 5 April, when clocks went from 3:00 am back to 2:00 am, the same job may have run twice.

Neither failure throws an error. The scheduler did exactly what it was designed to do. The problem is that different schedulers are designed differently, and most people never check which behaviour they've got until a customer asks why Sunday's invoices went out twice.

This post explains what actually happens to scheduled jobs around a clock change, how a few common schedulers handle it, and the habits that make the whole problem go away.

What happens in the missing hour and the repeated hour#

A daylight saving change creates two kinds of awkward local time.

When clocks spring forward, a block of local time never exists. In Victoria that's every time from 2:00:00 to 2:59:59 on the October changeover day. A job scheduled for 2:30 am has no 2:30 am to match.

When clocks fall back, a block of local time happens twice. On the April changeover day in Victoria, 2:30 am occurs once in daylight time and again an hour later in standard time. A job scheduled for 2:30 am now has two candidates.

Each scheduler has to pick an answer for both cases. Some skip the missing time, some run late, some run the repeated time once, some run it twice. There's no universal rule, so you need to know what yours does.

Same schedule, different scheduler, different result#

Here's how three common schedulers describe their behaviour in their own documentation.

Scheduler Missing hour (spring forward) Repeated hour (fall back)
cron (cronie) with a local CRON_TZ Job never matches, so it doesn't run Matching jobs run twice
AWS EventBridge Scheduler with a time zone Invocation skipped that day Runs once, before the shift
Google Cloud Scheduler with a DST time zone Can fail to run unexpectedly "Execution anomalies" possible

The cron row comes from the cronie crontab(5) manual page. It says non-existent times such as the "missing hours" during a daylight saving change will never match, and that times which occur more than once will cause matching jobs to be run twice. The same page explains that the CRON_TZ variable sets the time zone for the whole crontab, while the daemon still writes its log timestamps in the machine's local zone. That last detail trips people up when they go looking for evidence of a run.

Other cron implementations have their own rules, so read man 5 crontab and man 8 cron on the machine that actually runs your jobs rather than assuming.

AWS is explicit. The EventBridge Scheduler guide uses cron(30 2 * * ? *) in America/Los_Angeles as its worked example: on the spring change the invocation is skipped and the schedule resumes the next day, and on the autumn change it runs once at 2:30 am before the shift and doesn't repeat. It also notes that schedules set in UTC, or in a zone without daylight saving, get no adjustment at all.

Google's Cloud Scheduler documentation is more cautious. It says Cloud Scheduler runs on wall clock time, that daylight saving can cause jobs to run or not run unexpectedly, and that jobs in a time that occurs twice might observe execution anomalies. Its advice is to pick a zone without daylight saving if your cadence matters, and it recommends UTC to avoid the problem completely. The default zone is UTC.

So the identical-looking schedule "2:30 am every day, Melbourne time" could run zero, one or two times on a changeover day, depending only on where you host it.

Intervals and clock times are different things#

There's a second, quieter version of this bug. Some schedules are defined as "every N hours" or "every day" rather than "at 9:00".

EventBridge Scheduler's rate(1 days) is a good example. Its documentation says days means a 24-hour duration, so when a day has 23 or 25 hours the next run still happens 24 hours after the last one. A rate job that ran at 9:00 am the day before a clock change will run at 8:00 am or 10:00 am local time afterwards, and it stays shifted until the next clock change.

That's correct behaviour for "refresh the cache every 24 hours". It's wrong for "send the morning summary at 9:00". Before you set up a schedule, decide which one you mean:

  • Elapsed-time jobs (polling, cache refreshes, token rotation, health checks) care about the gap between runs. Schedule them in UTC or as a rate, and daylight saving stops mattering.
  • Wall-clock jobs (a 9:00 am report for staff, end-of-business-day exports, anything a human expects at a particular local time) care about the local time. Schedule them in a named local zone such as Australia/Melbourne, and keep them out of the changeover window.

The changeover window is the important part. In Victoria the awkward hour is between 2:00 and 3:00 am on the changeover Sundays. A wall-clock job at 1:30 am or 4:00 am is never in the missing or repeated hour, so the scheduler's edge-case behaviour never comes into play. A lot of "midnight to 3 am" maintenance jobs sit exactly where they shouldn't.

Handling local times in your own code#

The scheduler is only half of it. If your job computes "yesterday's orders" or "records since the last run" using local times, it can be wrong on a changeover day even when it runs on time.

The fix is to store and compare timestamps in UTC, and convert to local time only for display or for deciding what a business day means. When you do have to turn a local time into an instant, be aware that some local times are ambiguous. Python handles this with the fold attribute added by PEP 495 in Python 3.6: for a repeated local time, fold=0 means the earlier of the two readings and fold=1 means the later one. For a time inside the spring-forward gap, fold=0 applies the offset from before the transition. Check what your own language's date library does with these cases before trusting it.

A daily job that pulls "the last 24 hours" will miss an hour of data on the spring change day and double-count an hour in autumn. A job that pulls "everything since the timestamp I last processed" gets it right on every day of the year. Keeping that high-water mark in UTC also makes the job safe to re-run, which matters for the next section.

Late, dropped and missed runs#

Daylight saving isn't the only reason a scheduled job doesn't run when you expect. Hosted schedulers make their own timing trade-offs.

GitHub's troubleshooting docs for Actions say scheduled workflows can be delayed when load is high, that the start of every hour is one of those high-load times, and that some queued jobs may be dropped if load gets high enough. The suggested fix is to schedule at a different time of the hour. So a job at 17 minutes past the hour should be less exposed than one on the hour.

On a Linux server, a job scheduled while the machine was off doesn't run at all unless you ask for it. The systemd.timer manual describes Persistent=true as a way to catch up on missed runs when the system was powered down, and RandomizedDelaySec= as a way to spread start times. It also notes that AccuracySec= defaults to one minute, so a timer can fire up to a minute after its nominal time.

All of this points to the same design rule. Don't write a job that assumes it runs exactly once at exactly the scheduled time. Write one that works out what still needs doing, does it, and records how far it got. That job copes with a skipped run, a doubled run, a late run and a catch-up run without special cases. We covered the mechanics in retries and idempotency, and the heartbeat checks in monitoring automated workflows will tell you when a run went missing.

Time zone rules change more often than you'd expect#

The rules behind a name like Australia/Melbourne come from the IANA Time Zone Database, and they aren't fixed. The IANA release list shows five releases so far in 2026. Among the changes: British Columbia moved to permanent UTC−7 on 9 March, Alberta moved to permanent UTC−6 on 18 June, Morocco moved to permanent UTC+0 on 20 September, and Manitoba is moving to permanent UTC−5 on 31 October.

If you schedule anything in those regions, or for customers there, an out-of-date tz database on your server or in your language runtime will compute the wrong local time without any warning. Keep the operating system's tzdata package (or your runtime's equivalent) updated like any other dependency, and prefer named zones such as America/Edmonton over fixed offsets so that updates actually take effect.

A quick audit you can do this week#

List every scheduled job you run, then answer four questions for each one.

  1. Is it an elapsed-time job or a wall-clock job? If it's elapsed time, move it to UTC.
  2. If it's wall-clock, which named zone is it in, and does its time fall inside a changeover window? If so, move it an hour or two away.
  3. Does it select data by "the last N hours" or by a stored high-water mark? Switch to the mark.
  4. Would anything break if it ran twice, or not at all? If yes, fix that first, because clock changes are only one of the ways it'll happen.

Victorian clocks go back again in autumn. That's plenty of time to make sure nobody has to explain a doubled payroll run on the Monday after.

Sources

  1. Daylight Saving Time Changes 2026 in Melbourne, Victoria, Australia — timeanddate.com (checked 2026-10-25)
  2. crontab(5) — Linux manual page — cronie project, via man7.org (checked 2026-10-25)
  3. Schedule types in EventBridge Scheduler — Amazon Web Services, EventBridge Scheduler User Guide (checked 2026-10-25)
  4. Cron job format and time zone — Google Cloud, Cloud Scheduler documentation (checked 2026-10-25)
  5. PEP 495 – Local Time Disambiguation — Alexander Belopolsky and Tim Peters, Python Enhancement Proposals (checked 2026-10-25)
  6. Troubleshooting workflows — GitHub Docs (GitHub Enterprise Server 3.15) (checked 2026-10-25)
  7. systemd.timer — systemd 259 manual, freedesktop.org (checked 2026-10-25)
  8. Time Zone Database Releases — Internet Assigned Numbers Authority (IANA) (checked 2026-10-25)

Dealing with this in your own business?

Tell us what you're working on and we'll say whether it's worth automating.

Get in touch