From 32e036f3374f604fcf9de9218995550813f541d3 Mon Sep 17 00:00:00 2001 From: Joscha Date: Mon, 6 Dec 2021 01:17:54 +0100 Subject: [PATCH] Write tests for all delta steps --- src/eval/delta.rs | 275 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 270 insertions(+), 5 deletions(-) diff --git a/src/eval/delta.rs b/src/eval/delta.rs index 423c587..08402e4 100644 --- a/src/eval/delta.rs +++ b/src/eval/delta.rs @@ -7,8 +7,6 @@ use crate::files::primitives::{Span, Spanned, Time, Weekday}; use super::{util, Error, Result}; -// TODO Test all these delta steps - /// Like [`commands::DeltaStep`] but includes a new constructor, /// [`DeltaStep::Time`]. #[derive(Debug, Clone, Copy)] @@ -217,10 +215,20 @@ impl DeltaEval { } fn step_month_reverse(&mut self, span: Span, amount: i32) -> Result<()> { - // Offset from the last day of the month - let end_offset = self.curr.day() - util::month_length(self.curr.year(), self.curr.month()); + // Calculate offset from the last day of the month + let month_length = util::month_length(self.curr.year(), self.curr.month()) as i32; + let end_offset = self.curr.day() as i32 - month_length; + let (year, month) = util::add_months(self.curr.year(), self.curr.month(), amount); - let day = end_offset + util::month_length(year, month); + + // Calculate day based on the offset from earlier + let month_length = util::month_length(year, month) as i32; + let day = if end_offset + month_length > 0 { + (end_offset + month_length) as u32 + } else { + return Err(self.err_step(span)); + }; + match NaiveDate::from_ymd_opt(year, month, day) { None => Err(self.err_step(span)), Some(next) => { @@ -318,3 +326,260 @@ impl Delta { Ok((date, time.expect("time was not preserved"))) } } + +#[cfg(test)] +mod tests { + use chrono::NaiveDate; + + use crate::files::primitives::{Span, Spanned, Time}; + + use super::super::Result; + use super::{Delta, DeltaStep as Step}; + + const SPAN: Span = Span { start: 12, end: 34 }; + + fn delta(step: Step) -> Delta { + Delta { + steps: vec![Spanned::new(SPAN, step)], + } + } + + fn apply_d(step: Step, from: (i32, u32, u32)) -> Result { + delta(step).apply_date(NaiveDate::from_ymd(from.0, from.1, from.2)) + } + + fn test_d(step: Step, from: (i32, u32, u32), expected: (i32, u32, u32)) { + assert_eq!( + apply_d(step, from).unwrap(), + NaiveDate::from_ymd(expected.0, expected.1, expected.2) + ); + } + + fn apply_dt(step: Step, from: (i32, u32, u32, u32, u32)) -> Result<(NaiveDate, Time)> { + delta(step).apply_date_time( + NaiveDate::from_ymd(from.0, from.1, from.2), + Time::new(from.3, from.4).unwrap(), + ) + } + + #[allow(clippy::too_many_arguments)] // This is just for writing tests + fn test_dt(step: Step, from: (i32, u32, u32, u32, u32), expected: (i32, u32, u32, u32, u32)) { + assert_eq!( + apply_dt(step, from).unwrap(), + ( + NaiveDate::from_ymd(expected.0, expected.1, expected.2), + Time::new(expected.3, expected.4).unwrap() + ) + ); + } + + #[test] + fn delta_year() { + test_d(Step::Year(-10000), (2021, 7, 3), (-7979, 7, 3)); + test_d(Step::Year(-100), (2021, 7, 3), (1921, 7, 3)); + test_d(Step::Year(-10), (2021, 7, 3), (2011, 7, 3)); + test_d(Step::Year(-2), (2021, 7, 3), (2019, 7, 3)); + test_d(Step::Year(-1), (2021, 7, 3), (2020, 7, 3)); + test_d(Step::Year(0), (2021, 7, 3), (2021, 7, 3)); + test_d(Step::Year(1), (2021, 7, 3), (2022, 7, 3)); + test_d(Step::Year(2), (2021, 7, 3), (2023, 7, 3)); + test_d(Step::Year(10), (2021, 7, 3), (2031, 7, 3)); + test_d(Step::Year(100), (2021, 7, 3), (2121, 7, 3)); + test_d(Step::Year(10000), (2021, 7, 3), (12021, 7, 3)); + + // Leap year shenanigans + test_d(Step::Year(4), (2020, 2, 29), (2024, 2, 29)); + test_d(Step::Year(2), (2020, 2, 28), (2022, 2, 28)); + test_d(Step::Year(2), (2020, 3, 1), (2022, 3, 1)); + test_d(Step::Year(-2), (2022, 2, 28), (2020, 2, 28)); + test_d(Step::Year(-2), (2022, 3, 1), (2020, 3, 1)); + assert!(apply_d(Step::Year(1), (2020, 2, 29)).is_err()); + + // Doesn't touch time + test_dt(Step::Year(1), (2021, 7, 3, 12, 34), (2022, 7, 3, 12, 34)); + } + + #[test] + fn delta_month() { + test_d(Step::Month(-48), (2021, 7, 3), (2017, 7, 3)); + test_d(Step::Month(-12), (2021, 7, 3), (2020, 7, 3)); + test_d(Step::Month(-2), (2021, 7, 3), (2021, 5, 3)); + test_d(Step::Month(-1), (2021, 7, 3), (2021, 6, 3)); + test_d(Step::Month(0), (2021, 7, 3), (2021, 7, 3)); + test_d(Step::Month(1), (2021, 7, 3), (2021, 8, 3)); + test_d(Step::Month(2), (2021, 7, 3), (2021, 9, 3)); + test_d(Step::Month(12), (2021, 7, 3), (2022, 7, 3)); + + // At end of months + test_d(Step::Month(2), (2021, 1, 31), (2021, 3, 31)); + test_d(Step::Month(3), (2021, 1, 30), (2021, 4, 30)); + assert!(apply_d(Step::Month(1), (2021, 1, 31)).is_err()); + + // Leap year shenanigans + test_d(Step::Month(1), (2020, 1, 29), (2020, 2, 29)); + assert!(apply_d(Step::Month(1), (2021, 1, 29)).is_err()); + + // Doesn't touch time + test_dt(Step::Month(1), (2021, 7, 3, 12, 34), (2021, 8, 3, 12, 34)); + } + + #[test] + fn delta_month_reverse() { + test_d(Step::MonthReverse(-48), (2021, 7, 31), (2017, 7, 31)); + test_d(Step::MonthReverse(-12), (2021, 7, 31), (2020, 7, 31)); + test_d(Step::MonthReverse(-2), (2021, 7, 31), (2021, 5, 31)); + test_d(Step::MonthReverse(-1), (2021, 7, 31), (2021, 6, 30)); + test_d(Step::MonthReverse(0), (2021, 7, 31), (2021, 7, 31)); + test_d(Step::MonthReverse(1), (2021, 7, 31), (2021, 8, 31)); + test_d(Step::MonthReverse(2), (2021, 7, 31), (2021, 9, 30)); + test_d(Step::MonthReverse(12), (2021, 7, 31), (2022, 7, 31)); + + // At start of months + test_d(Step::MonthReverse(2), (2021, 1, 1), (2021, 3, 1)); + test_d(Step::MonthReverse(3), (2021, 1, 2), (2021, 4, 1)); + assert!(apply_d(Step::MonthReverse(1), (2021, 1, 1)).is_err()); + + // Leap year shenanigans + test_d(Step::MonthReverse(1), (2020, 1, 30), (2020, 2, 28)); + test_d(Step::MonthReverse(-1), (2020, 2, 28), (2020, 1, 30)); + test_d(Step::MonthReverse(1), (2021, 1, 31), (2021, 2, 28)); + test_d(Step::MonthReverse(-1), (2021, 2, 28), (2021, 1, 31)); + + // Doesn't touch time + test_dt( + Step::MonthReverse(1), + (2021, 7, 3, 12, 34), + (2021, 8, 3, 12, 34), + ); + } + + #[test] + fn delta_day() { + test_d(Step::Day(-365), (2021, 7, 3), (2020, 7, 3)); + test_d(Step::Day(-30), (2021, 7, 3), (2021, 6, 3)); + test_d(Step::Day(-2), (2021, 7, 3), (2021, 7, 1)); + test_d(Step::Day(-1), (2021, 7, 3), (2021, 7, 2)); + test_d(Step::Day(0), (2021, 7, 3), (2021, 7, 3)); + test_d(Step::Day(1), (2021, 7, 3), (2021, 7, 4)); + test_d(Step::Day(2), (2021, 7, 3), (2021, 7, 5)); + test_d(Step::Day(31), (2021, 7, 3), (2021, 8, 3)); + test_d(Step::Day(365), (2021, 7, 3), (2022, 7, 3)); + + // Leap year shenanigans + test_d(Step::Day(1), (2020, 2, 28), (2020, 2, 29)); + test_d(Step::Day(1), (2020, 2, 29), (2020, 3, 1)); + test_d(Step::Day(1), (2021, 2, 28), (2021, 3, 1)); + test_d(Step::Day(-1), (2020, 3, 1), (2020, 2, 29)); + test_d(Step::Day(-1), (2020, 2, 29), (2020, 2, 28)); + test_d(Step::Day(-1), (2021, 3, 1), (2021, 2, 28)); + + // Doesn't touch time + test_dt(Step::Day(1), (2021, 7, 3, 12, 34), (2021, 7, 4, 12, 34)); + } + + #[test] + fn delta_week() { + test_d(Step::Week(-2), (2021, 7, 3), (2021, 6, 19)); + test_d(Step::Week(-1), (2021, 7, 3), (2021, 6, 26)); + test_d(Step::Week(0), (2021, 7, 3), (2021, 7, 3)); + test_d(Step::Week(1), (2021, 7, 3), (2021, 7, 10)); + test_d(Step::Week(2), (2021, 7, 3), (2021, 7, 17)); + + // Leap year shenanigans + test_d(Step::Week(1), (2020, 2, 25), (2020, 3, 3)); + test_d(Step::Week(1), (2021, 2, 25), (2021, 3, 4)); + + // Doesn't touch time + test_dt(Step::Week(1), (2021, 7, 3, 12, 34), (2021, 7, 10, 12, 34)); + } + + #[test] + fn delta_hour() { + test_dt(Step::Hour(-24), (2021, 7, 3, 12, 34), (2021, 7, 2, 12, 34)); + test_dt(Step::Hour(-12), (2021, 7, 3, 12, 34), (2021, 7, 3, 0, 34)); + test_dt(Step::Hour(-2), (2021, 7, 3, 12, 34), (2021, 7, 3, 10, 34)); + test_dt(Step::Hour(-1), (2021, 7, 3, 12, 34), (2021, 7, 3, 11, 34)); + test_dt(Step::Hour(0), (2021, 7, 3, 12, 34), (2021, 7, 3, 12, 34)); + test_dt(Step::Hour(1), (2021, 7, 3, 12, 34), (2021, 7, 3, 13, 34)); + test_dt(Step::Hour(2), (2021, 7, 3, 12, 34), (2021, 7, 3, 14, 34)); + test_dt(Step::Hour(12), (2021, 7, 3, 12, 34), (2021, 7, 4, 0, 34)); + test_dt(Step::Hour(24), (2021, 7, 3, 12, 34), (2021, 7, 4, 12, 34)); + + // 24:00 != 00:00 + test_dt(Step::Hour(1), (2021, 7, 3, 23, 0), (2021, 7, 3, 24, 0)); + test_dt(Step::Hour(2), (2021, 7, 3, 23, 0), (2021, 7, 4, 1, 0)); + test_dt(Step::Hour(-1), (2021, 7, 3, 1, 0), (2021, 7, 3, 0, 0)); + test_dt(Step::Hour(-2), (2021, 7, 3, 1, 0), (2021, 7, 2, 23, 0)); + + // Requires time + assert!(apply_d(Step::Hour(0), (2021, 7, 3)).is_err()); + } + + #[test] + fn delta_minute() { + test_dt( + Step::Minute(-60 * 24), + (2021, 7, 3, 12, 34), + (2021, 7, 2, 12, 34), + ); + test_dt( + Step::Minute(-60), + (2021, 7, 3, 12, 34), + (2021, 7, 3, 11, 34), + ); + test_dt(Step::Minute(-2), (2021, 7, 3, 12, 34), (2021, 7, 3, 12, 32)); + test_dt(Step::Minute(-1), (2021, 7, 3, 12, 34), (2021, 7, 3, 12, 33)); + test_dt(Step::Minute(0), (2021, 7, 3, 12, 34), (2021, 7, 3, 12, 34)); + test_dt(Step::Minute(1), (2021, 7, 3, 12, 34), (2021, 7, 3, 12, 35)); + test_dt(Step::Minute(2), (2021, 7, 3, 12, 34), (2021, 7, 3, 12, 36)); + test_dt(Step::Minute(60), (2021, 7, 3, 12, 34), (2021, 7, 3, 13, 34)); + test_dt( + Step::Minute(60 * 24), + (2021, 7, 3, 12, 34), + (2021, 7, 4, 12, 34), + ); + + // 24:00 != 00:00 + test_dt(Step::Minute(1), (2021, 7, 3, 23, 59), (2021, 7, 3, 24, 0)); + test_dt(Step::Minute(2), (2021, 7, 3, 23, 59), (2021, 7, 4, 0, 1)); + test_dt(Step::Minute(-1), (2021, 7, 3, 0, 1), (2021, 7, 3, 0, 0)); + test_dt(Step::Minute(-2), (2021, 7, 3, 0, 1), (2021, 7, 2, 23, 59)); + + // Requires time + assert!(apply_d(Step::Minute(0), (2021, 7, 3)).is_err()); + } + + #[test] + fn delta_time() { + test_dt( + Step::Time(Time::new(12, 33).unwrap()), + (2021, 7, 3, 12, 34), + (2021, 7, 4, 12, 33), + ); + test_dt( + Step::Time(Time::new(12, 34).unwrap()), + (2021, 7, 3, 12, 34), + (2021, 7, 3, 12, 34), + ); + test_dt( + Step::Time(Time::new(12, 35).unwrap()), + (2021, 7, 3, 12, 34), + (2021, 7, 3, 12, 35), + ); + + // 24:00 != 00:00 + test_dt( + Step::Time(Time::new(24, 0).unwrap()), + (2021, 7, 3, 12, 0), + (2021, 7, 3, 24, 0), + ); + test_dt( + Step::Time(Time::new(0, 0).unwrap()), + (2021, 7, 3, 12, 0), + (2021, 7, 4, 0, 0), + ); + + // Requires time + assert!(apply_d(Step::Time(Time::new(12, 34).unwrap()), (2021, 7, 3)).is_err()); + } +}