1use crate::calendar_dates::Date;
18use crate::deltas::{TimeDelta, ToDelta};
19use crate::time::Time;
20use crate::time_of_day::CivilTime;
21use crate::time_scales::Tai;
22use crate::utc::Utc;
23use lox_core::i64::consts::SECONDS_PER_DAY;
24
25pub const LEAP_SECOND_EPOCHS_UTC: [i64; 28] = [
27 -883656000, -867931200, -852033600, -820497600, -788961600, -757425600, -725803200, -694267200,
28 -662731200, -631195200, -583934400, -552398400, -520862400, -457704000, -378734400, -315576000,
29 -284040000, -236779200, -205243200, -173707200, -126273600, -79012800, -31579200, 189345600,
30 284040000, 394372800, 488980800, 536500800,
31];
32
33pub const LEAP_SECOND_EPOCHS_TAI: [i64; 28] = [
35 -883655991, -867931190, -852033589, -820497588, -788961587, -757425586, -725803185, -694267184,
36 -662731183, -631195182, -583934381, -552398380, -520862379, -457703978, -378734377, -315575976,
37 -284039975, -236779174, -205243173, -173707172, -126273571, -79012770, -31579169, 189345632,
38 284040033, 394372834, 488980835, 536500836,
39];
40
41pub const LEAP_SECONDS: [i64; 28] = [
43 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33,
44 34, 35, 36, 37,
45];
46
47pub trait LeapSecondsProvider {
50 fn delta_tai_utc(&self, tai: Time<Tai>) -> TimeDelta {
52 find_leap_seconds_tai(&LEAP_SECOND_EPOCHS_TAI, &LEAP_SECONDS, tai)
53 }
54
55 fn delta_utc_tai(&self, utc: Utc) -> TimeDelta {
57 find_leap_seconds_utc(&LEAP_SECOND_EPOCHS_UTC, &LEAP_SECONDS, utc)
58 }
59
60 fn is_leap_second_date(&self, date: Date) -> bool {
62 is_leap_second_date(&LEAP_SECOND_EPOCHS_UTC, date)
63 }
64
65 fn is_leap_second(&self, tai: Time<Tai>) -> bool {
67 is_leap_second(&LEAP_SECOND_EPOCHS_TAI, tai)
68 }
69}
70
71#[derive(Debug)]
78pub struct DefaultLeapSecondsProvider;
79
80impl LeapSecondsProvider for DefaultLeapSecondsProvider {}
81
82fn find_leap_seconds(epochs: &[i64], leap_seconds: &[i64], seconds: i64) -> TimeDelta {
83 if seconds < epochs[0] {
84 return TimeDelta::ZERO;
85 }
86 let idx = epochs.partition_point(|&epoch| epoch <= seconds) - 1;
87 let seconds = leap_seconds[idx];
88 TimeDelta::from_seconds(seconds)
89}
90
91pub fn find_leap_seconds_tai(epochs: &[i64], leap_seconds: &[i64], tai: Time<Tai>) -> TimeDelta {
93 let seconds = tai.seconds().expect("TAI time should have finite seconds");
94 find_leap_seconds(epochs, leap_seconds, seconds)
95}
96
97pub fn find_leap_seconds_utc(epochs: &[i64], leap_seconds: &[i64], utc: Utc) -> TimeDelta {
99 let seconds = utc
100 .to_delta()
101 .seconds()
102 .expect("UTC time should have finite seconds");
103 let mut ls = find_leap_seconds(epochs, leap_seconds, seconds);
104 if utc.second() == 60 {
105 ls -= TimeDelta::from_seconds(1);
106 }
107 -ls
108}
109
110pub fn is_leap_second_date(epochs: &[i64], date: Date) -> bool {
112 let epochs: Vec<i64> = epochs
113 .iter()
114 .map(|&epoch| epoch / SECONDS_PER_DAY)
115 .collect();
116 let day_number = date.j2000_day_number();
117 epochs.binary_search(&day_number).is_ok()
118}
119
120pub fn is_leap_second(epochs: &[i64], tai: Time<Tai>) -> bool {
122 match tai.seconds() {
123 Some(seconds) => epochs.binary_search(&seconds).is_ok(),
124 None => false,
125 }
126}
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131 use rstest::rstest;
132
133 use crate::deltas::TimeDelta;
134 use crate::time;
135 use crate::time::Time;
136 use crate::time_scales::Tai;
137 use crate::utc;
138 use crate::utc::LeapSecondsProvider;
139 use crate::utc::Utc;
140
141 #[rstest]
142 #[case::j2000(Time::default(), Utc::default(), 32)]
143 #[case::new_year_1972(time!(Tai, 1972, 1, 1, 0, 0, 10.0).unwrap(), utc!(1972, 1, 1).unwrap(), 10)]
144 #[case::new_year_2017(time!(Tai, 2017, 1, 1, 0, 0, 37.0).unwrap(), utc!(2017, 1, 1, 0, 0, 0.0).unwrap(), 37)]
145 #[case::new_year_2024(time!(Tai, 2024, 1, 1).unwrap(), utc!(2024, 1, 1).unwrap(), 37)]
146 fn test_builtin_leap_seconds(#[case] tai: Time<Tai>, #[case] utc: Utc, #[case] expected: i64) {
147 let ls_tai = DefaultLeapSecondsProvider.delta_tai_utc(tai);
148 let ls_utc = DefaultLeapSecondsProvider.delta_utc_tai(utc);
149 assert_eq!(ls_tai, TimeDelta::from_seconds(expected));
150 assert_eq!(ls_utc, TimeDelta::from_seconds(-expected));
151 }
152
153 #[rstest]
154 #[case(Date::new(2000, 12, 31).unwrap(), false)]
155 #[case(Date::new(2016, 12, 31).unwrap(), true)]
156 fn test_is_leap_second_date(#[case] date: Date, #[case] expected: bool) {
157 let actual = DefaultLeapSecondsProvider.is_leap_second_date(date);
158 assert_eq!(actual, expected);
159 }
160
161 #[rstest]
162 #[case(time!(Tai, 2017, 1, 1, 0, 0, 35.0).unwrap(), false)]
163 #[case(time!(Tai, 2017, 1, 1, 0, 0, 36.0).unwrap(), true)]
164 fn test_is_leap_second(#[case] tai: Time<Tai>, #[case] expected: bool) {
165 let actual = DefaultLeapSecondsProvider.is_leap_second(tai);
166 assert_eq!(actual, expected);
167 }
168}