Testland
Browse all skills & agents

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-reference
View source

dst-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

  1. Identify the transition the code crosses - spring-forward (skipped hour) or fall-back (repeated hour) - from the DST mechanics section below.
  2. Match the bug class the code is exposed to (cron, billing, duration arithmetic, recurring meeting, storage) and apply its mitigation.
  3. Turn it into an assertion using the testable-behaviours table - construct the transition timestamp and assert the library's documented result (see the worked example).
  4. Pick zones + fixture timestamps from references/jurisdictions-and-fixtures.md, then refresh them against IANA before each release.

When to use

  • Designing time-handling code that crosses DST boundaries.
  • Auditing existing code for DST-safety.
  • Writing test cases that exercise DST behaviour.
  • Investigating "scheduled job ran twice / didn't run" reports.

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:

LibraryBehaviour 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 ZonedDateTimeConstructor takes a resolver: STRICT / SMART_BACKWARD / SMART_FORWARD
JS IntlBrowsers 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:

  • Pick one (typically the first occurrence in pytz/zoneinfo)
  • Raise an error
  • Take an is_dst / fold flag (Python 3.6+ has fold=0|1)

Worked example - a spring-forward assertion

Goal: prove the code under test handles a non-existent local time deterministically.

  1. Pick the transition: America/New_York spring-forward on 2026-03-08 - 02:00 local jumps to 03:00, so 02:00-02:59 does not exist.
  2. Construct 2026-03-08 02:30 America/New_York in the code path.
  3. Assert against the library's documented behaviour (from the table above):
    • Python zoneinfo normalises to 03:30 EDT - assert the normalised value, never 02:30.
    • Python pytz raises NonExistentTimeError - assert the raise.
    • Java ZonedDateTime applies its resolver - assert per the chosen STRICT / SMART_FORWARD rule.
  4. Repeat for fall-back: 2026-11-01 01:30 America/New_York occurs twice; assert the fold / is_dst selection picks the intended offset.

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:

  • Spring-forward day: doesn't fire (02:30 doesn't exist).
  • Fall-back day: fires twice (02:30 EDT, then 02:30 EST).

Mitigation:

  • Use UTC cron expressions when possible.
  • For local-time business hours, accept the irregularity or schedule outside transition hours (04:00 is safe everywhere).
  • Per cron-job-test-author (in the qa-async-jobs plugin): always test DST + leap-day edge cases.

Billing periods

"Bill on the 1st of each month at 00:00 local time":

  • Fine in jurisdictions without DST.
  • Risk in DST-observing: 00:00 local on Nov 1 (US) might overlap with the fall-back hour if billing involves more than one event.

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"):

  • Spring-forward: result is 23 hours later in local time.
  • Fall-back: result is 25 hours later in local time.

Mitigation: distinguish "24 hours from now" (Duration) from "this time tomorrow" (calendar addition).

Recurring meeting

"Every Monday at 09:00 local time":

  • Crosses DST boundary → still 09:00 local, but 2 minutes before or after the UTC equivalent of the previous week.
  • Calendar systems handle this; custom scheduling code often doesn't.

Storage

Storing wall-clock-local strings ("2026-03-08 02:30") is unsafe across DST. Always store UTC + zone identifier.

Testable behaviours

BehaviourTest
Code handles non-existent local timeConstruct 2026-03-08 02:30 America/New_York; library raises or normalises; assert expected
Code handles ambiguous local timeConstruct 2026-11-01 01:30 America/New_York; library raises or picks; assert
Cron-equivalent fires 0 / 1 / 2 timesSimulate clock across the transition; count invocations
Duration vs calendar addition consistentAssert difference on transition day
Storage uses UTC + zoneParse 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-patternWhy it failsFix
Storing local times as stringsAmbiguous on fall-back; nonexistent on spring-forwardUTC + zone, or RFC 3339 with explicit offset
Assuming all jurisdictions observe DSTHalf the world doesn'tPer-zone testing
Using "24 hours" for "tomorrow"Off by 1 hour on transition daysCalendar arithmetic primitives
Pinning to a specific year's transition dateRules change annuallyUse IANA zoneinfo dynamically
Crossing DST with naive datetimeBehaviour undefinedAlways tz-aware
Cron in local time without DST testingMisses / duplicates jobsTest transition days
Hardcoded UTC offset (-5:00)Wrong when DST is in effectUse zone identifier

Limitations

  • IANA zoneinfo changes throughout the year. Sept 2026 may add or remove DST observance for some jurisdictions; test data goes stale.
  • OS / runtime zoneinfo versions differ. Java's tzdata ships with the JDK; system tzdata is separate; Python's zoneinfo reads system tzdata. Mismatches cause subtle bugs.
  • Polar regions, antimeridian, and historic timezones. Special cases not covered here.
  • Doesn't address leap seconds. See leap-second-reference.

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

RegionDST behaviour
US (most)Spring-forward 2nd Sun March; fall-back 1st Sun November
EULast 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, IndiaNo DST
RussiaAbolished DST in 2011
IranAbolished DST in 2022
MexicoAbolished mainland DST in 2022
BrazilAbolished 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):

RegionSpring-forward 2026Fall-back 2026
America/New_York2026-03-08 02:00 → 03:00 EDT2026-11-01 02:00 → 01:00 EST
Europe/London2026-03-29 01:00 → 02:00 BST2026-10-25 02:00 → 01:00 GMT
Australia/Sydney2026-10-04 02:00 → 03:00 AEDT2026-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.