use super::timezones::Tz;
use binary_search::binary_search;
use chrono::{Duration, FixedOffset, LocalResult, NaiveDate, NaiveDateTime, Offset, TimeZone};
use core::cmp::Ordering;
use core::fmt::{Debug, Display, Error, Formatter};
#[derive(Copy, Clone, PartialEq, Eq)]
pub struct FixedTimespan {
pub utc_offset: i32,
pub dst_offset: i32,
pub name: &'static str,
}
impl Offset for FixedTimespan {
fn fix(&self) -> FixedOffset {
FixedOffset::east(self.utc_offset + self.dst_offset)
}
}
impl Display for FixedTimespan {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
write!(f, "{}", self.name)
}
}
impl Debug for FixedTimespan {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
write!(f, "{}", self.name)
}
}
#[derive(Copy, Clone, PartialEq, Eq)]
pub struct TzOffset {
tz: Tz,
offset: FixedTimespan,
}
pub trait OffsetComponents {
fn base_utc_offset(&self) -> Duration;
fn dst_offset(&self) -> Duration;
}
pub trait OffsetName {
fn tz_id(&self) -> &str;
fn abbreviation(&self) -> &str;
}
impl TzOffset {
fn new(tz: Tz, offset: FixedTimespan) -> Self {
TzOffset { tz, offset }
}
fn map_localresult(tz: Tz, result: LocalResult<FixedTimespan>) -> LocalResult<Self> {
match result {
LocalResult::None => LocalResult::None,
LocalResult::Single(s) => LocalResult::Single(TzOffset::new(tz, s)),
LocalResult::Ambiguous(a, b) => {
LocalResult::Ambiguous(TzOffset::new(tz, a), TzOffset::new(tz, b))
}
}
}
}
impl OffsetComponents for TzOffset {
fn base_utc_offset(&self) -> Duration {
Duration::seconds(self.offset.utc_offset as i64)
}
fn dst_offset(&self) -> Duration {
Duration::seconds(self.offset.dst_offset as i64)
}
}
impl OffsetName for TzOffset {
fn tz_id(&self) -> &str {
self.tz.name()
}
fn abbreviation(&self) -> &str {
self.offset.name
}
}
impl Offset for TzOffset {
fn fix(&self) -> FixedOffset {
self.offset.fix()
}
}
impl Display for TzOffset {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
Display::fmt(&self.offset, f)
}
}
impl Debug for TzOffset {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
Debug::fmt(&self.offset, f)
}
}
struct Span {
begin: Option<i64>,
end: Option<i64>,
}
impl Span {
fn contains(&self, x: i64) -> bool {
match (self.begin, self.end) {
(Some(a), Some(b)) if a <= x && x < b => true,
(Some(a), None) if a <= x => true,
(None, Some(b)) if b > x => true,
(None, None) => true,
_ => false,
}
}
fn cmp(&self, x: i64) -> Ordering {
match (self.begin, 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,
}
}
}
#[derive(Copy, Clone)]
pub struct FixedTimespanSet {
pub first: FixedTimespan,
pub rest: &'static [(i64, FixedTimespan)],
}
impl FixedTimespanSet {
fn len(&self) -> usize {
1 + self.rest.len()
}
fn utc_span(&self, index: usize) -> Span {
debug_assert!(index < self.len());
Span {
begin: if index == 0 { None } else { Some(self.rest[index - 1].0) },
end: if index == self.rest.len() { None } else { Some(self.rest[index].0) },
}
}
fn local_span(&self, index: usize) -> Span {
debug_assert!(index < self.len());
Span {
begin: if index == 0 {
None
} else {
let span = self.rest[index - 1];
Some(span.0 + span.1.utc_offset as i64 + span.1.dst_offset as i64)
},
end: if index == self.rest.len() {
None
} else if index == 0 {
Some(
self.rest[index].0
+ self.first.utc_offset as i64
+ self.first.dst_offset as i64,
)
} else {
Some(
self.rest[index].0
+ self.rest[index - 1].1.utc_offset as i64
+ self.rest[index - 1].1.dst_offset as i64,
)
},
}
}
fn get(&self, index: usize) -> FixedTimespan {
debug_assert!(index < self.len());
if index == 0 {
self.first
} else {
self.rest[index - 1].1
}
}
}
pub trait TimeSpans {
fn timespans(&self) -> FixedTimespanSet;
}
impl TimeZone for Tz {
type Offset = TzOffset;
fn from_offset(offset: &Self::Offset) -> Self {
offset.tz
}
fn offset_from_local_date(&self, local: &NaiveDate) -> LocalResult<Self::Offset> {
let earliest = self.offset_from_local_datetime(&local.and_hms(0, 0, 0));
let latest = self.offset_from_local_datetime(&local.and_hms(23, 59, 59));
use chrono::LocalResult::*;
match (earliest, latest) {
(result @ Single(_), _) => result,
(_, result @ Single(_)) => result,
(Ambiguous(offset, _), _) => Single(offset),
(_, Ambiguous(offset, _)) => Single(offset),
(None, None) => None,
}
}
fn offset_from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult<Self::Offset> {
let timestamp = local.timestamp();
let timespans = self.timespans();
let index = binary_search(0, timespans.len(), |i| timespans.local_span(i).cmp(timestamp));
TzOffset::map_localresult(
*self,
match index {
Ok(0) if timespans.len() == 1 => LocalResult::Single(timespans.get(0)),
Ok(0) if timespans.local_span(1).contains(timestamp) => {
LocalResult::Ambiguous(timespans.get(0), timespans.get(1))
}
Ok(0) => LocalResult::Single(timespans.get(0)),
Ok(i) if timespans.local_span(i - 1).contains(timestamp) => {
LocalResult::Ambiguous(timespans.get(i - 1), timespans.get(i))
}
Ok(i) if i == timespans.len() - 1 => LocalResult::Single(timespans.get(i)),
Ok(i) if timespans.local_span(i + 1).contains(timestamp) => {
LocalResult::Ambiguous(timespans.get(i), timespans.get(i + 1))
}
Ok(i) => LocalResult::Single(timespans.get(i)),
Err(_) => LocalResult::None,
},
)
}
fn offset_from_utc_date(&self, utc: &NaiveDate) -> Self::Offset {
self.offset_from_utc_datetime(&utc.and_hms(12, 0, 0))
}
fn offset_from_utc_datetime(&self, utc: &NaiveDateTime) -> Self::Offset {
let timestamp = utc.timestamp();
let timespans = self.timespans();
let index =
binary_search(0, timespans.len(), |i| timespans.utc_span(i).cmp(timestamp)).unwrap();
TzOffset::new(*self, timespans.get(index))
}
}