DEV Community

40
40

Posted on

What your cron schedule does on the two DST nights, tested

Disclosure: I maintain Cronbell, a heartbeat monitor that evaluates cron schedules in a timezone. The behaviour below is general; the outputs are from my own implementation, so check how your scheduler treats these cases.

Two nights a year, local wall-clock time lies to schedulers.

Spring forward: a time that does not exist. In Europe/Amsterdam the clocks jump from 02:00 to 03:00 on 2027-03-28, so 30 2 * * * has no 02:30 that day. In my tests the next run after 2027-03-27 is 2027-03-29 02:30 local (00:30 UTC). The same happens in America/New_York on 2027-03-14. Some cron daemons run skipped jobs right after the jump instead, others skip them; this is worth looking up for yours.

Fall back: a time that happens twice. On 2026-10-25 Amsterdam repeats 02:00-03:00. 30 2 * * * could fire at 00:30 UTC (CEST) or at 01:30 UTC (CET). I treat the first as the real one and skip the repeat, so the job runs once. That gives a related quirk: 0 * * * * is evaluated once for the repeated 02:00 hour, so after 02:30 CEST the next expected run is 03:00 CET (02:00 UTC), not an extra one.

Consequences for monitoring

  • Fixed-interval monitors ("expect a ping every 24h") drift against a local-time cron for two days a year: either false alarms or a late one.
  • Evaluate the cron expression in the job's own timezone, and set a grace period of at least an hour for jobs near 02:00-03:00 local.
  • Safest of all: schedule jobs at times that are never skipped or repeated (for example 04:00 local), or run the host in UTC.

I found a real bug while writing this: my own implementation returned the later of the two occurrences on the fall-back night, which pushed one day's deadline back an hour. It is fixed and now has a regression test for these cases. If you maintain a scheduler, test your overlap case.

Related copy-paste heartbeat examples for crontab, systemd timers and Kubernetes (including timeZone): https://github.com/jinqo/cron-heartbeat-snippets

How does your cron or systemd timer behave on the repeated hour? I'd like to compare notes.

Top comments (2)

Collapse
 
launchgatecheck profile image
Launch Gate •

Timely post - Amsterdam's repeated hour is tomorrow night. Two implementation notes from the other side of this: cronie runs jobs from the skipped hour immediately after the spring jump (so 02:30 fires at 03:00), while systemd OnCalendar timers elide them entirely. Same expression, same host timezone, different behaviour - which is why "check how your scheduler treats these cases" deserves the emphasis you gave it.

On the repeated hour: accepting either occurrence as a valid ping is the part monitors get wrong. A fixed 24h expectation sees the fall-back night as a 25-hour gap and pages. Evaluating the expression in the job's timezone fixes the false alarm, as you say; the subtler bug is a monitor that fires its missed-deadline alert during the overlap and then recovers silently, leaving no record that the schedule itself was ambiguous.

The regression test for the overlap case is the right fix. Worth pinning both nights as fixtures: one timestamp that does not exist, one that exists twice.

Collapse
 
robertpaschal profile image
Odinaka Robert Nnamani •

The fall-back double-fire is a duplicate-execution bug wearing a timezone costume, and it is the one I would underline hardest for anyone running scheduled jobs. A job that fires twice because the hour repeats will double-send, double-report, or double-charge unless the job itself dedupes — the scheduler cannot save you, because from its side both firings are legitimate. My rule is that every scheduled job carries an idempotency key derived from its logical run date, not from wall-clock time, so the second firing finds the first run's record and stands down. The monitor side matters just as much: a fixed 24-hour expectation pages on the 25-hour night, and a monitor that fires a missed-deadline alert during the overlap and then recovers silently leaves no record that the schedule itself was ambiguous. Pinning both nights as fixtures — one timestamp that does not exist, one that exists twice — is the right regression pair. Does your monitor dedupe by logical run date, or by occurrence?