#![allow(clippy::zero_prefixed_literal)]
mod data;
pub use data::ZONES;
#[cfg(test)]
mod tests;
use std::slice::Iter;
use greg::{
Point,
Frame,
utc
};
use greg::calendar::{
Utc,
DateTime,
};
use greg::calendar::zone::{
Shift,
Steady,
Unsteady
};
#[derive(Copy, Clone, Eq, PartialEq)]
pub struct Zone {
name: &'static str,
lmt: Offset,
offsets: &'static [(Point, Offset)]
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct Offset {
name: &'static str,
seconds: i64
}
pub struct OffsetFrames {
offsets: Iter<'static, (Point, Offset)>,
current: Option<(Point, Offset)>
}
impl Iterator for OffsetFrames {
type Item = (Frame, Offset);
fn next(&mut self) -> Option<Self::Item> {
match (self.current.take(), self.offsets.next()) {
(Some((curr_p, curr_off)), Some(&(next_p, next_off))) => {
self.current = Some((next_p, next_off));
let frame = Frame {
start: curr_p,
stop: next_p
};
Some((frame, curr_off))
},
(Some((last_p, last_off)), None) => Some((
Frame {start: last_p, stop: Point::from_epoch(i64::MAX)},
last_off
)),
(None, _) => None
}
}
}
impl ExactSizeIterator for OffsetFrames {
fn len(&self) -> usize {
self.offsets.len() + self.current.iter().count()
}
}
impl Offset {
const fn new(name: &'static str, seconds: i64) -> Self {
Self {name, seconds}
}
pub const fn name(self) -> &'static str {self.name}
}
impl Shift for Offset {
fn apply(&self, point: Point) -> Point {
let timestamp = point.timestamp + self.seconds;
Point {timestamp}
}
}
impl Steady for Offset {
fn revert(&self, point: Point) -> Point {
let timestamp = point.timestamp - self.seconds;
Point {timestamp}
}
}
impl Zone {
const fn define(
name: &'static str,
lmt: Offset,
offsets: &'static [(Point, Offset)])
-> Self
{
Self {name, lmt, offsets}
}
const fn alias(name: &'static str, alias: Self) -> Self {
Self {name, ..alias}
}
pub const fn name(self) -> &'static str {self.name}
fn contains(point: Point) -> bool {
(utc!(1800-01-01)..utc!(2100-01-01)).contains(&point)
}
pub fn format_with_name(&self, point: Point) -> String {
let offset = self.offset_at(point);
let DateTime(date, time) = Utc::lookup(offset.apply(point));
format!("{date} {time} {}", offset.name())
}
pub fn offset_at(&self, point: Point) -> Offset {
assert!(
Self::contains(point),
"Point out of valid time zone range 1800..2100"
);
self.offsets.iter()
.rev()
.find(|&&(p, _offset)| p <= point)
.map(|&(_p, offset)| offset)
.unwrap_or(self.lmt)
}
pub fn offsets(&self) -> &'static [(Point, Offset)] {self.offsets}
pub fn offset_frames(&self) -> OffsetFrames {
let offsets = self.offsets.iter();
let current = Some((Point::from_epoch(i64::MIN), self.lmt));
OffsetFrames {offsets, current}
}
}
impl Shift for Zone {
fn apply(&self, point: Point) -> Point {
self.offset_at(point).apply(point)
}
}
impl Unsteady for Zone {
fn revert_to_earliest(&self, utc_point: Point) -> Point {
assert!(
Self::contains(utc_point),
"Point out of valid time zone range 1800..2100"
);
for (frame, offset) in self.offset_frames() {
let reverted = offset.revert(utc_point);
if frame.start <= reverted && frame.stop > reverted {
return reverted;
}
else if frame.start > reverted {
return frame.start;
}
}
unreachable!()
}
}