Skip to content

spanMs

spanMs(start: string, end: string): number | null

Return the exact elapsed milliseconds between two ISO 8601 instant strings.

  • The universal span primitive for profiling and observability. Fractional, like performance.now() — a sub-millisecond span is reported as a fraction, not rounded away.
  • Signed: negative when start is after end, so spanMs(b, a) is exactly -spanMs(a, b).
  • Measures exact elapsed time, never calendar distance. A wall-clock day containing a DST transition is 23 or 25 hours (or 24.5, in Australia/Lord_Howe), and spanMs reports the hours that actually elapsed. spanWallClock answers the calendar question; conflating the two is the most common span bug there is.
  • Accepts anything toNanoseconds does: the full RFC 9557 instant grammar, no leap seconds; calendar and elective annotations are ignored, as Temporal.Instant.from ignores them. An offset designator (Z, ±HH:MM) is required, optionally followed by a bracketed IANA zone — a bracket alone is not enough, which is the one string shape spanWallClock accepts and these two do not. The endpoints need not share a zone; an instant is an instant.
  • The offset fixes the instant; a bracketed zone only resolves a rounded one. An offset written to the minute that is the bracketed zone’s sub-minute offset rounded (-00:45[Africa/Monrovia], for −00:44:30) names the instant the zone gives, as Temporal.ZonedDateTime.from reads it, so a zoned string Temporal wrote is read back as the instant it was written for, except a wall time repeated inside a sub-minute offset change, which reads as its first pass (see isValidInstant). Otherwise the bracket is not checked: a zone that does not exist, or that disagrees with the offset, changes nothing. spanWallClock, by contrast, rejects a mismatched offset.
  • Returns null on invalid input, not 0 — 0 is the span between an instant and itself. null, not NaN, because that is GMT’s sentinel for every number-returning function: it is the one a caller already checks for, and the only one strictNullChecks forces them to handle. A NaN would type as plain number and propagate silently through arithmetic. spanNs and spanWallClock return null for the same reason.
  • Safe range. Returns null when the exact span exceeds Number.MAX_SAFE_INTEGER milliseconds (±9_007_199_254_740_991 ms, about ±285,000 years), which two instants at opposite ends of Temporal.Instant’s range do. A sub-millisecond fraction counts toward that ceiling, so any number returned is a safe integer or below. Use spanNs past it — it carries the exact value as a bigint. Sub-millisecond fidelity goes first, long before the ceiling: a double carries the nanosecond digits exactly only while the span is under 2^53 ns, roughly 104 days.
  • 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. Leap-second-exact spans need a TAI scale, which GMT does not yet ship.
ParameterTypeDescription
startstringISO 8601 instant string the span is measured from
endstringISO 8601 instant string the span is measured to

exact elapsed milliseconds, negative when start is after end, or null on invalid input

import { spanMs } from "@northguild/gmt/span/calculate";
spanMs("2024-03-10T12:00:00Z", "2024-03-10T12:00:01Z") // 1000
output 

span/calculate/spanMs.ts