use table::{Table, Saving, RuleInfo, ZoneInfo};
use datetime::LocalDateTime;
#[derive(PartialEq, Debug, Clone)]
pub struct FixedTimespanSet {
pub first: FixedTimespan,
pub rest: Vec<(i64, FixedTimespan)>,
}
#[derive(PartialEq, Debug, Clone)]
pub struct FixedTimespan {
pub utc_offset: i64,
pub dst_offset: i64,
pub name: String,
}
impl FixedTimespan {
pub fn total_offset(&self) -> i64 {
self.utc_offset + self.dst_offset
}
}
pub trait TableTransitions {
fn timespans(&self, zone_name: &str) -> Option<FixedTimespanSet>;
}
impl TableTransitions for Table {
fn timespans(&self, zone_name: &str) -> Option<FixedTimespanSet> {
let mut builder = FixedTimespanSetBuilder::default();
let zoneset = match self.get_zoneset(zone_name) {
Some(zones) => zones,
None => return None,
};
for (i, zone_info) in zoneset.iter().enumerate() {
let mut dst_offset = 0;
let use_until = i != zoneset.len() - 1;
let utc_offset = zone_info.offset;
let mut insert_start_transition = i > 0;
let mut start_zone_id = None;
let mut start_utc_offset = zone_info.offset;
let mut start_dst_offset = 0;
match zone_info.saving {
Saving::NoSaving => {
builder.add_fixed_saving(zone_info, 0, &mut dst_offset, utc_offset, &mut insert_start_transition, &mut start_zone_id);
},
Saving::OneOff(amount) => {
builder.add_fixed_saving(zone_info, amount, &mut dst_offset, utc_offset, &mut insert_start_transition, &mut start_zone_id);
},
Saving::Multiple(ref rules) => {
let rules = &self.rulesets[&*rules];
builder.add_multiple_saving(zone_info, &*rules, &mut dst_offset, use_until, utc_offset, &mut insert_start_transition, &mut start_zone_id, &mut start_utc_offset, &mut start_dst_offset);
}
}
if insert_start_transition && start_zone_id.is_some() {
let t = (builder.start_time.expect("Start time"), FixedTimespan {
utc_offset: start_utc_offset,
dst_offset: start_dst_offset,
name: start_zone_id.clone().expect("Start zone ID"),
});
builder.rest.push(t);
}
if use_until {
builder.start_time = Some(zone_info.end_time.expect("End time").to_timestamp() - utc_offset - dst_offset);
}
}
Some(builder.build())
}
}
#[derive(Debug, Default)]
struct FixedTimespanSetBuilder {
first: Option<FixedTimespan>,
rest: Vec<(i64, FixedTimespan)>,
start_time: Option<i64>,
until_time: Option<i64>,
}
impl FixedTimespanSetBuilder {
fn add_fixed_saving(&mut self, timespan: &ZoneInfo, amount: i64,
dst_offset: &mut i64, utc_offset: i64, insert_start_transition: &mut bool,
start_zone_id: &mut Option<String>)
{
*dst_offset = amount;
*start_zone_id = Some(timespan.format.format(*dst_offset, None));
if *insert_start_transition {
let time = self.start_time.unwrap();
let timespan = FixedTimespan {
utc_offset: timespan.offset,
dst_offset: *dst_offset,
name: start_zone_id.clone().unwrap_or("".to_owned()),
};
self.rest.push((time, timespan));
*insert_start_transition = false;
}
else {
self.first = Some(FixedTimespan {
utc_offset: utc_offset,
dst_offset: *dst_offset,
name: start_zone_id.clone().unwrap_or("".to_owned()),
});
}
}
#[allow(unused_results)]
fn add_multiple_saving(&mut self, timespan: &ZoneInfo, rules: &[RuleInfo],
dst_offset: &mut i64, use_until: bool, utc_offset: i64, insert_start_transition: &mut bool,
start_zone_id: &mut Option<String>, start_utc_offset: &mut i64, start_dst_offset: &mut i64)
{
use std::mem::replace;
use datetime::DatePiece;
for year in 1800..2100 {
if use_until && year > LocalDateTime::at(timespan.end_time.unwrap().to_timestamp()).year() {
break;
}
let mut activated_rules = rules.iter()
.filter(|r| r.applies_to_year(year))
.collect::<Vec<_>>();
loop {
if use_until {
self.until_time = Some(timespan.end_time.unwrap().to_timestamp() - utc_offset - *dst_offset);
}
let pos = {
let earliest = activated_rules.iter().enumerate()
.map(|(i, r)| (r.absolute_datetime(year, utc_offset, *dst_offset), i))
.min()
.map(|(_, i)| i);
match earliest {
Some(p) => p,
None => break,
}
};
let earliest_rule = activated_rules.remove(pos);
let earliest_at = earliest_rule.absolute_datetime(year, utc_offset, *dst_offset).to_instant().seconds();
if use_until && earliest_at >= self.until_time.unwrap() {
break;
}
*dst_offset = earliest_rule.time_to_add;
if *insert_start_transition && earliest_at == self.start_time.unwrap() {
*insert_start_transition = false;
}
if *insert_start_transition {
if earliest_at < self.start_time.unwrap() {
replace(start_utc_offset, timespan.offset);
replace(start_dst_offset, *dst_offset);
replace(start_zone_id, Some(timespan.format.format(*dst_offset, earliest_rule.letters.as_ref())));
continue;
}
if start_zone_id.is_none() && *start_utc_offset + *start_dst_offset == timespan.offset + *dst_offset {
replace(start_zone_id, Some(timespan.format.format(*dst_offset, earliest_rule.letters.as_ref())));
}
}
let t = (earliest_at, FixedTimespan {
utc_offset: timespan.offset,
dst_offset: earliest_rule.time_to_add,
name: timespan.format.format(earliest_rule.time_to_add, earliest_rule.letters.as_ref()),
});
self.rest.push(t);
}
}
}
fn build(mut self) -> FixedTimespanSet {
self.rest.sort_by(|a, b| a.0.cmp(&b.0));
let first = match self.first {
Some(ft) => ft,
None => self.rest.iter().find(|t| t.1.dst_offset == 0).unwrap().1.clone(),
};
let mut zoneset = FixedTimespanSet {
first: first,
rest: self.rest,
};
optimise(&mut zoneset);
zoneset
}
}
#[allow(unused_results)] fn optimise(transitions: &mut FixedTimespanSet) {
let mut from_i = 0;
let mut to_i = 0;
while from_i < transitions.rest.len() {
if to_i > 1 {
let from = transitions.rest[from_i].0;
let to = transitions.rest[to_i - 1].0;
if from + transitions.rest[to_i - 1].1.total_offset() <= to + transitions.rest[to_i - 2].1.total_offset() {
transitions.rest[to_i - 1].1 = transitions.rest[from_i].1.clone();
from_i += 1;
continue;
}
}
if to_i == 0 || transitions.rest[to_i - 1].1 != transitions.rest[from_i].1 {
transitions.rest[to_i] = transitions.rest[from_i].clone();
to_i += 1;
}
from_i += 1
}
transitions.rest.truncate(to_i);
if !transitions.rest.is_empty() && transitions.first == transitions.rest[0].1 {
transitions.rest.remove(0);
}
}
#[cfg(test)]
mod test {
use super::*;
use super::optimise;
#[test]
#[allow(unused_results)]
fn optimise_macquarie() {
let mut transitions = FixedTimespanSet {
first: FixedTimespan { utc_offset: 0, dst_offset: 0, name: "zzz".to_owned() },
rest: vec![
(-2_214_259_200, FixedTimespan { utc_offset: 36000, dst_offset: 0, name: "AEST".to_owned() }),
(-1_680_508_800, FixedTimespan { utc_offset: 36000, dst_offset: 3600, name: "AEDT".to_owned() }),
(-1_669_892_400, FixedTimespan { utc_offset: 36000, dst_offset: 3600, name: "AEDT".to_owned() }), (-1_665_392_400, FixedTimespan { utc_offset: 36000, dst_offset: 0, name: "AEST".to_owned() }),
(-1_601_719_200, FixedTimespan { utc_offset: 0, dst_offset: 0, name: "zzz".to_owned() }),
( -687_052_800, FixedTimespan { utc_offset: 36000, dst_offset: 0, name: "AEST".to_owned() }),
( -94_730_400, FixedTimespan { utc_offset: 36000, dst_offset: 0, name: "AEST".to_owned() }), ( -71_136_000, FixedTimespan { utc_offset: 36000, dst_offset: 3600, name: "AEDT".to_owned() }),
( -55_411_200, FixedTimespan { utc_offset: 36000, dst_offset: 0, name: "AEST".to_owned() }),
( -37_267_200, FixedTimespan { utc_offset: 36000, dst_offset: 3600, name: "AEDT".to_owned() }),
( -25_776_000, FixedTimespan { utc_offset: 36000, dst_offset: 0, name: "AEST".to_owned() }),
( -5_817_600, FixedTimespan { utc_offset: 36000, dst_offset: 3600, name: "AEDT".to_owned() }),
],
};
let mut result = transitions.clone();
result.rest.remove(6);
result.rest.remove(2);
optimise(&mut transitions);
assert_eq!(transitions, result);
}
}