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`](struct.Dt.html) can represent.
6/// Each [`Dt`](struct.Dt.html) 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` | 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/// 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))]
52#[cfg_attr(feature = "defmt", derive(defmt::Format))]
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 /// (TAI + 32.184 s constant offset).
62 TT,
63
64 /// Ephemeris Time (NAIF/SPICE simplified model).
65 ///
66 /// Uses the official NAIF simplified single-term model for interoperability
67 /// with NASA/NAIF SPICE (~30 µs accuracy). For higher-fidelity relativistic
68 /// ephemeris calculations, use [`TDB`](Scale::TDB) instead.
69 ET,
70
71 /// Barycentric Dynamical Time (TDB).
72 ///
73 /// High-fidelity relativistic ephemeris time tuned to DE440/LTE440 + VSOP2013.
74 /// Used for precise planetary and spacecraft trajectory calculations.
75 TDB,
76
77 /// Coordinated Universal Time (UTC) using modern leap second rules.
78 UTC,
79
80 /// Coordinated Universal Time using the SPICE historical model
81 /// (fixed +9 s offset before 1972-01-01).
82 UtcSpice,
83
84 /// Coordinated Universal Time using the historical SOFA model
85 /// (with "rubber seconds" between 1961–1972).
86 ///
87 /// Round-tripping is not supported.
88 UtcHist,
89
90 /// GPS Time.
91 ///
92 /// The time scale used by the U.S. GPS satellite navigation system.
93 GPS,
94
95 /// Galileo Time.
96 ///
97 /// The time scale used by Europe’s Galileo satellite navigation system.
98 GST,
99
100 /// BeiDou Time.
101 ///
102 /// The time scale used by China’s BeiDou satellite navigation system.
103 BDT,
104
105 /// QZSS Time.
106 ///
107 /// The time scale used by Japan’s QZSS satellite system (similar to GPS).
108 QZSS,
109
110 /// Geocentric Coordinate Time (TCG).
111 ///
112 /// A relativistic time scale centered on Earth, used for high-precision
113 /// work near Earth (e.g. satellite orbits).
114 TCG,
115
116 /// Barycentric Coordinate Time (TCB).
117 ///
118 /// A relativistic time scale for the entire solar system.
119 TCB,
120
121 /// Coordinated Lunar Time (LTC).
122 ///
123 /// Operational lunar time scale intended for cislunar operations.
124 /// Based on the LTE440 model.
125 LTC,
126
127 /// Lunar Coordinate Time (TCL).
128 ///
129 /// Theoretical relativistic coordinate time at the Moon’s center of mass.
130 /// Based on the LTE440 model.
131 TCL,
132
133 /// Custom / user-defined scale.
134 ///
135 /// Can be useful when a user doesn't want to use TAI, and instead wants their own
136 /// time scale to mess about with.
137 Custom,
138}
139
140impl Scale {
141 /// Returns `true` if this scale is TAI.
142 #[inline]
143 pub const fn is_tai(&self) -> bool {
144 matches!(self, Self::TAI)
145 }
146
147 /// Converts this [`Scale`] to UTC.
148 /// - If the scale is already one of the UTC variants
149 /// including historical UTC then no change occurs.
150 #[inline]
151 pub const fn to_utc(&self) -> Scale {
152 if self.uses_leap_seconds() {
153 *self
154 } else {
155 Scale::UTC
156 }
157 }
158
159 /// Returns `true` if this scale accounts for leap seconds
160 /// (or historical UTC civil time rules).
161 #[inline]
162 pub const fn uses_leap_seconds(&self) -> bool {
163 matches!(self, Self::UTC | Self::UtcSpice | Self::UtcHist)
164 }
165
166 /// Returns `true` if this scale is based off a GNSS constellation.
167 #[inline]
168 pub const fn is_gnss(&self) -> bool {
169 matches!(self, Self::GPS | Self::GST | Self::BDT | Self::QZSS)
170 }
171
172 /// Parse scale from abbreviation.
173 /// Returns `None` for any non-ASCII input.
174 pub fn from_abbrev(s: &str) -> Option<Self> {
175 let bytes = s.as_bytes();
176 let mut buf = [0u8; 8];
177 let mut len = 0;
178
179 for &byte in bytes {
180 if len >= 8 || !byte.is_ascii_alphabetic() {
181 break;
182 }
183 buf[len] = byte.to_ascii_uppercase();
184 len += 1;
185 }
186
187 match &buf[..len] {
188 b"TAI" => Some(Self::TAI),
189 b"TT" => Some(Self::TT),
190 b"ET" => Some(Self::ET),
191 b"TDB" => Some(Self::TDB),
192 b"UTC" => Some(Self::UTC),
193 b"UTCSPICE" => Some(Self::UtcSpice),
194 b"UTCHIST" => Some(Self::UtcHist),
195 b"GPS" => Some(Self::GPS),
196 b"GST" => Some(Self::GST),
197 b"BDT" => Some(Self::BDT),
198 b"QZSS" => Some(Self::QZSS),
199 b"TCG" => Some(Self::TCG),
200 b"TCB" => Some(Self::TCB),
201 b"LTC" => Some(Self::LTC),
202 b"TCL" => Some(Self::TCL),
203 b"CUSTOM" => Some(Self::Custom),
204 _ => None,
205 }
206 }
207
208 /// Short abbreviation used for formatting / display (e.g. "TAI", "UTC", "UtcSpice").
209 pub const fn abbrev(&self) -> &'static str {
210 match self {
211 Self::TAI => "TAI",
212 Self::TT => "TT",
213 Self::ET => "ET",
214 Self::TDB => "TDB",
215 Self::UTC => "UTC",
216 Self::UtcSpice => "UTCSPICE",
217 Self::UtcHist => "UTCHIST",
218 Self::TCG => "TCG",
219 Self::TCB => "TCB",
220 Self::GPS => "GPS",
221 Self::GST => "GST",
222 Self::BDT => "BDT",
223 Self::QZSS => "QZSS",
224 Self::LTC => "LTC",
225 Self::TCL => "TCL",
226 Self::Custom => "CUSTOM",
227 }
228 }
229
230 /// Const-friendly equality comparison.
231 #[inline(always)]
232 pub const fn eq(self, other: Self) -> bool {
233 self.to_u8() == other.to_u8()
234 }
235
236 /// Size of the canonical wire representation in bytes.
237 pub const WIRE_SIZE: usize = 1;
238
239 /// Attempts to reconstruct a `Scale` from its wire byte representation.
240 ///
241 /// - Returns `Custom` for any value that does not correspond to a known variant.
242 /// - This provides safe deserialization from untrusted sources.
243 pub const fn from_u8(v: u8) -> Scale {
244 match v {
245 0 => Self::TAI,
246 1 => Self::TT,
247 2 => Self::ET,
248 3 => Self::TDB,
249 4 => Self::UTC,
250 5 => Self::UtcSpice,
251 6 => Self::UtcHist,
252 7 => Self::GPS,
253 8 => Self::GST,
254 9 => Self::BDT,
255 10 => Self::QZSS,
256 11 => Self::TCG,
257 12 => Self::TCB,
258 13 => Self::LTC,
259 14 => Self::TCL,
260 _ => Self::Custom,
261 }
262 }
263
264 /// Returns the wire representation of this `Scale` as a single byte.
265 ///
266 /// The returned byte is the `repr(u8)` discriminant of the enum.
267 /// This is the canonical on-wire form used by [`Dt`](struct.Dt.html).
268 #[inline(always)]
269 pub const fn to_u8(self) -> u8 {
270 self as u8
271 }
272}
273
274impl fmt::Display for Scale {
275 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
276 f.write_str(self.abbrev())
277 }
278}