use crate::constants::{
LEAP_SECOND_TABLE, LEAP_SECOND_TABLE_VALID_UNTIL_UNIX_S,
};
use std::sync::atomic::{AtomicBool, Ordering};
static STALE_WARNED: AtomicBool = AtomicBool::new(false);
fn warn_if_stale(utc_unix_seconds: i64) {
if utc_unix_seconds <= LEAP_SECOND_TABLE_VALID_UNTIL_UNIX_S {
return;
}
if STALE_WARNED.swap(true, Ordering::Relaxed) {
return;
}
eprintln!(
"[brightdate] Leap-second table may be stale (queried Unix s={}). \
TAI calculations could be off by ±1 s if a new leap second was \
inserted after the table was reviewed. Update LEAP_SECOND_TABLE \
from IERS Bulletin C if you need authoritative TAI past that date.",
utc_unix_seconds
);
}
pub fn get_tai_utc_offset(utc_unix_seconds: i64) -> i32 {
warn_if_stale(utc_unix_seconds);
if LEAP_SECOND_TABLE.is_empty() || utc_unix_seconds < LEAP_SECOND_TABLE[0].0 {
return 10;
}
let mut lo = 0usize;
let mut hi = LEAP_SECOND_TABLE.len() - 1;
while lo < hi {
let mid = lo + (hi - lo).div_ceil(2);
if LEAP_SECOND_TABLE[mid].0 <= utc_unix_seconds {
lo = mid;
} else {
hi = mid - 1;
}
}
LEAP_SECOND_TABLE[lo].1
}
pub fn utc_to_tai(utc_unix_seconds: i64) -> i64 {
utc_unix_seconds + get_tai_utc_offset(utc_unix_seconds) as i64
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TaiToUtc {
pub utc_unix_seconds: i64,
pub is_leap_second: bool,
}
pub fn tai_to_utc_full(tai_unix_seconds: i64) -> TaiToUtc {
let probe_utc = tai_unix_seconds - 37;
let offset_at_probe = get_tai_utc_offset(probe_utc);
let candidate_utc = tai_unix_seconds - offset_at_probe as i64;
let offset_at_candidate = get_tai_utc_offset(candidate_utc);
if offset_at_candidate == offset_at_probe {
return TaiToUtc {
utc_unix_seconds: candidate_utc,
is_leap_second: false,
};
}
let new_offset = offset_at_candidate as i64;
let utc_under_new = tai_unix_seconds - new_offset;
let utc_under_old = tai_unix_seconds - offset_at_probe as i64;
if utc_under_old == utc_under_new - 1 {
TaiToUtc {
utc_unix_seconds: utc_under_new - 1,
is_leap_second: true,
}
} else {
TaiToUtc {
utc_unix_seconds: utc_under_new,
is_leap_second: false,
}
}
}
pub fn tai_to_utc(tai_unix_seconds: i64) -> i64 {
tai_to_utc_full(tai_unix_seconds).utc_unix_seconds
}
pub fn get_tai_utc_offset_at_j2000() -> i32 {
get_tai_utc_offset(946_728_000)
}
pub fn is_during_leap_second(utc_unix_seconds: i64) -> bool {
get_tai_utc_offset(utc_unix_seconds + 1) > get_tai_utc_offset(utc_unix_seconds)
}
pub fn leap_seconds_between(from: i64, to: i64) -> i32 {
let a = get_tai_utc_offset(from);
let b = get_tai_utc_offset(to);
(a - b).abs()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn offset_at_j2000() {
assert_eq!(get_tai_utc_offset(946_728_000), 32);
}
#[test]
fn offset_after_2017() {
assert_eq!(get_tai_utc_offset(1_592_179_200), 37);
}
#[test]
fn pre_table_returns_10() {
assert_eq!(get_tai_utc_offset(0), 10);
}
}