use crate::utc::leap_seconds_list::{LEAP_SECS, LeapSec};
use crate::{Dt, Scale};
#[cfg(feature = "std")]
use std::{fs, io, path::Path};
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub enum IsLeapSec {
#[default]
None,
Add,
Sub,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct LeapInfo {
pub offset: i64,
pub n_entries_at_or_before: usize,
pub is_leap_sec: IsLeapSec,
}
impl Dt {
pub const fn leap_sec_using_sec64_and_list(
sec64: i64,
is_utc: bool,
list: &[LeapSec],
) -> Option<LeapInfo> {
let len = list.len();
if len == 0 {
return None;
}
let mut low = 0usize;
let mut high = len;
if is_utc {
while low < high {
let mid = low + (high - low) / 2;
if list[mid].utc_sec <= sec64 {
low = mid + 1;
} else {
high = mid;
}
}
} else {
while low < high {
let mid = low + (high - low) / 2;
if list[mid].tai_sec <= sec64 {
low = mid + 1;
} else {
high = mid;
}
}
}
if low == 0 {
return None;
}
let idx = low - 1;
let entry = &list[idx];
let is_leap = {
if sec64 != if is_utc { entry.utc_sec } else { entry.tai_sec } {
IsLeapSec::None
} else if idx != 0 {
let prev_leap_sec_after = list[idx - 1].leap_sec_after;
if entry.leap_sec_after > prev_leap_sec_after {
IsLeapSec::Add
} else if entry.leap_sec_after < prev_leap_sec_after {
IsLeapSec::Sub
} else {
IsLeapSec::None
}
} else if entry.leap_sec_after > 0 {
IsLeapSec::Add
} else if entry.leap_sec_after < 0 {
IsLeapSec::Sub
} else {
IsLeapSec::None
}
};
Some(LeapInfo {
offset: entry.leap_sec_after,
n_entries_at_or_before: low,
is_leap_sec: is_leap,
})
}
#[inline(always)]
pub const fn leap_sec_using_sec64(sec64: i64, is_utc: bool) -> Option<LeapInfo> {
Self::leap_sec_using_sec64_and_list(sec64, is_utc, LEAP_SECS)
}
#[inline(always)]
pub const fn leap_sec(&self, is_utc: bool) -> Option<LeapInfo> {
Self::leap_sec_using_sec64_and_list(self.to_sec64(), is_utc, LEAP_SECS)
}
#[inline(always)]
pub const fn leap_sec_using_list(&self, is_utc: bool, list: &[LeapSec]) -> Option<LeapInfo> {
Self::leap_sec_using_sec64_and_list(self.to_sec64(), is_utc, list)
}
#[inline(always)]
pub(crate) const fn utc_to_tai_using_list(&self, list: &[LeapSec]) -> Option<Dt> {
match self.leap_sec_using_list(true, list) {
Some(info) => Some(self.add_sec(info.offset as i128)),
None => None,
}
}
#[inline(always)]
pub(crate) const fn tai_to_utc_using_list(&self, list: &[LeapSec]) -> Option<Dt> {
match self.leap_sec_using_list(false, list) {
Some(info) => Some(self.add_sec(-info.offset as i128)),
None => None,
}
}
pub const fn to_tai_from_utc_using_list(&self, list: &[LeapSec]) -> Dt {
match self.scale {
Scale::UTC | Scale::UtcHist | Scale::UtcSpice => {
match self.utc_to_tai_using_list(list) {
Some(dt) => dt.with(Scale::TAI),
None => match self.scale {
Scale::UtcHist => match self.historical_utc_offset() {
Some(offset) => self.add(Dt::span_f(offset)).with(Scale::TAI),
None => self.with(Scale::TAI),
},
Scale::UtcSpice => self.add_sec(9).with(Scale::TAI),
_ => self.with(Scale::TAI),
},
}
}
_ => self.to(Scale::TAI),
}
}
pub const fn to_utc_from_tai_using_list(&self, new: Scale, list: &[LeapSec]) -> Dt {
match new {
Scale::UTC | Scale::UtcHist | Scale::UtcSpice => {
match self.tai_to_utc_using_list(list) {
Some(dt) => dt.with(new),
None => match new {
Scale::UtcHist => match self.historical_utc_offset() {
Some(offset) => self.sub(Dt::span_f(offset)).with(new),
None => self.with(new),
},
Scale::UtcSpice => self.add_sec(-9).with(new),
_ => self.with(new),
},
}
}
_ => self.to(new),
}
}
}
#[cfg(feature = "alloc")]
impl Dt {
pub fn leap_sec_list_from_str(s: &str) -> Vec<LeapSec> {
use crate::Scale;
let mut list = Vec::new();
let mut prev_leap_sec_after: i64 = 0;
let mut entries_pushed: usize = 0;
for line in s.lines() {
let trimmed = line.trim();
if trimmed.is_empty() || trimmed.starts_with("#") {
continue;
}
let mut parts = trimmed.split_whitespace();
let ntp_timestamp = if let Some(num) = parts.next() {
match num.parse::<i64>() {
Ok(n) => n,
Err(_) => continue,
}
} else {
continue;
};
let leap_sec_after = if let Some(num) = parts.next() {
match num.parse::<i64>() {
Ok(n) => n,
Err(_) => continue,
}
} else {
continue;
};
let utc_sec = Dt::from_ntp(Dt::from_sec(ntp_timestamp as i128, Scale::TAI)).to_sec64();
let tai_sec = if entries_pushed == 0 {
if leap_sec_after > 0 {
utc_sec + leap_sec_after - 1
} else {
utc_sec + leap_sec_after
}
} else {
utc_sec + prev_leap_sec_after
};
list.push(LeapSec {
ntp_timestamp,
leap_sec_after,
utc_sec,
tai_sec,
});
prev_leap_sec_after = leap_sec_after;
entries_pushed += 1;
}
list
}
}
#[cfg(feature = "std")]
impl Dt {
#[inline]
pub fn leap_sec_list_from_file<P: AsRef<Path>>(path: P) -> io::Result<Vec<LeapSec>> {
let content = fs::read_to_string(path)?;
Ok(Self::leap_sec_list_from_str(&content))
}
}