use std::ops::RangeBounds;
use std::slice::Iter;
use rusqlite::{
OptionalExtension,
types::FromSqlError,
Result as SqlResult
};
use greg::{
Point,
Frame,
};
use greg::calendar::Calendar;
use greg::calendar::zone::{
Shift,
Steady,
Unsteady
};
use greg_tz::{
ZONES,
Offset,
Ambiguity,
Zone as TZone
};
use crate::cli::arg::TimeArg;
use super::Database;
#[derive(Debug, Default, Clone)]
pub struct Table {
offsets: Vec<(Point, Offset)>
}
pub struct OffsetFrames<'t> {
offsets: Iter<'t, (Point, Offset)>,
current: Option<(Point, Offset)>
}
impl<'t> Iterator for OffsetFrames<'t> {
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<'t> ExactSizeIterator for OffsetFrames<'t> {
fn len(&self) -> usize {
self.offsets.len() + self.current.iter().count()
}
}
pub trait Zone: Database {
fn list(&self) -> SqlResult<Vec<(Point, TZone)>> {
let mut zones = Vec::new();
let mut stmt = self.db().prepare(
"SELECT name, timestamp FROM time_zone \
JOIN time_zone_encounter ON id = time_zone_id \
ORDER BY timestamp ASC"
)?;
let mut rows = stmt.query([])?;
while let Some(row) = rows.next()? {
let (name, at): (String, Point) = row.try_into()?;
let zone = ZONES.get(&name)
.ok_or_else(|| format!("invalid zone {name}"))
.map_err(Into::into)
.map_err(FromSqlError::Other)?;
zones.push((at, *zone));
}
Ok(zones)
}
fn change(&self, when: Point, zone: TZone) -> SqlResult<bool> {
let zone_name = zone.name();
let tx = self.db().unchecked_transaction()?;
let maybe_zone_id = self.db().query_row(
"SELECT id FROM time_zone WHERE name LIKE ?1",
[zone_name],
|r| r.get(0)
).optional()?;
let id = match maybe_zone_id {
Some(id) => id,
None => {
let mut stmt = self.db().prepare(
"INSERT INTO time_zone (name) VALUES (?1)"
)?;
stmt.insert([zone_name])?
}
};
let mut stmt = self.db().prepare(
"INSERT INTO time_zone_encounter \
(timestamp, time_zone_id) VALUES (?1, ?2)"
)?;
stmt.insert((when, id))?;
tx.commit().map(|()| true)
}
fn load_zone_table(&self) -> SqlResult<Table> {
let mut offsets = Vec::new();
let mut zones = Vec::new();
let mut stmt = self.db().prepare(
"SELECT name, timestamp FROM time_zone \
JOIN time_zone_encounter ON id = time_zone_id \
ORDER BY timestamp ASC"
)?;
let mut rows = stmt.query([])?;
while let Some(row) = rows.next()? {
let (name, at): (String, Point) = row.try_into()?;
let zone = ZONES.get(&name)
.ok_or_else(|| format!("invalid zone {name}"))
.map_err(Into::into)
.map_err(FromSqlError::Other)?;
zones.push((at, zone));
}
let mut iter = zones.into_iter();
let mut current = iter.next();
for next in iter {
let (start, zone) = current.unwrap();
let stop = next.0;
let frames = zone.offset_frames()
.skip_while(|(frame, _off)| !frame.contains(&start))
.take_while(|(frame, _off)| frame.start < stop)
.map(|(frame, off)| (frame.start.max(start), off));
offsets.extend(frames);
current = Some(next);
}
if let Some((start, zone)) = current {
let frames = zone.offset_frames()
.skip_while(|(frame, _off)| !frame.contains(&start))
.map(|(frame, off)| (frame.start.max(start), off));
offsets.extend(frames);
}
Ok(Table {offsets})
}
fn load_calendar(&self) -> SqlResult<Calendar<Table>> {
self.load_zone_table().map(Calendar)
}
}
impl<Db: Database + ?Sized> Zone for Db {}
impl Table {
pub fn offset_frames(&'_ self) -> OffsetFrames<'_> {
let offsets = self.offsets.iter();
let current = Some((Point::from_epoch(i64::MIN), Offset::UTC));
OffsetFrames {offsets, current}
}
pub fn offset_at(&self, point: Point) -> Offset {
self.offsets.iter()
.rev()
.find(|&&(p, _offset)| p <= point)
.map(|&(_p, offset)| offset)
.unwrap_or(Offset::UTC)
}
pub(crate) fn revert_frame(&self, frame: Frame) -> Frame {
let start = self.revert_lossy(frame.start);
let stop = self.revert_lossy(frame.stop);
Frame {start, stop}
}
pub fn resolve(&self, arg: TimeArg) -> Point {
arg.resolve_with(&Calendar(self))
}
}
impl Shift for Table {
fn apply(&self, point: Point) -> Point {
self.offset_at(point).apply(point)
}
}
impl Unsteady for Table {
type Ambiguity = Ambiguity;
fn revert_lossy(&self, point: Point) -> Point {
for (frame, offset) in self.offset_frames() {
let reverted = offset.revert(point);
if frame.start <= reverted && frame.stop > reverted {
return reverted;
}
else if frame.start > reverted {
return frame.start;
}
}
unreachable!()
}
fn try_revert(&self, point: Point) -> Result<Point, Ambiguity> {
let mut found = None;
for (frame, offset) in self.offset_frames() {
let reverted = offset.revert(point);
match frame.contains(&reverted) {
true => match found {
Some(previous) => return Err(Ambiguity::Repeated(
[previous, (reverted, offset)]
)),
None => found = Some((reverted, offset))
},
false => match found {
Some((point, _)) => return Ok(point),
None if frame.start > reverted => return Err(
Ambiguity::Skipped(frame.start)
),
None => continue
}
}
}
unreachable!()
}
}
impl Shift for &Table {
fn apply(&self, point: Point) -> Point {
(*self).apply(point)
}
}
impl Unsteady for &Table {
type Ambiguity = <Table as Unsteady>::Ambiguity;
fn revert_lossy(&self, point: Point) -> Point {
(*self).revert_lossy(point)
}
fn try_revert(&self, point: Point) -> Result<Point, Self::Ambiguity> {
(*self).try_revert(point)
}
}
#[test]
fn offsets() -> SqlResult<()> {
use greg::utc;
let nunc = crate::Nunc::testing().unwrap();
assert!(nunc.change(utc!(2020-01-01), TZone::Europe__Berlin)?);
assert!(nunc.change(utc!(2021-01-01), TZone::Iceland)?);
let table = nunc.load_zone_table()?;
let cet = TZone::Europe__Berlin.offset_at(utc!(2020-01-01));
let cest = TZone::Europe__Berlin.offset_at(utc!(2020-06-01));
assert_eq!(table.offsets[0], (utc!(2020-01-01), cet));
assert_eq!(table.offsets[1], (utc!(2020-03-29 01:00:00), cest));
assert_eq!(table.offsets[2], (utc!(2020-10-25 01:00:00), cet));
assert_eq!(table.offsets[3], (utc!(2021-01-01), Offset::GMT));
assert_eq!(table.offsets.len(), 4);
assert_eq!(
table.try_revert(utc!(2020-01-01 00:30:00)),
Err(Ambiguity::Skipped(utc!(2020-01-01)))
);
assert_eq!(
table.try_revert(utc!(2020-03-29 02:00:00)),
Err(Ambiguity::Skipped(utc!(2020-03-29 01:00:00)))
);
assert_eq!(
table.try_revert(utc!(2020-10-25 02:00:00)),
Err(Ambiguity::Repeated([
(utc!(2020-10-25 00:00:00), cest),
(utc!(2020-10-25 01:00:00), cet)
]))
);
assert_eq!(
table.try_revert(utc!(2021-01-01)),
Err(Ambiguity::Repeated([
(utc!(2020-12-31 23:00:00), cet),
(utc!(2021-01-01), Offset::GMT)
]))
);
Ok(())
}