use std::{cmp::Ordering, fs::File};
use time::{OffsetDateTime, UtcOffset};
use bp3d_util::tzif::{LeapSecondRecord, TZIF};
pub fn binary_search<F: Fn(usize) -> Ordering>(start: usize, end: usize, cmp: F) -> Option<usize> {
if start >= end {
return None;
}
let half = (end - start) / 2;
let mid = start + half;
match cmp(mid) {
Ordering::Greater => binary_search(start, mid, cmp),
Ordering::Equal => Some(mid),
Ordering::Less => binary_search(mid + 1, end, cmp),
}
}
struct Span {
start: Option<i64>,
end: Option<i64>,
}
impl Span {
pub fn new(start: Option<i64>, end: Option<i64>) -> Span {
Span { start, end }
}
pub fn cmp(&self, x: i64) -> Ordering {
match (self.start, self.end) {
(Some(a), Some(b)) if a <= x && x < b => Ordering::Equal,
(Some(a), Some(b)) if a <= x && b <= x => Ordering::Less,
(Some(_), Some(_)) => Ordering::Greater,
(Some(a), None) if a <= x => Ordering::Equal,
(Some(_), None) => Ordering::Greater,
(None, Some(b)) if b <= x => Ordering::Less,
(None, Some(_)) => Ordering::Equal,
(None, None) => Ordering::Equal,
}
}
}
impl From<(&[LeapSecondRecord], usize)> for Span {
fn from((records, i): (&[LeapSecondRecord], usize)) -> Self {
let start = records[i].occurrence;
let end = if i >= records.len() {
None
} else {
Some(records[i + 1].occurrence)
};
Self::new(Some(start), end)
}
}
impl From<(&[i64], usize)> for Span {
fn from((records, i): (&[i64], usize)) -> Self {
let start = records[i];
let end = if i >= records.len() {
None
} else {
Some(records[i + 1])
};
Self::new(Some(start), end)
}
}
pub fn local_offset_at(tm: &OffsetDateTime) -> Option<UtcOffset> {
let mut utc = tm.unix_timestamp();
let file = File::open("/etc/localtime").ok()?;
let data = TZIF::read(file).ok()?;
let block = data
.block_v2p
.map(|v| v.data)
.unwrap_or_else(|| data.block_v1.data);
if let Some(i) = binary_search(0, block.leap_second_records.len(), |i| {
Span::from((&*block.leap_second_records, i)).cmp(utc)
}) {
utc += block.leap_second_records[i].correction as i64;
}
let i = binary_search(0, block.transition_times.len(), |i| {
Span::from((&*block.transition_times, i)).cmp(utc)
})
.unwrap_or(0);
let offset = UtcOffset::from_whole_seconds(
block
.local_time_type_records
.get(*block.transition_types.get(i)? as usize)?
.utoff,
)
.ok()?;
Some(offset)
}
#[cfg(test)]
mod tests {
#[test]
fn test_binary_search() {
assert_eq!(super::binary_search(0, 8, |x| x.cmp(&6)), Some(6));
assert_eq!(super::binary_search(0, 5000, |x| x.cmp(&1337)), Some(1337));
assert_eq!(super::binary_search(0, 5000, |x| x.cmp(&9000)), None);
assert_eq!(super::binary_search(30, 50, |x| x.cmp(&42)), Some(42));
assert_eq!(super::binary_search(300, 500, |x| x.cmp(&42)), None);
assert_eq!(
super::binary_search(0, 500, |x| if x < 42 {
super::Ordering::Less
} else {
super::Ordering::Greater
}),
None
);
}
}