use std::borrow::Cow;
use std::sync::Arc;
use duration::Duration;
use instant::Instant;
use cal::{LocalDateTime, DatePiece, TimePiece, Month, Weekday};
use util::RangeExt;
#[derive(Debug, Clone)]
pub struct TimeZone(pub TimeZoneSource<'static>);
#[derive(Debug, Clone)]
pub enum TimeZoneSource<'a> {
Static(&'a StaticTimeZone<'a>),
Runtime(Arc<runtime::OwnedTimeZone>),
}
#[derive(PartialEq, Debug)]
pub struct StaticTimeZone<'a> {
pub name: &'a str,
pub fixed_timespans: FixedTimespanSet<'a>,
}
impl TimeZone {
pub fn zone_name(&self) -> Option<&str> {
match self.0 {
TimeZoneSource::Static(ref tz) => Some(tz.name),
TimeZoneSource::Runtime(ref arc) => arc.name.as_ref().map(|x| &**x),
}
}
pub fn offset(&self, datetime: LocalDateTime) -> i64 {
match self.0 {
TimeZoneSource::Static(ref tz) => tz.fixed_timespans.offset(datetime),
TimeZoneSource::Runtime(ref arc) => arc.fixed_timespans.borrow().offset(datetime),
}
}
pub fn name(&self, datetime: LocalDateTime) -> String {
match self.0 {
TimeZoneSource::Static(ref tz) => tz.fixed_timespans.name(datetime),
TimeZoneSource::Runtime(ref arc) => arc.fixed_timespans.borrow().name(datetime),
}
}
pub fn is_fixed(&self) -> bool {
match self.0 {
TimeZoneSource::Static(ref tz) => tz.fixed_timespans.is_fixed(),
TimeZoneSource::Runtime(ref arc) => arc.fixed_timespans.borrow().is_fixed(),
}
}
pub fn to_zoned(&self, datetime: LocalDateTime) -> LocalDateTime {
datetime + Duration::of(self.offset(datetime))
}
pub fn convert_local(&self, local: LocalDateTime) -> LocalTimes {
match self.0 {
TimeZoneSource::Static(ref tz) => tz.fixed_timespans.convert_local(local, &self.0),
TimeZoneSource::Runtime(ref arc) => arc.fixed_timespans.borrow().convert_local(local, &self.0),
}
}
}
#[derive(PartialEq, Debug, Clone)]
pub struct FixedTimespanSet<'a> {
pub first: FixedTimespan<'a>,
pub rest: &'a [ (i64, FixedTimespan<'a>) ],
}
#[derive(PartialEq, Debug, Clone)]
pub struct FixedTimespan<'a> {
pub offset: i64,
pub is_dst: bool,
pub name: Cow<'a, str>,
}
impl<'a> FixedTimespanSet<'a> {
fn find(&self, time: i64) -> &FixedTimespan {
match self.rest.iter().take_while(|t| t.0 < time).last() {
None => &self.first,
Some(zd) => &zd.1,
}
}
fn offset(&self, datetime: LocalDateTime) -> i64 {
let unix_timestamp = datetime.to_instant().seconds();
self.find(unix_timestamp).offset
}
fn name(&self, datetime: LocalDateTime) -> String {
let unix_timestamp = datetime.to_instant().seconds();
self.find(unix_timestamp).name.to_string()
}
fn is_fixed(&self) -> bool {
self.rest.is_empty()
}
fn convert_local(&self, local: LocalDateTime, source: &TimeZoneSource<'a>) -> LocalTimes<'a> {
let unix_timestamp = local.to_instant().seconds();
let zonify = |offset| ZonedDateTime {
adjusted: local,
current_offset: offset,
time_zone: source.clone(),
};
let timespans = self.find_with_surroundings(unix_timestamp);
if let Some((previous_zone, previous_transition_time)) = timespans.previous {
assert!(timespans.current.offset != previous_zone.offset,
"Offsets cannot be equal! Is this a non-transition transition?");
println!("unix timestamp {:?}, previous time {:?}", unix_timestamp, previous_transition_time);
if previous_zone.offset > timespans.current.offset
&& (unix_timestamp - previous_transition_time).is_within(timespans.current.offset .. previous_zone.offset) {
return LocalTimes::Ambiguous {
earlier: zonify(previous_zone.offset),
later: zonify(timespans.current.offset),
};
}
if previous_zone.offset < timespans.current.offset
&& (unix_timestamp - previous_transition_time).is_within(previous_zone.offset .. timespans.current.offset) {
return LocalTimes::Impossible;
}
}
if let Some(&(next_transition_time, ref next_zone)) = timespans.next {
assert!(timespans.current.offset != next_zone.offset,
"Offsets cannot be equal! Is this a non-transition transition?");
println!("unix timestamp {:?}, next time {:?}", unix_timestamp, next_transition_time);
println!("offset 1 {:?}, offset 2 {:?}", next_zone.offset, timespans.current.offset);
if timespans.current.offset > next_zone.offset
&& (unix_timestamp - next_transition_time).is_within(next_zone.offset .. timespans.current.offset) {
return LocalTimes::Ambiguous {
earlier: zonify(timespans.current.offset),
later: zonify(next_zone.offset),
};
}
if timespans.current.offset < next_zone.offset
&& (unix_timestamp - next_transition_time).is_within(timespans.current.offset .. next_zone.offset) {
return LocalTimes::Impossible;
}
}
LocalTimes::Precise(zonify(timespans.current.offset))
}
fn find_with_surroundings(&self, time: i64) -> Surroundings {
if let Some((position, _)) = self.rest.iter().enumerate().take_while(|&(_, t)| t.0 < time).last() {
let previous_details = if position == 0 {
&self.first
}
else {
&self.rest[position - 1].1
};
Surroundings {
previous: Some((previous_details, self.rest[position].0)),
current: &self.rest[position].1,
next: self.rest.get(position + 1),
}
}
else {
Surroundings {
previous: None,
current: &self.first,
next: self.rest.get(0),
}
}
}
}
#[derive(PartialEq, Debug)]
struct Surroundings<'a> {
previous: Option<(&'a FixedTimespan<'a>, i64)>,
current: &'a FixedTimespan<'a>,
next: Option<&'a (i64, FixedTimespan<'a>)>,
}
#[derive(Debug)]
pub enum LocalTimes<'a> {
Impossible,
Precise(ZonedDateTime<'a>),
Ambiguous { earlier: ZonedDateTime<'a>, later: ZonedDateTime<'a> },
}
impl<'a> LocalTimes<'a> {
pub fn unwrap_precise(self) -> ZonedDateTime<'a> {
match self {
LocalTimes::Precise(p) => p,
LocalTimes::Impossible => panic!("called `LocalTimes::unwrap()` on an `Impossible` value"),
LocalTimes::Ambiguous { .. } => panic!("called `LocalTimes::unwrap()` on an `Ambiguous` value: {:?}", self),
}
}
pub fn is_impossible(&self) -> bool {
match *self {
LocalTimes::Impossible => true,
_ => false,
}
}
pub fn is_ambiguous(&self) -> bool {
match *self {
LocalTimes::Ambiguous { .. } => true,
_ => false,
}
}
}
#[derive(Debug)]
pub struct ZonedDateTime<'a> {
adjusted: LocalDateTime,
current_offset: i64,
time_zone: TimeZoneSource<'a>,
}
impl<'a> ZonedDateTime<'a> {
pub fn to_instant(&self) -> Instant {
(self.adjusted - Duration::of(self.current_offset)).to_instant()
}
}
impl<'a> DatePiece for ZonedDateTime<'a> {
fn year(&self) -> i64 { self.adjusted.year() }
fn month(&self) -> Month { self.adjusted.month() }
fn day(&self) -> i8 { self.adjusted.day() }
fn yearday(&self) -> i16 { self.adjusted.yearday() }
fn weekday(&self) -> Weekday { self.adjusted.weekday() }
}
impl<'a> TimePiece for ZonedDateTime<'a> {
fn hour(&self) -> i8 { self.adjusted.hour() }
fn minute(&self) -> i8 { self.adjusted.minute() }
fn second(&self) -> i8 { self.adjusted.second() }
fn millisecond(&self) -> i16 { self.adjusted.millisecond() }
}
#[derive(PartialEq, Debug, Copy, Clone)]
pub enum TimeType {
Wall,
Standard,
UTC,
}
pub mod runtime {
use super::{FixedTimespan, FixedTimespanSet};
#[derive(PartialEq, Debug)]
pub struct OwnedTimeZone {
pub name: Option<String>,
pub fixed_timespans: OwnedFixedTimespanSet,
}
#[derive(PartialEq, Debug)]
pub struct OwnedFixedTimespanSet {
pub first: FixedTimespan<'static>,
pub rest: Vec<(i64, FixedTimespan<'static>)>,
}
impl OwnedFixedTimespanSet {
pub fn borrow(&self) -> FixedTimespanSet {
FixedTimespanSet {
first: self.first.clone(),
rest: &*self.rest,
}
}
}
}
#[cfg(test)]
mod test {
use super::*;
use super::Surroundings;
use std::borrow::Cow;
const NONE: FixedTimespanSet<'static> = FixedTimespanSet {
first: FixedTimespan {
offset: 0,
is_dst: false,
name: Cow::Borrowed("ZONE_A"),
},
rest: &[],
};
#[test]
fn empty() {
assert_eq!(NONE.find_with_surroundings(1184000000), Surroundings {
previous: None,
current: &FixedTimespan {
offset: 0,
is_dst: false,
name: Cow::Borrowed("ZONE_A"),
},
next: None,
})
}
const ONE: FixedTimespanSet<'static> = FixedTimespanSet {
first: FixedTimespan {
offset: 0,
is_dst: false,
name: Cow::Borrowed("ZONE_A"),
},
rest: &[
(1174784400, FixedTimespan {
offset: 3600,
is_dst: false,
name: Cow::Borrowed("ZONE_B"),
}),
],
};
#[test]
fn just_one_first() {
assert_eq!(ONE.find_with_surroundings(1184000000), Surroundings {
previous: Some((
&FixedTimespan {
offset: 0,
is_dst: false,
name: Cow::Borrowed("ZONE_A"),
},
1174784400,
)),
current: &FixedTimespan {
offset: 3600,
is_dst: false,
name: Cow::Borrowed("ZONE_B"),
},
next: None,
});
}
#[test]
fn just_one_other() {
assert_eq!(ONE.find_with_surroundings(1174000000), Surroundings {
previous: None,
current: &FixedTimespan {
offset: 0,
is_dst: false,
name: Cow::Borrowed("ZONE_A"),
},
next: Some(&(
1174784400,
FixedTimespan {
offset: 3600,
is_dst: false,
name: Cow::Borrowed("ZONE_B"),
},
)),
})
}
const MANY: FixedTimespanSet<'static> = FixedTimespanSet {
first: FixedTimespan {
offset: 0,
is_dst: false,
name: Cow::Borrowed("ZONE_A"),
},
rest: &[
(1174784400, FixedTimespan {
offset: 3600,
is_dst: false,
name: Cow::Borrowed("ZONE_B"),
}),
(1193533200, FixedTimespan {
offset: 0,
is_dst: false,
name: Cow::Borrowed("ZONE_C"),
}),
],
};
#[test]
fn multiple_second() {
assert_eq!(MANY.find_with_surroundings(1184000000), Surroundings {
previous: Some((
&FixedTimespan {
offset: 0,
is_dst: false,
name: Cow::Borrowed("ZONE_A"),
},
1174784400,
)),
current: &FixedTimespan {
offset: 3600,
is_dst: false,
name: Cow::Borrowed("ZONE_B"),
},
next: Some(&(
1193533200,
FixedTimespan {
offset: 0,
is_dst: false,
name: Cow::Borrowed("ZONE_C"),
}
)),
});
}
#[test]
fn multiple_last() {
assert_eq!(MANY.find_with_surroundings(1200000000), Surroundings {
previous: Some((
&FixedTimespan {
offset: 3600,
is_dst: false,
name: Cow::Borrowed("ZONE_B"),
},
1193533200,
)),
current: &FixedTimespan {
offset: 0,
is_dst: false,
name: Cow::Borrowed("ZONE_C"),
},
next: None,
});
}
}