factor calculations to weeks_from function and add tests

tests for weeks_from and num_days_from

fix array iter MSRV issue
This commit is contained in:
Eric Sheppard 2023-02-16 12:33:39 +00:00 committed by Dirkjan Ochtman
parent 8197700ccd
commit cf2a2f95f7
4 changed files with 123 additions and 45 deletions

View File

@ -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()))),

View File

@ -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

View File

@ -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() {
@ -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)
}
}
}
}

View File

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