BrightDate: A Decimal-SI Universal Time Standard Anchored at J2000.0

Version: 1.1.0 (component) — Bright Spacetime Protocol (BSP) suite member

Organization: Digital Defiance (501c3)

Author: Jessica Mulein

Reference implementation: @brightchain/brightdate

Status: Stable component release (1.0.0); part of the BSP suite — comments and corrections welcome.

Date: 2026


Abstract

Modern software engineering is burdened by a fragmented time landscape: UTC timezones, Unix milliseconds, Julian Dates, GPS weeks, TAI offsets, and leap-second ambiguities proliferate across distributed systems with no shared conceptual anchor. Every timestamp library re-derives the same conversions from the same patchwork of calendar artefacts, with errors accumulating at every interface boundary.

BrightDate separates the engine from the dashboard, like Unix time: canonical storage is a signed integer count of TAI attoseconds since J2000.0 (ExactBrightAtto, 128-bit / BigInt); human-facing decimal SI days (BrightDate, Float64) are a presentation lens derived by integer divmod, not the source of truth for consensus. The reference implementation also offers ExactBrightDate (picoseconds) when you prefer that divisor — the class name documents the tick size. Pre-J2000.0 values are signed in the integer engine; the PBD display prefix renders negative day labels without a leading minus sign.


1. The Problem: A Fragmented Time Landscape

1.1 The Legacy Stack

Every engineering team that works seriously with time eventually collides with the same set of incompatible standards:

Standard Definition Core Problem
Unix time Integer seconds since 1970-01-01T00:00:00 UTC Ambiguous at leap seconds; Y2038 for 32-bit representations
UTC Civil timescale with leap-second corrections to UT1 Leap seconds create discontinuities; timezone management is a major source of bugs
Julian Date (JD) Days since 4713 BC noon (Julian proleptic calendar) Enormous values (~2.45 million for the current era); origin is an arbitrary historical artefact
GPS time Seconds since 1980-01-06T00:00:00 UTC, no leap seconds Useful but niche; requires a week-number rollover table
ISO 8601 String encoding of calendar dates Not a timescale; requires parsing before arithmetic; timezone variants proliferate

What does not exist is a public, vendor-neutral scalar that is:

  1. Astronomically grounded — anchored to a living standard used by every observatory and space agency.
  2. TAI-based — monotonic and free of leap-second discontinuities internally.
  3. Decimal and sortable — trivially comparable and diffable with native operators.
  4. Honest about precision — documents Float64 limits explicitly and provides a BigInt companion when they are insufficient.
  5. Extendable to deep time — handles pre-epoch dates without sign confusion or floating-point catastrophe.

1.2 Why J2000.0?

The epoch J2000.0 — defined as 2000-01-01T12:00:00 Terrestrial Time (TT) — is the standard reference epoch of modern astronomy, used by every major observatory, the IAU, JPL ephemerides, and the IERS. It is not an arbitrary calendar date: it is a precisely defined physical instant whose exact relationship to TAI and UTC is fully specified. Anchoring a time standard here connects software engineering to the broader scientific community without inventing new constants.

1.3 Why TAI?

Coordinated Universal Time (UTC) is a civil timescale that periodically inserts leap seconds to keep clocks within 0.9 seconds of the Earth’s rotation. These insertions create discontinuities: the UTC second :59 is sometimes followed by :60 before :00. For timekeeping arithmetic — computing elapsed durations, sorting, diffing — discontinuities are bugs waiting to happen. International Atomic Time (TAI) ticks at the same rate as UTC but never skips or repeats. BrightDate uses TAI as its substrate, relegating leap-second awareness to the conversion layer at UTC boundaries only.


2. Definitions

2.0 Canonical Epoch Definition

The BrightDate epoch is J2000.0, defined as 2000-01-01T12:00:00.000 TT. In TAI, this is 2000-01-01T11:59:27.816 (Unix TAI second 946,727,967.816). The UTC label, as returned by standard software clocks, is 2000-01-01T11:58:55.816Z (Unix millisecond 946,727,935,816). All BrightDate and BrightSpacetime coordinate times are measured from this instant on a TAI substrate.

This block is identical across the BrightDate, Bright Spacetime, and BrightSpace specifications. The detail that follows in §2.1–§2.4 unpacks each piece for implementers.

2.1 The BrightDate Scalar

1 BrightDate unit = 1 SI day = 86,400 SI seconds

A BrightDate value is a signed floating-point count of SI days elapsed since J2000.0, computed on a TAI substrate:

bd = (taiUnixSeconds − J2000_TAI_UNIX_S) / 86400

where J2000_TAI_UNIX_S = 946,727,967.816 (the TAI Unix-second representation of J2000.0).

The UTC label of J2000.0 as seen by standard software clocks is 2000-01-01T11:58:55.816Znot UTC noon. The 64.184-second gap arises from two fixed offsets: the TT−TAI offset (32.184 seconds, defined by the IAU) and the TAI−UTC offset at J2000.0 (32 seconds). Any implementation that places BrightDate = 0 at UTC noon is astronomically incorrect.

2.2 Epoch Relationships

Timescale Representation Unix value
TT (definition) 2000-01-01T12:00:00.000 946,728,000 s
TAI 2000-01-01T11:59:27.816 946,727,967.816 s
UTC label 2000-01-01T11:58:55.816Z 946,727,935,816 ms

BrightDate = 0 at 946,727,935,816 Unix milliseconds. This is the only astronomically correct choice for the epoch.

2.3 Metric Sub-Units

Because BrightDate is measured in decimal days, SI prefixes yield intuitive sub-units:

Unit Value (days) SI seconds Real-world feel
1 day 1.0 86,400 24 hours
1 milliday (md) 0.001 86.4 1 min 26.4 s
1 microday (μd) 0.000001 0.0864 86.4 ms
1 nanoday (nd) 0.000000001 0.0000000864 86.4 µs

Elapsed durations in BrightDate express naturally: b - a = elapsed_days. No unit algebra enters the computation.

This is not the Bright-Second hierarchy. BrightDate sub-units (md, μd, nd) are prefixes on the day — the native unit of the decimal dashboard (BD 9622.50417 means days + fraction of a day). Bright Spacetime uses a separate hierarchy: Bright-Seconds (bs, kbs, Mbs, Gbs) and Brights (bm, kbm, …) at the light-second scale for c = 1 engineering. The bridge is exact: 1 day = 86_400 bs and 1 md = 86.4 s. You are not expected to replace millidays with kilobrights; you pick the sub-system for the job:

Sub-system Time prefixes Space partner Typical use
Calendar / dashboard md, μd, nd (on days) Light-Day (Ld) if vectors need days BD labels, scheduling, logs
Engineering / spacetime μbs, mbs, bs, kbs, Mbs, Gbs Bright (bm, kbm, …) latency, distance bounding, [t,x,y,z]

Canonical storage (ExactBrightAtto) sits below both: attoseconds since J2000.0, from which either hierarchy is a derived view.

2.4 TAI Substrate

Internally, BrightDate advances in strict SI seconds with no discontinuities. Two consecutive UTC seconds straddling a leap-second boundary correspond to exactly 2 SI seconds in BrightDate, because TAI advances by 2 during that transition. This is the physically correct behavior. Leap seconds appear only at UTC conversion boundaries (toISO, fromISO, toUnixMs, fromDate), where :60 is rendered correctly.

2.5 Use as a Spacetime-Vector Time Component

The BrightDate day scalar is the right unit for calendars, scheduling, logging, analytics, and human display. It is not the unit used for the time axis of a BrightSpace / Bright Spacetime 4D vector [t, x, y, z]. Those vectors require c = 1 to hold across all four components, which means the time axis must use the same ruler as the BrightMeter spatial axes — the Bright-Second (bs), where 1 bs = 1 light-second = 1 SI second (Bright Spacetime Standard §2.2).

Convert between the two by the exact factor 86,400:

t_bs           = brightDateDays × 86,400     // day scalar  → vector time component
brightDateDays = t_bs / 86,400               // vector time component → display label

Numerically, the Bright-Second count from J2000.0 is exactly the TAI-second count carried by BrightInstant (§3.2). The recommended practice is therefore: store and compute the spacetime-vector time component as BrightInstant (or its raw second count), and render it as a BD / PBD day label only for display. Using a day-scalar directly as t alongside BrightMeter spatial components silently makes c = 86,400 and breaks every relativistic formula downstream.

If an application genuinely wants the calendar day to remain the time unit, the consistent choice is to pair BrightDate days with Light-Days as the spatial unit (1 Ld = 86,400 bm), which preserves c = 1 at calendar scale. See Bright Spacetime Standard §2.4.

2.6 Canonical integer engine (v2)

1 attosecond tick since J2000.0 on the TAI substrate is the recommended canonical representation for databases, APIs, network serialization, and spacetime vectors under the Bright c = 1 convention (one tick = one light-attosecond). Wire form: 16-byte big-endian two’s-complement signed i128 (TypeScript BigInt; Rust i128). Text debug form: EBA1:<attoseconds>.

Decimal BrightDate days are derived from the integer without float division on the canonical value:

days_whole   = t_as div_euclid 86_400_000_000_000_000_000
remainder_as = t_as rem_euclid  86_400_000_000_000_000_000
BD_display   = days_whole + format_decimal(remainder_as / 86_400_000_000_000_000_000)

Two-way conversion with BrightDate (Float64) is supported and explicitly lossy when the float carries more digits than the tick quantum or when arithmetic uses IEEE 754 (fromBrightDateValue / toBrightDateValue). Parsing a formatted BD string for UI is fine; consensus and signatures must use the integer, not the decimal string.

ExactBrightDate (picoseconds, EBD1:) remains available for deployments that already store picosecond ticks; convert to attoseconds exactly via × 10⁶.


3. Representation layers (engine vs dashboard)

BrightDate is intentionally bi-scalar at the human layer (decimal days) and mono-scalar at the storage layer (integer attoseconds). The types below are lenses on the same J2000.0 / TAI epoch — pick the engine first, then project to the dashboard.

3.1 ExactBrightAtto — canonical engine (attoseconds)

Signed attoseconds since J2000.0. TypeScript: bigint; Rust: i128. Range at this resolution: ±~5.4×10¹² years. Sub-nanometre spatial resolution when the same tick is used for Bright spacetime axes (Bright Spacetime Standard §2.6).

Choose ExactBrightAtto when: building consensus timestamps, archival storage, spacetime vectors, or any path where a microsecond today must remain exactly one microsecond in fifty years.

3.2 ExactBrightDate — picosecond engine (optional)

Same semantics as §3.1 but internal unit is picoseconds (EBD1: wire prefix). Bit-exact round-trip with integer Unix milliseconds. Convert to ExactBrightAtto without loss when sub-picosecond ticks are zero.

Choose ExactBrightDate when: you standardised on picoseconds before attoseconds, or you only need millisecond-aligned Unix-ms consensus.

3.3 BrightInstant — BigInt TAI seconds + nanoseconds

Nanosecond rigour without attosecond magnitude in the type name. Ideal for GPS / NTP pipelines that already think in seconds + ns.

3.4 BrightDate — Float64 decimal days (v1 dashboard)

The presentation lens. Stores signed decimal SI days for ergonomic astronomy, scheduling, logging, and widgets. For the current era (~BD 9,600–11,000), Float64 provides ~190 ns worst-case ULP — fine for display, risky as the only storage layer in consensus systems.

Float64 resolution by era (worst-case ULP at the upper end of the row’s magnitude):

BrightDate magnitude ULP (days) ULP (seconds)
~1 (one day from epoch) 2.2×10⁻¹⁶ ~19 ps
~9,600 (current era) 2.2×10⁻¹² ~190 ns
~100,000 (year 2273) 2.2×10⁻¹¹ ~1.9 µs
~1,000,000 (year 4737) 2.2×10⁻¹⁰ ~19 µs

Float64 covers 287,000+ years without losing sub-microsecond resolution. The resolution at the epoch itself is bounded only by IEEE 754 sub-normals and is not engineering-meaningful; treat the 1-day row as the practical floor — any timestamp other than the epoch instant lives there or further out.

Choose BrightDate when: doing astronomy, scheduling, analytics, logging, display, or interval arithmetic on the v1 Float64 API — and always back it with ExactBrightAtto (or ExactBrightDate) at persistence boundaries.

3.5 Mixing types

// Canonical store → dashboard (divmod; lossy only in the final Float64)
const canonical = ExactBrightAtto.fromUnixMs(Date.now());
const bd = BrightDate.fromExactBrightAtto(canonical); // v1 Float64 view

// Dashboard → canonical (lossy if float has sub-tick detail)
const back = bd.toExactBrightAtto();

// Picosecond engine ↔ attosecond engine (exact when ps % 10⁶ === 0)
const ps = ExactBrightDate.fromUnixMs(1_700_000_000_123);
const atto = ExactBrightAtto.fromExactBrightDate(ps);

4. Float64 Precision and Honest Trade-offs

4.1 The Round-Trip Tax

The reference implementation routes integer Unix-ms inputs through exact-integer paths, so a substantial set of common inputs round-trips bit-exactly:

fromUnixMs(0).toUnixMs()                === 0;                // Unix epoch: exact
fromUnixMs(946_727_935_816).toUnixMs()  === 946_727_935_816;  // J2000.0 (UTC label): exact
fromUnixMs(946_728_000_000).toUnixMs()  === 946_728_000_000;  // TT noon: exact
fromUnixMs(1_700_000_000_000).toUnixMs() === 1_700_000_000_000; // also exact

Bit-exactness is not guaranteed for arbitrary Unix-ms inputs. For an input ms of typical current-era magnitude (~10¹² ms), the round-trip error is bounded by ~2⁻⁵² × |ms| ≈ 0.00012 ms (≈ 120 ns) — well under any realistic clock resolution. Systems that can tolerate sub-microsecond error at the Unix-ms boundary should use BrightDate. Systems that cannot — blockchain consensus, byte-identical archival — should use ExactBrightDate, which round-trips bit-exactly for every integer Unix-ms input by construction.

4.2 Algebraic Identity Limits

Most identities hold bit-exactly for realistic inputs:

  • lerp(a, b, 0) === a — always.
  • lerp(a, b, 1) === b — holds for realistic inputs; can differ by 1–2 ULP when |b − a| is comparable to |a|.
  • (v + d) − d === v — holds for realistic inputs; the same IEEE 754 catastrophic-cancellation regime applies.

These are properties of 64-bit floating-point arithmetic, not BrightDate defects. The reference implementation’s property-based tests assert the honest bounds.


5. Pre-J2000.0 Display Convention (PBD)

5.1 The Convention

Pre-J2000.0 instants are negative BrightDate scalars in the canonical representation. Negative numbers are mathematically fine for storage, comparison, and arithmetic, but they read poorly in user-facing displays — leading minus signs invite sign-flip mistakes, and large negative scientific notation (-4.35e17) is hostile to skim-reading.

To address this without changing storage or arithmetic, BrightDate defines a display-only label convention:

  • A non-negative scalar bd ≥ 0 is rendered as BD <bd>.
  • A negative scalar bd < 0 is rendered as PBD <|bd|>.
  • BD 0 is the canonical label for J2000.0. There is no PBD 0. A formatter MUST NOT produce PBD 0; a parser MUST reject PBD 0 as invalid.

The PBD prefix is shorthand for Pre-BrightDate: a sign-flipping cosmetic for negative values, nothing more. The internal scalar is unchanged; round-tripping through the display layer is exact.

5.2 Worked Examples

Internal scalar (days) Display label Civil meaning
0 BD 0 J2000.0
9622.504 BD 9622.504 ~2026 (current era)
-1 PBD 1 One day before J2000.0
-11125.154 PBD 11125.154 Apollo 11 landing (1969-07-20)
-1826388.89 PBD 1826388.89 ~3000 BC (earliest writing)

5.3 Sort Rule for Labels

When sorting label strings (rather than the underlying scalars), the rule is:

  1. Any BD value is later than any PBD value.
  2. Within BD, larger number is later.
  3. Within PBD, smaller number is later (closer to J2000.0).

In code:

function isLater(a: BrightLabel, b: BrightLabel): boolean {
  if (a.kind !== b.kind) return a.kind === 'BD';   // any BD > any PBD
  if (a.kind === 'BD')   return a.value > b.value;
  return a.value < b.value;                        // within PBD, smaller is later
}

When the underlying numeric scalars are available, native numeric comparison is the right tool — it agrees with the label-string rule without needing the prefix at all.

5.4 Format and Parse API

formatBD(0)              // "BD 0"
formatBD(9622.504)       // "BD 9622.504"
formatBD(-11125.154)     // "PBD 11125.154"
formatBD(-1)             // "PBD 1"

parseBD("BD 9622.504")   // 9622.504
parseBD("PBD 11125.154") // -11125.154
parseBD("BD 0")          // 0
parseBD("PBD 0")         // throws — PBD 0 is invalid

5.5 Precision Note for Deep Time

BrightDate Float64 covers ~287,000 years from J2000.0 with sub-microsecond ULP, in either direction. For deep-time work that needs full precision indefinitely far from the epoch — geological timescales, cosmological references — use BrightInstant (BigInt TAI seconds + integer nanoseconds) or ExactBrightDate (BigInt picoseconds). Both are integer-backed and have no magnitude-dependent precision degradation. The display convention above applies identically: their human-facing labels use the same BD/PBD prefix rule.


6. Serialization and Storage

6.1 Canonical Encoded Form

encode(9622.50417);              // "BD1:9622.50417000"
encode(9622.50417, 'tai');       // "BD1:9622.50417000:tai"
decode('BD1:9622.50417000');     // { value: 9622.50417, timescale: 'utc' }

6.2 Sortable String (Database Index)

For storage in systems that rely on lexicographic ordering (most key-value stores, wide-column databases), BrightDate provides a sortable string encoding where lexicographic order exactly matches numeric order across both positive and negative values:

toSortableString(9622.50417);  // "+0009622.50417000"
toSortableString(-10957.5);    // "!9989042.49999999"  (nine's-complement)

The ! prefix and nine’s-complement encoding are necessary because ASCII + (0x2B) sorts before - (0x2D), which would invert the expected ordering for mixed-sign timestamps.

6.3 Binary Form

toBinary(9622.50417);          // 8-byte ArrayBuffer (big-endian IEEE 754)
fromBinary(buf);               // bit-exact round-trip

ExactBrightAtto and ExactBrightDate both use the same 16-byte big-endian two’s-complement wire layout; only the tick quantum (attoseconds vs picoseconds) differs. Encoded text forms: EBA1:<attoseconds>, EBD1:<picoseconds>.


7. Reference Dates

Event ISO 8601 (UTC) BrightDate
J2000.0 (epoch anchor) 2000-01-01T11:58:55.816Z 0.000000000
TT noon (definition moment) 2000-01-01T12:00:00.000Z ≈ 0.000742870
Y2K midnight 2000-01-01T00:00:00Z ≈ −0.499257130
Unix epoch 1970-01-01T00:00:00Z ≈ −10,957.499512
GPS epoch 1980-01-06T00:00:00Z ≈ −7,300.499408
Apollo 11 landing 1969-07-20T20:17:40Z ≈ −11,125.154
Current era (~2026) ≈ 9,600

8. Key Constants

J2000_UTC_UNIX_MS       = 946_727_935_816   // UTC label of J2000.0 in Unix ms
J2000_TAI_UNIX_S        = 946_727_967.816   // TAI Unix seconds at J2000.0
J2000_TT_UNIX_S         = 946_728_000       // TT (definition) Unix seconds
J2000_JD                = 2_451_545.0       // Julian Date at J2000.0
J2000_MJD               = 51_544.5          // Modified Julian Date at J2000.0
TAI_UTC_OFFSET_AT_J2000 = 32               // TAI − UTC at J2000.0 (seconds)
TT_TAI_OFFSET_SECONDS   = 32.184           // TT − TAI (fixed by IAU definition)
CURRENT_TAI_UTC_OFFSET  = 37               // TAI − UTC as of 2017
GPS_EPOCH_UNIX_TAI      = 315_964_819      // GPS epoch as TAI Unix seconds
SECONDS_PER_DAY         = 86_400           // SI seconds per day

All constants are stored as exact bigint literals in the reference implementation. No floating-point conversion enters the definition path.


9. Leap Seconds

Leap seconds are a civil-time artefact, not a physical phenomenon. BrightDate treats them accordingly:

  • Internally: leap seconds do not exist. BrightDate ticks in strict SI seconds on a TAI substrate.
  • At UTC boundaries: the leap second table (LEAP_SECOND_TABLE) maps TAI seconds to UTC seconds. The table is valid through LEAP_SECOND_TABLE_VALID_UNTIL_UNIX_S and carries a LEAP_SECOND_TABLE_REVIEWED_AT timestamp.
  • toISO() during a leap second: renders the moment as :60, e.g. 1998-12-31T23:59:60.000Z.
  • Table expiry: getTaiUtcOffset() throws LeapSecondTableExpiredError if the table has lapsed. Update the library to continue.

Two consecutive UTC seconds straddling a positive leap second boundary correspond to exactly 2 SI seconds in BrightDate. This is the physically correct elapsed time; applications that require UTC wall-clock elapsed time must handle the boundary explicitly in their UTC conversion layer.


  • Unix time: monotonic seconds since 1970-01-01T00:00:00 UTC. Ubiquitous but ambiguous at leap-second boundaries; Y2038 problem for 32-bit representations; epoch is not astronomically motivated.
  • Julian Date (JD): count of days since the Julian proleptic calendar date 4713 BC January 1 noon. Values in the current era are enormous (~2.45 million); the origin is an arbitrary historical convention.
  • Modified Julian Date (MJD): JD − 2,400,000.5. Slightly more ergonomic but inherits JD’s arbitrary anchor and midnight-vs-noon ambiguity.
  • GPS time: seconds since 1980-01-06T00:00:00 UTC, no leap seconds. Well-defined but niche; requires a rollover table for week numbers; epoch is not an astronomical standard.
  • CCSDS Time Code Formats (CCSDS 301.0-B-4): standardises encoding of time tags for spacecraft telemetry, not an underlying unit system or scalar.
  • TAI directly: monotonic and physically correct, but provides no agreed epoch, no decimal-day hierarchy, and no reference implementation oriented toward general engineering use.

BrightDate fills a specific gap: a TAI-substrate scalar anchored at the standard astronomical epoch, with SI decimal sub-units, a sign-flipping BD / PBD display convention for pre-epoch instants, honest Float64 precision documentation, and an open-source reference implementation in TypeScript and Rust.


11. Discussion

11.1 What This Is Not

BrightDate is not new physics and does not propose new fundamental constants. It is a units and encoding convention — a choice of epoch, substrate, scalar unit, and serialization format — alongside a reference implementation. Its claim to attention is operational: reduced conversion errors, improved sortability, and an honest account of precision limits.

11.2 Limitations

  • BrightDate does not replace TAI or UTC at the metrology level. It is an application-layer convention that consumes those standards.
  • Below sub-microsecond scales — high-precision pulsar timing, optical clock comparisons — the relevant unit is the SI second itself and BrightDate Float64 offers no advantage over BrightInstant or ExactBrightDate.
  • The standard does not specify coordinate systems, energy units, or momentum. These are intentionally out of scope.

11.3 Invitation

The standard is published under the MIT licence and the reference implementation is open source. Critiques, alternative designs, and formal review from the metrology, astronomy, and spaceflight communities are explicitly welcomed. The author’s expectation is not that this exact proposal becomes a formal standard, but that publishing a complete, runnable, exact implementation makes the conversation tractable for the first time.


References

  1. International Astronomical Union, Resolution B2 on the re-definition of the astronomical unit of length (XXVIII General Assembly, Beijing, 2012).
  2. BIPM, The International System of Units (SI), 9th edition (2019).
  3. IERS, Bulletin C — leap second announcements (ongoing).
  4. CCSDS, Time Code Formats, CCSDS 301.0-B-4 (2010).
  5. P. K. Seidelmann (ed.), Explanatory Supplement to the Astronomical Almanac (University Science Books, 1992).
  6. D. D. McCarthy & G. Petit (eds.), IERS Conventions (2003), IERS Technical Note 32.
  7. J. Mulein, @brightchain/brightdate reference implementation, https://github.com/Digital-Defiance/brightdate.
  8. J. Mulein, brightdate Rust crate, https://crates.io/crates/brightdate.
  9. BrightDate interactive companion site, https://brightdate.org.