use num_derive::FromPrimitive;
use num_traits::FromPrimitive;
use crate::date::Date;
use crate::span::Span;
use crate::time::Time;
#[derive(Debug, Eq, PartialEq, Hash, Copy, Clone, Ord, PartialOrd, FromPrimitive)]
pub enum TOp {
AdvMon = 0,
AdvTue = 1,
AdvWed = 2,
AdvThu = 3,
AdvFri = 4,
AdvSat = 5,
AdvSun = 6,
FindMon = 7,
FindTue = 8,
FindWed = 9,
FindThu = 10,
FindFri = 11,
FindSat = 12,
FindSun = 13,
AddYears = 14,
AddMonths = 15,
AddDays = 16,
SetYear = 17,
SetMonth = 18,
SetDay = 19,
Nop = 20,
AddHours,
AddMins,
AddSecs,
SetHour,
SetMin,
SetSec,
}
#[derive(Debug, Eq, PartialEq, Hash, Copy, Clone, Ord, PartialOrd)]
pub struct TimeOp {
op: TOp,
n: i64,
}
impl TimeOp {
#[must_use]
pub const fn new(op: TOp, n: i64) -> Self {
Self { op, n }
}
pub fn apply(&self, t: impl Into<Time>) -> Time {
let t = t.into();
match self.op {
TOp::AddHours => t.add_hours(self.n),
TOp::AddMins => t.add_mins(self.n),
TOp::AddSecs => t.add_secs(self.n),
TOp::SetHour => t.with_hour(self.n as u32),
TOp::SetMin => t.with_min(self.n as u32),
TOp::SetSec => t.with_sec(self.n as u32),
_ => t.with_date(apply_dop(
t.date(),
FromPrimitive::from_i32(self.op as i32).unwrap(),
self.n,
)),
}
}
}
#[derive(Debug, Eq, PartialEq, Hash, Copy, Clone, Ord, PartialOrd, FromPrimitive)]
pub enum DOp {
AdvMon = 0,
AdvTue = 1,
AdvWed = 2,
AdvThu = 3,
AdvFri = 4,
AdvSat = 5,
AdvSun = 6,
FindMon = 7,
FindTue = 8,
FindWed = 9,
FindThu = 10,
FindFri = 11,
FindSat = 12,
FindSun = 13,
AddYears = 14,
AddMonths = 15,
AddDays = 16,
SetYear = 17,
SetMonth = 18,
SetDay = 19,
Nop = 20,
}
#[derive(Debug, Eq, PartialEq, Hash, Copy, Clone, Ord, PartialOrd)]
pub struct DateOp {
op: DOp,
n: i64,
}
impl DateOp {
#[must_use]
pub const fn new(op: DOp, n: i64) -> Self {
Self { op, n }
}
pub fn apply(&self, d: impl Into<Date>) -> Date {
apply_dop(d.into(), self.op, self.n)
}
}
fn apply_dop(d: Date, op: DOp, n: i64) -> Date {
match op {
DOp::AddYears => d.add_years(n as i32),
DOp::AddMonths => d.add_months(n as i32),
DOp::AddDays => d.add_days(n as i32),
_ if (DOp::AdvMon..=DOp::AdvSun).contains(&op) => {
let offset = (op as i32 - DOp::AdvMon as i32 - d.weekday() as i32).rem_euclid(7);
let n = if offset != 0 && n > 0 { n - 1 } else { n };
d.add_days(offset + 7 * n as i32)
}
_ if (DOp::FindMon..=DOp::FindSun).contains(&op) => {
let offset = (op as i32 - DOp::FindMon as i32 - d.weekday() as i32).rem_euclid(7);
let n = if n < 0 && offset != 0 { n - 1 } else { n };
let n = n - n.signum();
d.add_days(offset + 7 * n as i32)
}
DOp::SetYear => d.with_year(n as i32),
DOp::SetMonth => d.with_month(n as u32),
DOp::SetDay => d.with_day(n as u32),
_ => d,
}
}
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Copy, Clone)]
pub struct SpanOp {
pub st: TimeOp,
pub en: TimeOp, }
impl SpanOp {
#[must_use]
pub const fn new(st: TimeOp, en: TimeOp) -> Self {
Self { st, en }
}
pub fn apply(&self, t: impl Into<Time>) -> Span<Time> {
let t = t.into();
Span::new(self.st.apply(t), self.en.apply(t))
}
}
#[cfg(test)]
mod tests {
use chrono::TimeZone;
use chrono_tz::{Australia, Tz, US, UTC};
use super::*;
const TZ: [Tz; 3] = [US::Eastern, UTC, Australia::Eucla];
#[test]
fn leap_years() {
for tz in &TZ {
assert_eq!(
Time::from_date(tz.ymd(2020, 2, 29)).with_year(2019),
tz.ymd(2019, 2, 28).into()
);
assert_eq!(
Time::from_date(tz.ymd(2020, 1, 29)).with_month(2),
tz.ymd(2020, 2, 29).into()
);
assert_eq!(
Time::from_date(tz.ymd(2020, 1, 30)).with_month(2),
tz.ymd(2020, 2, 29).into()
);
assert_eq!(
Time::from_date(tz.ymd(2019, 1, 29)).with_month(2),
tz.ymd(2019, 2, 28).into()
);
assert_eq!(
Time::from_date(tz.ymd(2019, 1, 30)).with_month(2),
tz.ymd(2019, 2, 28).into()
);
}
}
#[test]
fn time_offset() {
for tz in &TZ {
assert_eq!(
Time::op(TOp::AddYears, 1).apply(tz.ymd(2019, 1, 30)),
tz.ymd(2020, 1, 30).into(),
);
assert_eq!(
Time::op(TOp::AddYears, 1).apply(tz.ymd(2020, 2, 29)),
tz.ymd(2021, 2, 28).into(),
);
assert_eq!(
Time::op(TOp::AddMonths, 1).apply(tz.ymd(2019, 1, 30)),
tz.ymd(2019, 2, 28).into(),
);
assert_eq!(
Time::op(TOp::AddMonths, 1).apply(tz.ymd(2020, 1, 30)),
tz.ymd(2020, 2, 29).into(),
);
assert_eq!(
Time::op(TOp::AddMonths, 13).apply(tz.ymd(2020, 2, 29)),
tz.ymd(2021, 3, 29).into(),
);
assert_eq!(
Time::op(TOp::AddDays, 1).apply(tz.ymd(2020, 2, 28)),
tz.ymd(2020, 2, 29).into(),
);
assert_eq!(
Time::op(TOp::AddDays, 1).apply(tz.ymd(2019, 2, 28)),
tz.ymd(2019, 3, 1).into(),
);
assert_eq!(
Time::op(TOp::AdvMon, 2).apply(tz.ymd(2020, 12, 6)),
tz.ymd(2020, 12, 14).into(),
);
assert_eq!(
Time::op(TOp::AdvMon, 2).apply(tz.ymd(2020, 12, 7)),
tz.ymd(2020, 12, 21).into(),
);
assert_eq!(
Time::op(TOp::AdvMon, 1).apply(tz.ymd(2020, 12, 6)),
tz.ymd(2020, 12, 7).into(),
);
assert_eq!(
Time::op(TOp::AdvMon, 1).apply(tz.ymd(2020, 12, 7)),
tz.ymd(2020, 12, 14).into(),
);
assert_eq!(
Time::op(TOp::AdvMon, 0).apply(tz.ymd(2020, 12, 6)),
tz.ymd(2020, 12, 7).into(),
);
assert_eq!(
Time::op(TOp::AdvMon, 0).apply(tz.ymd(2020, 12, 7)),
tz.ymd(2020, 12, 7).into(),
);
assert_eq!(
Time::op(TOp::AdvMon, -1).apply(tz.ymd(2020, 12, 6)),
tz.ymd(2020, 11, 30).into(),
);
assert_eq!(
Time::op(TOp::AdvMon, -1).apply(tz.ymd(2020, 12, 7)),
tz.ymd(2020, 11, 30).into(),
);
assert_eq!(
Time::op(TOp::FindMon, 2).apply(tz.ymd(2020, 12, 6)),
tz.ymd(2020, 12, 14).into(),
);
assert_eq!(
Time::op(TOp::FindMon, 2).apply(tz.ymd(2020, 12, 7)),
tz.ymd(2020, 12, 14).into(),
);
assert_eq!(
Time::op(TOp::FindMon, 1).apply(tz.ymd(2020, 12, 6)),
tz.ymd(2020, 12, 7).into(),
);
assert_eq!(
Time::op(TOp::FindMon, 1).apply(tz.ymd(2020, 12, 7)),
tz.ymd(2020, 12, 7).into(),
);
assert_eq!(
Time::op(TOp::FindMon, 0).apply(tz.ymd(2020, 12, 6)),
tz.ymd(2020, 12, 7).into(),
);
assert_eq!(
Time::op(TOp::FindMon, 0).apply(tz.ymd(2020, 12, 7)),
tz.ymd(2020, 12, 7).into(),
);
assert_eq!(
Time::op(TOp::FindMon, -1).apply(tz.ymd(2020, 12, 6)),
tz.ymd(2020, 11, 30).into(),
);
assert_eq!(
Time::op(TOp::FindMon, -1).apply(tz.ymd(2020, 12, 7)),
tz.ymd(2020, 12, 7).into(),
);
}
}
}