astrotime
v0.12.0
Published
Zero-dependency spacecraft & astronomy time for TypeScript: nanosecond instants, leap-second-aware UTC, TAI/TT/GPS/TDB, Julian dates, strict ISO-8601 / day-of-year / duration parse & format
Maintainers
Readme
astrotime
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 convention: exact epoch, approximate TDB model — < 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 — ~27 KB gzipped, tree-shakeable, no runtime dependencies; ~2.6M pattern formats/s and ~2.5M ISO parses/s on a laptop — format throughput is on par with native
Date#toISOString, and well ahead of moment/luxon/date-fns for pattern work. 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)Install
npm install astrotime
pnpm add astrotime
yarn add astrotime
bun add astrotime
deno add npm:astrotimeWhy
JavaScript 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.
Why you can trust the numbers
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 | 43 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 (84.1% across 3 045 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.
Choosing a time library
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)).
Which scale do I want?
| 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) |
Concepts
| 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) always
throw RangeError.
Usage
Parse and format
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.
Unix time, Date, now
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) // nearest Date
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.)
UTC fields and leap seconds
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.
Time scales, J2000, Julian dates, GPS
import {
instantToJ2000Seconds, instantFromJ2000Seconds, instantToJulianDate, instantToJulianDateParts,
instantToModifiedJulianDate, instantFromJulianDate, instantToGpsWeek, instantFromGpsWeek,
instantToCivil, J2000_INSTANT, GPS_EPOCH_INSTANT,
} from 'astrotime'
instantToJ2000Seconds(i, 'tdb') // 840414965.97… — SPICE ET convention (approximate TDB: < 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 three leading periodic terms of the Fairhead & Bretagnon series (USNO Circular 179 eq. 2.6, < 30 µs vs ERFA's full series, 1972–2100).
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.
Durations
import { duration, parseDuration, formatDuration, durationToComponents, addDuration } 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 }
addDuration(i, d)Ordering, ranges, truncation
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.
Keeping the leap-second table fresh
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 })
}Working with numeric APIs (plots, charts, existing code)
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.
Serialization
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).
Interop
- 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.
Precision & limits
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.
Intended use
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.
Development
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)API reference
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) · INSTANT_TOKEN · 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 | addDuration · subtractDuration · 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 · ok · err · 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.
License
MIT
