attotime/time_scale/
tai.rs

1//! Implementation of International Atomic Time (TAI).
2
3use num_traits::ConstZero;
4
5use crate::{
6    Date, Duration, FromTimeScale, IntoTimeScale, Month, TimePoint,
7    time_scale::{AbsoluteTimeScale, TerrestrialTime, TimeScale, datetime::UniformDateTimeScale},
8};
9
10pub type TaiTime = TimePoint<Tai>;
11
12/// Time scale representing the International Atomic Time standard (TAI). TAI has no leap seconds
13/// and increases monotonically at a constant rate. This makes it highly suitable for scientific
14/// and high-accuracy timekeeping.
15#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
16pub struct Tai;
17
18impl TimeScale for Tai {
19    const NAME: &'static str = "International Atomic Time";
20
21    const ABBREVIATION: &'static str = "TAI";
22}
23
24impl AbsoluteTimeScale for Tai {
25    const EPOCH: Date = match Date::from_historic_date(1958, Month::January, 1) {
26        Ok(epoch) => epoch,
27        Err(_) => unreachable!(),
28    };
29}
30
31impl UniformDateTimeScale for Tai {}
32
33impl<Scale: ?Sized> TimePoint<Scale> {
34    pub fn from_tai(time_point: TaiTime) -> Self
35    where
36        Self: FromTimeScale<Tai>,
37    {
38        Self::from_time_scale(time_point)
39    }
40
41    pub fn into_tai(self) -> TaiTime
42    where
43        Self: IntoTimeScale<Tai>,
44    {
45        self.into_time_scale()
46    }
47}
48
49impl TerrestrialTime for Tai {
50    const TAI_OFFSET: Duration = Duration::ZERO;
51}
52
53/// Test function that verifies whether a given Gregorian date-time maps to the provided time since
54/// epoch (in seconds). If not, panics.
55#[cfg(test)]
56fn check_gregorian_datetime(
57    year: i32,
58    month: Month,
59    day: u8,
60    hour: u8,
61    minute: u8,
62    second: u8,
63    time_since_epoch: Duration,
64) {
65    assert_eq!(
66        TaiTime::from_gregorian_datetime(year, month, day, hour, minute, second)
67            .unwrap()
68            .time_since_epoch(),
69        time_since_epoch
70    );
71}
72
73/// Verifies this implementation by computing the `TaiTime` for some known (computed manually or
74/// obtained elsewhere) time stamps.
75#[test]
76fn known_timestamps() {
77    use crate::Month::*;
78
79    check_gregorian_datetime(1958, January, 1, 0, 0, 0, Duration::seconds(0));
80    check_gregorian_datetime(1958, January, 2, 0, 0, 0, Duration::seconds(86400));
81    check_gregorian_datetime(1960, January, 1, 0, 0, 0, Duration::seconds(63072000));
82    check_gregorian_datetime(1961, January, 1, 0, 0, 0, Duration::seconds(94694400));
83    check_gregorian_datetime(1970, January, 1, 0, 0, 0, Duration::seconds(378691200));
84    check_gregorian_datetime(1976, January, 1, 0, 0, 0, Duration::seconds(567993600));
85    check_gregorian_datetime(2025, July, 16, 16, 23, 24, Duration::seconds(2131374204));
86    check_gregorian_datetime(2034, December, 26, 8, 2, 37, Duration::seconds(2429424157));
87    check_gregorian_datetime(2760, April, 1, 21, 59, 58, Duration::seconds(25316575198));
88    check_gregorian_datetime(1643, January, 4, 1, 1, 33, Duration::seconds(-9940143507));
89}
90
91#[cfg(test)]
92fn gregorian_datetime_roundtrip(
93    year: i32,
94    month: Month,
95    day: u8,
96    hour: u8,
97    minute: u8,
98    second: u8,
99) {
100    use crate::GregorianDate;
101    use crate::IntoDateTime;
102
103    let time = TaiTime::from_gregorian_datetime(year, month, day, hour, minute, second).unwrap();
104    let (date, hour2, minute2, second2) = time.into_datetime();
105    let gregorian_date = GregorianDate::from_date(date);
106    assert_eq!(gregorian_date.year(), year);
107    assert_eq!(gregorian_date.month(), month);
108    assert_eq!(gregorian_date.day(), day);
109    assert_eq!(hour, hour2);
110    assert_eq!(minute, minute2);
111    assert_eq!(second, second2);
112}
113
114#[test]
115fn date_decomposition() {
116    gregorian_datetime_roundtrip(1999, Month::August, 22, 0, 0, 0);
117    gregorian_datetime_roundtrip(1958, Month::January, 1, 0, 0, 0);
118    gregorian_datetime_roundtrip(1958, Month::January, 2, 0, 0, 0);
119    gregorian_datetime_roundtrip(1960, Month::January, 1, 0, 0, 0);
120    gregorian_datetime_roundtrip(1961, Month::January, 1, 0, 0, 0);
121    gregorian_datetime_roundtrip(1970, Month::January, 1, 0, 0, 0);
122    gregorian_datetime_roundtrip(1976, Month::January, 1, 0, 0, 0);
123    gregorian_datetime_roundtrip(2025, Month::July, 16, 16, 23, 24);
124    gregorian_datetime_roundtrip(2034, Month::December, 26, 8, 2, 37);
125    gregorian_datetime_roundtrip(2760, Month::April, 1, 21, 59, 58);
126    gregorian_datetime_roundtrip(1643, Month::January, 4, 1, 1, 33);
127    gregorian_datetime_roundtrip(1996, Month::January, 1, 3, 0, 0);
128}