first commit
This commit is contained in:
88
docs/foundation/timing/cron/src/timezone/timezone.md
Normal file
88
docs/foundation/timing/cron/src/timezone/timezone.md
Normal file
@@ -0,0 +1,88 @@
|
||||
# timezone
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## 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<WallClock, "weekday">, 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.
|
||||
Reference in New Issue
Block a user