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
- See also: https://github.brightchain.org/docs/papers/bright-space-standard
- See also: https://github.brightchain.org/docs/papers/bright-spacetime-standard
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:
- Astronomically grounded — anchored to a living standard used by every observatory and space agency.
- TAI-based — monotonic and free of leap-second discontinuities internally.
- Decimal and sortable — trivially comparable and diffable with native operators.
- Honest about precision — documents Float64 limits explicitly and provides a BigInt companion when they are insufficient.
- 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 is2000-01-01T11:59:27.816(Unix TAI second946,727,967.816). The UTC label, as returned by standard software clocks, is2000-01-01T11:58:55.816Z(Unix millisecond946,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.816Z — not 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 = 1convention (one tick = one light-attosecond). Wire form: 16-byte big-endian two’s-complement signed i128 (TypeScriptBigInt; Rusti128). 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 ≥ 0is rendered asBD <bd>. - A negative scalar
bd < 0is rendered asPBD <|bd|>. BD 0is the canonical label for J2000.0. There is noPBD 0. A formatter MUST NOT producePBD 0; a parser MUST rejectPBD 0as 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:
- Any
BDvalue is later than anyPBDvalue. - Within
BD, larger number is later. - 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 throughLEAP_SECOND_TABLE_VALID_UNTIL_UNIX_Sand carries aLEAP_SECOND_TABLE_REVIEWED_ATtimestamp. toISO()during a leap second: renders the moment as:60, e.g.1998-12-31T23:59:60.000Z.- Table expiry:
getTaiUtcOffset()throwsLeapSecondTableExpiredErrorif 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.
10. Related Work
- 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
BrightInstantorExactBrightDate. - 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
- International Astronomical Union, Resolution B2 on the re-definition of the astronomical unit of length (XXVIII General Assembly, Beijing, 2012).
- BIPM, The International System of Units (SI), 9th edition (2019).
- IERS, Bulletin C — leap second announcements (ongoing).
- CCSDS, Time Code Formats, CCSDS 301.0-B-4 (2010).
- P. K. Seidelmann (ed.), Explanatory Supplement to the Astronomical Almanac (University Science Books, 1992).
- D. D. McCarthy & G. Petit (eds.), IERS Conventions (2003), IERS Technical Note 32.
- J. Mulein,
@brightchain/brightdatereference implementation, https://github.com/Digital-Defiance/brightdate. - J. Mulein,
brightdateRust crate, https://crates.io/crates/brightdate. - BrightDate interactive companion site, https://brightdate.org.