Skip to content

Commit

Permalink
factor calculations to weeks_from function and add tests
Browse files Browse the repository at this point in the history
tests for weeks_from and num_days_from

fix array iter MSRV issue
  • Loading branch information
esheppa authored and djc committed Mar 4, 2023
1 parent 8197700 commit cf2a2f9
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 45 deletions.
8 changes: 2 additions & 6 deletions src/format/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()))),
Expand Down
5 changes: 2 additions & 3 deletions src/format/parsed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
66 changes: 66 additions & 0 deletions src/naive/date.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<NaiveDate> {
if (MIN_YEAR..=MAX_YEAR).contains(&year) && of.valid() {
Expand Down Expand Up @@ -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)
}
}
}
}
89 changes: 53 additions & 36 deletions src/weekday.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
Expand All @@ -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.
Expand All @@ -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
}
}

Expand Down Expand Up @@ -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")]
Expand Down

0 comments on commit cf2a2f9

Please sign in to comment.