use crate::table::{RuleInfo, Saving, Table, ZoneInfo};
#[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 = self.get_zoneset(zone_name)?;
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(utc_offset, *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_default(),
};
self.rest.push((time, timespan));
*insert_start_transition = false;
} else {
self.first = Some(FixedTimespan {
utc_offset,
dst_offset: *dst_offset,
name: start_zone_id.clone().unwrap_or_default(),
});
}
}
#[allow(unused_results)]
#[allow(clippy::too_many_arguments)]
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;
for year in 1800..2100 {
if use_until && year > timespan.end_time.unwrap().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 earliest = activated_rules
.iter()
.enumerate()
.map(|(i, r)| (i, r.absolute_datetime(year, utc_offset, *dst_offset)))
.min_by_key(|&(_, time)| time);
let (pos, earliest_at) = match earliest {
Some((pos, time)) => (pos, time),
None => break,
};
let earliest_rule = activated_rules.remove(pos);
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() {
let _ = replace(start_utc_offset, timespan.offset);
let _ = replace(start_dst_offset, *dst_offset);
let _ = start_zone_id.replace(timespan.format.format(
utc_offset,
*dst_offset,
earliest_rule.letters.as_ref(),
));
continue;
}
if start_zone_id.is_none()
&& *start_utc_offset + *start_dst_offset == timespan.offset + *dst_offset
{
let _ = start_zone_id.replace(timespan.format.format(
utc_offset,
*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(
timespan.offset,
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,
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::optimise;
use super::*;
#[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);
}
}