Temporal Concept Map
A decoder for readers who know Temporal: where equivalent capability surfaces in zdate, where zdate takes a different approach, and why some Temporal types and options are intentionally absent. It is a capability map, not a lineage — the two models developed independently.
Types
| Temporal | zdate |
|---|---|
Temporal.Instant | The epoch bigint itself, with ZEpoch as its function library — a primitive, not a class |
Temporal.ZonedDateTime | ZDate — the instant viewed through zone and calendar, where zone is a view option, not identity |
Temporal.PlainDate | ZDateOnly — epoch-backed, see Why Nothing Here Is Plain |
Temporal.PlainTime | ZTimeOnly — epoch-backed, see Why Nothing Here Is Plain |
Temporal.PlainDateTime | Calendar fields as construction input; a view's fields on the way out |
Temporal.PlainYearMonth | ZDateOnly, day anchored by convention |
Temporal.PlainMonthDay | ZDateOnly, year anchored by convention |
Temporal.Duration | ZDuration |
Temporal.Now | zdate() / ZEpoch.now() |
| Time zone objects | ZTimeZone, exposed on every view as view.zone |
| Calendar identifiers | CalendarId view option; engines behind ZCalendar |
| — | ZInterval, ZRepair, pattern parsing and formatting, natural dates |
Epoch Identity versus Zoned Identity
This is the load-bearing difference; most rows above follow from it.
Concretely: a ZDate is an epoch with two projections, utc and local.
A ZonedDateTime binds instant, zone, and calendar together as one identity — converting the zone produces a differently-identified object, and each type in the Plain family reifies a field combination into its own identity with its own arithmetic. zdate holds one identity, the epoch, and treats zone, calendar, and locale as parameters of a view. Converting a zone changes nothing but which projection you read. Two ZDates are equal when their epochs are equal, whatever views they were created with.
The consequence shows at collection scale: parse timestamps from many sources and zdate gives you uniform instants viewed however the consumer chooses, rather than objects that each insist on their source's local fields. See Architecture.
Source Data versus View Options
Temporal adopts an RFC 9557 annotation as the object's zone and calendar — the wire format configures the object. In zdate, the suffix belongs to the source: it resolves the epoch, remains available on parsedSuffix, and is adopted into views only when requested with 'source'. Nothing from the suffix is applied implicitly. See Creating Instants.
Parse Options versus Policy
Temporal surfaces resolution choices as parse options; zdate parses with one deterministic behavior and moves the choices into explicit repair:
| Temporal option | zdate |
|---|---|
offset: 'reject' | The only parsing behavior |
offset: 'use' | ZRepair policy rfc9557: {disagreement: 'offset'} |
offset: 'ignore' | ZRepair policy rfc9557: {disagreement: 'zone'} |
offset: 'prefer' | Deliberately absent — one call site, two data-dependent meanings |
disambiguation: 'reject' | The only parsing behavior |
disambiguation: 'compatible' | Repair policy 'default'; also the fixed rule for internal wall-time math |
disambiguation: 'earlier' / 'later' | Repair policies ambiguity / nonexistent, configured independently |
overflow: 'reject' / 'constrain' | ZEpoch.from() overflow, which adds 'cascade' |
The policy shape serves both workflows: catch-and-repair at ingest, or sweep stored timestamps after time-zone rules change. See Repairing Timestamps for the full treatment.
Reject versus Resolve
The asymmetry in that table is deliberate. Parsing is the front door: an ambiguous or impossible wall time in external input has no correct answer, and a guessed answer becomes stored data — so the parser refuses. Arithmetic is different: to() and by() were asked to move, the result has to land somewhere, and one fixed rule matching JavaScript Date — earlier in a fold, forward across a gap — keeps the landing predictable. Strictness where a guess would invent data; predictability where an answer is mandatory. The full treatment is on Time Zones and DST.
Signed Zero Offsets
Temporal normalizes -00:00 to +00:00 for zone matching. zdate follows RFC 9557 sections 2.2 and 3.4 instead: Z and -00:00 mean "UTC is known, local offset is unknown" and skip both zone matching and wall-time resolution — the instant is identified directly. +00:00 asserts a local offset and must agree with the zone. Noncanonical negative-zero forms (-0000, -00) are invalid.
Projections and Arithmetic
Temporal distributes arithmetic across its types — each Plain type and ZonedDateTime carries its own add, subtract, until, since, round, and with. zdate concentrates the same capability into six verbs on one type — with, format, compare, by, to, diff — made calendar- and zone-aware by the view they operate in. The method count differs because the type count differs, not the capability.
Rounding vocabulary translates directly: Temporal's round({smallestUnit, roundingIncrement, roundingMode}) is zdate's to({nearest, interval, snap}). roundingMode: 'ceil' becomes snap: 'future', 'floor' becomes 'past', and the default 'distance' snaps to the nearest boundary.
Conversion collapses the same way. Temporal moves between its types through a pairwise method matrix — toPlainDate(), toPlainTime(), toZonedDateTime(), and the rest. zdate has one interchange shape, calendar fields: toFields() on the way out, to() or a constructor on the way in. zdate('2025-01-04').to(timeOnly.toFields()) works because everything in the system already speaks fields — there is no conversion layer because there is nothing to convert between.
Why Nothing Here Is Plain
Temporal's Plain types are field-backed: a PlainDate is its year, month, and day — no instant exists anywhere inside it. zdate's date-only and time-only types are epoch-backed: a restricted interface over an internal instant. The table maps them because they answer the same needs, not because they are the same construction.
The trade-off is real in both directions. A field-backed type cannot leak an instant it does not have; an epoch-backed type must keep its instant from leaking. ZDateOnly and ZTimeOnly hold that boundary structurally: neither exposes the internal instant or its epoch, ZDateOnly exposes no time fields, ZTimeOnly exposes no date fields, and both block adjustments of the wrong kind at the type level — ZDateOnly.by() accepts only years, months, weeks, and days; ZTimeOnly.by() accepts only time units. Even valueOf() refuses to leak: ZDateOnly hands back days, ZTimeOnly nanoseconds since midnight — domain-scaled quantities, never the instant. There is no exit.
A PlainYearMonth or PlainMonthDay maps to ZDateOnly by convention — the unused field is anchored. The convention is parser-honored, not userland: new ZDateOnly('2025-03') is a year-month with the day anchored to 1, and new ZDateOnly('03-15') is a month-day with the year anchored to 0.
zdate could add equivalents for the remaining Plain types — each would be another restricted wrapper — but a year-month or month-day does not clear the bar for a permanent public type; ZDateOnly already expresses both. Why the two wrappers exist at all is on Date and Time Only.
Durations and Anchors
Temporal's relativeTo exists because durations with calendar units are indeterminate without an anchor date — round(), total(), and compare() each drag a date along for the ride. The parameter is a symptom of the model: a duration with field identity cannot resolve itself, so the anchor has to travel with it.
A
ZDurationis an orphan. No relatives.
ZDate keeps durations as pure quantities — normalize() and collapse() use approximate factors and say so. Calendar-accurate resolution happens where a date exists: measure with a.diff(b), apply with a.by(duration). The duration doesn't need to know where it is. The date does.
What Temporal Does Not Map To
ZInterval (interval algebra and calendar-aware iteration), ZRepair (explicit timestamp repair with per-condition policies), pattern-based parsing and formatting, and grammar-based natural dates have no Temporal counterpart — they were out of scope for the standard and in scope for zdate. See Parsing and Formatting.
