Spans
diffZoned measures in calendar units and returns a
Duration. Profiling, tracing and telemetry want a raw number, and the span/ namespace
gives one: spanMs,
spanNs and
spanWallClock.
import { spanMs, spanNs, spanWallClock } from "@northguild/gmt";
spanMs("2024-03-10T12:00:00Z", "2024-03-10T12:00:01Z");// 1000
spanMs("2024-03-10T12:00:00Z", "2024-03-10T12:00:00.123456789Z");// 123.456789 — fractional, like performance.now()
spanNs("2024-03-10T12:00:00.123456789Z", "2024-03-10T12:00:00.123456790Z");// 1nExact time is not calendar distance
Section titled “Exact time is not calendar distance”Both spanMs and spanNs are signed. spanMs(b, a) is exactly -spanMs(a, b). Both
measure exact elapsed time.
That is not the same question as calendar distance, and conflating the two is the most
common span bug there is. A wall-clock day containing a DST transition is 23 or 25 hours
long (24.5 in Australia/Lord_Howe), so:
const start = "2024-03-09T12:00:00-05:00[America/New_York]";const end = "2024-03-10T12:00:00-04:00[America/New_York]";
spanMs(start, end);// 82800000 — 23 hours actually elapsed
spanWallClock(start, end, "hours");// 24 — the clock face advanced a full day
spanWallClock(start, end, "days");// 1spanWallClock reads each endpoint’s own local wall clock. It reads it straight off the
string, never via an instant, so DST disambiguation cannot distort it, and a local time that
never occurred is measured as written.
The two endpoints need not share a zone. A flight leaving New York at 23:00 and landing in Berlin at 11:00 the next local day is 12 wall-clock hours and 7 elapsed hours. It truncates toward zero, and it is not a count of midnights crossed.
Check before you measure
Section titled “Check before you measure”isValidSpan(start, end) answers “will these two
produce a span?”. It is true exactly when spanMs and spanNs will return a value. It is
symmetric, since a reversed pair is a negative span rather than an invalid one.
spanWallClock has a different input grammar and is covered by
isValidZonedDateTime.
Three limits, all deliberate
Section titled “Three limits, all deliberate”0and0nare valid spans, so invalid input returnsnullfrom all three rather than the zero the rest of the library uses for numbers. It isnulland notNaNbecausenullis the sentinel every other number-returning function uses, and the only one thatstrictNullChecksforces a caller to handle. ANaNtypes as plainnumberand propagates silently through arithmetic.spanMsreturnsnullpastNumber.MAX_SAFE_INTEGERmilliseconds. That is about ±285,000 years, which two instants at opposite ends ofTemporal.Instant’s range exceed. A sub-millisecond fraction counts toward that ceiling, so what comes back is always a safe integer or below. UsespanNsthere. Its result is a duration, not an instant, and can be twice the epoch-nanosecond range.- Leap seconds are not counted. UTC repeats a second rather than numbering a 61st one, so a span across one is a second short of the physical elapsed time. Against a smeared clock (Google, AWS, Meta) the error is up to a second spread over the smear window.
See also
Section titled “See also”- Nanosecond Precision: the
bigintinstant behindspanNs - Durations: calendar-unit differences as ISO 8601 strings
- Zoned Operations:
diffZonedand calendar distance across DST