# timezone _(no submodules)_ ## Purpose The timezone module converts between epoch-millisecond timestamps and the wall-clock components observed in a specific IANA timezone using only the platform-native `Intl.DateTimeFormat` — no external timezone database is shipped or required. It powers the cron next-run algorithm by translating the user's "every day at 02:00 New York time" into the actual moments when that wall-clock instant occurs, including the awkward edge cases of daylight-saving-time spring-forward gaps and fall-back overlaps. ## Features - `toWallClock(epochMs, tz)` — decompose an epoch instant into year / month / day / hour / minute / second / weekday in the requested zone. - `fromWallClock(components, tz)` — the inverse; performs up to four bounded fixed-point iterations to converge on the correct epoch even across DST transitions. - `detectDstTransition(before, after, tz)` — classifies a transition between two adjacent instants as `"spring-forward"`, `"fall-back"`, or `null` when no transition is crossed. - `assertTimezone(tz)` — eagerly validates an IANA name so the caller can fail fast at the API boundary. - Per-zone formatter cache keeps Intl construction off the hot path. ## Usage: Basic Example ```typescript import { toWallClock, fromWallClock } from "@suaveplan/cron"; const wall = toWallClock(Date.UTC(2026, 0, 1, 5, 0, 0), "America/New_York"); console.log(wall.hour); // 0 — midnight in NY const back = fromWallClock( { year: 2026, month: 1, day: 1, hour: 0, minute: 0, second: 0 }, "America/New_York" ); console.log(back === Date.UTC(2026, 0, 1, 5, 0, 0)); // true ``` ## Usage: Advanced Example ```typescript import { detectDstTransition, fromWallClock, toWallClock, } from "@suaveplan/cron"; const start = Date.UTC(2026, 2, 8, 6, 0, 0); const end = Date.UTC(2026, 2, 8, 8, 0, 0); const transition = detectDstTransition(start, end, "America/New_York"); console.log(transition); // "spring-forward" const requested = { year: 2026, month: 3, day: 8, hour: 2, minute: 30, second: 0 }; const real = fromWallClock(requested, "America/New_York"); const observed = toWallClock(real, "America/New_York"); console.log(observed.hour, observed.minute); // not 02:30 — the slot was skipped ``` ## API Reference - `interface WallClock` — `{ year, month, day, hour, minute, second, weekday }` with `weekday ∈ 0..6` (Sunday = 0, matching cron numbering). - `toWallClock(epochMs: number, tz: string): WallClock`. - `fromWallClock(components: Omit, tz: string): number`. - `detectDstTransition(beforeEpochMs, afterEpochMs, tz): "spring-forward" | "fall-back" | null`. - `assertTimezone(tz: string): void` — throws `CronError(CRON.TIMEZONE_INVALID)` for invalid IANA names. ## Implementation Notes `Intl.DateTimeFormat` is the only timezone authority. The package never ships a tz database — keeping it universal and dependency-free. The inverse `fromWallClock` works by guessing UTC, observing the wall clock offset that produces, and applying it back. Two iterations suffice for every IANA zone in normal time; we run up to four to handle the awkward spring-forward case where the first correction overshoots the gap. Inside the gap the function picks the canonical real-instant that `Intl.DateTimeFormat` reports, and callers re-verify against their field set so cron schedules that fall in the gap fire at the next existing minute. Formatter construction is cached per IANA name to keep the hot path allocation-free.