Summary
Extend time warping beyond Date to time-zone-aware behavior: virtualize Intl.DateTimeFormat, Intl.RelativeTimeFormat, and (optionally) the Temporal polyfill so tests/simulations can deterministically exercise DST transitions, custom UTC offsets, and clock drift—without mutating the host environment (no global TZ side effects).
Why
- Many bugs only appear around DST boundaries/offset changes.
- Apps format with
Intl.* or use Temporal; warping Date alone isn’t enough.
- CI hosts differ in
TZ; reproducible tests need a virtual tz layer.
Scope
- Virtual TZ layer: specify IANA zone (e.g.,
"America/New_York") or a custom fixed offset (e.g., UTC+05:45), independent of host TZ.
- DST simulation: step over ambiguous/missing local times; expose flags (
isAmbiguous, isGap).
- Clock drift/skew: add linear drift (ppm) or step skew (+/- ms) on top of virtual wall clock.
Intl.* patching: intercept new Intl.DateTimeFormat(...).format() (and formatToParts), Intl.RelativeTimeFormat, honoring the virtual tz/locale.
- Temporal (polyfill) hook: optional adapter so
Temporal.Now.*() and ZonedDateTime use the virtual clock/tz.
- No global
process.env.TZ writes; sandboxed patches only.
API (draft)
import { installTimeZone, updateTimeZone, uninstallTimeZone } from "time-warp-manipulation/tz";
installTimeZone({
zone: "America/Los_Angeles", // or { fixedOffsetMinutes: -420 }
driftPpm: 0, // e.g., +50 ppm to simulate fast clocks
skewMs: 0, // instantaneous skew applied to wall clock
patch: { intl: true, temporal: true } // temporal hooks optional
});
// Change zone/drift at runtime
updateTimeZone({ zone: "Europe/Berlin", driftPpm: 25 });
// Tear down
uninstallTimeZone();
Acceptance Criteria
new Date(utcMs) + Intl.DateTimeFormat(tz).format() equals output produced by virtual layer, regardless of host TZ.
- Deterministic behavior across DST gap (spring forward) and fold (fall back); surface
isGap/isAmbiguous.
- Works in Node, Bun, and browser/JSDOM; graceful no-op where
Intl lacks zone data.
- Plays nicely with existing time warp (no double offsets); documented order of installation.
Tests
- Golden tests for multiple IANA zones across 5+ years (including leap years).
- Property tests around DST transitions (gap/fold windows).
- Drift/skew round-trip: after advancing virtual 1h with +100 ppm drift, expected delta ≈ 3600.36s.
- Temporal polyfill integration tests (
Temporal.Now.zonedDateTimeISO(zone)).
Implementation Notes
- Use a compact TZ data bundle (e.g., subset of IANA via
@formatjs/intl-timezone or generated tables) to avoid heavy deps; tree-shake by zone.
- Wrap
Intl.DateTimeFormat.prototype.format & formatToParts; map input instants → virtual local time using our tz table.
- Provide
withVirtualTimeZone(fn) helper for scoped application in tests.
Summary
Extend time warping beyond
Dateto time-zone-aware behavior: virtualizeIntl.DateTimeFormat,Intl.RelativeTimeFormat, and (optionally) the Temporal polyfill so tests/simulations can deterministically exercise DST transitions, custom UTC offsets, and clock drift—without mutating the host environment (no globalTZside effects).Why
Intl.*or use Temporal; warpingDatealone isn’t enough.TZ; reproducible tests need a virtual tz layer.Scope
"America/New_York") or a custom fixed offset (e.g.,UTC+05:45), independent of hostTZ.isAmbiguous,isGap).Intl.*patching: interceptnew Intl.DateTimeFormat(...).format()(andformatToParts),Intl.RelativeTimeFormat, honoring the virtual tz/locale.Temporal.Now.*()andZonedDateTimeuse the virtual clock/tz.process.env.TZwrites; sandboxed patches only.API (draft)
Acceptance Criteria
new Date(utcMs)+Intl.DateTimeFormat(tz).format()equals output produced by virtual layer, regardless of hostTZ.isGap/isAmbiguous.Intllacks zone data.Tests
Temporal.Now.zonedDateTimeISO(zone)).Implementation Notes
@formatjs/intl-timezoneor generated tables) to avoid heavy deps; tree-shake by zone.Intl.DateTimeFormat.prototype.format&formatToParts; map input instants → virtual local time using our tz table.withVirtualTimeZone(fn)helper for scoped application in tests.