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