3.5 KiB
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", ornullwhen 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
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
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 }withweekday ∈ 0..6(Sunday = 0, matching cron numbering).toWallClock(epochMs: number, tz: string): WallClock.fromWallClock(components: Omit<WallClock, "weekday">, tz: string): number.detectDstTransition(beforeEpochMs, afterEpochMs, tz): "spring-forward" | "fall-back" | null.assertTimezone(tz: string): void— throwsCronError(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.