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` / `ClockDrift` when utmost precision is required.
53 LTC,
54 /// **Custom / user-defined type** – for experimental or mission-specific timescales.
55 /// Most powerful when paired with `ClockModel` (self-describing polynomial).
56 Custom,
57}
58
59impl Scale {
60 #[inline]
61 pub const fn to_ut(&self) -> Self {
62 if self.is_ut() {
63 return *self;
64 } else {
65 return Scale::UTC;
66 }
67 }
68
69 /// Returns `true` if this scale accounts for leap seconds
70 /// (or historical UTC civil time rules).
71 #[inline]
72 pub const fn is_ut(&self) -> bool {
73 matches!(self, Self::UTC | Self::UTCSpice | Self::UTCSofa | Self::UT1)
74 }
75
76 /// Returns `true` if this scale is based off a GNSS constellation.
77 #[inline]
78 pub const fn is_gnss(&self) -> bool {
79 matches!(self, Self::GPS | Self::GST | Self::BDT | Self::QZSS)
80 }
81
82 /// Parse scale from abbreviation.
83 /// Returns `None` for any non-ASCII input.
84 pub fn from_abbrev(s: &str) -> Option<Self> {
85 let bytes = s.as_bytes();
86 if !bytes.is_ascii() {
87 return None;
88 }
89 let mut buf = [0u8; 8];
90 let mut len = 0;
91 for &byte in bytes {
92 if len >= 8 {
93 return None;
94 }
95 buf[len] = if byte.is_ascii_lowercase() {
96 byte - 32
97 } else {
98 byte
99 };
100 len += 1;
101 }
102 let upper = core::str::from_utf8(&buf[..len]).ok()?;
103 match upper {
104 "TAI" => Some(Self::TAI),
105 "TT" => Some(Self::TT),
106 "ET" => Some(Self::ET),
107 "TDB" => Some(Self::TDB),
108 "UTC" => Some(Self::UTC),
109 "UT1" => Some(Self::UT1),
110 "UTCSPICE" => Some(Self::UTCSpice),
111 "UTCSOFA" => Some(Self::UTCSofa),
112 "GPS" => Some(Self::GPS),
113 "GST" => Some(Self::GST),
114 "BDT" => Some(Self::BDT),
115 "QZSS" => Some(Self::QZSS),
116 "TCG" => Some(Self::TCG),
117 "TCB" => Some(Self::TCB),
118 "LTC" => Some(Self::LTC),
119 "CUSTOM" => Some(Self::Custom),
120 _ => None,
121 }
122 }
123
124 /// Short abbreviation used for formatting / display (e.g. "TAI", "UTC", "UTCSpice").
125 pub const fn abbrev(&self) -> &'static str {
126 match self {
127 Self::TAI => "TAI",
128 Self::TT => "TT",
129 Self::ET => "ET",
130 Self::TDB => "TDB",
131 Self::UTC => "UTC",
132 Self::UT1 => "UT1",
133 Self::UTCSpice => "UTCSPICE",
134 Self::UTCSofa => "UTCSOFA",
135 Self::TCG => "TCG",
136 Self::TCB => "TCB",
137 Self::GPS => "GPS",
138 Self::GST => "GST",
139 Self::BDT => "BDT",
140 Self::QZSS => "QZSS",
141 Self::LTC => "LTC",
142 Self::Custom => "CUSTOM",
143 }
144 }
145
146 /// Const-friendly equality comparison (does **not** rely on `==` for the enum itself).
147 #[inline]
148 pub const fn eq(self, other: Self) -> bool {
149 self.to_wire_byte() == other.to_wire_byte()
150 }
151
152 /// Size of the canonical wire representation in bytes.
153 pub const WIRE_SIZE: usize = 1;
154
155 /// Attempts to reconstruct a `Scale` from its wire byte representation.
156 ///
157 /// Returns `None` for any value that does not correspond to a known variant.
158 /// This provides safe deserialization from untrusted sources.
159 pub const fn from_u8(v: u8) -> Option<Self> {
160 match v {
161 0 => Some(Self::TAI),
162 1 => Some(Self::TT),
163 2 => Some(Self::ET),
164 3 => Some(Self::TDB),
165 4 => Some(Self::UTC),
166 5 => Some(Self::UT1),
167 6 => Some(Self::UTCSpice),
168 7 => Some(Self::UTCSofa),
169 8 => Some(Self::GPS),
170 9 => Some(Self::GST),
171 10 => Some(Self::BDT),
172 11 => Some(Self::QZSS),
173 12 => Some(Self::TCG),
174 13 => Some(Self::TCB),
175 14 => Some(Self::LTC),
176 15 => Some(Self::Custom),
177 _ => None,
178 }
179 }
180
181 /// Returns the wire representation of this `Scale` as a single byte.
182 ///
183 /// The returned byte is the `repr(u8)` discriminant of the enum.
184 /// This is the canonical on-wire form used by [`Dt`] and [`ClockModel`].
185 #[inline]
186 pub const fn to_wire_byte(self) -> u8 {
187 self as u8
188 }
189}
190
191impl fmt::Display for Scale {
192 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
193 f.write_str(self.abbrev())
194 }
195}