From cf2a2f95f7030860b0eda2b78eff968f1d7b4228 Mon Sep 17 00:00:00 2001 From: Eric Sheppard Date: Thu, 16 Feb 2023 12:33:39 +0000 Subject: [PATCH] factor calculations to weeks_from function and add tests tests for weeks_from and num_days_from fix array iter MSRV issue --- src/format/mod.rs | 8 +--- src/format/parsed.rs | 5 +-- src/naive/date.rs | 66 ++++++++++++++++++++++++++++++++ src/weekday.rs | 89 ++++++++++++++++++++++++++------------------ 4 files changed, 123 insertions(+), 45 deletions(-) diff --git a/src/format/mod.rs b/src/format/mod.rs index e3b9b51344..c6b8eaee09 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -520,12 +520,8 @@ fn format_inner( Item::Numeric(ref spec, ref pad) => { use self::Numeric::*; - let week_from_sun = |d: &NaiveDate| { - (d.ordinal() as i32 - d.weekday().num_days_from_sunday() as i32 + 6) / 7 - }; - let week_from_mon = |d: &NaiveDate| { - (d.ordinal() as i32 - d.weekday().num_days_from_monday() as i32 + 6) / 7 - }; + let week_from_sun = |d: &NaiveDate| d.weeks_from(Weekday::Sun); + let week_from_mon = |d: &NaiveDate| d.weeks_from(Weekday::Mon); let (width, v) = match *spec { Year => (4, date.map(|d| i64::from(d.year()))), diff --git a/src/format/parsed.rs b/src/format/parsed.rs index eb697e12bf..6cc29e9d40 100644 --- a/src/format/parsed.rs +++ b/src/format/parsed.rs @@ -379,9 +379,8 @@ impl Parsed { // verify the ordinal and other (non-ISO) week dates. let verify_ordinal = |date: NaiveDate| { let ordinal = date.ordinal(); - let weekday = date.weekday(); - let week_from_sun = (ordinal as i32 - weekday.num_days_from_sunday() as i32 + 6) / 7; - let week_from_mon = (ordinal as i32 - weekday.num_days_from_monday() as i32 + 6) / 7; + let week_from_sun = date.weeks_from(Weekday::Sun); + let week_from_mon = date.weeks_from(Weekday::Mon); self.ordinal.unwrap_or(ordinal) == ordinal && self.week_from_sun.map_or(week_from_sun, |v| v as i32) == week_from_sun && self.week_from_mon.map_or(week_from_mon, |v| v as i32) == week_from_mon diff --git a/src/naive/date.rs b/src/naive/date.rs index eccbcf2ff4..46c2092513 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -236,6 +236,9 @@ fn test_date_bounds() { } impl NaiveDate { + pub(crate) fn weeks_from(&self, day: Weekday) -> i32 { + (self.ordinal() as i32 - self.weekday().num_days_from(day) as i32 + 6) / 7 + } /// Makes a new `NaiveDate` from year and packed ordinal-flags, with a verification. fn from_of(year: i32, of: Of) -> Option { if (MIN_YEAR..=MAX_YEAR).contains(&year) && of.valid() { @@ -2928,4 +2931,67 @@ mod tests { assert!(days.contains(&date)); } } + + #[test] + fn test_weeks_from() { + // tests per: https://github.com/chronotope/chrono/issues/961 + // these internally use `weeks_from` via the parsing infrastructure + assert_eq!( + NaiveDate::parse_from_str("2020-01-0", "%Y-%W-%w").ok(), + NaiveDate::from_ymd_opt(2020, 1, 12), + ); + assert_eq!( + NaiveDate::parse_from_str("2019-01-0", "%Y-%W-%w").ok(), + NaiveDate::from_ymd_opt(2019, 1, 13), + ); + + // direct tests + for (y, starts_on) in &[ + (2019, Weekday::Tue), + (2020, Weekday::Wed), + (2021, Weekday::Fri), + (2022, Weekday::Sat), + (2023, Weekday::Sun), + (2024, Weekday::Mon), + (2025, Weekday::Wed), + (2026, Weekday::Thu), + ] { + for day in &[ + Weekday::Mon, + Weekday::Tue, + Weekday::Wed, + Weekday::Thu, + Weekday::Fri, + Weekday::Sat, + Weekday::Sun, + ] { + assert_eq!( + NaiveDate::from_ymd_opt(*y, 1, 1).map(|d| d.weeks_from(*day)), + Some(if day == starts_on { 1 } else { 0 }) + ); + + // last day must always be in week 52 or 53 + assert!([52, 53] + .contains(&NaiveDate::from_ymd_opt(*y, 12, 31).unwrap().weeks_from(*day)),); + } + } + + let base = NaiveDate::from_ymd_opt(2019, 1, 1).unwrap(); + + // 400 years covers all year types + for day in &[ + Weekday::Mon, + Weekday::Tue, + Weekday::Wed, + Weekday::Thu, + Weekday::Fri, + Weekday::Sat, + Weekday::Sun, + ] { + // must always be below 54 + for dplus in 1..(400 * 366) { + assert!((base + Days::new(dplus)).weeks_from(*day) < 54) + } + } + } } diff --git a/src/weekday.rs b/src/weekday.rs index bd30e1934d..c12dd01c64 100644 --- a/src/weekday.rs +++ b/src/weekday.rs @@ -73,15 +73,7 @@ impl Weekday { /// `w.number_from_monday()`: | 1 | 2 | 3 | 4 | 5 | 6 | 7 #[inline] pub fn number_from_monday(&self) -> u32 { - match *self { - Weekday::Mon => 1, - Weekday::Tue => 2, - Weekday::Wed => 3, - Weekday::Thu => 4, - Weekday::Fri => 5, - Weekday::Sat => 6, - Weekday::Sun => 7, - } + self.num_days_from(Weekday::Mon) + 1 } /// Returns a day-of-week number starting from Sunday = 1. @@ -91,15 +83,7 @@ impl Weekday { /// `w.number_from_sunday()`: | 2 | 3 | 4 | 5 | 6 | 7 | 1 #[inline] pub fn number_from_sunday(&self) -> u32 { - match *self { - Weekday::Mon => 2, - Weekday::Tue => 3, - Weekday::Wed => 4, - Weekday::Thu => 5, - Weekday::Fri => 6, - Weekday::Sat => 7, - Weekday::Sun => 1, - } + self.num_days_from(Weekday::Sun) + 1 } /// Returns a day-of-week number starting from Monday = 0. @@ -109,15 +93,7 @@ impl Weekday { /// `w.num_days_from_monday()`: | 0 | 1 | 2 | 3 | 4 | 5 | 6 #[inline] pub fn num_days_from_monday(&self) -> u32 { - match *self { - Weekday::Mon => 0, - Weekday::Tue => 1, - Weekday::Wed => 2, - Weekday::Thu => 3, - Weekday::Fri => 4, - Weekday::Sat => 5, - Weekday::Sun => 6, - } + self.num_days_from(Weekday::Mon) } /// Returns a day-of-week number starting from Sunday = 0. @@ -127,15 +103,17 @@ impl Weekday { /// `w.num_days_from_sunday()`: | 1 | 2 | 3 | 4 | 5 | 6 | 0 #[inline] pub fn num_days_from_sunday(&self) -> u32 { - match *self { - Weekday::Mon => 1, - Weekday::Tue => 2, - Weekday::Wed => 3, - Weekday::Thu => 4, - Weekday::Fri => 5, - Weekday::Sat => 6, - Weekday::Sun => 0, - } + self.num_days_from(Weekday::Sun) + } + + /// Returns a day-of-week number starting from the parameter `day` (D) = 0. + /// + /// `w`: | `D` | `D+1` | `D+2` | `D+3` | `D+4` | `D+5` | `D+6` + /// --------------------------- | ----- | ----- | ----- | ----- | ----- | ----- | ----- + /// `w.num_days_from(wd)`: | 0 | 1 | 2 | 3 | 4 | 5 | 6 + #[inline] + pub(crate) fn num_days_from(&self, day: Weekday) -> u32 { + (*self as u32 + 7 - day as u32) % 7 } } @@ -208,6 +186,45 @@ impl fmt::Debug for ParseWeekdayError { } } +#[cfg(test)] +mod tests { + use num_traits::FromPrimitive; + + use super::Weekday; + + #[test] + fn test_num_days_from() { + for i in 0..7 { + let base_day = Weekday::from_u64(i).unwrap(); + + assert_eq!(base_day.num_days_from_monday(), base_day.num_days_from(Weekday::Mon)); + assert_eq!(base_day.num_days_from_sunday(), base_day.num_days_from(Weekday::Sun)); + + assert_eq!(base_day.num_days_from(base_day), 0); + + assert_eq!(base_day.num_days_from(base_day.pred()), 1); + assert_eq!(base_day.num_days_from(base_day.pred().pred()), 2); + assert_eq!(base_day.num_days_from(base_day.pred().pred().pred()), 3); + assert_eq!(base_day.num_days_from(base_day.pred().pred().pred().pred()), 4); + assert_eq!(base_day.num_days_from(base_day.pred().pred().pred().pred().pred()), 5); + assert_eq!( + base_day.num_days_from(base_day.pred().pred().pred().pred().pred().pred()), + 6 + ); + + assert_eq!(base_day.num_days_from(base_day.succ()), 6); + assert_eq!(base_day.num_days_from(base_day.succ().succ()), 5); + assert_eq!(base_day.num_days_from(base_day.succ().succ().succ()), 4); + assert_eq!(base_day.num_days_from(base_day.succ().succ().succ().succ()), 3); + assert_eq!(base_day.num_days_from(base_day.succ().succ().succ().succ().succ()), 2); + assert_eq!( + base_day.num_days_from(base_day.succ().succ().succ().succ().succ().succ()), + 1 + ); + } + } +} + // the actual `FromStr` implementation is in the `format` module to leverage the existing code #[cfg(feature = "serde")]