1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
/*
 * Hifitime, part of the Nyx Space tools
 * Copyright (C) 2023 Christopher Rabotin <christopher.rabotin@gmail.com> et al. (cf. AUTHORS.md)
 * This Source Code Form is subject to the terms of the Apache
 * v. 2.0. If a copy of the Apache License was not distributed with this
 * file, You can obtain one at https://www.apache.org/licenses/LICENSE-2.0.
 *
 * Documentation: https://nyxspace.com/
 */

#[cfg(feature = "python")]
use pyo3::prelude::*;

#[cfg(feature = "std")]
pub use super::leap_seconds_file::LeapSecondsFile;

use core::ops::Index;

pub trait LeapSecondProvider: DoubleEndedIterator<Item = LeapSecond> + Index<usize> {}

/// A structure representing a leap second
#[repr(C)]
#[cfg_attr(feature = "python", pyclass)]
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct LeapSecond {
    /// Timestamp in TAI seconds for this leap second, e.g. `2_272_060_800.0` for the first IERS leap second.
    pub timestamp_tai_s: f64,
    /// ΔAT is the accumulated time offset after this leap second has past.
    pub delta_at: f64,
    /// Whether or not this leap second was announced by the IERS.
    pub announced_by_iers: bool,
}

impl LeapSecond {
    pub const fn new(timestamp_tai_s: f64, delta_at: f64, announced: bool) -> Self {
        Self {
            timestamp_tai_s,
            delta_at,
            announced_by_iers: announced,
        }
    }
}

const LATEST_LEAP_SECONDS: [LeapSecond; 42] = [
    LeapSecond::new(1_893_369_600.0, 1.417818, false), // SOFA: 01 Jan 1960
    LeapSecond::new(1_924_992_000.0, 1.422818, false), // SOFA: 01 Jan 1961
    LeapSecond::new(1_943_308_800.0, 1.372818, false), // SOFA: 01 Aug 1961
    LeapSecond::new(1_956_528_000.0, 1.845858, false), // SOFA: 01 Jan 1962
    LeapSecond::new(2_014_329_600.0, 1.945858, false), // SOFA: 01 Jan 1963
    LeapSecond::new(2_019_600_000.0, 3.24013, false),  // SOFA: 01 Jan 1964
    LeapSecond::new(2_027_462_400.0, 3.34013, false),  // SOFA: 01 Apr 1964
    LeapSecond::new(2_040_681_600.0, 3.44013, false),  // SOFA: 01 Sep 1964
    LeapSecond::new(2_051_222_400.0, 3.54013, false),  // SOFA: 01 Jan 1965
    LeapSecond::new(2_056_320_000.0, 3.64013, false),  // SOFA: 01 Mar 1965
    LeapSecond::new(2_066_860_800.0, 3.74013, false),  // SOFA: 01 Jul 1965
    LeapSecond::new(2_072_217_600.0, 3.84013, false),  // SOFA: 01 Sep 1965
    LeapSecond::new(2_082_758_400.0, 4.31317, false),  // SOFA: 01 Jan 1966
    LeapSecond::new(2_148_508_800.0, 4.21317, false),  // SOFA: 01 Feb 1968
    LeapSecond::new(2_272_060_800.0, 10.0, true),      // IERS: 01 Jan 1972
    LeapSecond::new(2_287_785_600.0, 11.0, true),      // IERS: 01 Jul 1972
    LeapSecond::new(2_303_683_200.0, 12.0, true),      // IERS: 01 Jan 1973
    LeapSecond::new(2_335_219_200.0, 13.0, true),      // IERS: 01 Jan 1974
    LeapSecond::new(2_366_755_200.0, 14.0, true),      // IERS: 01 Jan 1975
    LeapSecond::new(2_398_291_200.0, 15.0, true),      // IERS: 01 Jan 1976
    LeapSecond::new(2_429_913_600.0, 16.0, true),      // IERS: 01 Jan 1977
    LeapSecond::new(2_461_449_600.0, 17.0, true),      // IERS: 01 Jan 1978
    LeapSecond::new(2_492_985_600.0, 18.0, true),      // IERS: 01 Jan 1979
    LeapSecond::new(2_524_521_600.0, 19.0, true),      // IERS: 01 Jan 1980
    LeapSecond::new(2_571_782_400.0, 20.0, true),      // IERS: 01 Jul 1981
    LeapSecond::new(2_603_318_400.0, 21.0, true),      // IERS: 01 Jul 1982
    LeapSecond::new(2_634_854_400.0, 22.0, true),      // IERS: 01 Jul 1983
    LeapSecond::new(2_698_012_800.0, 23.0, true),      // IERS: 01 Jul 1985
    LeapSecond::new(2_776_982_400.0, 24.0, true),      // IERS: 01 Jan 1988
    LeapSecond::new(2_840_140_800.0, 25.0, true),      // IERS: 01 Jan 1990
    LeapSecond::new(2_871_676_800.0, 26.0, true),      // IERS: 01 Jan 1991
    LeapSecond::new(2_918_937_600.0, 27.0, true),      // IERS: 01 Jul 1992
    LeapSecond::new(2_950_473_600.0, 28.0, true),      // IERS: 01 Jul 1993
    LeapSecond::new(2_982_009_600.0, 29.0, true),      // IERS: 01 Jul 1994
    LeapSecond::new(3_029_443_200.0, 30.0, true),      // IERS: 01 Jan 1996
    LeapSecond::new(3_076_704_000.0, 31.0, true),      // IERS: 01 Jul 1997
    LeapSecond::new(3_124_137_600.0, 32.0, true),      // IERS: 01 Jan 1999
    LeapSecond::new(3_345_062_400.0, 33.0, true),      // IERS: 01 Jan 2006
    LeapSecond::new(3_439_756_800.0, 34.0, true),      // IERS: 01 Jan 2009
    LeapSecond::new(3_550_089_600.0, 35.0, true),      // IERS: 01 Jul 2012
    LeapSecond::new(3_644_697_600.0, 36.0, true),      // IERS: 01 Jul 2015
    LeapSecond::new(3_692_217_600.0, 37.0, true),      // IERS: 01 Jan 2017
];

/// List of leap seconds from https://www.ietf.org/timezones/data/leap-seconds.list .
/// This list corresponds the number of seconds in TAI to the UTC offset and to whether it was an announced leap second or not.
/// The unannoucned leap seconds come from dat.c in the SOFA library.
#[cfg_attr(feature = "python", pyclass)]
#[derive(Clone, Debug)]
pub struct LatestLeapSeconds {
    data: [LeapSecond; 42],
    iter_pos: usize,
}

#[cfg(feature = "python")]
#[cfg_attr(feature = "python", pymethods)]
impl LatestLeapSeconds {
    #[new]
    pub fn __new__() -> Self {
        Self::default()
    }

    fn __repr__(&self) -> String {
        format!("{self:?} @ {self:p}")
    }
}

impl Default for LatestLeapSeconds {
    fn default() -> Self {
        Self {
            data: LATEST_LEAP_SECONDS,
            iter_pos: 0,
        }
    }
}

impl Iterator for LatestLeapSeconds {
    type Item = LeapSecond;

    fn next(&mut self) -> Option<Self::Item> {
        self.iter_pos += 1;
        self.data.get(self.iter_pos - 1).copied()
    }
}

impl DoubleEndedIterator for LatestLeapSeconds {
    fn next_back(&mut self) -> Option<Self::Item> {
        if self.iter_pos == self.data.len() {
            None
        } else {
            self.iter_pos += 1;
            self.data.get(self.data.len() - self.iter_pos).copied()
        }
    }
}

impl Index<usize> for LatestLeapSeconds {
    type Output = LeapSecond;

    fn index(&self, index: usize) -> &Self::Output {
        self.data.index(index)
    }
}

impl LeapSecondProvider for LatestLeapSeconds {}

#[test]
fn leap_second_fetch() {
    let leap_seconds = LatestLeapSeconds::default();

    assert_eq!(
        leap_seconds[0],
        LeapSecond::new(1_893_369_600.0, 1.417818, false),
    );
    assert_eq!(
        leap_seconds[41],
        LeapSecond::new(3_692_217_600.0, 37.0, true)
    );
}