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`](crate::Dt) can represent.
6/// Each [`Dt`](crate::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`       | Mean-selenoid lunar time: TCL scaled by \(L_m\) (like TT from TCG). ~+56 µs/day vs TT. Not a finalized international LTC standard. |
38/// | `TCL`       | IAU Lunar Coordinate Time (approx.): \(L_D^M\) vs TDB from 1977 + 13-term series. Not the full LTE440 product. |
39/// | `Custom`    | Custom time scale. Can be useful when a user doesn't want to use TAI but wants similar behavior in conversion functions. |
40///
41/// ## Lunar Time Scales (LTC / TCL)
42///
43/// - **`TCL`** — IAU LCRS coordinate time at the Moon’s center of mass. This crate
44///   uses \(L_D^M\) (LTE440 rate) from the 1977 epoch plus the published 13-term
45///   Fourier sketch of TCL−TDB. That is **not** the full LTE440 Chebyshev kernel.
46/// - **`LTC`** — `LTC = TCL − L_m·(TCL − t₀)` with Ashby & Patla (2024) \(L_m\)
47///   (same pattern as TT from TCG). Mean rate vs TT ≈ +56.02 µs/day. The name
48///   matches common “coordinated lunar time” language but is **not** a claim to
49///   implement a finished multi-agency LTC standard.
50#[non_exhaustive]
51#[repr(u8)]
52#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
53#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
54#[cfg_attr(feature = "tsify", derive(tsify::Tsify))]
55#[cfg_attr(feature = "defmt", derive(defmt::Format))]
56pub enum Scale {
57    /// International Atomic Time (TAI).
58    #[default]
59    TAI,
60
61    /// Terrestrial Time (TT).
62    ///
63    /// A smooth, continuous atomic time scale used in astronomy and dynamics
64    /// (TAI + 32.184 s constant offset).
65    TT,
66
67    /// Ephemeris Time (NAIF/SPICE simplified model).
68    ///
69    /// Uses the official NAIF simplified single-term model for interoperability
70    /// with NASA/NAIF SPICE (~30 µs accuracy). For higher-fidelity relativistic
71    /// ephemeris calculations, use [`TDB`](Scale::TDB).
72    ET,
73
74    /// Barycentric Dynamical Time (TDB).
75    ///
76    /// High-fidelity relativistic ephemeris time tuned to DE440/LTE440 + VSOP2013.
77    /// Used for precise planetary and spacecraft trajectory calculations.
78    TDB,
79
80    /// Coordinated Universal Time (UTC) using modern leap second rules.
81    UTC,
82
83    /// Coordinated Universal Time using the SPICE historical model
84    /// (fixed +9 s offset before 1972-01-01).
85    UtcSpice,
86
87    /// Coordinated Universal Time using the historical SOFA model
88    /// (with "rubber seconds" between 1961–1972).
89    ///
90    /// Round-tripping is not supported.
91    UtcHist,
92
93    /// GPS Time.
94    ///
95    /// The time scale used by the U.S. GPS satellite navigation system.
96    GPS,
97
98    /// Galileo Time.
99    ///
100    /// The time scale used by Europe’s Galileo satellite navigation system.
101    GST,
102
103    /// BeiDou Time.
104    ///
105    /// The time scale used by China’s BeiDou satellite navigation system.
106    BDT,
107
108    /// QZSS Time.
109    ///
110    /// The time scale used by Japan’s QZSS satellite system (similar to GPS).
111    QZSS,
112
113    /// Geocentric Coordinate Time (TCG).
114    ///
115    /// A relativistic time scale centered on Earth, used for high-precision
116    /// work near Earth (e.g. satellite orbits).
117    TCG,
118
119    /// Barycentric Coordinate Time (TCB).
120    ///
121    /// A relativistic time scale for the entire solar system.
122    TCB,
123
124    /// Mean-selenoid lunar time (library `LTC`).
125    ///
126    /// TCL scaled by \(L_m\) (Ashby & Patla 2024), like TT from TCG.
127    /// Mean rate vs TT ≈ +56.02 µs/day. Not a finalized international LTC.
128    LTC,
129
130    /// Lunar Coordinate Time (TCL).
131    ///
132    /// IAU LCRS coordinate time at the Moon’s center of mass. Approximate
133    /// model: \(L_D^M\) vs TDB from 1977 plus a 13-term series (not full LTE440).
134    TCL,
135
136    /// Custom / user-defined scale.
137    ///
138    /// Can be useful when a user doesn't want to use TAI, and instead wants their own
139    /// time scale to mess about with.
140    Custom,
141}
142
143impl Scale {
144    /// Returns `true` if this scale is TAI.
145    #[inline]
146    pub const fn is_tai(&self) -> bool {
147        matches!(self, Self::TAI)
148    }
149
150    /// Converts this [`Scale`] to UTC.
151    /// - If the scale is already one of the UTC variants
152    ///   including historical UTC then no change occurs.
153    #[inline]
154    pub const fn to_utc(&self) -> Scale {
155        if self.uses_leap_seconds() {
156            *self
157        } else {
158            Scale::UTC
159        }
160    }
161
162    /// Returns `true` if this scale accounts for leap seconds
163    /// (or historical UTC civil time rules).
164    #[inline]
165    pub const fn uses_leap_seconds(&self) -> bool {
166        matches!(self, Self::UTC | Self::UtcSpice | Self::UtcHist)
167    }
168
169    /// Returns `true` if this scale is based off a GNSS constellation.
170    #[inline]
171    pub const fn is_gnss(&self) -> bool {
172        matches!(self, Self::GPS | Self::GST | Self::BDT | Self::QZSS)
173    }
174
175    /// Parse scale from an ASCII abbreviation (e.g. `b"TAI"`, `b"UtcSpice"`).
176    ///
177    /// Reads up to 8 leading alphabetic bytes, case-insensitively. Stops at the
178    /// first non-letter. Returns `None` if no known scale matches.
179    pub fn from_abbrev(bytes: &[u8]) -> Option<Self> {
180        let mut buf = [0u8; 8];
181        let mut len = 0;
182
183        for &byte in bytes {
184            if len >= 8 || !byte.is_ascii_alphabetic() {
185                break;
186            }
187            buf[len] = byte.to_ascii_uppercase();
188            len += 1;
189        }
190
191        match &buf[..len] {
192            b"TAI" => Some(Self::TAI),
193            b"TT" => Some(Self::TT),
194            b"ET" => Some(Self::ET),
195            b"TDB" => Some(Self::TDB),
196            b"UTC" => Some(Self::UTC),
197            b"UTCSPICE" => Some(Self::UtcSpice),
198            b"UTCHIST" => Some(Self::UtcHist),
199            b"GPS" => Some(Self::GPS),
200            b"GST" => Some(Self::GST),
201            b"BDT" => Some(Self::BDT),
202            b"QZSS" => Some(Self::QZSS),
203            b"TCG" => Some(Self::TCG),
204            b"TCB" => Some(Self::TCB),
205            b"LTC" => Some(Self::LTC),
206            b"TCL" => Some(Self::TCL),
207            b"CUSTOM" => Some(Self::Custom),
208            _ => None,
209        }
210    }
211
212    /// Short abbreviation used for formatting / display (e.g. "TAI", "UTC", "UtcSpice").
213    pub const fn abbrev(&self) -> &'static str {
214        match self {
215            Self::TAI => "TAI",
216            Self::TT => "TT",
217            Self::ET => "ET",
218            Self::TDB => "TDB",
219            Self::UTC => "UTC",
220            Self::UtcSpice => "UTCSPICE",
221            Self::UtcHist => "UTCHIST",
222            Self::TCG => "TCG",
223            Self::TCB => "TCB",
224            Self::GPS => "GPS",
225            Self::GST => "GST",
226            Self::BDT => "BDT",
227            Self::QZSS => "QZSS",
228            Self::LTC => "LTC",
229            Self::TCL => "TCL",
230            Self::Custom => "CUSTOM",
231        }
232    }
233
234    /// Const-friendly equality comparison.
235    #[inline(always)]
236    pub const fn eq(self, other: Self) -> bool {
237        self.to_u8() == other.to_u8()
238    }
239
240    /// Size of the canonical wire representation in bytes.
241    pub const WIRE_SIZE: usize = 1;
242
243    /// Reconstructs a [`Scale`] from its single-byte wire form.
244    ///
245    /// Always succeeds. Known values map to the matching variant; any other
246    /// byte becomes [`Scale::Custom`]. Safe for untrusted input.
247    pub const fn from_u8(v: u8) -> Scale {
248        match v {
249            0 => Self::TAI,
250            1 => Self::TT,
251            2 => Self::ET,
252            3 => Self::TDB,
253            4 => Self::UTC,
254            5 => Self::UtcSpice,
255            6 => Self::UtcHist,
256            7 => Self::GPS,
257            8 => Self::GST,
258            9 => Self::BDT,
259            10 => Self::QZSS,
260            11 => Self::TCG,
261            12 => Self::TCB,
262            13 => Self::LTC,
263            14 => Self::TCL,
264            _ => Self::Custom,
265        }
266    }
267
268    /// Returns the wire representation of this `Scale` as a single byte.
269    ///
270    /// This is the canonical on-wire form used by [`Dt`](crate::Dt)
271    /// (`0` = TAI, `1` = TT, … — the enum’s `repr(u8)` order).
272    #[inline(always)]
273    pub const fn to_u8(self) -> u8 {
274        self as u8
275    }
276}
277
278impl fmt::Display for Scale {
279    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
280        f.write_str(self.abbrev())
281    }
282}