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 abbreviation.
176 /// Returns `None` for any non-ASCII input.
177 pub fn from_abbrev(s: &str) -> Option<Self> {
178 let bytes = s.as_bytes();
179 let mut buf = [0u8; 8];
180 let mut len = 0;
181
182 for &byte in bytes {
183 if len >= 8 || !byte.is_ascii_alphabetic() {
184 break;
185 }
186 buf[len] = byte.to_ascii_uppercase();
187 len += 1;
188 }
189
190 match &buf[..len] {
191 b"TAI" => Some(Self::TAI),
192 b"TT" => Some(Self::TT),
193 b"ET" => Some(Self::ET),
194 b"TDB" => Some(Self::TDB),
195 b"UTC" => Some(Self::UTC),
196 b"UTCSPICE" => Some(Self::UtcSpice),
197 b"UTCHIST" => Some(Self::UtcHist),
198 b"GPS" => Some(Self::GPS),
199 b"GST" => Some(Self::GST),
200 b"BDT" => Some(Self::BDT),
201 b"QZSS" => Some(Self::QZSS),
202 b"TCG" => Some(Self::TCG),
203 b"TCB" => Some(Self::TCB),
204 b"LTC" => Some(Self::LTC),
205 b"TCL" => Some(Self::TCL),
206 b"CUSTOM" => Some(Self::Custom),
207 _ => None,
208 }
209 }
210
211 /// Short abbreviation used for formatting / display (e.g. "TAI", "UTC", "UtcSpice").
212 pub const fn abbrev(&self) -> &'static str {
213 match self {
214 Self::TAI => "TAI",
215 Self::TT => "TT",
216 Self::ET => "ET",
217 Self::TDB => "TDB",
218 Self::UTC => "UTC",
219 Self::UtcSpice => "UTCSPICE",
220 Self::UtcHist => "UTCHIST",
221 Self::TCG => "TCG",
222 Self::TCB => "TCB",
223 Self::GPS => "GPS",
224 Self::GST => "GST",
225 Self::BDT => "BDT",
226 Self::QZSS => "QZSS",
227 Self::LTC => "LTC",
228 Self::TCL => "TCL",
229 Self::Custom => "CUSTOM",
230 }
231 }
232
233 /// Const-friendly equality comparison.
234 #[inline(always)]
235 pub const fn eq(self, other: Self) -> bool {
236 self.to_u8() == other.to_u8()
237 }
238
239 /// Size of the canonical wire representation in bytes.
240 pub const WIRE_SIZE: usize = 1;
241
242 /// Reconstructs a [`Scale`] from its single-byte wire form.
243 ///
244 /// Always succeeds. Known values map to the matching variant; any other
245 /// byte becomes [`Scale::Custom`]. Safe for untrusted input.
246 pub const fn from_u8(v: u8) -> Scale {
247 match v {
248 0 => Self::TAI,
249 1 => Self::TT,
250 2 => Self::ET,
251 3 => Self::TDB,
252 4 => Self::UTC,
253 5 => Self::UtcSpice,
254 6 => Self::UtcHist,
255 7 => Self::GPS,
256 8 => Self::GST,
257 9 => Self::BDT,
258 10 => Self::QZSS,
259 11 => Self::TCG,
260 12 => Self::TCB,
261 13 => Self::LTC,
262 14 => Self::TCL,
263 _ => Self::Custom,
264 }
265 }
266
267 /// Returns the wire representation of this `Scale` as a single byte.
268 ///
269 /// This is the canonical on-wire form used by [`Dt`](crate::Dt)
270 /// (`0` = TAI, `1` = TT, … — the enum’s `repr(u8)` order).
271 #[inline(always)]
272 pub const fn to_u8(self) -> u8 {
273 self as u8
274 }
275}
276
277impl fmt::Display for Scale {
278 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
279 f.write_str(self.abbrev())
280 }
281}