Time Systems and Conversions

Purpose

This specification defines the mathematical contract for LuPNT’s time systems and the conversions between them. It fixes the epoch origin, the scale definitions, the exact offset/rate/periodic equations, the body and reference-system each scale is proper to, and the leap-second and Earth-orientation inputs. Every conversion is tied to the function that implements it in cpp/lupnt/conversions/time_conversions.{h,cc}; the Python surface is pnt.convert_time and friends from python/bindings/py_time_converter.cc. The relativistic content follows the author’s PhD thesis, Chapter 2.3 (Iiyama), and the Turyshev 2026 (ApJ 997:97) lunar-timescale formulation; equations are cited inline.

Epoch, Argument, and Unit Contract

Every scalar time in the API is a coordinate offset in seconds from the J2000 epoch of the corresponding scale, not a calendar date. The J2000 origin is

\[\mathrm{MJD}_\mathrm{J2000} = 51544.5\ \text{(TT days)}, \qquad \mathrm{JD}_\mathrm{J2000} = 2451545.0\ \text{(TT days)}.\]

The Julian/Modified-Julian bridges are exact linear maps (MjdToTime/TimeToMjd/JdToTime/TimeToJd):

\[t = (\mathrm{MJD} - 51544.5)\cdot 86400, \qquad \mathrm{MJD} = t/86400 + 51544.5 .\]
// time_conversions.cc :: MjdToTime / TimeToMjd
Real MjdToTime(Real mjd) { return (mjd - MJD_J2000_TT) * SECS_DAY; }
Real TimeToMjd(Real t)   { return t / SECS_DAY + MJD_J2000_TT; }

The Time enum (cpp/lupnt/core/constants.h) enumerates the supported scales:

\[\texttt{UT1},\ \texttt{UTC},\ \texttt{TAI},\ \texttt{TDB},\ \texttt{TT},\ \texttt{TCG},\ \texttt{TCB},\ \texttt{GPS},\ \texttt{JD\_TT},\ \texttt{JD\_TDB},\ \texttt{TCL},\ \texttt{LT}.\]

Conversion Graph

ConvertTime(t, from, to) (time_conversions.cc :: ConvertTime) routes any pair through the canonical chain, with TAI/TT as the two hubs. The vectorized overload ConvertTime(VecX, from, to) maps element-wise, with special handling for the position-dependent lunar scales. The Python bindings expose exactly this:

import pylupnt as pnt
t_tt = pnt.convert_time(t_tai, pnt.Time.TAI, pnt.Time.TT)   # scalar
t    = pnt.convert_time(t_vec, pnt.Time.GPS, pnt.Time.TDB)  # VecX

Proper vs. Coordinate Time

Following the thesis (Ch. 2.3.1), a clock measures proper time \(\tau\) along its worldline, while a coordinate time \(t\) labels events in a 4-D relativistic reference system (BCRS, GCRS, LCRS) and cannot be read off any single physical clock. IsCoordinateTimeScale partitions the enum: TT, TCG, TDB, TCB, TCL, and LT are coordinate scales; UT1, UTC, TAI, GPS are not.

// time_conversions.cc :: IsCoordinateTimeScale
case Time::TT: case Time::TCG: case Time::TDB:
case Time::TCB: case Time::TCL: case Time::LT: return true;

ConvertCoordinateTime is the coordinate-only entry point; its position-aware overloads take a BCRS position \(x_\mathrm{bcrs}\) so that the position-dependent terms of the TDB/TCB/TCL transforms (below) can be evaluated at the event location rather than at the body center.

Atomic and Terrestrial Scales

TAI -> TT is a defining constant offset (thesis Eq. 2.38):

\[\mathrm{TT} = \mathrm{TAI} + 32.184\ \text{s} .\]
// time_conversions.cc :: TaiToTt / TtToTai   (TT_TAI_OFFSET = 32.184 s)
Real TaiToTt(Real t_tai) { return t_tai + TT_TAI_OFFSET; }
Real TtToTai(Real t_tt)  { return t_tt  - TT_TAI_OFFSET; }

TAI -> GPS is the fixed 19-second offset that removes the leap seconds accumulated at the GPS epoch (thesis Eq. 2.39); GPS time runs without leap seconds thereafter (TaiToGps/GpsToTai):

\[\mathrm{GPS} = \mathrm{TAI} - 19\ \text{s} .\]

Note

Only GPS is implemented among the GNSS scales. The thesis also lists GLONASS \(= \mathrm{TAI}-34\) s (leap-tracking, offset by UTC), Galileo \(= \mathrm{TAI}-19\) s, and BeiDou \(= \mathrm{TAI}-33\) s (Eqs. 2.40-2.42); these are not in the Time enum.

Earth-Rotation and Leap-Second Scales (UTC, UT1)

TAI <-> UTC applies the integer leap-second table \(\Delta_\mathrm{AT}(\mathrm{MJD})\) from interfaces/tai_utc.h (thesis Eq. 2.43):

\[\mathrm{UTC} = \mathrm{TAI} - \Delta_\mathrm{AT}, \qquad \Delta_\mathrm{AT} = \texttt{GetTaiUtcDifference}(\mathrm{MJD}) .\]

UTC <-> UT1 adds the observed Earth-orientation difference \((\mathrm{UT1}-\mathrm{UTC})\) looked up from the EOP table (interfaces/eop.h):

\[\mathrm{UT1} = \mathrm{UTC} + (\mathrm{UT1}-\mathrm{UTC}), \qquad (\mathrm{UT1}-\mathrm{UTC}) = \texttt{GetUt1UtcDifference}(\mathrm{MJD}) .\]
// time_conversions.cc :: UtcToUt1
Real mjd_utc = t_utc / SECS_DAY + MJD_J2000_TT;
Real ut1_utc = GetUt1UtcDifference(mjd_utc);
return t_utc + ut1_utc;

UT1 in turn drives Earth rotation: EarthRotationAngle gives the ERA \(\theta = 2\pi(0.7790572732640 + 1.00273781191135448\, t_\mathrm{UT1}/86400)\), and GreenwichMeanSiderealTime/GreenwichApparentSiderealTime give GMST/GAST (the latter adding the equation of the equinoxes). These are the only rotation quantities keyed on UT1.

Barycentric and Geocentric Coordinate Scales

The four Einstein-scaled coordinate times share the reference epoch \(T_0 =\) 1977-01-01 00:00:00 TAI and the defining rates

\[L_B = 1.550519768\times10^{-8},\quad L_G = 6.969290134\times10^{-10},\quad \mathrm{TDB}_0 = -65.5\ \mu\text{s},\]

with \(\mathrm{MJD}_{T_0} = \texttt{MJD\_COORDINATE\_TT\_TCG\_TCB}\).

TCG <-> TT (geocentric rate, thesis Eq. 2.37) removes the geoid potential secular rate:

\[\mathrm{TT} = \mathrm{TCG} - L_G\,(\mathrm{JD}_\mathrm{TCG}-\mathrm{JD}_0)\cdot 86400 .\]
// time_conversions.cc :: TtToTcg  (inverse of Eq. 2.37 in offset form)
Real tt_tcg = -L_G / (1.0 - L_G) * (mjd_tt - MJD_COORDINATE_TT_TCG_TCB) * SECS_DAY;
return t_tt - tt_tcg;

TCB <-> TDB (barycentric rate + offset, thesis Eq. 2.32):

\[\mathrm{TDB} = \mathrm{TCB} - L_B\,(\mathrm{JD}_\mathrm{TCB}-\mathrm{JD}_0)\cdot 86400 + \mathrm{TDB}_0 .\]
// time_conversions.cc :: TcbToTdb
Real t_tdb = t_tcb - L_B * (mjd_tcb - MJD_COORDINATE_TT_TCG_TCB) * SECS_DAY + TDB_0;

TT <-> TDB has two implementations that intentionally differ:

  • Forward TtToTdb uses the analytic Fairhead/Moyer series (thesis Eqs. 2.33-2.34), \(M_E = (357.53 + 0.9856003\,d_\mathrm{J2000})^\circ\),

    \[\mathrm{TDB} = \mathrm{TT} + 0.001658\sin M_E + 0.000014\sin 2M_E\ \text{s} .\]
  • Inverse TDBToTt (no position) uses the NAIF single-term expansion \(\mathrm{TT} = \mathrm{TDB} - k\sin E\), with \(k=1.657\times10^{-3}\), eccentric anomaly \(E = M + e_b\sin M\), \(M = 6.239996 + 1.99096871\times10^{-7}\,t_\mathrm{TDB}\).

Note

The scalar TT<->TDB pair is a periodic approximation good to \(\lesssim 2\) ms (thesis: “deviates by a periodic oscillation of less than 2 milliseconds”). The position-aware overloads TtToTdb(t, x_bcrs) / TDBToTt(t, x_bcrs) instead evaluate the full GCRS<->BCRS 4-D transform (thesis Eq. 2.35, Turyshev Eq. 21/22): a trapezoidal integral of \(c^{-2}\) and \(c^{-4}\) integrands built from the Earth’s barycentric velocity and the external Solar-System potential \(w_\mathrm{ext}=\sum_{B\neq E} GM_B/r_{EB}\), plus a position term \(-v_E\!\cdot\!r_E/c^2\). TtToTdb(t, x_bcrs) inverts this by 10-step Newton iteration. See TdbToTtMinusTdbEq21 / IntegrateTdbToTtTerms.

Lunicentric Coordinate Scales (TCL, LT)

The Moon’s analogues follow Turyshev 2026, with LCRS as the lunar 4-D system. TCB -> TCL is the barycentric-to-lunicentric 4-D transform (thesis Eqs. 2.44-2.45; Turyshev Eq. 23/25), structurally identical to the Earth case with the Moon substituted:

\[\mathrm{TCL} = \mathrm{TCB} - \frac{1}{c^2}\!\int \!\Big(\tfrac{1}{2}v_M^2 + w_\mathrm{ext}(x_M)\Big)d\mathrm{TCB} - \frac{1}{c^4}\!\int (\cdots)\,d\mathrm{TCB} - \frac{v_M\!\cdot r_M}{c^2} - \cdots\]

where \(r_M = x_\mathrm{bcrs}-x_M\), \(v_M = dx_M/d\mathrm{TCB}\), and \(w_\mathrm{ext}(x_M)=\sum_{B\neq M} GM_B/|x_M-x_B|\) sums the Sun, Mercury, Venus, Earth, Mars, and the outer planets (kTclExternalBodies). Passing no position places the clock at the lunar center (\(v_M\!\cdot r_M = 0\)). TclToTcb inverts by Newton iteration.

// time_conversions.cc :: TcbToTcl  (Turyshev Eq. 23/25)
auto [i2, i4] = IntegrateTcbToTclTerms(t_tcb);
return t_tcb - i2/(C*C) - i4/(C*C*C*C) + TcbToTclPositionTerm(t_tcb, x_bcrs);

TCL <-> LT is the lunar analogue of TCG->TT, a defining rate on the selenoid (thesis Eqs. 2.48-2.49) with \(L_L = 3.13905\times10^{-11}\):

\[\mathrm{LT} = \mathrm{TCL} - L_L\,(\mathrm{TCL}-T_{L0}), \qquad \frac{d\,\mathrm{LT}}{d\,\mathrm{TCL}} = 1 - L_L .\]
// time_conversions.cc :: TclToLt  (T_L0 assumed = T_0)
Real lt_tcl = -L_L * (mjd_tcl - MJD_COORDINATE_TT_TCG_TCB) * SECS_DAY;
return t_tcl + lt_tcl;

Note

\(L_L\) is not yet internationally standardized; the code uses the Turyshev selenoid value \(3.13905\times10^{-11}\) (potential \(\Phi_L = 2.82123744381\times10^{6}\ \mathrm{m^2/s^2}\)), whereas Kopeikin adopts \(3.14027\times10^{-11}\) (thesis Ch. 2.3.5).

Lunar Time as a Function of TT (direct path)

Rather than chaining TT->TDB->TCB->TCL->LT, LuPNT implements the closed-form \(\mathrm{TL}-\mathrm{TT}(\mathrm{TDB})\) of Turyshev 2026 Eq. 57 (the TDB-argument form of thesis Eq. 2.50) in TdbToLtMinusTt. The integral is a trapezoidal sweep at \(dt = 0.01\) day. For repeated queries in a window, InitLtMinusTtFit fits piecewise Chebyshev segments (degree 12, 1-day segments) over the window in a single forward sweep and caches them in s_lt_minus_tt_fit; TdbToLtMinusTt then uses the fast path (ChebyshevFitModel::Eval, numerics/cheby_fit.h). TdbToLt composes \(\mathrm{TL} = \mathrm{TT}(\mathrm{TDB}) + (\mathrm{TL}-\mathrm{TT})\); LtToTdb/LtToTt invert by 10-step Newton iteration.

// time_conversions.cc :: TdbToLt / TtToLt
Real TdbToLt(Real t_tdb) { return TDBToTt(t_tdb) + TdbToLtMinusTt(t_tdb); }
Real TtToLt(Real t_tt)   { return TdbToLt(TtToTdb(t_tt)); }

A separate lunar-surface proper-time term is provided by GetProperTimeCorrectionTcl(t_tcg, x_mci), the first-order clock correction \(-v_{LE}\cdot(r_x - r_{LE})/c^2\) for a clock at MCI position \(x_\mathrm{mci}\) relative to the Moon’s geocentric motion.

Conversion of Physical Quantities and Constants

Because the scaled coordinate times (TT, TDB, LT) run at rates \((1-L_G)\), \((1-L_B)\), \((1-L_L)\) relative to their unscaled partners (TCG, TCB, TCL), spatial coordinates and gravitational parameters must be rescaled to keep \(c\) and the equations of motion invariant (thesis Eqs. 2.52-2.57):

\[\mathcal{X}_\mathrm{TT} = (1-L_G)\,\mathcal{X}_\mathrm{TCG},\quad \mu_\mathrm{TT} = (1-L_G)\,\mu_\mathrm{TCG},\]
\[\mathcal{X}_\mathrm{TDB} = (1-L_B)\,\mathcal{X}_\mathrm{TCB},\quad \mathcal{X}_\mathrm{LT} = (1-L_L)\,\mathcal{X}_\mathrm{TCL}.\]

LuPNT encodes these as the CoordinateScale factors \(1-L_x\) (constants.h :: CoordinateScaleFactor), with CoordinateScaleRatio(from, to) returning \((1-L_\mathrm{to})/(1-L_\mathrm{from})\) for the rescale, guarded by AreCoordinateScalesConvertible (only barycentric<->barycentric, geocentric<->geocentric, lunicentric<->lunicentric pairs share a constant factor).

// constants.h :: CoordinateScaleFactor
case CoordinateScale::TDB: return 1.0 - L_B;
case CoordinateScale::TT:  return 1.0 - L_G;
case CoordinateScale::TL:  return 1.0 - L_L;

Model Boundaries

  • All API times are seconds from the per-scale J2000 origin; calendar I/O goes through GregorianToTime / TimeToGregorianString.

  • Only GPS is implemented among GNSS scales; GLONASS/Galileo/BeiDou offsets are documented but not exposed.

  • The scalar TT<->TDB and TDB->TT paths are periodic approximations (\(\lesssim 2\) ms); sub-microsecond and position-dependent work must use the x_bcrs overloads via ConvertCoordinateTime.

  • TCL/TDB position-aware transforms integrate from \(T_0\) per call unless a Chebyshev fit window is installed (LT only, via InitLtMinusFit).

  • \(L_L\) (and hence LT) is provisional pending international standardization.