Nanosecond Precision
convertZonedToUnix and its siblings return
number milliseconds. Systems that record telemetry, trades or sensor readings need
nanoseconds, and a number cannot hold them. Integers are exact only to 2^53 − 1, about
9.0 × 10^15, and nanoseconds since the epoch passed that in April 1970.
Date.now() * 1e6 is not a nanosecond timestamp. It is a millisecond timestamp with three
zeroes appended.
The precision/ namespace works in bigint:
import { toNanoseconds, fromNanoseconds, formatNanoseconds, parseNanoseconds, truncateNanoseconds,} from "@northguild/gmt";
toNanoseconds("2024-03-10T12:00:00.123456789Z");// 1710072000123456789n
fromNanoseconds(1710072000123456789n);// "2024-03-10T12:00:00.123456789Z"
fromNanoseconds(1710072000123456789n, "America/New_York");// "2024-03-10T08:00:00.123456789-04:00[America/New_York]"Send a nanosecond value over a wire
Section titled “Send a nanosecond value over a wire”JSON.stringify throws a TypeError on a bigint, so the value crosses a wire as a
canonical decimal string:
JSON.stringify({ observedAt: formatNanoseconds(1710072000123456789n) });// '{"observedAt":"1710072000123456789"}'
parseNanoseconds("1710072000123456789");// 1710072000123456789nTruncate before you store
Section titled “Truncate before you store”Most storage engines cannot hold nanoseconds. PostgreSQL timestamptz and MySQL
DATETIME(6) hold microseconds, so a naive write-then-read loses the last digits silently.
Truncate to the target precision first, and the round trip is exact:
truncateNanoseconds(1710072000123456789n, "us");// 1710072000123456000n — safe to write to a microsecond column
truncateNanoseconds(1710072000123456789n, "ms");// 1710072000123000000nTruncation floors toward negative infinity, so pre-1970 values truncate the same way
post-1970 ones do. truncateNanoseconds(-1500n, "us") is -2000n, not -1000n. Rounding
toward zero would make the result jump direction on either side of the epoch.
Tell the epoch from the sentinel
Section titled “Tell the epoch from the sentinel”0n is both the epoch and the invalid-input sentinel, so precision/validate exists to
tell them apart. Each predicate accepts exactly what its partner parses:
import { isValidInstant, isValidNanoseconds, isValidNanoPattern,} from "@northguild/gmt";
isValidInstant("1970-01-01T00:00:00Z"); // true — toNanoseconds returns 0n, the epochisValidInstant("garbage"); // false — toNanoseconds returns 0n, the sentinel
isValidNanoPattern("0"); // true — parseNanoseconds returns 0n, the epochisValidNanoPattern("1e18"); // false
isValidNanoseconds(0n); // true — in range for truncateNanoseconds / fromNanosecondsisValidNanoseconds(0); // false — a number cannot carry a nanosecond timestampReach for isValidInstant rather than isValidUtc. The latter gates on the library’s
stricter <date>T<time>Z shape and rejects the offsets and bracketed zones that
toNanoseconds accepts, so validating with it discards valid input.
Limits
Section titled “Limits”Every precision/ function returns a sentinel ("" for strings, 0n for bigints) on
invalid input. It accepts only values inside the range Temporal.Instant can represent,
±8_640_000_000_000_000_000_000n. 0n is also the epoch itself, so validate the input
first when the two must be told apart.
Leap-second-aware time scales (TAI, GPS) are not part of this namespace. These are plain instant conversions.
A zoned string names one instant in every reader
Section titled “A zoned string names one instant in every reader”Temporal writes a zone’s offset rounded to the minute. A zone with a sub-minute offset is
therefore written with an offset up to 30 seconds from the real one. Africa/Monrovia
stood at −00:44:30 until 1972 and is written -00:45.
Every function in the library that reads a moment reads such a string as
Temporal.ZonedDateTime.from does. TC39 ToTemporalZonedDateTime matches a minute-precision
offset by minutes. A zoned string the library wrote therefore reads back as the instant it
was written for:
import { isValidZonedDateTime, toNanoseconds } from "@northguild/gmt";
toNanoseconds("1960-01-01T00:20:00-00:45[Africa/Monrovia]"); // -315615330000000000n (01:04:30Z)toNanoseconds("1960-01-01T00:20:00-00:45"); // -315615300000000000n (01:05:00Z; no zone, so the offset is exact)toNanoseconds("2024-06-15T10:00:00Z[Not/AZone]"); // 1718445600000000000n (an instant reader does not validate the bracket)
isValidZonedDateTime("1960-01-01T00:20:00-00:45[Africa/Monrovia]"); // true (the rounded offset)isValidZonedDateTime("1960-01-01T00:20:00-00:44:30[Africa/Monrovia]"); // true (the exact offset)isValidZonedDateTime("1960-01-01T00:20:00-00:45:00[Africa/Monrovia]"); // false (an offset with seconds must be exact)- Where the rule applies. It applies to
precision/,span/,interval/,calendar/,intermodal/,transport/andgetTimeZoneOffsetalike. Any other string keeps its written offset: one without a bracket, aZinstant, an offset written with seconds, and a bracket whose zone does not exist or does not fit the offset. - Every string Temporal writes for an instant in range is valid, the first and last
instants included.
-271821-04-19T23:58:45-00:01[Europe/London]is the first instant. - Functions that keep the zone are stricter.
isValidZonedDateTime,transitTime,toOffsetInstant, everyzoned/function and a zonedrelativeToreject a bracket that contradicts the offset. They require an offset written with seconds to be the zone’s offset exactly. - Two limits are Temporal’s own. A wall time repeated inside a sub-minute offset change,
written to the minute, reads as its first pass (
1952-10-15T23:59:59-11:20[Pacific/Niue]). Write the offset with seconds (-11:20:00) to name the second. A zoned read also refuses a local date of −271821-04-19, which a zone west of Greenwich shows for the first hours of the instant range. Pass that instant inZform.
See also
Section titled “See also”- Spans: exact elapsed time between two instants, as a number
- Foreign Epoch Bridges: NTP,
FILETIME, .NET ticks, Excel and PostgreSQL toNanosecondsreferencefromNanosecondsreferenceformatNanosecondsreferenceparseNanosecondsreferencetruncateNanosecondsreferenceisValidInstantreferenceisValidNanoPatternreferenceisValidNanosecondsreferenceisValidZonedDateTimereference