Pure-reference catalog of Daylight Saving Time (DST) transition patterns and their canonical bug classes. Covers the spring-forward (skipped hour: 02:00 → 03:00 local) and fall-back (repeated hour: 02:00 → 01:00 local) transitions, the historical irregularity of DST (different jurisdictions, transitions on different dates, some regions abolish DST or never adopted it), the IANA timezone database (tz / Olson DB) as the canonical source, and the testable behaviors DST creates (duplicate / missing local timestamps, cron jobs that fire 0 or 2 times, billing periods that miss / double-count, recurring meetings on transition days). Per-jurisdiction DST-rule tables, refreshable per-region test-data fixtures, and the leap-second reference (23:59:60 insertion, time_t stalls, leap-smear vs step, monotonic-clock fixes) live in references/. Use when designing or auditing time-handling code or test cases, or when auditing leap-second assumptions.
68
85%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
Passed
No findings from the security scan
DST transitions cause a large share of production time-bugs: spring-forward creates non-existent local times, fall-back creates duplicate local times. The IANA Time Zone Database (iana.org/time-zones) is the canonical source of historical and current DST rules.
Per en.wikipedia.org/wiki/Daylight_saving_time, in US Eastern: on the 2nd Sunday of March, at 02:00 local time the clock jumps to 03:00. The 02:00-02:59 hour does not exist in local time.
Tests against 2026-03-08 02:30 America/New_York produce
ambiguous or invalid results depending on library:
| Library | Behaviour at non-existent local time |
|---|---|
Python pytz (legacy) | pytz.exceptions.NonExistentTimeError |
Python zoneinfo (3.9+) | Returns the "would-be" time + 1h (=03:30 EDT) |
Java ZonedDateTime | Constructor takes a resolver: STRICT / SMART_BACKWARD / SMART_FORWARD |
| JS Intl | Browsers vary; often returns the post-transition time |
In US Eastern: 1st Sunday of November at 02:00 local time, the clock falls back to 01:00. The 01:00-01:59 hour occurs twice - once as EDT (UTC-4), once as EST (UTC-5).
2026-11-01 01:30 America/New_York is ambiguous. Libraries
either:
is_dst / fold flag (Python 3.6+ has fold=0|1)Goal: prove the code under test handles a non-existent local time deterministically.
2026-03-08 02:30 America/New_York in the code path.zoneinfo normalises to 03:30 EDT - assert the
normalised value, never 02:30.pytz raises NonExistentTimeError - assert the raise.ZonedDateTime applies its resolver - assert per the
chosen STRICT / SMART_FORWARD rule.2026-11-01 01:30 America/New_York occurs
twice; assert the fold / is_dst selection picks the intended
offset.Per-region DST rules (US, EU, Australia, and the growing list of regions that abolished DST) and refreshable 2026 fixture timestamps live in references/jurisdictions-and-fixtures.md. Per IANA, rules change frequently - test against current zoneinfo, not assumptions.
A "daily at 02:30" cron in America/New_York:
Mitigation:
cron-job-test-author (in the qa-async-jobs plugin):
always test DST + leap-day edge cases."Bill on the 1st of each month at 00:00 local time":
Mitigation: bill at UTC, or at a local hour known to be safe (e.g., 06:00).
tomorrow_same_time = today_same_time + Duration("24 hours"):
Mitigation: distinguish "24 hours from now" (Duration) from "this time tomorrow" (calendar addition).
"Every Monday at 09:00 local time":
Storing wall-clock-local strings ("2026-03-08 02:30") is unsafe across DST. Always store UTC + zone identifier.
| Behaviour | Test |
|---|---|
| Code handles non-existent local time | Construct 2026-03-08 02:30 America/New_York; library raises or normalises; assert expected |
| Code handles ambiguous local time | Construct 2026-11-01 01:30 America/New_York; library raises or picks; assert |
| Cron-equivalent fires 0 / 1 / 2 times | Simulate clock across the transition; count invocations |
| Duration vs calendar addition consistent | Assert difference on transition day |
| Storage uses UTC + zone | Parse stored value; expect ISO format with offset or Z |
Per
timezone-test-matrix-builder,
the test matrix combines (zone, transition-type, library-version).
| Anti-pattern | Why it fails | Fix |
|---|---|---|
| Storing local times as strings | Ambiguous on fall-back; nonexistent on spring-forward | UTC + zone, or RFC 3339 with explicit offset |
| Assuming all jurisdictions observe DST | Half the world doesn't | Per-zone testing |
| Using "24 hours" for "tomorrow" | Off by 1 hour on transition days | Calendar arithmetic primitives |
| Pinning to a specific year's transition date | Rules change annually | Use IANA zoneinfo dynamically |
Crossing DST with naive datetime | Behaviour undefined | Always tz-aware |
| Cron in local time without DST testing | Misses / duplicates jobs | Test transition days |
| Hardcoded UTC offset (-5:00) | Wrong when DST is in effect | Use zone identifier |
zoneinfo reads system tzdata. Mismatches cause subtle bugs.cron-job-test-author (qa-async-jobs).fake-clock-testing,
timezone-test-matrix-builder.