Testland
Browse all skills & agents

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.

Install with skills.sh (any agent)

npx skills add testland/qa --skill timezone-test-matrix-builder
View source

timezone-test-matrix-builder

Overview

Time-related bugs are scattered across the codebase - storage, display, business logic, scheduled jobs. The test matrix needs to systematically exercise the canonical edge cases at each touchpoint.

When to use

  • Introducing time-test coverage to a new codebase.
  • After a time-related incident (DST bug, leap-day failure).
  • Migrating from one timezone library to another.
  • Periodic audit of time-handling.

Step 1 - Inventory time touchpoints

# Generic
grep -rn 'datetime\|Date\|Instant\|time.time\|moment\.\|dayjs\|chrono' \
  --include='*.{py,js,ts,java,kt,rb,go,rs,cs}' .

# Per-language
grep -rn 'datetime.now\|datetime.utcnow\|Date.now\|Instant.now' .
grep -rn 'tz\|timezone\|zoneinfo\|ZoneId' .
grep -rn 'cron\|schedule' .

Categorise each match:

CategoryExamplesTest needs
StorageDB columns; serialised datesRFC 3339 round-trip (offset or Z preserved)
Business logicAge calculation; duration; expiryDST, leap-day, monotonic
DisplayUser-facing datesPer-user-tz formatting
Cron / scheduledPeriodic jobsDST transition behaviour per dst-transition-reference
BillingPeriod boundariesDST + month-end + leap year
Audit / loggingTimestamp emissionMonotonic; leap-second tolerance
External APIThird-party datetime stringsTolerant parsing

Step 2 - Per-category test catalog

For each touchpoint, pull the test cases matching its category from references/test-catalog.md, which lists the storage, business-logic, cron, billing, and display tests to exercise.

Step 3 - Per-language test harness

All per-language fake-clock harnesses live in fake-clock-testing:

Languagefake-clock-testing reference
Python (freezegun)references/python.md
JS/TS (Jest + Sinon fake timers)references/js.md
Ruby (timecop)references/ruby.md
JVM (Clock / InstantSource)references/jvm.md
.NET (TimeProvider / FakeTimeProvider)references/dotnet.md
C / native binary (libfaketime)references/libfaketime.md

Step 4 - Build the matrix

For each (category, touchpoint, language) cell, generate test stubs:

# tests/time/matrix.yaml
matrix:
  - touchpoint: BillingService.createCharge
    category: billing
    tests:
      - dst-fall-back
      - leap-year-feb-29
      - month-end-rollover
      - timezone-multi-tenant
    language: java
    harness: jvm-clock-injection

  - touchpoint: ScheduledTask.runDaily
    category: cron
    tests:
      - dst-spring-forward
      - dst-fall-back
      - leap-day
    language: ruby
    harness: timecop

  # ...

Step 5 - Emit per-cell test files

# tests/time/test_billing_service.py
import pytest
from freezegun import freeze_time
from billing import BillingService

@freeze_time("2024-02-29T00:00:00Z")
def test_billing_handles_leap_day():
    charge = BillingService.create_charge_for_month(2024, 2)
    assert charge.days_in_period == 29

@freeze_time("2025-02-28T00:00:00Z")
def test_billing_handles_non_leap_february():
    charge = BillingService.create_charge_for_month(2025, 2)
    assert charge.days_in_period == 28

@freeze_time("2026-11-01T05:30:00Z")  # Just past fall-back in NY
def test_billing_period_spans_dst_fall_back():
    # Period from Nov 1 00:00 to Nov 2 00:00 in New_York
    # is 25 hours of UTC due to fall-back
    period = BillingService.month_period(year=2026, month=11, zone="America/New_York")
    assert period.duration.total_seconds() == 30 * 24 * 3600 + 3600  # 1 extra hour

Step 6 - Run and validate the generated tests

Verify before recording coverage: run the emitted files (pytest tests/time/, mvn test, etc.) and assert every DST and leap-day case produces its expected pass (or the expected failure for a known-bug reproduction). If a case errors instead of asserting, the fake-clock wiring is wrong - fix the harness mapping (Step 3) or add the missing TZ / zone for local-time cases - and re-run until the matrix is green.

Step 7 - Coverage doc

# Time Test Matrix Coverage

## Touchpoints covered

| Service | Category | Tests | File |
|---|---|---|---|
| BillingService | billing | leap-day, dst-fall-back, month-end | tests/time/test_billing.py |
| ScheduledTask | cron | dst-spring-forward, leap-day | tests/time/test_cron.py |
| API serialiser | storage | rfc-3339-round-trip | tests/time/test_api_format.py |

## Coverage gaps

- BillingService - leap-second tolerance: deferred (low likelihood)
- Display layer: per-user-TZ rendering - manual QA only

## How to add a new touchpoint

1. Run inventory grep (Step 1).
2. Categorise (Step 2).
3. Update matrix.yaml.
4. Generate test from template (per Step 5).

Worked example

Adding leap-day coverage to a Python BillingService.create_charge_for_month: inventory (Step 1) categorises it as billing, whose catalog rows call for month-end-across-leap-year, DST-window, and multi-tenant-timezone tests. Python maps to freezegun (fake-clock-testing references/python.md, Step 3), so the emitted tests/time/test_billing_service.py (Step 5) freezes 2024-02-29 asserting days_in_period == 29 and 2025-02-28 asserting 28. Running the file (Step 6) confirms both pass, and the coverage doc (Step 7) then records BillingService's billing category as covered - the leap-year February boundary is now exercised on every run.

Anti-patterns

Anti-patternWhy it failsFix
Test only the happy pathTime bugs are edge casesDST + leap-day mandatory
Live system time in testsAnnual / quarterly flakesAlways fake-clock
One mega-test for all time edge casesFailures opaquePer-category, per-touchpoint
Skip storage round-tripSchema drift / serialiser bug hidesRFC 3339 round-trip everywhere
Test in UTC onlyMisses local-zone DST / display bugsPer-zone testing
Hardcoded dates that ageRe-write needed annuallyUse relative dates or fake clock
No coverage docGaps invisibleStep 7
Ignore display-layerReal users see wrong datesEven if manual, document the manual coverage

References

  • IANA Time Zone Database: www.iana.org/time-zones (opens in new window).
  • Companion catalog: dst-transition-reference (DST bug classes; leap seconds in its references/leap-seconds.md).
  • Per-language harnesses: fake-clock-testing (freezegun, Jest/Sinon fake timers, timecop, JVM Clock injection, .NET TimeProvider, libfaketime).
  • Cross-plugin (cron): cron-job-test-author (qa-async-jobs).

Per-category test catalog

View source (opens in new window)

Per-category test catalog

The test cases each touchpoint category should exercise. Pick the rows matching the category assigned during inventory (Step 1).

Storage tests

TestPattern
Round-trip RFC 3339parse → emit → parse → assert equal
Round-trip via JSONserialise object → deserialise → assert
Microsecond precision preserved.123456Z survives DB store/load
Zone information preserved or normalized to UTCDocument the policy

Business-logic tests

TestPattern
DST spring-forwardSchedule at 02:30 local on transition day; verify behaviour
DST fall-backSame 01:30 local appearing twice; verify ordering
Leap day Feb 29"1 year from Feb 29 2024" → Feb 28 2025 (Per ICU)
Year-end rollover"Tomorrow" on Dec 31
Month-endJan 31 + 1 month = Feb 28 / 29 (per library)
Negative durationsOperations on "5 minutes ago"
Leap second toleranceCode uses monotonic time per dst-transition-reference references/leap-seconds.md

Cron tests

TestPattern
Daily 02:30 EST cron on spring-forwardDoesn't fire OR fires at 03:30 (per cron spec)
Daily 01:30 EST cron on fall-backFires once vs twice (per spec)
Monthly on Feb 29 (non-leap year)Fires on Feb 28 OR not at all
Weekly cron crossing DST1-hour offset for one week

Billing tests

TestPattern
Billing on month-end across leap yearFeb 28 vs Feb 29 handling
Billing window across DSTHour gain / loss in the period
Pro-ration calculationAcross DST boundary
Multi-tenant timezone varianceSame wall-clock hour ≠ same UTC

Display tests

TestPattern
Per-user TZ formattingUser in Asia/Tokyo sees 09:00 JST; user in Europe/London sees 00:00 GMT
ISO 8601 vs human-readableLocalised display ≠ wire format
24h vs 12h conventionPer user locale
Relative time ("2 hours ago")Per system locale

Related skills

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, 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.

fake-clock-testing

Fake clocks / freeze time in tests across every mainstream runtime: freezegun (Python), Jest fake timers + Sinon @sinonjs/fake-timers (JS/TS), timecop (Ruby), java.time.Clock / InstantSource injection (JVM), .NET TimeProvider / FakeTimeProvider, and libfaketime (LD_PRELOAD for any native binary). Covers the language-agnostic discipline - inject or patch the clock, freeze vs tick vs advance vs set-system-time semantics, teardown so fake clocks never leak between tests - plus the shared anti-pattern table (real sleep under a frozen clock, leaked clock state, timezone-dependent assertions). Per-library setup, API, and CI recipes live in references/{python,js,ruby,jvm,dotnet,libfaketime}.md. Use when tests need deterministic control of now(), timers, or timeouts in any language, or when choosing the right fake-clock tool for a stack.