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}