Skip to main content

deep_time/
scale.rs

1use core::fmt;
2
3/// Time scales supported by the library.
4///
5/// This `#[non_exhaustive]` enum defines all time scales that [`Dt`] can represent.
6/// Each [`Dt`] instance stores its internal time value on the scale indicated by
7/// its `scale` field.
8///
9/// The reference epoch used for conversions between scales is **2000-01-01 12:00:00 TAI**.
10///
11/// ## UTC Variants and Leap Seconds
12///
13/// The library supports three UTC variants:
14///
15/// - **`UTC`** — Modern UTC using the built-in IERS leap second table (recommended for most uses).
16/// - **`UtcSpice`** — SPICE-compatible model with a fixed +9 s offset before 1972-01-01.
17/// - **`UtcHist`** — Historical SOFA model with piecewise linear offsets (“rubber seconds”) from 1961–1972.
18///   Round-tripping is **not supported** for this variant.
19///
20/// ## Supported Time Scales
21///
22/// | Scale       | Description |
23/// |-------------|-------------|
24/// | `TAI`       | International Atomic Time. The primary internal continuous atomic time scale. |
25/// | `TT`        | Terrestrial Time. Smooth atomic time used in astronomy and dynamics (TAI + 32.184 s). |
26/// | `ET`        | Ephemeris Time using the **NAIF/SPICE simplified model** (~30 µs accuracy). Matches NASA/NAIF SPICE for interoperability. Use `TDB` for higher-fidelity. |
27/// | `TDB`       | Barycentric Dynamical Time. High-fidelity relativistic ephemeris time (DE440/LTE440 + VSOP2013 tuned model). |
28/// | `UTC`       | Coordinated Universal Time using modern IERS leap second rules. |
29/// | `UtcSpice`  | Coordinated Universal Time using the SPICE historical model (fixed +9 s offset before 1972-01-01). |
30/// | `UtcHist`   | Coordinated Universal Time using the historical SOFA model with “rubber seconds” (1961–1972). Round-tripping is not supported. |
31/// | `GPS`       | GPS Time (used by the U.S. GPS navigation constellation). |
32/// | `GST`       | Galileo Time (used by Europe’s Galileo navigation system). |
33/// | `BDT`       | BeiDou Time (used by China’s BeiDou navigation system). |
34/// | `QZSS`      | QZSS Time (used by Japan’s QZSS satellite system). |
35/// | `TCG`       | Geocentric Coordinate Time. Relativistic time scale in the GCRS (Earth-centered). |
36/// | `TCB`       | Barycentric Coordinate Time. Relativistic time scale in the BCRS (solar-system barycenter). |
37/// | `LTC`       | Coordinated Lunar Time. Operational lunar time for cislunar use based on the LTE440 model. |
38/// | `TCL`       | Lunar Coordinate Time. IAU relativistic coordinate time in the LCRS based on the LTE440 model. |
39/// | `Custom`    | User-defined or experimental time scale. |
40///
41/// ## Lunar Time Scales (LTC / TCL)
42///
43/// Both `LTC` and `TCL` are based on the **LTE440** model (Lu et al. 2025):
44///
45/// - `LTC` (Coordinated Lunar Time) — Intended for operational cislunar use. Applies a secular rate of **+56.02 µs/day** relative to TT plus the dominant periodic terms.
46/// - `TCL` (Lunar Coordinate Time) — Theoretical IAU relativistic coordinate time at the Moon’s center of mass. Includes the secular rate versus TDB, periodic terms, and a J2000 bias calibrated to published LTE440 values.
47#[non_exhaustive]
48#[repr(u8)]
49#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
50#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
51#[cfg_attr(feature = "tsify", derive(tsify::Tsify))]
52pub enum Scale {
53    /// International Atomic Time (TAI).
54    #[default]
55    TAI,
56
57    /// Terrestrial Time (TT).
58    ///
59    /// A smooth, continuous atomic time scale used in astronomy and dynamics
60    /// (TAI + 32.184 s constant offset).
61    TT,
62
63    /// Ephemeris Time (NAIF/SPICE simplified model).
64    ///
65    /// Uses the official NAIF simplified single-term model for interoperability
66    /// with NASA/NAIF SPICE (~30 µs accuracy). For higher-fidelity relativistic
67    /// ephemeris calculations, use [`TDB`](Scale::TDB) instead.
68    ET,
69
70    /// Barycentric Dynamical Time (TDB).
71    ///
72    /// High-fidelity relativistic ephemeris time tuned to DE440/LTE440 + VSOP2013.
73    /// Used for precise planetary and spacecraft trajectory calculations.
74    TDB,
75
76    /// Coordinated Universal Time (UTC) using modern leap second rules.
77    UTC,
78
79    /// Coordinated Universal Time using the SPICE historical model
80    /// (fixed +9 s offset before 1972-01-01).
81    UtcSpice,
82
83    /// Coordinated Universal Time using the historical SOFA model
84    /// (with "rubber seconds" between 1961–1972).
85    ///
86    /// Round-tripping is not supported.
87    UtcHist,
88
89    /// GPS Time.
90    ///
91    /// The time scale used by the U.S. GPS satellite navigation system.
92    GPS,
93
94    /// Galileo Time.
95    ///
96    /// The time scale used by Europe’s Galileo satellite navigation system.
97    GST,
98
99    /// BeiDou Time.
100    ///
101    /// The time scale used by China’s BeiDou satellite navigation system.
102    BDT,
103
104    /// QZSS Time.
105    ///
106    /// The time scale used by Japan’s QZSS satellite system (similar to GPS).
107    QZSS,
108
109    /// Geocentric Coordinate Time (TCG).
110    ///
111    /// A relativistic time scale centered on Earth, used for high-precision
112    /// work near Earth (e.g. satellite orbits).
113    TCG,
114
115    /// Barycentric Coordinate Time (TCB).
116    ///
117    /// A relativistic time scale for the entire solar system.
118    TCB,
119
120    /// Coordinated Lunar Time (LTC).
121    ///
122    /// Operational lunar time scale intended for cislunar operations.
123    /// Based on the LTE440 model.
124    LTC,
125
126    /// Lunar Coordinate Time (TCL).
127    ///
128    /// Theoretical relativistic coordinate time at the Moon’s center of mass.
129    /// Based on the LTE440 model.
130    TCL,
131
132    /// Custom / user-defined scale.
133    Custom,
134}
135
136impl Scale {
137    /// Returns `true` if this scale is TAI.
138    #[inline]
139    pub const fn is_tai(&self) -> bool {
140        matches!(self, Self::TAI)
141    }
142
143    /// Converts this [`Scale`] to UTC.
144    /// - If the scale is already one of the UTC variants
145    ///   including historical UTC then no change occurs.
146    #[inline]
147    pub const fn to_utc(&self) -> Scale {
148        if self.uses_leap_seconds() {
149            *self
150        } else {
151            Scale::UTC
152        }
153    }
154
155    /// Returns `true` if this scale accounts for leap seconds
156    /// (or historical UTC civil time rules).
157    #[inline]
158    pub const fn uses_leap_seconds(&self) -> bool {
159        matches!(self, Self::UTC | Self::UtcSpice | Self::UtcHist)
160    }
161
162    /// Returns `true` if this scale is based off a GNSS constellation.
163    #[inline]
164    pub const fn is_gnss(&self) -> bool {
165        matches!(self, Self::GPS | Self::GST | Self::BDT | Self::QZSS)
166    }
167
168    /// Parse scale from abbreviation.
169    /// Returns `None` for any non-ASCII input.
170    pub fn from_abbrev(s: &str) -> Option<Self> {
171        let bytes = s.as_bytes();
172        let mut buf = [0u8; 8];
173        let mut len = 0;
174
175        for &byte in bytes {
176            if len >= 8 || !byte.is_ascii_alphabetic() {
177                break;
178            }
179            buf[len] = byte.to_ascii_uppercase();
180            len += 1;
181        }
182
183        match &buf[..len] {
184            b"TAI" => Some(Self::TAI),
185            b"TT" => Some(Self::TT),
186            b"ET" => Some(Self::ET),
187            b"TDB" => Some(Self::TDB),
188            b"UTC" => Some(Self::UTC),
189            b"UTCSPICE" => Some(Self::UtcSpice),
190            b"UTCHIST" => Some(Self::UtcHist),
191            b"GPS" => Some(Self::GPS),
192            b"GST" => Some(Self::GST),
193            b"BDT" => Some(Self::BDT),
194            b"QZSS" => Some(Self::QZSS),
195            b"TCG" => Some(Self::TCG),
196            b"TCB" => Some(Self::TCB),
197            b"LTC" => Some(Self::LTC),
198            b"TCL" => Some(Self::TCL),
199            b"CUSTOM" => Some(Self::Custom),
200            _ => None,
201        }
202    }
203
204    /// Short abbreviation used for formatting / display (e.g. "TAI", "UTC", "UtcSpice").
205    pub const fn abbrev(&self) -> &'static str {
206        match self {
207            Self::TAI => "TAI",
208            Self::TT => "TT",
209            Self::ET => "ET",
210            Self::TDB => "TDB",
211            Self::UTC => "UTC",
212            Self::UtcSpice => "UTCSPICE",
213            Self::UtcHist => "UTCHIST",
214            Self::TCG => "TCG",
215            Self::TCB => "TCB",
216            Self::GPS => "GPS",
217            Self::GST => "GST",
218            Self::BDT => "BDT",
219            Self::QZSS => "QZSS",
220            Self::LTC => "LTC",
221            Self::TCL => "TCL",
222            Self::Custom => "CUSTOM",
223        }
224    }
225
226    /// Const-friendly equality comparison.
227    #[inline(always)]
228    pub const fn eq(self, other: Self) -> bool {
229        self.to_u8() == other.to_u8()
230    }
231
232    /// Size of the canonical wire representation in bytes.
233    pub const WIRE_SIZE: usize = 1;
234
235    /// Attempts to reconstruct a `Scale` from its wire byte representation.
236    ///
237    /// - Returns `Custom` for any value that does not correspond to a known variant.
238    /// - This provides safe deserialization from untrusted sources.
239    pub const fn from_u8(v: u8) -> Scale {
240        match v {
241            0 => Self::TAI,
242            1 => Self::TT,
243            2 => Self::ET,
244            3 => Self::TDB,
245            4 => Self::UTC,
246            5 => Self::UtcSpice,
247            6 => Self::UtcHist,
248            7 => Self::GPS,
249            8 => Self::GST,
250            9 => Self::BDT,
251            10 => Self::QZSS,
252            11 => Self::TCG,
253            12 => Self::TCB,
254            13 => Self::LTC,
255            14 => Self::TCL,
256            _ => Self::Custom,
257        }
258    }
259
260    /// Returns the wire representation of this `Scale` as a single byte.
261    ///
262    /// The returned byte is the `repr(u8)` discriminant of the enum.
263    /// This is the canonical on-wire form used by [`Dt`].
264    #[inline(always)]
265    pub const fn to_u8(self) -> u8 {
266        self as u8
267    }
268}
269
270impl fmt::Display for Scale {
271    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
272        f.write_str(self.abbrev())
273    }
274}