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