1use crate::Dt;
8
9#[cfg(feature = "std")]
10use std::{fs, io, path::Path};
11
12#[cfg(feature = "alloc")]
13use alloc::vec::Vec;
14
15#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
16pub struct LeapSec {
17 pub ntp_timestamp: i64,
18 pub leap_sec_after: i64,
19 pub utc_sec: i64,
20 pub tai_sec: i64,
21}
22
23pub const LEAP_SECS: &[LeapSec] = &[
24 LeapSec {
25 ntp_timestamp: 2272060800,
26 leap_sec_after: 10,
27 utc_sec: -883656000,
28 tai_sec: -883655991, }, LeapSec {
31 ntp_timestamp: 2287785600,
32 leap_sec_after: 11,
33 utc_sec: -867931200,
34 tai_sec: -867931190, }, LeapSec {
37 ntp_timestamp: 2303683200,
38 leap_sec_after: 12,
39 utc_sec: -852033600,
40 tai_sec: -852033589, }, LeapSec {
43 ntp_timestamp: 2335219200,
44 leap_sec_after: 13,
45 utc_sec: -820497600,
46 tai_sec: -820497588, }, LeapSec {
49 ntp_timestamp: 2366755200,
50 leap_sec_after: 14,
51 utc_sec: -788961600,
52 tai_sec: -788961587, }, LeapSec {
55 ntp_timestamp: 2398291200,
56 leap_sec_after: 15,
57 utc_sec: -757425600,
58 tai_sec: -757425586, }, LeapSec {
61 ntp_timestamp: 2429913600,
62 leap_sec_after: 16,
63 utc_sec: -725803200,
64 tai_sec: -725803185, }, LeapSec {
67 ntp_timestamp: 2461449600,
68 leap_sec_after: 17,
69 utc_sec: -694267200,
70 tai_sec: -694267184, }, LeapSec {
73 ntp_timestamp: 2492985600,
74 leap_sec_after: 18,
75 utc_sec: -662731200,
76 tai_sec: -662731183, }, LeapSec {
79 ntp_timestamp: 2524521600,
80 leap_sec_after: 19,
81 utc_sec: -631195200,
82 tai_sec: -631195182, }, LeapSec {
85 ntp_timestamp: 2571782400,
86 leap_sec_after: 20,
87 utc_sec: -583934400,
88 tai_sec: -583934381, }, LeapSec {
91 ntp_timestamp: 2603318400,
92 leap_sec_after: 21,
93 utc_sec: -552398400,
94 tai_sec: -552398380, }, LeapSec {
97 ntp_timestamp: 2634854400,
98 leap_sec_after: 22,
99 utc_sec: -520862400,
100 tai_sec: -520862379, }, LeapSec {
103 ntp_timestamp: 2698012800,
104 leap_sec_after: 23,
105 utc_sec: -457704000,
106 tai_sec: -457703978, }, LeapSec {
109 ntp_timestamp: 2776982400,
110 leap_sec_after: 24,
111 utc_sec: -378734400,
112 tai_sec: -378734377, }, LeapSec {
115 ntp_timestamp: 2840140800,
116 leap_sec_after: 25,
117 utc_sec: -315576000,
118 tai_sec: -315575976, }, LeapSec {
121 ntp_timestamp: 2871676800,
122 leap_sec_after: 26,
123 utc_sec: -284040000,
124 tai_sec: -284039975, }, LeapSec {
127 ntp_timestamp: 2918937600,
128 leap_sec_after: 27,
129 utc_sec: -236779200,
130 tai_sec: -236779174, }, LeapSec {
133 ntp_timestamp: 2950473600,
134 leap_sec_after: 28,
135 utc_sec: -205243200,
136 tai_sec: -205243173, }, LeapSec {
139 ntp_timestamp: 2982009600,
140 leap_sec_after: 29,
141 utc_sec: -173707200,
142 tai_sec: -173707172, }, LeapSec {
145 ntp_timestamp: 3029443200,
146 leap_sec_after: 30,
147 utc_sec: -126273600,
148 tai_sec: -126273571, }, LeapSec {
151 ntp_timestamp: 3076704000,
152 leap_sec_after: 31,
153 utc_sec: -79012800,
154 tai_sec: -79012770, }, LeapSec {
157 ntp_timestamp: 3124137600,
158 leap_sec_after: 32,
159 utc_sec: -31579200,
160 tai_sec: -31579169, }, LeapSec {
163 ntp_timestamp: 3345062400,
164 leap_sec_after: 33,
165 utc_sec: 189345600,
166 tai_sec: 189345632, }, LeapSec {
169 ntp_timestamp: 3439756800,
170 leap_sec_after: 34,
171 utc_sec: 284040000,
172 tai_sec: 284040033, }, LeapSec {
175 ntp_timestamp: 3550089600,
176 leap_sec_after: 35,
177 utc_sec: 394372800,
178 tai_sec: 394372834, }, LeapSec {
181 ntp_timestamp: 3644697600,
182 leap_sec_after: 36,
183 utc_sec: 488980800,
184 tai_sec: 488980835, }, LeapSec {
187 ntp_timestamp: 3692217600,
188 leap_sec_after: 37,
189 utc_sec: 536500800,
190 tai_sec: 536500836, }, ];
193
194#[derive(Copy, Clone, Debug)]
195pub struct LeapInfo {
196 pub offset: i64,
197 pub leaps_inserted: i64,
198 pub is_leap_sec: bool,
199}
200
201impl Dt {
202 #[inline(always)]
206 pub const fn leap_sec(&self, is_utc: bool) -> Option<LeapInfo> {
207 leap_sec(self.to_sec64(), is_utc)
208 }
209
210 #[inline(always)]
212 pub const fn leap_sec_using(&self, is_utc: bool, table: &[LeapSec]) -> Option<LeapInfo> {
213 leap_sec_using(self.to_sec64(), is_utc, table)
214 }
215}
216
217#[inline(always)]
218pub const fn leap_sec(target: i64, is_utc: bool) -> Option<LeapInfo> {
219 leap_sec_using(target, is_utc, LEAP_SECS)
220}
221
222pub const fn leap_sec_using(target: i64, is_utc: bool, table: &[LeapSec]) -> Option<LeapInfo> {
223 let len = table.len();
224 if len == 0 {
225 return None;
226 }
227
228 let mut low = 0usize;
230 let mut high = len;
231 if is_utc {
232 while low < high {
233 let mid = low + (high - low) / 2;
234 if table[mid].utc_sec <= target {
235 low = mid + 1;
236 } else {
237 high = mid;
238 }
239 }
240 } else {
241 while low < high {
242 let mid = low + (high - low) / 2;
243 if table[mid].tai_sec <= target {
244 low = mid + 1;
245 } else {
246 high = mid;
247 }
248 }
249 }
250
251 if low == 0 {
253 return None;
254 }
255
256 let idx = low - 1;
257 let entry = &table[idx];
258 let entry_sec = if is_utc { entry.utc_sec } else { entry.tai_sec };
259 let is_leap = target == entry_sec;
260
261 Some(LeapInfo {
262 offset: entry.leap_sec_after,
263 leaps_inserted: low as i64,
264 is_leap_sec: is_leap,
265 })
266}
267
268#[cfg(feature = "std")]
269impl Dt {
270 #[inline]
289 pub fn leap_sec_data_from_file<P: AsRef<Path>>(path: P) -> io::Result<Vec<LeapSec>> {
290 let content = fs::read_to_string(path)?;
291 Ok(Self::leap_sec_data_from_str(&content))
292 }
293}
294
295#[cfg(feature = "alloc")]
296impl Dt {
297 pub fn leap_sec_data_from_str(s: &str) -> Vec<LeapSec> {
322 use crate::Scale;
323
324 let mut table = Vec::new();
325 let mut prev_leap_sec_after: i64 = 0;
326
327 for line in s.lines() {
328 let trimmed = line.trim();
329
330 if trimmed.is_empty() || trimmed.starts_with("#") {
331 continue;
332 }
333
334 let parts: Vec<&str> = trimmed.split_whitespace().collect();
335
336 if parts.len() < 2 {
337 continue;
338 }
339 let Ok(ntp_timestamp) = parts[0].parse::<i64>() else {
340 continue;
341 };
342 let Ok(leap_sec_after) = parts[1].parse::<i64>() else {
343 continue;
344 };
345
346 let utc_sec = Dt::from_ntp(Dt::from_sec(ntp_timestamp as i128, Scale::TAI)).to_sec64();
348
349 let tai_sec = if prev_leap_sec_after == 0 {
350 utc_sec + leap_sec_after - 1
351 } else {
352 utc_sec + leap_sec_after - (leap_sec_after - prev_leap_sec_after)
353 };
354
355 table.push(LeapSec {
356 ntp_timestamp,
357 leap_sec_after,
358 utc_sec,
359 tai_sec,
360 });
361
362 prev_leap_sec_after = leap_sec_after;
363 }
364
365 table
366 }
367}