Skip to main content

deep_time/
leap_seconds.rs

1//! Leap seconds table from the official IANA
2//! [leap-seconds.list](https://data.iana.org/time-zones/data/leap-seconds.list)
3//! Updated through IERS Bulletin C as of April 2026.
4//! Last leap second: 2017-01-01 (TAI-UTC = 37 s)
5//! File expires: 28 December 2026
6
7use crate::Dt;
8
9pub struct LeapSecond {
10    pub ntp_timestamp: i64,
11    pub leap_seconds_after: i64,
12    pub utc_sec: i64,
13    pub tai_sec: i64,
14}
15
16pub const LEAP_SECS: &[LeapSecond] = &[
17    LeapSecond {
18        ntp_timestamp: 2272060800,
19        leap_seconds_after: 10,
20        utc_sec: -883656000,
21        tai_sec: -883655991, // was -883655990
22    }, // 1 Jan 1972 (start of modern UTC definition)
23    LeapSecond {
24        ntp_timestamp: 2287785600,
25        leap_seconds_after: 11,
26        utc_sec: -867931200,
27        tai_sec: -867931190, // was -867931189
28    }, // 1 Jul 1972
29    LeapSecond {
30        ntp_timestamp: 2303683200,
31        leap_seconds_after: 12,
32        utc_sec: -852033600,
33        tai_sec: -852033589, // was -852033588
34    }, // 1 Jan 1973
35    LeapSecond {
36        ntp_timestamp: 2335219200,
37        leap_seconds_after: 13,
38        utc_sec: -820497600,
39        tai_sec: -820497588, // was -820497587
40    }, // 1 Jan 1974
41    LeapSecond {
42        ntp_timestamp: 2366755200,
43        leap_seconds_after: 14,
44        utc_sec: -788961600,
45        tai_sec: -788961587, // was -788961586
46    }, // 1 Jan 1975
47    LeapSecond {
48        ntp_timestamp: 2398291200,
49        leap_seconds_after: 15,
50        utc_sec: -757425600,
51        tai_sec: -757425586, // was -757425585
52    }, // 1 Jan 1976
53    LeapSecond {
54        ntp_timestamp: 2429913600,
55        leap_seconds_after: 16,
56        utc_sec: -725803200,
57        tai_sec: -725803185, // was -725803184
58    }, // 1 Jan 1977
59    LeapSecond {
60        ntp_timestamp: 2461449600,
61        leap_seconds_after: 17,
62        utc_sec: -694267200,
63        tai_sec: -694267184, // was -694267183
64    }, // 1 Jan 1978
65    LeapSecond {
66        ntp_timestamp: 2492985600,
67        leap_seconds_after: 18,
68        utc_sec: -662731200,
69        tai_sec: -662731183, // was -662731182
70    }, // 1 Jan 1979
71    LeapSecond {
72        ntp_timestamp: 2524521600,
73        leap_seconds_after: 19,
74        utc_sec: -631195200,
75        tai_sec: -631195182, // was -631195181
76    }, // 1 Jan 1980
77    LeapSecond {
78        ntp_timestamp: 2571782400,
79        leap_seconds_after: 20,
80        utc_sec: -583934400,
81        tai_sec: -583934381, // was -583934380
82    }, // 1 Jul 1981
83    LeapSecond {
84        ntp_timestamp: 2603318400,
85        leap_seconds_after: 21,
86        utc_sec: -552398400,
87        tai_sec: -552398380, // was -552398379
88    }, // 1 Jul 1982
89    LeapSecond {
90        ntp_timestamp: 2634854400,
91        leap_seconds_after: 22,
92        utc_sec: -520862400,
93        tai_sec: -520862379, // was -520862378
94    }, // 1 Jul 1983
95    LeapSecond {
96        ntp_timestamp: 2698012800,
97        leap_seconds_after: 23,
98        utc_sec: -457704000,
99        tai_sec: -457703978, // was -457703977
100    }, // 1 Jul 1985
101    LeapSecond {
102        ntp_timestamp: 2776982400,
103        leap_seconds_after: 24,
104        utc_sec: -378734400,
105        tai_sec: -378734377, // was -378734376
106    }, // 1 Jan 1988
107    LeapSecond {
108        ntp_timestamp: 2840140800,
109        leap_seconds_after: 25,
110        utc_sec: -315576000,
111        tai_sec: -315575976, // was -315575975
112    }, // 1 Jan 1990
113    LeapSecond {
114        ntp_timestamp: 2871676800,
115        leap_seconds_after: 26,
116        utc_sec: -284040000,
117        tai_sec: -284039975, // was -284039974
118    }, // 1 Jan 1991
119    LeapSecond {
120        ntp_timestamp: 2918937600,
121        leap_seconds_after: 27,
122        utc_sec: -236779200,
123        tai_sec: -236779174, // was -236779173
124    }, // 1 Jul 1992
125    LeapSecond {
126        ntp_timestamp: 2950473600,
127        leap_seconds_after: 28,
128        utc_sec: -205243200,
129        tai_sec: -205243173, // was -205243172
130    }, // 1 Jul 1993
131    LeapSecond {
132        ntp_timestamp: 2982009600,
133        leap_seconds_after: 29,
134        utc_sec: -173707200,
135        tai_sec: -173707172, // was -173707171
136    }, // 1 Jul 1994
137    LeapSecond {
138        ntp_timestamp: 3029443200,
139        leap_seconds_after: 30,
140        utc_sec: -126273600,
141        tai_sec: -126273571, // was -126273570
142    }, // 1 Jan 1996
143    LeapSecond {
144        ntp_timestamp: 3076704000,
145        leap_seconds_after: 31,
146        utc_sec: -79012800,
147        tai_sec: -79012770, // was -79012769
148    }, // 1 Jul 1997
149    LeapSecond {
150        ntp_timestamp: 3124137600,
151        leap_seconds_after: 32,
152        utc_sec: -31579200,
153        tai_sec: -31579169, // was -31579168
154    }, // 1 Jan 1999
155    LeapSecond {
156        ntp_timestamp: 3345062400,
157        leap_seconds_after: 33,
158        utc_sec: 189345600,
159        tai_sec: 189345632, // was 189345633
160    }, // 1 Jan 2006
161    LeapSecond {
162        ntp_timestamp: 3439756800,
163        leap_seconds_after: 34,
164        utc_sec: 284040000,
165        tai_sec: 284040033, // was 284040034
166    }, // 1 Jan 2009
167    LeapSecond {
168        ntp_timestamp: 3550089600,
169        leap_seconds_after: 35,
170        utc_sec: 394372800,
171        tai_sec: 394372834, // was 394372835
172    }, // 1 Jul 2012
173    LeapSecond {
174        ntp_timestamp: 3644697600,
175        leap_seconds_after: 36,
176        utc_sec: 488980800,
177        tai_sec: 488980835, // was 488980836
178    }, // 1 Jul 2015
179    LeapSecond {
180        ntp_timestamp: 3692217600,
181        leap_seconds_after: 37,
182        utc_sec: 536500800,
183        tai_sec: 536500836, // was 536500837
184    }, // 1 Jan 2017
185];
186
187#[derive(Copy, Clone, Debug)]
188pub struct LeapInfo {
189    pub offset: i64,
190    pub leaps_inserted: i64,
191    pub is_leap_second: bool,
192}
193
194impl Dt {
195    #[inline]
196    pub const fn leap_seconds(&self, from_civil: bool) -> LeapInfo {
197        get_leap_seconds(self, from_civil)
198    }
199
200    #[inline]
201    pub const fn leap_seconds_using(&self, from_civil: bool, table: &[LeapSecond]) -> LeapInfo {
202        get_leap_seconds_with_table(self, from_civil, table)
203    }
204}
205
206#[inline]
207pub const fn get_leap_seconds(dt: &Dt, from_civil: bool) -> LeapInfo {
208    get_leap_seconds_with_table(dt, from_civil, LEAP_SECS)
209}
210
211pub const fn get_leap_seconds_with_table(
212    dt: &Dt,
213    from_civil: bool,
214    table: &[LeapSecond],
215) -> LeapInfo {
216    let len = table.len();
217    if len == 0 {
218        return LeapInfo {
219            offset: 0,
220            leaps_inserted: 0,
221            is_leap_second: false,
222        };
223    }
224
225    let target = dt.sec();
226
227    // Binary search for upper_bound: first index where entry_sec > target
228    let mut low = 0usize;
229    let mut high = len;
230
231    while low < high {
232        let mid = low + (high - low) / 2;
233        let entry_sec = if from_civil {
234            table[mid].utc_sec
235        } else {
236            table[mid].tai_sec
237        };
238
239        if entry_sec <= target {
240            low = mid + 1;
241        } else {
242            high = mid;
243        }
244    }
245
246    // low == first index with entry_sec > target (or len)
247    if low == 0 {
248        return LeapInfo {
249            offset: 0,
250            leaps_inserted: 0,
251            is_leap_second: false,
252        };
253    }
254
255    let idx = low - 1;
256    let entry = &table[idx];
257    let entry_sec = if from_civil {
258        entry.utc_sec
259    } else {
260        entry.tai_sec
261    };
262
263    let is_leap = target == entry_sec;
264
265    LeapInfo {
266        offset: entry.leap_seconds_after,
267        leaps_inserted: (idx + 1) as i64,
268        is_leap_second: is_leap,
269    }
270}