1. Check a schedule for DST-skip / DST-overlap risk
| Status | Local time | UTC instant |
|---|
2. Check a fixed-UTC-only scheduler for drift
For a scheduler with no timezone field — a plain crontab on a UTC host, or GitHub Actions' schedule: cron, which its own docs say always runs in UTC — a single expression cannot hold a local wall-clock time steady across a DST-observing zone. This shows what UTC time you actually need per segment of the year, and the drift between them.
| From (local calendar date) | UTC offset in effect | UTC clock time needed |
|---|
Prefer a CLI, or want this in CI before a deploy?
Same logic, zero dependencies, one file:
node cli.mjs next "30 2 * * *" --tz America/New_York node cli.mjs next "30 2 * * *" --tz America/New_York --count 5 --from 2027-03-13T00:00:00Z --json node cli.mjs drift 09:00 --tz America/New_York node cli.mjs drift 09:00 --tz America/New_York --year 2027 --json
Get cli.mjs — Node 18+, nothing to install, no network call ever. Both subcommands exit 1 when they find DST risk, so either works as a one-line CI check before a new scheduled job ships.
What's checked
| Check | How |
|---|---|
| Spring-forward skip | A cron occurrence's local wall-clock time (e.g. 02:30) does not exist on that calendar date in the chosen timezone, because clocks jumped forward past it |
| Fall-back overlap | A cron occurrence's local wall-clock time occurs twice on that calendar date, because clocks were set back through it — both real UTC instants are reported |
| Fixed-UTC-cron drift | A target local time is converted to UTC once per DST segment of the year; more than one distinct UTC clock time means a single fixed-UTC schedule cannot hold the local time steady |
Month and day-of-week accept the standard 3-letter names (JAN–DEC, SUN–SAT), case-insensitive, anywhere a number is allowed — singly, in a list, or as a range (MON-FRI). Minute, hour, and day-of-month stay numeric. Day-of-month and day-of-week follow the standard cron rule: when both are restricted (neither is *), a date matches either one, not both — the single most common cron misreading. See why this happens, and this checker's own named limits, or how this compares to just checking a DST calendar by hand.
Common questions
Why does a cron job skip or double-fire around daylight saving?
A cron expression that names a wall-clock time (like 02:30) in a timezone that observes DST is naming a local time that, twice a year, either does not exist (spring forward: clocks jump from 02:00 straight to 03:00, so 02:30 never happens that day) or happens twice (fall back: 01:00-01:59 occurs twice). Cron implementations do not agree on what to do then — some skip the run entirely, some fire once, some fire twice — so this tool reports the ambiguity rather than guessing which scheduler you run.
Why does a schedule drift an hour twice a year even without a skip or double-fire?
Many schedulers — GitHub Actions' own schedule: cron among them, by its own documentation — always interpret the expression in UTC, with no timezone field at all. A single UTC cron expression cannot hit the same local wall-clock time year-round in any zone that observes DST: it is exactly right for half the year and off by the DST offset, almost always one hour, for the other half. That is not a skip or a double-fire, just a job that quietly runs an hour early or late for months at a time.
Does this tool know what my actual cron daemon or scheduler will do?
No, and it does not claim to. Different cron implementations genuinely disagree on spring-forward and fall-back behavior. This tool computes the two facts that are true regardless of implementation — whether a named local time exists uniquely, doesn't exist, or exists twice on a given date — and leaves the scheduler-specific behavior to your own scheduler's documentation.
Is anything uploaded?
No. Everything is computed in your browser using the standard Intl timezone database your browser already ships, and nothing you type is sent anywhere. The CLI makes no network call either — it is pure Intl/Date arithmetic.