dst-transition-reference
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 and refreshable per-region test-data fixtures live in references/. Use when designing or auditing time-handling code or test cases.
Install with skills.sh (any agent)
npx skills add testland/qa --skill dst-transition-referencedst-transition-reference
Overview
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 (opens in new window)) is the canonical source of historical and current DST rules.
How to use this reference
When to use
DST mechanics
Spring-forward (skipped hour)
Per en.wikipedia.org/wiki/Daylight_saving_time (opens in new window), 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 |
Fall-back (repeated hour)
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:
Worked example - a spring-forward assertion
Goal: prove the code under test handles a non-existent local time deterministically.
Per-jurisdiction differences
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.
Common bug classes
Cron jobs
A "daily at 02:30" cron in America/New_York:
Mitigation:
Billing periods
"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).
Duration arithmetic
tomorrow_same_time = today_same_time + Duration("24 hours"):
Mitigation: distinguish "24 hours from now" (Duration) from "this time tomorrow" (calendar addition).
Recurring meeting
"Every Monday at 09:00 local time":
Storage
Storing wall-clock-local strings ("2026-03-08 02:30") is unsafe across DST. Always store UTC + zone identifier.
Testable behaviours
| 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-patterns
| 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 |
Limitations
References
Per-jurisdiction DST rules and test-data fixtures
View source (opens in new window)Per-jurisdiction DST rules and test-data fixtures
Deep reference for dst-transition-reference SKILL.md. Consult when choosing which zones to cover and for refreshable per-region fixture timestamps.
Per-jurisdiction differences
| Region | DST behaviour |
|---|---|
| US (most) | Spring-forward 2nd Sun March; fall-back 1st Sun November |
| EU | Last Sun March; last Sun October (one hour earlier) |
| Australia (most of NSW/VIC) | First Sun October; first Sun April (Southern hemisphere - reversed) |
| Australia (QLD, NT, WA, NT) | No DST |
| Japan, China, India | No DST |
| Russia | Abolished DST in 2011 |
| Iran | Abolished DST in 2022 |
| Mexico | Abolished mainland DST in 2022 |
| Brazil | Abolished DST in 2019 |
Per IANA: rules change frequently. Test against current zoneinfo, not assumptions.
Test data fixtures
Useful canonical timestamps per region (refresh against IANA):
| Region | Spring-forward 2026 | Fall-back 2026 |
|---|---|---|
| America/New_York | 2026-03-08 02:00 → 03:00 EDT | 2026-11-01 02:00 → 01:00 EST |
| Europe/London | 2026-03-29 01:00 → 02:00 BST | 2026-10-25 02:00 → 01:00 GMT |
| Australia/Sydney | 2026-10-04 02:00 → 03:00 AEDT | 2026-04-05 03:00 → 02:00 AEST |
These dates change year to year (some); commit a current fixture and refresh annually.
Related skills
dotnet-faketime
Wraps .NET's TimeProvider abstraction (System.TimeProvider, introduced .NET 8) and FakeTimeProvider from Microsoft.Extensions.TimeProvider.Testing: SetUtcNow, Advance, AutoAdvanceAmount, CreateTimer, Delay, and the pre-.NET-8 ISystemClock migration path. Use when testing C# or F# code that reads the current time, uses timers, or awaits Task.Delay.
freezegun-python
Wraps freezegun (github.com/spulec/freezegun), the Python time-mocking library: @freeze_time decorator / context manager, freeze_time(...).start() + stop(), tick / move_to / tz_offset, and integration with datetime.now / time.time / time.localtime. Use when testing Python code that calls datetime / time.
iso-8601-vs-rfc-3339-reference
Pure-reference catalog of the ISO 8601 vs RFC 3339 distinction. Covers the relationship (RFC 3339 is a strict subset of ISO 8601 designed for internet protocols), the syntactic differences (RFC 3339 disallows ISO 8601's '+02' offset short-form requires '+02:00'; RFC 3339 mandates a date-time separator T or space; ISO 8601 allows much more), the canonical date-time string format (YYYY-MM-DDTHH:MM:SS[.fff]±HH:MM or Z), per-language parser behaviour (Python isoformat, Java Instant.parse, JS Date.parse non-spec), and serialisation rules for APIs. Use when choosing a wire format, parsing third-party datetimes, or auditing time-string handling.
jest-fake-timers
Wraps Jest's built-in modern fake-timers (built on @sinonjs/fake-timers since Jest 27): jest.useFakeTimers(), jest.setSystemTime(), jest.advanceTimersByTime(), jest.runAllTimers(), and jest.useRealTimers() for selective restoration. Use when testing JS/TS code in Jest where setTimeout / setInterval / Date / Date.now need deterministic control.
leap-second-reference
Pure-reference catalog of leap-second mechanics and the bugs they cause: the 23:59:60 UTC insertion (announced ~6 months ahead by IERS Bulletin C; 27 inserted 1972-2016; abolished by 2035 per CGPM 2022), the Google/AWS leap-smear alternative, and the four bug classes a real insertion exposes - time_t non-monotonicity, negative durations, NTP cascading, and cross-node clock skew - each with a monotonic-clock fix and a freezegun simulation. Use when auditing time-sensitive code (financial timestamping, distributed logs, NTP-driven schedulers) for second-by-second progress assumptions; for the far more common daylight-saving-time transition hazards, use dst-transition-reference instead.
libfaketime-c
Wraps libfaketime (github.com/wolfcw/libfaketime), the LD_PRELOAD library that fakes the clock for any binary by intercepting time() / gettimeofday() / clock_gettime(). Covers absolute-date mode (FAKETIME='2026-12-31 23:59:00'), relative offset (FAKETIME='-1d'), advance-rate (FAKETIME='@2026-12-31 23:59:00 x5' for 5x speed), per-process scope via LD_PRELOAD, and FAKETIME_NO_CACHE for high-resolution mocking. Use when you need to fake time, mock the clock, or freeze time for C/C++ or any native binary that needs deterministic wall-clock time.
mockclock-jvm
Wraps Java's java.time.Clock + InstantSource dependency-injection pattern for testing time-sensitive code. Covers Clock.fixed(instant, zone), Clock.offset(baseClock, duration), Clock.systemDefaultZone() for production, the InstantSource interface (Java 17+), and the recommended dependency-injection pattern (constructor-inject Clock instead of calling Instant.now() directly). Use when you need to wire the clock-injection pattern (Clock.fixed, Clock.offset, MutableClock, InstantSource, Spring @Bean) into JVM (Java / Kotlin / Scala) production or test code. For pure DST transition reference (skipped or repeated hours, IANA DB, cron-double-fire bug classes) without a clock-injection need, use dst-transition-reference instead.
sinon-fake-timers-js
Wraps Sinon's standalone @sinonjs/fake-timers library for JS/TS testing: install(), tick() / tickAsync(), setSystemTime(), restore(); covers timers (setTimeout / setInterval / requestAnimationFrame), Date / performance.now() / hrtime, and the toFake option for selective override. Runner-agnostic - drives the clock directly in Mocha, AVA, Jasmine, node:test, or the browser. Use when JS/TS code needs deterministic timer or clock control and the test runner does not already expose this library behind its own built-in fake-timer API.
timecop-ruby
Wraps timecop (github.com/travisjeffery/timecop), the Ruby time-mocking gem: Timecop.freeze, Timecop.travel, Timecop.scale (time-speedup), Timecop.return (cleanup), and RSpec-friendly helpers. Use when testing Ruby/Rails code that calls Time / Date / DateTime.
timezone-test-matrix-builder
Builds a timezone, daylight saving time (DST), and leap year / leap second test matrix from wherever a codebase reads or formats dates and times. Finds time-handling code (grep for datetime / Date / Instant / time.time / timezone), sorts each spot into storage, business-logic, display, cron, or billing, picks the edge cases that matter (DST spring-forward / fall-back, ambiguous local time, leap day Feb 29, ISO 8601 / RFC 3339 round-trip, zone-database updates), and emits per-spot test stubs wired to the language's fake-clock (mock-time) library. Use when a codebase needs timezone, DST, and leap-year test coverage derived from its own date/time usage.