Persists UTC instants for events and IANA time zones for local recurring schedules, displays times in the viewer zone, and treats DST transitions as explicit rules rather than naive local clock arithmetic.
Package status: reference context ready for human review. The contract and test scenarios are complete, but no claim is made that an adopting implementation has passed them.
Decision
Absolute events persist as UTC instants. Recurring civil schedules persist a local time plus an IANA time-zone identifier, not a single offset. User interfaces convert instants into the viewer IANA zone at read time and never store that conversion as the source of truth. Daylight saving gaps and overlaps use named policies in the adoption profile (skip missing civil times; pick the earlier offset on overlap). The deployed tz database version is recorded so historical conversions stay reproducible.
Scope
- Scheduling, calendars, and timestamped reports that mix storage and display.
- Backend persistence and web formatting.
- DST gap and overlap policies for recurring series.
Outside this block
- Leap seconds and astronomical time.
- Business-day calendars and holiday sets.
- Mobile OS timezone permission prompts.
Contract
- One-off events store
occurred_at_utcwith a trailing Z or equivalent instant type; zoneless local datetime is rejected. - Recurring series store
local_timeandiana_timezonefrom the IANA database, plus tzdb_version used at write. - Display uses the viewer IANA zone; storage is not rewritten when the viewer changes zone.
- On a DST gap,
dst_gap_policyskip-occurrence means no row is materialized for that civil time. - On a DST overlap,
dst_overlap_policyearlier-offset selects the first offset (profile example +01:00). - Unknown IANA names fail with
timezone_unresolvablerather than falling back to server local time.
Implementation guidance
- Persist instants, not naive timestamps. Pin tzdb. Format with an explicit viewer zone.
- Materialize recurrences with IANA-aware libraries, not a database session timezone.
Failure handling and safeguards
- Unresolvable IANA names fail reads. Missing viewer zone uses the account default, never the server zone.
- Recurring rules need a zone, not a bare offset.
Verification and operations
- Fixtures: Berlin 09:00 to 07:00Z, New York display, October overlap, spring gap.
- Pin tzdb_version 2026a; regenerate goldens when it bumps.
The executable-looking examples in this package are fixtures and acceptance contracts. Run
python tools/validate.py from the collection root to check package structure and metadata; then
implement and execute the scenarios in the target repository.
Adoption assumptions
- Names and numeric values in
example.yamlare an adoption profile, not universal defaults. - The adopting team must map actors, data classes, error vocabulary, and ownership to its system.