Zero-dependency spacecraft & astronomy time for TypeScript.
- Exact instants —
bigintnanoseconds on the TAI timeline; arithmetic never drifts, never skips, never double-counts a leap second. - Leap-second-aware UTC —
23:59:60is a real, validated time; the IERS table ships in the box and can be replaced at runtime fromleap-seconds.list/Leap_Second.dat(with#hintegrity verification). - Time scales — UTC, TAI, TT, GPS, TDB; seconds since J2000 with per-scale origins (
tdbfollows the SPICE ET epoch convention with an approximate TDB model — < 10 µs vs ERFA and < 30 µs vs CSPICE over 1972–2100), Julian / Modified Julian dates (single and two-part, SOFA quasi-JD for UTC), GPS week/seconds-of-week. - Strict parse & format — ISO 8601 calendar and ordinal (day-of-year / "SCET") forms with 1–9 fraction digits, UTC offsets and scale designators (
… TAI); small token patterns (YYYY-DDDTHH:mm:ss.SSSSSS); ISO 8601 and clock durations. - Correct by reference — conversions are tested against astropy (ERFA/SOFA) and NAIF CSPICE golden vectors: every inserted leap second since 1972 probed at ±1 ns, SPICE ET epochs, pairwise elapsed-TAI cross-checks against
naif0012.tls, plus fast-check round-trip properties and an adversarial suite (mutable tables, negative leaps, stale data, pathological input). - Deterministic — bit-identical output on V8, JavaScriptCore and Hermes (no
Math.sinvariance: the TDB series uses a built-in deterministic sine), enforced by cross-engine digests in CI. - Small and fast — ~28 KB gzipped, tree-shakeable, no runtime dependencies; formatting and parsing both run at million-scale operations per second in the repository benchmark (machine-dependent, so run
bun run benchfor a relevant number). CI-verified on Node ≥ 22, Bun, and Hermes (React Native ≥ 0.70, where BigInt became available) — the Hermes check runs the real engine after the same Babel transform Metro applies. Plain ES2022 with zero dependencies, so browsers and Deno are supported targets too.
import { parseInstantOrThrow, formatIso, formatOrdinal, durationBetween, durationToSeconds } from 'astrotime'
const leap = parseInstantOrThrow('2016-12-31T23:59:60.5Z')
formatIso(leap, { scale: 'tai' }) // '2017-01-01T00:00:36.500 TAI'
formatOrdinal(leap) // '2016-366T23:59:60.500'
const next = parseInstantOrThrow('2017-01-01T00:00:00.5Z')
durationToSeconds(durationBetween(leap, next)) // 1 (a Date-based subtraction says 0)npm install astrotime
pnpm add astrotime
yarn add astrotime
bun add astrotime
deno add npm:astrotimeJavaScript has no equivalent of astropy.time. Mission-control and ground-station UIs in JS
(Open MCT, Aerie, cubesat dashboards, satellite trackers) tend to push ms since epoch
around and reach for moment or luxon for formatting, which means:
- no microsecond/nanosecond telemetry timestamps,
- no TAI / TT / GPS, so
Datesubtraction across a leap second is wrong by a second, - no day-of-year (
2026-231T12:34:56) parsing without hand-rolled regexes, - leap-second tables hardcoded and forgotten.
astrotime is the small, typed, dependency-free piece that fixes those four things — and nothing else.
Time zones and locale formatting are deliberately left to Intl (see Interop).
The model (uniform-scale epoch + integer nanoseconds + explicit scales) is the same one used by
astropy, SOFA/ERFA, Rust's hifitime and Orekit's AbsoluteDate.
Time libraries are easy to write and hard to get right, so every claim here is backed by something you can re-run:
| Claim | How it is checked |
|---|---|
| Conversions match the reference implementations | Golden vectors from astropy (ERFA/SOFA) and NAIF CSPICE, committed to the repo, plus a monthly differential sweep of 100 000 random instants against astropy — currently zero mismatches |
| Every leap second is handled, including the awkward ones | Every inserted leap second since 1972 probed at ±1 ns; negative leap seconds (never yet announced) exercised across Unix, Julian dates, truncation and ranges |
| Output is identical everywhere | A 135 000-value digest is compared across V8, JavaScriptCore and Hermes (the React Native engine) — the TDB series uses a built-in deterministic sine because Math.sin is not specified bit-exactly |
| The documentation is true | 47 documented requirements are mapped to the tests that verify them in REQUIREMENTS.md; CI fails if a requirement loses its test |
| The tests would notice a bug | Mutation testing (82.82% across 3 335 mutants) with a published survivor analysis and runner limitation, rather than coverage alone |
| The published package is the source | Reproducible npm pack shasum, SLSA provenance, signed tags, and an evidence bundle attached to every release |
| Leap-second data stays current | A monthly job compares the bundled table with IANA and fails on drift; #h integrity records are verified when parsing |
The library is scoped for ground tooling — displays, planning, analysis.
It is not certified for flight, navigation or command paths; see
ASSURANCE-ROADMAP.md for exactly what that means.
Most projects should not use astrotime. Use it when leap seconds or non-UTC time scales actually matter to you:
| astrotime | Temporal | Luxon / date-fns | Moment | |
|---|---|---|---|---|
| Sub-millisecond precision | ✅ nanosecond | ✅ nanosecond | ❌ millisecond | ❌ millisecond |
Leap seconds (23:59:60) |
✅ validated | ❌ ignored by design | ❌ | ❌ |
| TAI / TT / GPS / TDB scales | ✅ | ❌ | ❌ | ❌ |
| Julian dates, day-of-year, SPICE ET | ✅ | ❌ | partial (ordinal only) | partial |
| Time zones & locale formatting | ❌ use Intl |
✅ | ✅ | ✅ |
| Calendar arithmetic (add a month) | ❌ out of scope | ✅ | ✅ | ✅ |
| Available today without a polyfill | ✅ | partial | ✅ | ✅ (maintenance mode) |
Use Temporal or Luxon if you want zones, locales and calendar maths and
your timestamps are wall-clock civil time. Use astrotime — possibly
alongside them — if a second matters across a leap boundary, if you need
TAI/TT/GPS/TDB, or if your telemetry is finer than a millisecond. They
compose: Temporal.Instant.fromEpochNanoseconds(instantToUnixNanos(i)).
| You have / need | Use |
|---|---|
| Telemetry timestamps, logs, anything user-facing | utc |
| Elapsed time, intervals, rates | Duration (or tai) |
| Ephemerides, SPICE ET, orbit propagation | tdb (or tt) |
| GPS receivers, RINEX, week/seconds-of-week | gps |
| Astronomy epochs, Julian dates | the scale your source says (be explicit) |
| Type | What it is |
|---|---|
Instant |
A point in time: nanoseconds since 1970-01-01T00:00:00 TAI. Immutable, frozen; JSON.stringify/String() give the canonical ns-precision TAI string, independent of leap-table updates. |
Duration |
Signed nanoseconds of elapsed SI time. Days are exactly 86 400 s. No months/years. Serializes as ISO 8601 (PT1H30M). |
CivilDateTime |
Broken-down time (year, month, day, dayOfYear, hour, minute, second (0–60), nanosecond). |
CivilFields |
Input fields: { year, month, day } or { year, dayOfYear } plus optional time. |
LeapSecondTable |
{ entries: [{ unixSeconds, deltaAt }], expires, updated } — TAI−UTC history. |
TimeScale |
'utc' | 'tai' | 'tt' | 'gps' | 'tdb' |
Errors: expected failures (parsing, invalid fields) return a Result<T, E>
rather than throwing — malformed timestamps are ordinary data in a telemetry
stream, and the type system then forces you to handle them:
const result = parseInstant(userInput) // Result<Instant, TimeParseError | InvalidTimeError>
if (result.ok) {
formatIso(result.value) // .value only exists after the .ok check
} else {
console.warn(result.error.code, result.error.message) // typed: TimeParseError | InvalidTimeError
}Each error carries a stable code, structured fields and toJSON()
(TimeParseError, InvalidTimeError, LeapSecondTableError). When you'd
rather throw — one-off scripts, inputs you already trust, or plain JavaScript
without the compiler enforcing the .ok check — use parseInstantOrThrow /
parseDurationOrThrow, or unwrap(result) / unwrapOr(result, fallback).
Programmer errors (non-finite numbers, malformed hand-built tables passed to
conversion APIs) throw RangeError; the explicit validation functions return
typed Result failures instead.
import { parseInstant, parseInstantOrThrow, formatInstant, formatIso, formatOrdinal, isValidInstant } from 'astrotime'
// ISO 8601 calendar or ordinal, optional time, 1–9 fraction digits,
// Z / ±HH:mm offsets, or a scale designator
parseInstant('2026-08-19T12:34:56.789012345Z')
parseInstant('2026-231T12:34:56.789') // day-of-year ("SCET"-style)
parseInstant('2026-08-19T13:34:56+01:00')
parseInstant('2017-01-01T00:59:60+01:00') // the 2016 UTC leap second, shifted
parseInstant('2017-01-01T00:00:37 TAI') // scale designators parse back
parseInstant('2026-08-19') // midnight UTC
// Only the ordinal form
parseInstant('2026-231', { format: 'ordinal' })
// Strict token patterns (YYYY MM DD DDD HH mm ss S…S Z [literal])
parseInstant('2026-08-19 12:34:56.789', { format: 'YYYY-MM-DD HH:mm:ss.SSS' })
isValidInstant('2026-08-19 12:34', { format: 'YYYY-MM-DD HH:mm' }) // true
// Other scales: the text is read on that scale's clock
parseInstant('2017-01-01T00:00:37', { scale: 'tai' }) // == 2017-01-01T00:00:00Z
const i = parseInstantOrThrow('2026-08-19T12:34:56.789012Z')
formatIso(i) // '2026-08-19T12:34:56.789Z'
formatIso(i, { precision: 'auto' }) // '2026-08-19T12:34:56.789012Z'
formatIso(i, { scale: 'tt', precision: 'nanos' }) // '2026-08-19T12:36:05.973012000 TT'
formatIso(i, { scale: 'tt', designator: 'none' }) // '2026-08-19T12:36:05.973'
formatOrdinal(i) // '2026-231T12:34:56.789'
formatInstant(i, 'YYYY-DDD[T]HH:mm:ss.SSSSSS') // '2026-231T12:34:56.789012'Formatting rejects a pattern it cannot render faithfully rather than
quietly producing something else, on the same principle as the year-range
check: a plausible-but-wrong string is worse than an error. Use [text] for
a literal.
This is not a hypothetical class of bug. NASA Open MCT stamps notifications
with 'YYYY-MM-DD hh:mm:ss.ms', which Moment renders as
2026-08-19 12:34:56.3456 — .ms is not milliseconds but the minute and
second over again, and hh is a 12-hour clock with no AM/PM to disambiguate
it. Every library involved accepted the pattern and returned a string that
looks like a timestamp.
formatInstant(i, 'YYYY-MM-DD HH:mm:ss.SSS') // fine
formatInstant(i, 'YYYY-MM-DD[T]HH:mm:ss[Z]') // fine — bracketed text is a literal
formatInstant(i, 'YYYY-MM-DD hh:mm:ss.ms') // RangeError: unknown token(s) "hh", "ms"
formatInstant(i, 'YYYY-MM-DDTHH:mm:ss') // RangeError: unknown token(s) "T"
formatInstant(i, 'YYYY [MM') // RangeError: unterminated "["
formatInstant(i, 'HH:mm:ss.SSSSSSSSSS') // RangeError: longer than the longest "S" token
formatInstant(i, 'YYYY-MM-DD DD') // RangeError: field "DD" appears more than onceThe last two are the quiet ones. Ten Ses split into a nine-digit token
plus a one-digit token and rendered .7890000007, which reads back as
.700000000; and a repeated field printed the same number twice. Both
produced a string that looked right. DD and DDD are different fields
that share a letter, so a pattern may legitimately carry both, and
parseInstant checks that they agree.
A parse pattern is held to the same standard, and a defect in it throws
rather than reporting the text as unparseable — the pattern comes from
your source, the text does not. Parsing asks more of a pattern than
formatting does, so it has its own check: 'HH:mm' renders perfectly well
and names no instant to read back.
import { parsePatternError } from 'astrotime'
parsePatternError('YYYY-MM-DD HH:mm') // null
parsePatternError('HH:mm') // 'no year (YYYY), so it cannot identify a date'
parsePatternError('YYYY-MM DDD') // 'combines DDD with MM but not DD, …'When a pattern comes from configuration or a user, check it first rather than catching:
import { formatPatternError, isValidFormatPattern } from 'astrotime'
isValidFormatPattern(fromConfig) // false
formatPatternError(fromConfig) // 'unknown token(s) "hh", "ms"' — show this to the operatorNon-UTC ISO output carries its scale (… TT) by default so the string is never
ambiguous, and parseInstant accepts it back — the convention SPICE, astropy and
Orekit use. Fractions are truncated, not rounded, when digits are dropped.
import { instantFromUnixMillis, instantToUnixMillis, instantFromDate, instantToDate, instantNow } from 'astrotime'
const i = instantFromUnixMillis(Date.now())
instantToUnixMillis(i) // POSIX ms (sub-ms kept as a fraction)
instantToDate(i) // Date truncated toward −∞ to milliseconds
instantFromDate(new Date())
instantNow() // from Date.now() (ms precision); pass { now } to inject a clockPOSIX Unix time cannot represent a leap second, so during 23:59:60.x the
functions return the same values as 00:00:00.x of the next day, and
instantToDate of a leap second renders as that next second. The reverse
direction is never ambiguous. (UTC Julian dates do not do this — see below.)
import { instantFromUtc, instantToUtc, deltaAt, isLeapSecond, isUtcDefined, unwrap } from 'astrotime'
const leap = instantFromUtc({ year: 2016, month: 12, day: 31, hour: 23, minute: 59, second: 60 }) // ok: true
instantFromUtc({ year: 2016, month: 12, day: 30, hour: 23, minute: 59, second: 60 })
// { ok: false, error: InvalidTimeError('second', 60, 'no leap second is inserted at this time') }
instantFromUtc({ year: 2024, dayOfYear: 366, hour: 6 }) // ordinal input
instantToUtc(unwrap(leap))
// { year: 2016, month: 12, day: 31, dayOfYear: 366, hour: 23, minute: 59, second: 60, nanosecond: 0 }
deltaAt(unwrap(leap)) // 36 (TAI − UTC in effect; 37 from 2017-01-01)
isLeapSecond(unwrap(leap)) // trueNegative leap seconds (a deleted 23:59:59) are validated too, should the IERS ever announce one.
UTC is only defined from 1972. Earlier instants are approximated with
TAI − UTC = 10 s by default; pass { before1972: 'reject' } to get an
InvalidTimeError instead, and use isUtcDefined(i) to test.
Future leap seconds are unknowable: past the table's expires stamp,
conversions fail open by default (last known ΔAT — the right trade-off
for plotting and display, where future timestamps are routine). Pass
{ tableValidity: 'reject' } when uncertain UTC must not be presented as
certain — every conversion path then errors past the expiry.
Custom tables must contain the complete known leap-second history (the
bundled entries, verbatim) before any appended future entries — a partial
snapshot is rejected everywhere, because it would silently misapply old
ΔAT values to modern epochs. If a negative leap second is ever announced,
Unix labels inside the deleted second fold forward by default; pass
{ leapGap: 'reject' } to error on them instead.
For authoritative UTC, use both { before1972: 'reject', tableValidity: 'reject' }
and require a table with a non-null expiry. A null expiry provides no boundary
for the latter policy; it is useful for synthetic tables and deliberately does
not mean that future UTC is knowable. Runtime misspellings of any safety policy
throw RangeError instead of silently selecting a permissive default.
import {
instantToJ2000Seconds, instantFromJ2000Seconds, instantToJulianDate, instantToJulianDateParts,
instantToModifiedJulianDate, instantFromJulianDate, instantToGpsWeek, instantFromGpsWeek,
instantToCivil, J2000_INSTANT, GPS_EPOCH_INSTANT,
} from 'astrotime'
instantToJ2000Seconds(i, 'tdb') // 840414965.97… — SPICE ET epoch convention (approximate TDB: < 10 µs vs ERFA, < 30 µs vs CSPICE, 1972–2100)
instantToJ2000Seconds(i, 'tt')
instantFromJ2000Seconds(8.4e8, 'tdb')
instantToJulianDate(i, 'utc') // 2461272.0242… (≈50 µs float resolution)
instantToJulianDateParts(i, 'tt') // { jd1: 2461271.5, jd2: 0.52506… } (full precision)
instantToModifiedJulianDate(i, 'tai') // 61271.5246…
instantFromJulianDate(2451545.0, 'tt') // J2000
instantToGpsWeek(i) // { week: 2432, secondsOfWeek: 304514.789012 }
instantFromGpsWeek(2432, 304514.789012)
instantToCivil(i, 'tai') // broken-down TAI clock reading
formatIso(J2000_INSTANT) // '2000-01-01T11:58:55.816Z'
formatIso(GPS_EPOCH_INSTANT) // '1980-01-06T00:00:00.000Z'The scale argument is required on all of these — a Julian date without a
stated scale is a bug waiting to happen. UTC Julian dates follow the SOFA/ERFA
quasi-JD convention (a leap-second day is 86 401 s long), so they are monotonic
and match astropy on leap days.
Definitions: TT = TAI + 32.184 s; GPS = TAI − 19 s; TDB = TT + the seven-term Fairhead & Bretagnon truncation (USNO Circular 179 eq. 2.6, < 10 µs vs ERFA's full series, 1972–2100).
TAI, TT and GPS integer-nanosecond conversions are exact inverses. TDB's periodic offset varies continuously, so its two integer-nanosecond lattices cannot be bijective at every rounding boundary; its inverse selects the closest TT reading and round-trips within 1 ns over the supported civil range.
GPS seconds-of-week inputs must be in [0, 604800) and GPS week numbers must
be safe integers; out-of-range values throw rather than silently normalizing
into an adjacent week.
import { duration, parseDuration, formatDuration, durationToComponents, addToInstant } from 'astrotime'
const d = duration({ days: 1, hours: 2, minutes: 3, seconds: 4.5 })
formatDuration(d) // 'P1DT2H3M4.5S'
formatDuration(d, 'clock') // '26:03:04' (hours absorb days)
formatDuration(d, 'D[d] HH:mm:ss.SSS')// '1d 02:03:04.500'
parseDuration('PT90M') // ISO 8601
parseDuration('1T02:03:04.005') // day-count prefix + clock time
parseDuration('36:00:00') // clock, hours unbounded
durationToComponents(d) // { sign: 1, days: 1, hours: 2, minutes: 3, seconds: 4, nanos: 500000000 }
addToInstant(i, d)import { compareInstants, duration, instantRange, parseInstantOrThrow, truncateInstant, formatIso } from 'astrotime'
const start = parseInstantOrThrow('2026-08-19T00:00:00Z')
const end = parseInstantOrThrow('2026-08-19T00:30:00Z')
const ticks = [...instantRange(start, end, duration({ minutes: 10 }))] // 00:00, 00:10, 00:20
const ordered = [end, start].sort(compareInstants) // → [start, end]
const i = parseInstantOrThrow('2026-08-19T12:34:56.789Z')
formatIso(truncateInstant(i, 'day', 'utc')) // '2026-08-19T00:00:00.000Z' (scale-aware: TAI days differ by ΔAT)
formatIso(truncateInstant(i, 'hour', 'gps')) // '2026-08-19T11:59:42.000Z' (a GPS hour boundary)Also available: isBefore / isAfter, minInstant / maxInstant, clampInstant, instantsEqual.
The bundled table is current as of its updated stamp and valid until expires
(IERS Bulletin C). A monthly CI job in this repo fails if it drifts from IANA,
and every UTC-dependent function takes { leapSeconds } — no global state:
import { IERS_LEAP_SECONDS, isLeapSecondTableExpired, parseLeapSecondsList, formatIso, instantNow, unwrap } from 'astrotime'
if (isLeapSecondTableExpired(IERS_LEAP_SECONDS, instantNow())) {
const text = await (await fetch('https://data.iana.org/time-zones/data/leap-seconds.list')).text()
const leapSeconds = unwrap(parseLeapSecondsList(text)) // also accepts IERS Leap_Second.dat
formatIso(someInstant, { leapSeconds })
}Plot scales, chart libraries and most existing code accept a number, not a
bigint. The precision you get from a double depends on its magnitude,
not on the library that produced it — and absolute Unix milliseconds are a
large magnitude:
| Carried as | Value today | Finest representable difference |
|---|---|---|
| Unix milliseconds since 1970 | ~1.8 × 10¹² | ~244 ns |
| Offset in ms from the start of the day | ~4.5 × 10⁷ | ~7 ps |
| Offset in ms from the start of the hour | ~2.1 × 10⁶ | ~0.2 ps |
So keep the exact Instant, and hand the numeric layer an offset from a
nearby origin — typically the start of your view window:
import { instantFromOffsetMillis, instantToOffsetMillis, truncateInstant, unixMillisResolutionNanos } from 'astrotime'
const origin = truncateInstant(i, 'day', 'utc') // any nearby instant works
const x = instantToOffsetMillis(i, origin) // 45296789.012345 — feed this to the plot
const back = instantFromOffsetMillis(x, origin) // exactly i, to the nanosecond
unixMillisResolutionNanos(i) // 244.1 — what you'd have lostinstantToOffsetSeconds / instantFromOffsetSeconds are the same in seconds.
Because the round trip is exact, sub-microsecond timestamps survive a journey
through a plain-number API that would otherwise quantise them.
Instant and Duration are frozen objects with toJSON. An instant
serializes as its TAI reading ("2026-08-19T12:35:33.789012345 TAI"),
never as UTC: the TAI string is independent of any leap-second table, so a
value constructed with a custom or future table still round-trips losslessly
through parseInstant. Durations serialize as ISO 8601 ("PT1H30M"). Format
explicitly with formatIso(i) when you want a UTC string for display. For
binary protocols use instantToTaiNanos(i) / instantFromTaiNanos(n) (a bigint).
- Time zones / locales:
new Intl.DateTimeFormat('en-GB', { timeZone: 'America/Los_Angeles', … }).format(instantToDate(i)). - Temporal:
Temporal.Instant.fromEpochNanoseconds(instantToUnixNanos(i))and back viainstantFromUnixNanos. - satellite.js / TLE: feed
instantToDate(i)topropagate; useinstantToGpsWeek/instantToJ2000Secondsfor epochs. - Open MCT: a
TimeSystemformatter needs onlyformatInstant/parseInstant— seeexamples/openmct-utc-time-format.js.
InstantandDurationarithmetic is exact to 1 ns at any magnitude (bigint). Civil/ISO conversions cover years −999 999 … +999 999 and throwRangeErrorbeyond, rather than emitting strings that cannot round-trip;instantToDatethrows outside the ECMAScriptDaterange; decomposing/formatting a duration throws past 2^53 days.- Functions returning a float
numbercarry double resolution (~0.1 µs for J2000 seconds today; ~50 µs for a single-float Julian date — use the two-part form) and throw rather than return infinity when the value exceeds the finitenumberrange. instantNow()has millisecond precision (it readsDate.now()); nanosecond precision applies to stored and parsed timestamps.- UTC before 1972 is approximated with TAI − UTC = 10 s (the real pre-1972 "rubber second" UTC is out of scope); opt into rejection with
before1972: 'reject'. - ISO duration parsing deviates from ISO 8601 deliberately: comma or dot fractions and a leading sign are accepted; weeks are exclusive (
P1W2Drejected); only the last component may carry a fraction; components are capped at 16 digits. - UT1 / ΔT, SCLK and SPICE kernels are out of scope.
- Public calendar conversion helpers reject impossible dates/ordinals and unsafe-integer inputs; they never normalize an invalid date into a different one.
astrotime is built and verified for ground tooling: displays, dashboards, plotting, log analysis, mission-planning UIs. It is not certified for flight, navigation, command, or safety-critical paths — those carry process requirements (NPR 7150.2 class assessment, IV&V) that no library can satisfy on its own. Correctness evidence: astropy/ERFA and NAIF CSPICE golden vectors, property-based round-trips, and an adversarial suite, all in CI on Node and Bun.
bun install
bun run test # vitest: astropy golden + drift vectors, fast-check properties
bun run bench # vitest benchmarks
bun run check:release # format, lint, knip, typecheck, coverage, dist suite, traceability, conformance
bun run evidence # regenerate the release evidence bundle
bun run test:mutation # Stryker mutation testing
./scripts/conformance-hermes.sh # digest under the React Native engine
pip install astropy==6.0.1 spiceypy==6.0.0 # regenerate reference fixtures (see CONTRIBUTING.md)Everything is a flat, tree-shakeable named export. R<T> below means Result<T, InvalidTimeError | TimeParseError>.
| Group | Exports |
|---|---|
| Construct | instantFromUnixMillis/Seconds/Nanos · instantFromDate · instantNow · instantFromTaiNanos · instantFromUtc(fields) → R · instantFromCivil(fields, scale) → R |
| Read | instantToUnixMillis/Seconds/Nanos · instantToDate · instantToTaiNanos · instantToUtc · instantToCivil(i, scale) |
| Parse / format | parseInstant → R · parseInstantOrThrow · isValidInstant · formatIso · formatOrdinal · formatInstant(i, pattern) · isValidFormatPattern · formatPatternError · parsePatternError |
| Scales | instantToScaleNanos/Seconds · instantFromScaleNanos/Seconds · instantToJ2000Seconds/Nanos · instantFromJ2000Seconds/Nanos · instantToJulianDate[Parts] · instantFromJulianDate[Parts] · instantToModifiedJulianDate · instantFromModifiedJulianDate · instantToGpsWeek/Seconds · instantFromGpsWeek/Seconds |
| Durations | duration({…}) · durationFromDays/Hours/Minutes/Seconds/Millis/Nanos · durationToDays/…/Nanos · durationToComponents · parseDuration → R · parseDurationOrThrow · formatDuration(d, 'iso' | 'clock' | pattern) · addDurations · subtractDurations · negateDuration · absDuration · scaleDuration · durationPatternError · compareDurations · durationsEqual · isNegativeDuration · ZERO_DURATION |
| Numeric interop | instantToOffsetMillis/Seconds · instantFromOffsetMillis/Seconds · unixMillisResolutionNanos |
| Arithmetic & order | addToInstant · subtractFromInstant · durationBetween · compareInstants · instantsEqual · isBefore · isAfter · minInstant · maxInstant · clampInstant · instantRange · truncateInstant(i, unit, scale) |
| Leap seconds | deltaAt · deltaAtUnixSeconds · isLeapSecond · isUtcDefined · IERS_LEAP_SECONDS · parseLeapSecondsList → R · validateLeapSecondTable → R · freezeLeapSecondTable · isLeapSecondTableExpired · PRE_1972_DELTA_AT |
| Calendar | isLeapYear · daysInMonth · daysInYear · dayOfYear · daysFromCivil · civilFromDays · civilFromOrdinal |
| Constants | J2000_INSTANT · GPS_EPOCH_INSTANT · UNIX_EPOCH_INSTANT · UTC_START_INSTANT · TT_MINUS_TAI_NANOS · GPS_MINUS_TAI_NANOS · JD_UNIX_EPOCH · JD_J2000 · MJD_OFFSET · NANOS_PER_* · TIME_SCALES · TIME_SCALE_LABELS |
| Results & errors | unwrap · unwrapOr · isAstrotimeError · TimeParseError · InvalidTimeError · LeapSecondTableError · isInstant · isDuration |
Every export has JSDoc — hover in your editor for the details, read DESIGN.md for the model and its invariants, API-STABILITY.md for the compatibility promise, and ASSURANCE-ROADMAP.md with REQUIREMENTS.md for the evidence trail.
MIT