diff --git a/src/naive/date.rs b/src/naive/date.rs index 6b5c9f559f..b8aafdee87 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -38,7 +38,6 @@ use crate::{expect, try_opt}; use crate::{Datelike, TimeDelta, Weekday}; use super::internals::{self, Mdf, Of, YearFlags}; -use super::isoweek; /// A week represented by a [`NaiveDate`] and a [`Weekday`] which is the first /// day of the week. @@ -1474,10 +1473,24 @@ impl NaiveDate { self.mdf().day() } + /// Returns the day of week. // This duplicates `Datelike::weekday()`, because trait methods can't be const yet. #[inline] const fn weekday(&self) -> Weekday { - self.of().weekday() + match (((self.yof & ORDINAL_MASK) >> 4) + (self.yof & WEEKDAY_FLAGS_MASK)) % 7 { + 0 => Weekday::Mon, + 1 => Weekday::Tue, + 2 => Weekday::Wed, + 3 => Weekday::Thu, + 4 => Weekday::Fri, + 5 => Weekday::Sat, + _ => Weekday::Sun, + } + } + + #[inline] + const fn year_flags(&self) -> YearFlags { + YearFlags((self.yof & YEAR_FLAGS_MASK) as u8) } /// Counts the days in the proleptic Gregorian calendar, with January 1, Year 1 (CE) as day 1. @@ -1689,7 +1702,7 @@ impl Datelike for NaiveDate { #[inline] fn iso_week(&self) -> IsoWeek { - isoweek::iso_week_from_yof(self.year(), self.of()) + IsoWeek::from_yof(self.year(), self.ordinal(), self.year_flags()) } /// Makes a new `NaiveDate` with the year number changed, while keeping the same month and day. @@ -2333,6 +2346,12 @@ const LEAP_YEAR_MASK: i32 = 0b1000; const OL_MASK: i32 = ORDINAL_MASK | LEAP_YEAR_MASK; const MAX_OL: i32 = 366 << 4; +// Weekday of the last day in the preceding year. +// Allows for quick day of week calculation from the 1-based ordinal. +const WEEKDAY_FLAGS_MASK: i32 = 0b111; + +const YEAR_FLAGS_MASK: i32 = LEAP_YEAR_MASK | WEEKDAY_FLAGS_MASK; + #[cfg(all(test, any(feature = "rustc-serialize", feature = "serde")))] fn test_encodable_json(to_string: F) where @@ -2517,7 +2536,7 @@ mod serde { #[cfg(test)] mod tests { use super::{Days, Months, NaiveDate, MAX_YEAR, MIN_YEAR}; - use crate::naive::internals::YearFlags; + use crate::naive::internals::{YearFlags, A, AG, B, BA, C, CB, D, DC, E, ED, F, FE, G, GF}; use crate::{Datelike, TimeDelta, Weekday}; // as it is hard to verify year flags in `NaiveDate::MIN` and `NaiveDate::MAX`, @@ -2529,12 +2548,12 @@ mod tests { assert!( NaiveDate::MIN == calculated_min, "`NaiveDate::MIN` should have year flag {:?}", - calculated_min.of().flags() + calculated_min.year_flags() ); assert!( NaiveDate::MAX == calculated_max, "`NaiveDate::MAX` should have year flag {:?} and ordinal {}", - calculated_max.of().flags(), + calculated_max.year_flags(), calculated_max.ordinal() ); @@ -2549,11 +2568,11 @@ mod tests { ); const BEFORE_MIN: NaiveDate = NaiveDate::BEFORE_MIN; - assert_eq!(BEFORE_MIN.of().flags(), YearFlags::from_year(BEFORE_MIN.year())); + assert_eq!(BEFORE_MIN.year_flags(), YearFlags::from_year(BEFORE_MIN.year())); assert_eq!((BEFORE_MIN.month(), BEFORE_MIN.day()), (12, 31)); const AFTER_MAX: NaiveDate = NaiveDate::AFTER_MAX; - assert_eq!(AFTER_MAX.of().flags(), YearFlags::from_year(AFTER_MAX.year())); + assert_eq!(AFTER_MAX.year_flags(), YearFlags::from_year(AFTER_MAX.year())); assert_eq!((AFTER_MAX.month(), AFTER_MAX.day()), (1, 1)); } @@ -3376,6 +3395,61 @@ mod tests { } } + #[test] + fn test_date_yearflags() { + for (year, year_flags, _) in YEAR_FLAGS { + assert_eq!(NaiveDate::from_yo_opt(year, 1).unwrap().year_flags(), year_flags); + } + } + + #[test] + fn test_weekday_with_yearflags() { + for (year, year_flags, first_weekday) in YEAR_FLAGS { + let first_day_of_year = NaiveDate::from_yo_opt(year, 1).unwrap(); + dbg!(year); + assert_eq!(first_day_of_year.year_flags(), year_flags); + assert_eq!(first_day_of_year.weekday(), first_weekday); + + let mut prev = first_day_of_year.weekday(); + for ordinal in 2u32..=year_flags.ndays() { + let date = NaiveDate::from_yo_opt(year, ordinal).unwrap(); + let expected = prev.succ(); + assert_eq!(date.weekday(), expected); + prev = expected; + } + } + } + + #[test] + fn test_isoweekdate_with_yearflags() { + for (year, year_flags, _) in YEAR_FLAGS { + // January 4 should be in the first week + let jan4 = NaiveDate::from_ymd_opt(year, 1, 4).unwrap(); + let iso_week = jan4.iso_week(); + assert_eq!(jan4.year_flags(), year_flags); + assert_eq!(iso_week.week(), 1); + } + } + + // Used for testing some methods with all combinations of `YearFlags`. + // (year, flags, first weekday of year) + const YEAR_FLAGS: [(i32, YearFlags, Weekday); 14] = [ + (2006, A, Weekday::Sun), + (2005, B, Weekday::Sat), + (2010, C, Weekday::Fri), + (2009, D, Weekday::Thu), + (2003, E, Weekday::Wed), + (2002, F, Weekday::Tue), + (2001, G, Weekday::Mon), + (2012, AG, Weekday::Sun), + (2000, BA, Weekday::Sat), + (2016, CB, Weekday::Fri), + (2004, DC, Weekday::Thu), + (2020, ED, Weekday::Wed), + (2008, FE, Weekday::Tue), + (2024, GF, Weekday::Mon), + ]; + #[test] #[cfg(feature = "rkyv-validation")] fn test_rkyv_validation() { diff --git a/src/naive/internals.rs b/src/naive/internals.rs index 0af7d44923..95690d0364 100644 --- a/src/naive/internals.rs +++ b/src/naive/internals.rs @@ -15,7 +15,6 @@ #![cfg_attr(feature = "__internal_bench", allow(missing_docs))] -use crate::Weekday; use core::fmt; /// The year flags (aka the dominical letter). @@ -266,13 +265,6 @@ const OL_TO_MDL: &[u8; MAX_OL as usize + 1] = &[ pub(super) struct Of(u32); impl Of { - #[inline] - #[cfg(test)] - pub(super) const fn new(ordinal: u32, YearFlags(flags): YearFlags) -> Option { - let of = Of((ordinal << 4) | flags as u32); - of.validate() - } - pub(super) const fn from_date_impl(date_impl: i32) -> Of { // We assume the value in `date_impl` is valid. Of((date_impl & 0b1_1111_1111_1111) as u32) @@ -311,25 +303,6 @@ impl Of { } } - #[inline] - pub(super) const fn flags(&self) -> YearFlags { - YearFlags((self.0 & 0b1111) as u8) - } - - #[inline] - pub(super) const fn weekday(&self) -> Weekday { - let Of(of) = *self; - weekday_from_u32_mod7((of >> 4) + (of & 0b111)) - } - - #[inline] - pub(super) fn isoweekdate_raw(&self) -> (u32, Weekday) { - // week ordinal = ordinal + delta - let Of(of) = *self; - let weekord = (of >> 4).wrapping_add(self.flags().isoweek_delta()); - (weekord / 7, weekday_from_u32_mod7(weekord)) - } - #[cfg_attr(feature = "cargo-clippy", allow(clippy::wrong_self_convention))] #[inline] pub(super) const fn to_mdf(&self) -> Mdf { @@ -455,27 +428,10 @@ impl fmt::Debug for Mdf { } } -/// Create a `Weekday` from an `u32`, with Monday = 0. -/// Infallible, takes any `n` and applies `% 7`. -#[inline] -const fn weekday_from_u32_mod7(n: u32) -> Weekday { - match n % 7 { - 0 => Weekday::Mon, - 1 => Weekday::Tue, - 2 => Weekday::Wed, - 3 => Weekday::Thu, - 4 => Weekday::Fri, - 5 => Weekday::Sat, - _ => Weekday::Sun, - } -} - #[cfg(test)] mod tests { - use super::weekday_from_u32_mod7; use super::{Mdf, Of}; use super::{YearFlags, A, AG, B, BA, C, CB, D, DC, E, ED, F, FE, G, GF}; - use crate::Weekday; const NONLEAP_FLAGS: [YearFlags; 7] = [A, B, C, D, E, F, G]; const LEAP_FLAGS: [YearFlags; 7] = [AG, BA, CB, DC, ED, FE, GF]; @@ -516,42 +472,6 @@ mod tests { assert_eq!(GF.nisoweeks(), 52); } - #[test] - fn test_of() { - fn check(expected: bool, flags: YearFlags, ordinal1: u32, ordinal2: u32) { - for ordinal in ordinal1..=ordinal2 { - let of = match Of::new(ordinal, flags) { - Some(of) => of, - None if !expected => continue, - None => panic!("Of::new({}, {:?}) returned None", ordinal, flags), - }; - - assert!( - of.validate().is_some() == expected, - "ordinal {} = {:?} should be {} for dominical year {:?}", - ordinal, - of, - if expected { "valid" } else { "invalid" }, - flags - ); - } - } - - for &flags in NONLEAP_FLAGS.iter() { - check(false, flags, 0, 0); - check(true, flags, 1, 365); - check(false, flags, 366, 1024); - check(false, flags, u32::MAX, u32::MAX); - } - - for &flags in LEAP_FLAGS.iter() { - check(false, flags, 0, 0); - check(true, flags, 1, 366); - check(false, flags, 367, 1024); - check(false, flags, u32::MAX, u32::MAX); - } - } - #[test] fn test_mdf_valid() { fn check(expected: bool, flags: YearFlags, month1: u32, day1: u32, month2: u32, day2: u32) { @@ -643,34 +563,6 @@ mod tests { } } - #[test] - fn test_of_weekday() { - assert_eq!(Of::new(1, A).unwrap().weekday(), Weekday::Sun); - assert_eq!(Of::new(1, B).unwrap().weekday(), Weekday::Sat); - assert_eq!(Of::new(1, C).unwrap().weekday(), Weekday::Fri); - assert_eq!(Of::new(1, D).unwrap().weekday(), Weekday::Thu); - assert_eq!(Of::new(1, E).unwrap().weekday(), Weekday::Wed); - assert_eq!(Of::new(1, F).unwrap().weekday(), Weekday::Tue); - assert_eq!(Of::new(1, G).unwrap().weekday(), Weekday::Mon); - assert_eq!(Of::new(1, AG).unwrap().weekday(), Weekday::Sun); - assert_eq!(Of::new(1, BA).unwrap().weekday(), Weekday::Sat); - assert_eq!(Of::new(1, CB).unwrap().weekday(), Weekday::Fri); - assert_eq!(Of::new(1, DC).unwrap().weekday(), Weekday::Thu); - assert_eq!(Of::new(1, ED).unwrap().weekday(), Weekday::Wed); - assert_eq!(Of::new(1, FE).unwrap().weekday(), Weekday::Tue); - assert_eq!(Of::new(1, GF).unwrap().weekday(), Weekday::Mon); - - for &flags in FLAGS.iter() { - let mut prev = Of::new(1, flags).unwrap().weekday(); - for ordinal in 2u32..=flags.ndays() { - let of = Of::new(ordinal, flags).unwrap(); - let expected = prev.succ(); - assert_eq!(of.weekday(), expected); - prev = expected; - } - } - } - #[test] fn test_mdf_fields() { for &flags in FLAGS.iter() { @@ -740,15 +632,6 @@ mod tests { } } - #[test] - fn test_of_isoweekdate_raw() { - for &flags in FLAGS.iter() { - // January 4 should be in the first week - let (week, _) = Of::new(4 /* January 4 */, flags).unwrap().isoweekdate_raw(); - assert_eq!(week, 1); - } - } - #[test] fn test_of_to_mdf() { for i in 0u32..=8192 { @@ -789,11 +672,6 @@ mod tests { fn test_invalid_returns_none() { let regular_year = YearFlags::from_year(2023); let leap_year = YearFlags::from_year(2024); - assert!(Of::new(0, regular_year).is_none()); - assert!(Of::new(366, regular_year).is_none()); - assert!(Of::new(366, leap_year).is_some()); - assert!(Of::new(367, regular_year).is_none()); - assert!(Mdf::new(0, 1, regular_year).is_none()); assert!(Mdf::new(13, 1, regular_year).is_none()); assert!(Mdf::new(1, 0, regular_year).is_none()); @@ -806,12 +684,4 @@ mod tests { assert!(Of::from_mdf(Mdf::new(2, 29, leap_year).unwrap()).is_some()); assert!(Of::from_mdf(Mdf::new(2, 28, regular_year).unwrap()).is_some()); } - - #[test] - fn test_weekday_from_u32_mod7() { - for i in 0..=1000 { - assert_eq!(weekday_from_u32_mod7(i), Weekday::try_from((i % 7) as u8).unwrap()); - } - assert_eq!(weekday_from_u32_mod7(u32::MAX), Weekday::Thu); - } } diff --git a/src/naive/isoweek.rs b/src/naive/isoweek.rs index c4649db557..1d0ddfba36 100644 --- a/src/naive/isoweek.rs +++ b/src/naive/isoweek.rs @@ -5,7 +5,7 @@ use core::fmt; -use super::internals::{Of, YearFlags}; +use super::internals::YearFlags; #[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] use rkyv::{Archive, Deserialize, Serialize}; @@ -31,32 +31,32 @@ pub struct IsoWeek { ywf: i32, // (year << 10) | (week << 4) | flag } -/// Returns the corresponding `IsoWeek` from the year and the `Of` internal value. -// -// internal use only. we don't expose the public constructor for `IsoWeek` for now, -// because the year range for the week date and the calendar date do not match and -// it is confusing to have a date that is out of range in one and not in another. -// currently we sidestep this issue by making `IsoWeek` fully dependent of `Datelike`. -pub(super) fn iso_week_from_yof(year: i32, of: Of) -> IsoWeek { - let (rawweek, _) = of.isoweekdate_raw(); - let (year, week) = if rawweek < 1 { - // previous year - let prevlastweek = YearFlags::from_year(year - 1).nisoweeks(); - (year - 1, prevlastweek) - } else { - let lastweek = of.flags().nisoweeks(); - if rawweek > lastweek { - // next year - (year + 1, 1) +impl IsoWeek { + /// Returns the corresponding `IsoWeek` from the year and the `Of` internal value. + // + // internal use only. we don't expose the public constructor for `IsoWeek` for now, + // because the year range for the week date and the calendar date do not match and + // it is confusing to have a date that is out of range in one and not in another. + // currently we sidestep this issue by making `IsoWeek` fully dependent of `Datelike`. + pub(super) fn from_yof(year: i32, ordinal: u32, year_flags: YearFlags) -> Self { + let rawweek = (ordinal + year_flags.isoweek_delta()) / 7; + let (year, week) = if rawweek < 1 { + // previous year + let prevlastweek = YearFlags::from_year(year - 1).nisoweeks(); + (year - 1, prevlastweek) } else { - (year, rawweek) - } - }; - let flags = YearFlags::from_year(year); - IsoWeek { ywf: (year << 10) | (week << 4) as i32 | i32::from(flags.0) } -} + let lastweek = year_flags.nisoweeks(); + if rawweek > lastweek { + // next year + (year + 1, 1) + } else { + (year, rawweek) + } + }; + let flags = YearFlags::from_year(year); + IsoWeek { ywf: (year << 10) | (week << 4) as i32 | i32::from(flags.0) } + } -impl IsoWeek { /// Returns the year number for this ISO week. /// /// # Example