Skip to main content

deep_time/
scale.rs

1use core::fmt;
2
3#[non_exhaustive]
4#[repr(u8)]
5#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
6#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
7#[cfg_attr(feature = "js", derive(tsify::Tsify))]
8pub enum Scale {
9    /// TAI is the representation of an Epoch internally.
10    #[default]
11    TAI,
12    /// Terrestrial Time (TT) (previously called Terrestrial Dynamical Time (TDT)).
13    TT,
14    /// Ephemeris Time as defined by NASA/NAIF SPICE (identical to TDB).
15    ET,
16    /// Barycentric Dynamical Time (TDB) — SPICE ephemeris time (ET is an alias for this).
17    TDB,
18    /// Universal Coordinated Time using modern IERS leap second rules.
19    UTC,
20    /// UT1
21    UT1,
22    /// Universal Coordinated Time using the SPICE historical model
23    /// (fixed +9 s offset against TAI for all dates before 1972-01-01).
24    UTCSpice,
25    /// Universal Coordinated Time using the full SOFA historical model
26    /// (varying fractional "rubber second" offsets from 1960–1971).
27    UTCSofa,
28    /// GPS Time scale whose reference epoch is UTC midnight between 05 January and
29    /// 06 January 1980.
30    GPS,
31    /// Galileo Time scale.
32    GST,
33    /// BeiDou Time scale.
34    BDT,
35    /// QZSS Time scale has the same properties as GPS but with dedicated clocks.
36    QZSS,
37    /// **Geocentric Coordinate Time (TCG)** – relativistic coordinate time in the
38    /// Geocentric Celestial Reference System (GCRS).
39    TCG,
40    /// **Barycentric Coordinate Time (TCB)** – relativistic coordinate time in the
41    /// Barycentric Celestial Reference System (BCRS).
42    TCB,
43    /// **Coordinated Lunar Time (LTC)** – NASA’s official lunar coordinate time scale
44    /// (analogous to TCG). Defined from the NIST/Ashby & Patla (2024) relativistic
45    /// framework adopted for Artemis and cislunar operations.
46    ///
47    /// Lunar clocks on the selenoid run faster than terrestrial clocks by a
48    /// constant secular rate of **+56.02 µs per Earth day** (L_M = 6.48378 × 10^{-10}).
49    /// A small additional periodic variation exists due to lunar orbital eccentricity
50    /// (±0.108 µs/day in instantaneous rate, ~±0.75 µs accumulated over one orbit).
51    /// The periodic term is **not** part of the defining LTC conversion; it is
52    /// handled via `ClockModel` / `Drift` when utmost precision is required.
53    LTC,
54    TCL, // TODO: add doc
55    /// **Custom / user-defined type** – for experimental or mission-specific timescales.
56    /// Most powerful when paired with `ClockModel` (self-describing polynomial).
57    Custom,
58}
59
60impl Scale {
61    /// Returns `true` if this scale is TAI.
62    #[inline]
63    pub const fn is_tai(&self) -> bool {
64        matches!(self, Self::TAI)
65    }
66
67    #[inline]
68    pub const fn to_ut(&self) -> Self {
69        if self.uses_leap_seconds() {
70            *self
71        } else {
72            Scale::UTC
73        }
74    }
75
76    /// Returns `true` if this scale accounts for leap seconds
77    /// (or historical UTC civil time rules).
78    #[inline]
79    pub const fn uses_leap_seconds(&self) -> bool {
80        matches!(self, Self::UTC | Self::UTCSpice | Self::UTCSofa)
81    }
82
83    /// Returns `true` if this scale is based off a GNSS constellation.
84    #[inline]
85    pub const fn is_gnss(&self) -> bool {
86        matches!(self, Self::GPS | Self::GST | Self::BDT | Self::QZSS)
87    }
88
89    /// Parse scale from abbreviation.
90    /// Returns `None` for any non-ASCII input.
91    pub fn from_abbrev(s: &str) -> Option<Self> {
92        let bytes = s.as_bytes();
93        if !bytes.is_ascii() {
94            return None;
95        }
96        let mut buf = [0u8; 8];
97        let mut len = 0;
98        for &byte in bytes {
99            if len >= 8 {
100                return None;
101            }
102            buf[len] = if byte.is_ascii_lowercase() {
103                byte - 32
104            } else {
105                byte
106            };
107            len += 1;
108        }
109        let upper = core::str::from_utf8(&buf[..len]).ok()?;
110        match upper {
111            "TAI" => Some(Self::TAI),
112            "TT" => Some(Self::TT),
113            "ET" => Some(Self::ET),
114            "TDB" => Some(Self::TDB),
115            "UTC" => Some(Self::UTC),
116            "UT1" => Some(Self::UT1),
117            "UTCSPICE" => Some(Self::UTCSpice),
118            "UTCSOFA" => Some(Self::UTCSofa),
119            "GPS" => Some(Self::GPS),
120            "GST" => Some(Self::GST),
121            "BDT" => Some(Self::BDT),
122            "QZSS" => Some(Self::QZSS),
123            "TCG" => Some(Self::TCG),
124            "TCB" => Some(Self::TCB),
125            "LTC" => Some(Self::LTC),
126            "TCL" => Some(Self::TCL),
127            "CUSTOM" => Some(Self::Custom),
128            _ => None,
129        }
130    }
131
132    /// Short abbreviation used for formatting / display (e.g. "TAI", "UTC", "UTCSpice").
133    pub const fn abbrev(&self) -> &'static str {
134        match self {
135            Self::TAI => "TAI",
136            Self::TT => "TT",
137            Self::ET => "ET",
138            Self::TDB => "TDB",
139            Self::UTC => "UTC",
140            Self::UT1 => "UT1",
141            Self::UTCSpice => "UTCSPICE",
142            Self::UTCSofa => "UTCSOFA",
143            Self::TCG => "TCG",
144            Self::TCB => "TCB",
145            Self::GPS => "GPS",
146            Self::GST => "GST",
147            Self::BDT => "BDT",
148            Self::QZSS => "QZSS",
149            Self::LTC => "LTC",
150            Self::TCL => "TCL",
151            Self::Custom => "CUSTOM",
152        }
153    }
154
155    /// Const-friendly equality comparison.
156    #[inline]
157    pub const fn eq(self, other: Self) -> bool {
158        self.to_wire_byte() == other.to_wire_byte()
159    }
160
161    /// Size of the canonical wire representation in bytes.
162    pub const WIRE_SIZE: usize = 1;
163
164    /// Attempts to reconstruct a `Scale` from its wire byte representation.
165    ///
166    /// Returns `None` for any value that does not correspond to a known variant.
167    /// This provides safe deserialization from untrusted sources.
168    pub const fn from_u8(v: u8) -> Self {
169        match v {
170            0 => Self::TAI,
171            1 => Self::TT,
172            2 => Self::ET,
173            3 => Self::TDB,
174            4 => Self::UTC,
175            5 => Self::UT1,
176            6 => Self::UTCSpice,
177            7 => Self::UTCSofa,
178            8 => Self::GPS,
179            9 => Self::GST,
180            10 => Self::BDT,
181            11 => Self::QZSS,
182            12 => Self::TCG,
183            13 => Self::TCB,
184            14 => Self::LTC,
185            15 => Self::TCL,
186            _ => Self::Custom,
187        }
188    }
189
190    /// Returns the wire representation of this `Scale` as a single byte.
191    ///
192    /// The returned byte is the `repr(u8)` discriminant of the enum.
193    /// This is the canonical on-wire form used by [`Dt`] and [`ClockModel`].
194    #[inline]
195    pub const fn to_wire_byte(self) -> u8 {
196        self as u8
197    }
198}
199
200impl fmt::Display for Scale {
201    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
202        f.write_str(self.abbrev())
203    }
204}