diff --git a/src/eval.rs b/src/eval.rs index c00e42e..aefd33c 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -12,6 +12,7 @@ mod delta; mod entry; mod error; mod range; +mod util; impl Files { pub fn eval(&self, mode: EntryMode, range: DateRange) -> Result> { diff --git a/src/eval/delta.rs b/src/eval/delta.rs index 6c63601..cc533ff 100644 --- a/src/eval/delta.rs +++ b/src/eval/delta.rs @@ -4,7 +4,7 @@ use chrono::{Datelike, Duration, NaiveDate}; use crate::files::commands::{self, Span, Spanned, Time, Weekday}; -use super::{Error, Result}; +use super::{util, Error, Result}; /// Like [`commands::DeltaStep`] but includes a new constructor, /// [`DeltaStep::Time`]. @@ -178,21 +178,22 @@ impl DeltaEval { fn apply(&mut self, step: &Spanned) -> Result<()> { match step.value { - DeltaStep::Year(n) => self.step_year(step.span, n), - DeltaStep::Month(n) => self.step_month(step.span, n), - DeltaStep::MonthReverse(n) => self.step_month_reverse(step.span, n), - DeltaStep::Day(n) => self.step_day(step.span, n), - DeltaStep::Week(n) => self.step_week(step.span, n), - DeltaStep::Hour(n) => self.step_hour(step.span, n), - DeltaStep::Minute(n) => self.step_minute(step.span, n), - DeltaStep::Weekday(n, wd) => self.step_weekday(step.span, n, wd), - DeltaStep::Time(time) => self.step_time(step.span, time), + DeltaStep::Year(n) => self.step_year(step.span, n)?, + DeltaStep::Month(n) => self.step_month(step.span, n)?, + DeltaStep::MonthReverse(n) => self.step_month_reverse(step.span, n)?, + DeltaStep::Day(n) => self.step_day(n), + DeltaStep::Week(n) => self.step_week(n), + DeltaStep::Hour(n) => self.step_hour(step.span, n)?, + DeltaStep::Minute(n) => self.step_minute(step.span, n)?, + DeltaStep::Weekday(n, wd) => self.step_weekday(n, wd), + DeltaStep::Time(time) => self.step_time(step.span, time)?, } + Ok(()) } fn step_year(&mut self, span: Span, amount: i32) -> Result<()> { - let curr = self.curr; - match NaiveDate::from_ymd_opt(curr.year() + amount, curr.month(), curr.day()) { + let year = self.curr.year() + amount; + match NaiveDate::from_ymd_opt(year, self.curr.month(), self.curr.day()) { None => Err(self.err_step(span)), Some(next) => { self.curr = next; @@ -202,10 +203,8 @@ impl DeltaEval { } fn step_month(&mut self, span: Span, amount: i32) -> Result<()> { - let month0 = self.curr.month0() as i32 + amount; - let year = self.curr.year() + month0.div_euclid(12); - let month0 = month0.rem_euclid(12) as u32; - match NaiveDate::from_ymd_opt(year, month0 + 1, self.curr.day()) { + let (year, month) = util::add_months(self.curr.year(), self.curr.month(), amount); + match NaiveDate::from_ymd_opt(year, month, self.curr.day()) { None => Err(self.err_step(span)), Some(next) => { self.curr = next; @@ -215,29 +214,27 @@ impl DeltaEval { } fn step_month_reverse(&mut self, span: Span, amount: i32) -> Result<()> { - todo!() + // Offset from the last day of the month + let end_offset = self.curr.day() - util::month_length(self.curr.year(), self.curr.month()); + let (year, month) = util::add_months(self.curr.year(), self.curr.month(), amount); + let day = end_offset + util::month_length(year, month); + match NaiveDate::from_ymd_opt(year, month, day) { + None => Err(self.err_step(span)), + Some(next) => { + self.curr = next; + Ok(()) + } + } } - fn step_day(&mut self, span: Span, amount: i32) -> Result<()> { + fn step_day(&mut self, amount: i32) { let delta = Duration::days(amount.into()); - match self.curr.checked_add_signed(delta) { - None => Err(self.err_step(span)), - Some(next) => { - self.curr = next; - Ok(()) - } - } + self.curr += delta; } - fn step_week(&mut self, span: Span, amount: i32) -> Result<()> { + fn step_week(&mut self, amount: i32) { let delta = Duration::days((7 * amount).into()); - match self.curr.checked_add_signed(delta) { - None => Err(self.err_step(span)), - Some(next) => { - self.curr = next; - Ok(()) - } - } + self.curr += delta; } fn step_hour(&mut self, span: Span, amount: i32) -> Result<()> { @@ -248,8 +245,18 @@ impl DeltaEval { todo!() } - fn step_weekday(&mut self, span: Span, amount: i32, weekday: Weekday) -> Result<()> { - todo!() + fn step_weekday(&mut self, amount: i32, weekday: Weekday) { + let curr_wd: Weekday = self.curr.weekday().into(); + #[allow(clippy::comparison_chain)] // The if looks better in this case + if amount > 0 { + let rest: i32 = curr_wd.until(weekday).into(); + let days = rest + (amount - 1) * 7; + self.curr += Duration::days(days.into()); + } else if amount < 0 { + let rest: i32 = weekday.until(curr_wd).into(); + let days = rest + (amount - 1) * 7; + self.curr -= Duration::days(days.into()); + } } fn step_time(&mut self, span: Span, time: Time) -> Result<()> { diff --git a/src/eval/util.rs b/src/eval/util.rs new file mode 100644 index 0000000..5a96bf1 --- /dev/null +++ b/src/eval/util.rs @@ -0,0 +1,19 @@ +use chrono::{Datelike, NaiveDate}; + +pub fn is_leap_year(year: i32) -> bool { + NaiveDate::from_ymd_opt(year, 2, 29).is_some() +} + +pub fn add_months(year: i32, month: u32, delta: i32) -> (i32, u32) { + let month0 = (month as i32) - 1 + delta; + let year = year + month0.div_euclid(12); + let month = month0.rem_euclid(12) as u32 + 1; + (year, month) +} + +pub fn month_length(year: i32, month: u32) -> u32 { + NaiveDate::from_ymd_opt(year, month + 1, 1) + .unwrap_or_else(|| NaiveDate::from_ymd(year + 1, 1, 1)) + .pred() + .day() +} diff --git a/src/files/commands.rs b/src/files/commands.rs index 34fe17b..d069de7 100644 --- a/src/files/commands.rs +++ b/src/files/commands.rs @@ -79,6 +79,20 @@ pub enum Weekday { Sunday, } +impl From for Weekday { + fn from(wd: chrono::Weekday) -> Self { + match wd { + chrono::Weekday::Mon => Self::Monday, + chrono::Weekday::Tue => Self::Tuesday, + chrono::Weekday::Wed => Self::Wednesday, + chrono::Weekday::Thu => Self::Thursday, + chrono::Weekday::Fri => Self::Friday, + chrono::Weekday::Sat => Self::Saturday, + chrono::Weekday::Sun => Self::Sunday, + } + } +} + impl Weekday { pub fn name(&self) -> &'static str { match self { @@ -91,6 +105,29 @@ impl Weekday { Weekday::Sunday => "sun", } } + + pub fn num(&self) -> u8 { + match self { + Weekday::Monday => 1, + Weekday::Tuesday => 2, + Weekday::Wednesday => 3, + Weekday::Thursday => 4, + Weekday::Friday => 5, + Weekday::Saturday => 6, + Weekday::Sunday => 7, + } + } + + /// How many days from now until the other weekday. + pub fn until(&self, other: Weekday) -> u8 { + let num_self = self.num(); + let num_other = other.num(); + if num_self <= num_other { + num_other - num_self + } else { + num_other + 7 - num_self + } + } } #[derive(Debug, Clone, Copy)]