Coverage Report

Created: 2026-07-30 06:46

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/hifitime-4.3.0/src/timescale/mod.rs
Line
Count
Source
1
/*
2
* Hifitime
3
* Copyright (C) 2017-onward Christopher Rabotin <christopher.rabotin@gmail.com> et al. (cf. https://github.com/nyx-space/hifitime/graphs/contributors)
4
* This Source Code Form is subject to the terms of the Mozilla Public
5
* License, v. 2.0. If a copy of the MPL was not distributed with this
6
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
7
*
8
* Documentation: https://nyxspace.com/
9
*/
10
11
#[cfg(feature = "python")]
12
use pyo3::prelude::*;
13
14
#[cfg(kani)]
15
mod kani;
16
17
#[cfg(feature = "serde")]
18
use serde_derive::{Deserialize, Serialize};
19
20
mod fmt;
21
22
/// EXPERIMENTAL Temps Lunaire Coordonnee / Lunar Coordinated Time
23
pub(crate) mod tcl;
24
25
use crate::{Duration, Epoch, Unit, SECONDS_PER_DAY};
26
27
/// The J1900 reference epoch (1900-01-01 at noon) TAI.
28
pub const J1900_REF_EPOCH: Epoch = Epoch {
29
    duration: Duration {
30
        centuries: 0,
31
        nanoseconds: 43200000000000,
32
    },
33
    time_scale: TimeScale::TAI,
34
};
35
36
/// The J2000 reference epoch (2000-01-01 at midnight) TAI.
37
/// |UTC - TAI| = XX Leap Seconds on that day.
38
pub const J2000_REF_EPOCH: Epoch = Epoch {
39
    duration: Duration {
40
        centuries: 1,
41
        nanoseconds: 43200000000000,
42
    },
43
    time_scale: TimeScale::TAI,
44
};
45
46
pub const GPST_REF_EPOCH: Epoch = Epoch::from_tai_duration(Duration {
47
    centuries: 0,
48
    nanoseconds: 2_524_953_619_000_000_000, // XXX
49
});
50
pub const SECONDS_GPS_TAI_OFFSET: f64 = 2_524_953_619.0;
51
pub const SECONDS_GPS_TAI_OFFSET_I64: i64 = 2_524_953_619;
52
pub const DAYS_GPS_TAI_OFFSET: f64 = SECONDS_GPS_TAI_OFFSET / SECONDS_PER_DAY;
53
54
/// QZSS and GPS share the same reference epoch.
55
pub const QZSST_REF_EPOCH: Epoch = GPST_REF_EPOCH;
56
57
/// GST (Galileo) reference epoch is 13 seconds before 1999 August 21 UTC at midnight.
58
/// |UTC - TAI| = XX Leap Seconds on that day.
59
pub const GST_REF_EPOCH: Epoch = Epoch::from_tai_duration(Duration {
60
    centuries: 0,
61
    nanoseconds: 3_144_268_819_000_000_000, // 3_144_268_800_000_000_000,
62
});
63
pub const SECONDS_GST_TAI_OFFSET: f64 = 3_144_268_819.0;
64
pub const SECONDS_GST_TAI_OFFSET_I64: i64 = 3_144_268_819;
65
66
/// BDT(BeiDou): 2005 Dec 31st Midnight
67
/// BDT (BeiDou) reference epoch is 2005 December 31st UTC at midnight. **This time scale is synchronized with UTC.**
68
/// |UTC - TAI| = XX Leap Seconds on that day.
69
pub const BDT_REF_EPOCH: Epoch = Epoch::from_tai_duration(Duration {
70
    centuries: 1,
71
    nanoseconds: 189_302_433_000_000_000, //189_302_400_000_000_000,
72
});
73
pub const SECONDS_BDT_TAI_OFFSET: f64 = 3_345_062_433.0;
74
pub const SECONDS_BDT_TAI_OFFSET_I64: i64 = 3_345_062_433;
75
76
/// The UNIX reference epoch of 1970-01-01 in TAI duration, accounting only for IERS leap seconds.
77
pub const UNIX_REF_EPOCH: Epoch = Epoch::from_tai_duration(Duration {
78
    centuries: 0,
79
    nanoseconds: 2_208_988_800_000_000_000,
80
});
81
82
/// Reference year of the Hifitime prime epoch.
83
pub(crate) const HIFITIME_REF_YEAR: i32 = 1900;
84
85
/// Enum of the different time systems available
86
#[non_exhaustive]
87
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
88
#[cfg_attr(feature = "python", pyclass(eq, eq_int))]
89
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
90
pub enum TimeScale {
91
    /// TAI is the representation of an Epoch internally
92
    TAI,
93
    /// Terrestrial Time (TT) (previously called Terrestrial Dynamical Time (TDT))
94
    TT,
95
    /// Ephemeris Time as defined by SPICE (slightly different from true TDB)
96
    ET,
97
    /// Dynamic Barycentric Time (TDB) (higher fidelity SPICE ephemeris time)
98
    TDB,
99
    /// Universal Coordinated Time
100
    UTC,
101
    /// GPS Time scale whose reference epoch is UTC midnight between 05 January and 06 January 1980;
102
    /// cf. <https://gssc.esa.int/navipedia/index.php/Time_References_in_GNSS#GPS_Time_.28GPST.29>. |UTC - TAI| = 19 Leap Seconds on that day.
103
    GPST,
104
    /// Galileo Time scale
105
    GST,
106
    /// BeiDou Time scale
107
    BDT,
108
    /// QZSS Time scale has the same properties as GPST but with dedicated clocks
109
    QZSST,
110
    /// Geocentric Coordinate Time
111
    TCG,
112
    /// Barycentric Coordinate Time
113
    TCB,
114
    /// Experimental Lunar Time, option (iii) from the Lunar Reference Timescale paper,  A Bourgoin*, P Defraigne and F Meynadier
115
    ///
116
    /// TL is defined as a linear scaling of TCL such that TL has no secular drift
117
    /// with respect to TT. Since this implementation omits the bounded periodic
118
    /// TCL-TT terms, TL is equivalent to TT after the common 1977 reference epoch.
119
    TL,
120
    /// Experimental mean Lunar Coordinate Time of Lunar reference timescale, A Bourgoin*, P Defraigne and F Meynadier
121
    ///
122
    /// This is not a full IAU-quality TCL realization. It models only the
123
    /// conventional secular mean rate between TCL and TT:
124
    /// ```text
125
    ///     d(TCL - TT) / dTT ≈ 6.8e-10
126
    /// ```
127
    /// The bounded periodic TCL-TT terms and ephemeris-dependent relativistic
128
    /// integral are intentionally omitted
129
    TCL,
130
}
131
132
impl Default for TimeScale {
133
    /// Builds default TAI time scale
134
0
    fn default() -> Self {
135
0
        Self::TAI
136
0
    }
137
}
138
139
impl TimeScale {
140
4.09k
    pub(crate) const fn formatted_len(&self) -> usize {
141
4.09k
        match &self {
142
0
            Self::QZSST => 5,
143
257
            Self::GPST => 4,
144
            Self::TAI
145
            | Self::TDB
146
            | Self::UTC
147
            | Self::GST
148
            | Self::BDT
149
            | Self::TCG
150
            | Self::TCB
151
3.70k
            | Self::TCL => 3,
152
141
            Self::ET | Self::TT | Self::TL => 2,
153
        }
154
4.09k
    }
155
156
    /// Returns true if Self is based off a GNSS constellation
157
0
    pub const fn is_gnss(&self) -> bool {
158
0
        matches!(self, Self::GPST | Self::GST | Self::BDT | Self::QZSST)
159
0
    }
160
161
    /// Returns this time scale's reference epoch: Time Scale initialization date,
162
    /// expressed as an Epoch in TAI
163
0
    pub const fn reference_epoch(self) -> Epoch {
164
0
        Epoch {
165
0
            duration: Duration::ZERO,
166
0
            time_scale: self,
167
0
        }
168
0
    }
169
170
    /// Returns the duration between this time scale's reference epoch and the hifitime "prime epoch" of 1900-01-01 00:00:00 TAI (the NTP prime epoch).
171
    /// This is used to compute the Gregorian date representations in any time scale.
172
24.6k
    pub(crate) const fn prime_epoch_offset(self) -> Duration {
173
24.6k
        match self {
174
            TimeScale::ET | TimeScale::TDB => {
175
                // Both ET and TDB are defined at J2000, which is 2000-01-01 12:00:00 and there were only 36524 days in the 20th century.
176
                // Hence, this math is the output of (Unit.Century*1 + Unit.Hour*12 - Unit.Day*1).to_parts() via Hifitime in Python.
177
20.2k
                Duration {
178
20.2k
                    centuries: 0,
179
20.2k
                    nanoseconds: 3155716800000000000,
180
20.2k
                }
181
            }
182
8
            TimeScale::GPST | TimeScale::QZSST => Duration {
183
8
                centuries: 0,
184
8
                nanoseconds: 2_524_953_619_000_000_000,
185
8
            },
186
6
            TimeScale::GST => Duration {
187
6
                centuries: 0,
188
6
                nanoseconds: 3_144_268_819_000_000_000,
189
6
            },
190
5
            TimeScale::BDT => Duration {
191
5
                centuries: 1,
192
5
                nanoseconds: 189_302_433_000_000_000,
193
5
            },
194
            TimeScale::TCG | TimeScale::TL | TimeScale::TCL => {
195
                // TCG reference epoch is 1977-01-01 00:00:32.184 TT.
196
5
                Duration {
197
5
                    centuries: 0,
198
5
                    nanoseconds: 2429913632184000000,
199
5
                }
200
            }
201
            TimeScale::TCB => {
202
                // TCB reference epoch is 1977-01-01 00:00:32.184 TT + 65.5 µs.
203
1
                Duration {
204
1
                    centuries: 0,
205
1
                    nanoseconds: 2429913632184065500,
206
1
                }
207
            }
208
4.33k
            _ => Duration::ZERO,
209
        }
210
24.6k
    }
211
212
3.70k
    pub(crate) fn gregorian_epoch_offset(self) -> Duration {
213
3.70k
        let prime_offset = self.prime_epoch_offset();
214
215
3.70k
        prime_offset - prime_offset.subdivision(Unit::Second).unwrap()
216
3.70k
    }
217
}
218
219
#[cfg_attr(feature = "python", pymethods)]
220
impl TimeScale {
221
    /// Returns true if self takes leap seconds into account
222
    /// :rtype: bool
223
0
    pub const fn uses_leap_seconds(&self) -> bool {
224
0
        matches!(self, Self::UTC)
225
0
    }
226
}
227
228
/// Allows conversion of a TimeSystem into a u8
229
/// Mapping: TAI: 0; TT: 1; ET: 2; TDB: 3; UTC: 4; GPST: 5; GST: 6; BDT: 7; QZSST: 8; TCG: 9; TCB: 10;
230
impl From<TimeScale> for u8 {
231
0
    fn from(ts: TimeScale) -> Self {
232
0
        match ts {
233
0
            TimeScale::TAI => 0,
234
0
            TimeScale::TT => 1,
235
0
            TimeScale::ET => 2,
236
0
            TimeScale::TDB => 3,
237
0
            TimeScale::UTC => 4,
238
0
            TimeScale::GPST => 5,
239
0
            TimeScale::GST => 6,
240
0
            TimeScale::BDT => 7,
241
0
            TimeScale::QZSST => 8,
242
0
            TimeScale::TCG => 9,
243
0
            TimeScale::TCB => 10,
244
0
            TimeScale::TL => 11,
245
0
            TimeScale::TCL => 12,
246
        }
247
0
    }
248
}
249
250
/// Allows conversion of a u8 into a TimeSystem.
251
/// Mapping: 1: TT; 2: ET; 3: TDB; 4: UTC; 5: GPST; 6: GST; 7: BDT; 8: QZSST; 9: TCG; 10: TCB; anything else: TAI
252
impl From<u8> for TimeScale {
253
0
    fn from(val: u8) -> Self {
254
0
        match val {
255
0
            1 => Self::TT,
256
0
            2 => Self::ET,
257
0
            3 => Self::TDB,
258
0
            4 => Self::UTC,
259
0
            5 => Self::GPST,
260
0
            6 => Self::GST,
261
0
            7 => Self::BDT,
262
0
            8 => Self::QZSST,
263
0
            9 => Self::TCG,
264
0
            10 => Self::TCB,
265
0
            11 => Self::TL,
266
0
            12 => Self::TCL,
267
0
            _ => Self::TAI,
268
        }
269
0
    }
270
}
271
272
#[cfg(test)]
273
mod ut_timescale {
274
    use super::TimeScale;
275
276
    #[test]
277
    #[cfg(feature = "serde")]
278
    fn test_serdes() {
279
        let ts = TimeScale::UTC;
280
        let content = "\"UTC\"";
281
        assert_eq!(content, serde_json::to_string(&ts).unwrap());
282
        let parsed: TimeScale = serde_json::from_str(content).unwrap();
283
        assert_eq!(ts, parsed);
284
    }
285
286
    #[test]
287
    fn test_ts() {
288
        for ts_u8 in 0..u8::MAX {
289
            let ts = TimeScale::from(ts_u8);
290
            let ts_u8_back: u8 = ts.into();
291
            // If the u8 is greater than 10, it isn't valid and necessarily encoded as TAI.
292
            if ts_u8 < 13 {
293
                assert_eq!(ts_u8_back, ts_u8, "got {ts_u8_back} want {ts_u8}");
294
            } else {
295
                assert_eq!(ts, TimeScale::TAI);
296
            }
297
        }
298
    }
299
300
    #[test]
301
    #[cfg(feature = "std")]
302
    fn test_ref_epoch() {
303
        use crate::{Duration, Epoch, Unit};
304
        let prime_e = Epoch::from_duration(Duration::ZERO, TimeScale::TAI);
305
        assert_eq!(prime_e.duration, Duration::ZERO);
306
        assert_eq!(format!("{prime_e}"), "1900-01-01T00:00:00 TAI");
307
        // NOTE: There are only 36524 days in the 20th century, but one century is 36425, so we "overflow" the next century by one day!
308
        assert_eq!(
309
            format!("{}", prime_e + Unit::Century * 1),
310
            "2000-01-02T00:00:00 TAI"
311
        );
312
313
        assert_eq!(
314
            format!("{}", TimeScale::ET.reference_epoch()),
315
            "2000-01-01T12:00:00 ET"
316
        );
317
    }
318
}