use chrono::{Offset, TimeZone, NaiveDate, NaiveDateTime, LocalResult, FixedOffset};
use std::fmt::{Debug, Display, Formatter, Error};
use std::cmp::Ordering;
use binary_search::binary_search;
use super::timezones::Tz;
#[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,
}
impl TzOffset {
fn new(tz: Tz, offset: FixedTimespan) -> Self {
TzOffset {
tz: tz,
offset: 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 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))
}
}