diff --git a/src/naive/date.rs b/src/naive/date.rs index 190a6bac60..990164fb35 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -2,6 +2,16 @@ // See README.md and LICENSE.txt for details. //! ISO 8601 calendar date without timezone. +//! +//! The implementation is optimized for determining year, month, day and day of week. +//! +//! Format of `NaiveDate`: +//! `YYYY_YYYY_YYYY_YYYY_YYYO_OOOO_OOOO_LWWW` +//! `Y`: Year +//! `O`: Ordinal +//! `L`: leap year flag (1 = common year, 0 is leap year) +//! `W`: weekday before the first day of the year +//! `LWWW`: will also be referred to as the year flags (`F`) #[cfg(feature = "alloc")] use core::borrow::Borrow; @@ -27,11 +37,7 @@ use crate::naive::{IsoWeek, NaiveDateTime, NaiveTime}; use crate::{expect, try_opt}; use crate::{Datelike, TimeDelta, Weekday}; -use super::internals::{self, DateImpl, Mdf, Of, YearFlags}; -use super::isoweek; - -const MAX_YEAR: i32 = internals::MAX_YEAR; -const MIN_YEAR: i32 = internals::MIN_YEAR; +use super::internals::{self, Mdf, YearFlags}; /// A week represented by a [`NaiveDate`] and a [`Weekday`] which is the first /// day of the week. @@ -196,7 +202,7 @@ impl Days { )] #[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] pub struct NaiveDate { - ymdf: DateImpl, // (year << 13) | of + yof: i32, // (year << 13) | of } /// The minimum possible `NaiveDate` (January 1, 262145 BCE). @@ -231,21 +237,22 @@ impl NaiveDate { if year < MIN_YEAR || year > MAX_YEAR { return None; // Out-of-range } + if ordinal == 0 || ordinal > 366 { + return None; // Invalid + } debug_assert!(YearFlags::from_year(year).0 == flags.0); - match Of::new(ordinal, flags) { - Some(of) => Some(NaiveDate { ymdf: (year << 13) | (of.inner() as DateImpl) }), - None => None, // Invalid: Ordinal outside of the nr of days in a year with those flags. + let yof = (year << 13) | (ordinal << 4) as i32 | flags.0 as i32; + match yof & OL_MASK <= MAX_OL { + true => Some(NaiveDate { yof }), + false => None, // Does not exist: Ordinal 366 in a common year. } } /// Makes a new `NaiveDate` from year and packed month-day-flags. /// Does not check whether the flags are correct for the provided year. const fn from_mdf(year: i32, mdf: Mdf) -> Option { - if year < MIN_YEAR || year > MAX_YEAR { - return None; // Out-of-range - } - match mdf.to_of() { - Some(of) => Some(NaiveDate { ymdf: (year << 13) | (of.inner() as DateImpl) }), + match mdf.ordinal() { + Some(ordinal) => NaiveDate::from_ordinal_and_flags(year, ordinal, mdf.year_flags()), None => None, // Non-existing date } } @@ -787,16 +794,16 @@ impl NaiveDate { pub(crate) const fn add_days(self, days: i32) -> Option { // fast path if the result is within the same year const ORDINAL_MASK: i32 = 0b1_1111_1111_0000; - if let Some(ordinal) = ((self.ymdf & ORDINAL_MASK) >> 4).checked_add(days) { + if let Some(ordinal) = ((self.yof & ORDINAL_MASK) >> 4).checked_add(days) { if ordinal > 0 && ordinal <= 365 { - let year_and_flags = self.ymdf & !ORDINAL_MASK; - return Some(NaiveDate { ymdf: year_and_flags | (ordinal << 4) }); + let year_and_flags = self.yof & !ORDINAL_MASK; + return Some(NaiveDate { yof: year_and_flags | (ordinal << 4) }); } } // do the full check let year = self.year(); let (mut year_div_400, year_mod_400) = div_mod_floor(year, 400); - let cycle = internals::yo_to_cycle(year_mod_400 as u32, self.of().ordinal()); + let cycle = internals::yo_to_cycle(year_mod_400 as u32, self.ordinal()); let cycle = try_opt!((cycle as i32).checked_add(days)); let (cycle_div_400y, cycle) = div_mod_floor(cycle, 146_097); year_div_400 += cycle_div_400y; @@ -1035,13 +1042,7 @@ impl NaiveDate { /// Returns the packed month-day-flags. #[inline] const fn mdf(&self) -> Mdf { - self.of().to_mdf() - } - - /// Returns the packed ordinal-flags. - #[inline] - const fn of(&self) -> Of { - Of::from_date_impl(self.ymdf) + Mdf::from_ol((self.yof & OL_MASK) >> 3, self.year_flags()) } /// Makes a new `NaiveDate` with the packed month-day-flags changed. @@ -1049,16 +1050,13 @@ impl NaiveDate { /// Returns `None` when the resulting `NaiveDate` would be invalid. #[inline] const fn with_mdf(&self, mdf: Mdf) -> Option { - Some(self.with_of(try_opt!(mdf.to_of()))) - } - - /// Makes a new `NaiveDate` with the packed ordinal-flags changed. - /// - /// Returns `None` when the resulting `NaiveDate` would be invalid. - /// Does not check if the year flags match the year. - #[inline] - const fn with_of(&self, of: Of) -> NaiveDate { - NaiveDate { ymdf: (self.ymdf & !0b1_1111_1111_1111) | of.inner() as DateImpl } + debug_assert!(self.year_flags().0 == mdf.year_flags().0); + match mdf.ordinal() { + Some(ordinal) => { + Some(NaiveDate { yof: (self.yof & !ORDINAL_MASK) | (ordinal << 4) as i32 }) + } + None => None, // Non-existing date + } } /// Makes a new `NaiveDate` for the next calendar date. @@ -1091,9 +1089,10 @@ impl NaiveDate { #[inline] #[must_use] pub const fn succ_opt(&self) -> Option { - match self.of().succ() { - Some(of) => Some(self.with_of(of)), - None => NaiveDate::from_ymd_opt(self.year() + 1, 1, 1), + let new_ol = (self.yof & OL_MASK) + (1 << 4); + match new_ol <= MAX_OL { + true => Some(NaiveDate { yof: self.yof & !OL_MASK | new_ol }), + false => NaiveDate::from_yo_opt(self.year() + 1, 1), } } @@ -1127,9 +1126,10 @@ impl NaiveDate { #[inline] #[must_use] pub const fn pred_opt(&self) -> Option { - match self.of().pred() { - Some(of) => Some(self.with_of(of)), - None => NaiveDate::from_ymd_opt(self.year() - 1, 12, 31), + let new_shifted_ordinal = (self.yof & ORDINAL_MASK) - (1 << 4); + match new_shifted_ordinal > 0 { + true => Some(NaiveDate { yof: self.yof & !ORDINAL_MASK | new_shifted_ordinal }), + false => NaiveDate::from_ymd_opt(self.year() - 1, 12, 31), } } @@ -1219,8 +1219,8 @@ impl NaiveDate { let year2 = rhs.year(); let (year1_div_400, year1_mod_400) = div_mod_floor(year1, 400); let (year2_div_400, year2_mod_400) = div_mod_floor(year2, 400); - let cycle1 = internals::yo_to_cycle(year1_mod_400 as u32, self.of().ordinal()) as i64; - let cycle2 = internals::yo_to_cycle(year2_mod_400 as u32, rhs.of().ordinal()) as i64; + let cycle1 = internals::yo_to_cycle(year1_mod_400 as u32, self.ordinal()) as i64; + let cycle2 = internals::yo_to_cycle(year2_mod_400 as u32, rhs.ordinal()) as i64; TimeDelta::days((year1_div_400 as i64 - year2_div_400 as i64) * 146_097 + (cycle1 - cycle2)) } @@ -1433,20 +1433,20 @@ impl NaiveDate { /// assert_eq!(NaiveDate::from_ymd_opt(2100, 1, 1).unwrap().leap_year(), false); /// ``` pub const fn leap_year(&self) -> bool { - self.ymdf & (0b1000) == 0 + self.yof & (0b1000) == 0 } // This duplicates `Datelike::year()`, because trait methods can't be const yet. #[inline] const fn year(&self) -> i32 { - self.ymdf >> 13 + self.yof >> 13 } /// Returns the day of year starting from 1. // This duplicates `Datelike::ordinal()`, because trait methods can't be const yet. #[inline] const fn ordinal(&self) -> u32 { - self.of().ordinal() + ((self.yof & ORDINAL_MASK) >> 4) as u32 } // This duplicates `Datelike::month()`, because trait methods can't be const yet. @@ -1461,10 +1461,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. @@ -1484,16 +1498,16 @@ impl NaiveDate { } /// The minimum possible `NaiveDate` (January 1, 262144 BCE). - pub const MIN: NaiveDate = NaiveDate { ymdf: (MIN_YEAR << 13) | (1 << 4) | 0o12 /*D*/ }; + pub const MIN: NaiveDate = NaiveDate { yof: (MIN_YEAR << 13) | (1 << 4) | 0o12 /*D*/ }; /// The maximum possible `NaiveDate` (December 31, 262142 CE). - pub const MAX: NaiveDate = NaiveDate { ymdf: (MAX_YEAR << 13) | (365 << 4) | 0o16 /*G*/ }; + pub const MAX: NaiveDate = NaiveDate { yof: (MAX_YEAR << 13) | (365 << 4) | 0o16 /*G*/ }; /// One day before the minimum possible `NaiveDate` (December 31, 262145 BCE). pub(crate) const BEFORE_MIN: NaiveDate = - NaiveDate { ymdf: ((MIN_YEAR - 1) << 13) | (366 << 4) | 0o07 /*FE*/ }; + NaiveDate { yof: ((MIN_YEAR - 1) << 13) | (366 << 4) | 0o07 /*FE*/ }; /// One day after the maximum possible `NaiveDate` (January 1, 262143 CE). pub(crate) const AFTER_MAX: NaiveDate = - NaiveDate { ymdf: ((MAX_YEAR + 1) << 13) | (1 << 4) | 0o17 /*F*/ }; + NaiveDate { yof: ((MAX_YEAR + 1) << 13) | (1 << 4) | 0o17 /*F*/ }; } impl Datelike for NaiveDate { @@ -1639,7 +1653,7 @@ impl Datelike for NaiveDate { /// ``` #[inline] fn ordinal(&self) -> u32 { - self.of().ordinal() + ((self.yof & ORDINAL_MASK) >> 4) as u32 } /// Returns the day of year starting from 0. @@ -1656,7 +1670,7 @@ impl Datelike for NaiveDate { /// ``` #[inline] fn ordinal0(&self) -> u32 { - self.of().ordinal() - 1 + self.ordinal() - 1 } /// Returns the day of week. @@ -1676,7 +1690,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. @@ -1827,7 +1841,14 @@ impl Datelike for NaiveDate { /// ``` #[inline] fn with_ordinal(&self, ordinal: u32) -> Option { - self.of().with_ordinal(ordinal).map(|of| self.with_of(of)) + if ordinal == 0 || ordinal > 366 { + return None; + } + let yof = (self.yof & !ORDINAL_MASK) | (ordinal << 4) as i32; + match yof & OL_MASK <= MAX_OL { + true => Some(NaiveDate { yof }), + false => None, // Does not exist: Ordinal 366 in a common year. + } } /// Makes a new `NaiveDate` with the day of year (starting from 0) changed. @@ -2292,6 +2313,33 @@ const fn div_mod_floor(val: i32, div: i32) -> (i32, i32) { (val.div_euclid(div), val.rem_euclid(div)) } +/// MAX_YEAR is one year less than the type is capable of representing. Internally we may sometimes +/// use the headroom, notably to handle cases where the offset of a `DateTime` constructed with +/// `NaiveDate::MAX` pushes it beyond the valid, representable range. +pub(super) const MAX_YEAR: i32 = (i32::MAX >> 13) - 1; + +/// MIN_YEAR is one year more than the type is capable of representing. Internally we may sometimes +/// use the headroom, notably to handle cases where the offset of a `DateTime` constructed with +/// `NaiveDate::MIN` pushes it beyond the valid, representable range. +pub(super) const MIN_YEAR: i32 = (i32::MIN >> 13) + 1; + +const ORDINAL_MASK: i32 = 0b1_1111_1111_0000; + +const LEAP_YEAR_MASK: i32 = 0b1000; + +// OL: ordinal and leap year flag. +// With only these parts of the date an ordinal 366 in a common year would be encoded as +// `((366 << 1) | 1) << 3`, and in a leap year as `((366 << 1) | 0) << 3`, which is less. +// This allows for efficiently checking the ordinal exists depending on whether this is a leap year. +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 @@ -2476,7 +2524,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`, @@ -2488,13 +2536,13 @@ 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.of().ordinal() + calculated_max.year_flags(), + calculated_max.ordinal() ); // let's also check that the entire range do not exceed 2^44 seconds @@ -2508,11 +2556,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)); } @@ -2658,6 +2706,7 @@ mod tests { assert_eq!(yo_opt(2012, 300), Some(ymd(2012, 10, 26))); assert_eq!(yo_opt(2012, 366), Some(ymd(2012, 12, 31))); assert_eq!(yo_opt(2012, 367), None); + assert_eq!(yo_opt(2012, 1 << 28 | 60), None); assert_eq!(yo_opt(2014, 0), None); assert_eq!(yo_opt(2014, 1), Some(ymd(2014, 1, 1))); @@ -2916,7 +2965,10 @@ mod tests { assert_eq!(d.with_day(29), Some(NaiveDate::from_ymd_opt(2000, 2, 29).unwrap())); assert_eq!(d.with_day(30), None); assert_eq!(d.with_day(u32::MAX), None); + } + #[test] + fn test_date_with_ordinal() { let d = NaiveDate::from_ymd_opt(2000, 5, 5).unwrap(); assert_eq!(d.with_ordinal(0), None); assert_eq!(d.with_ordinal(1), Some(NaiveDate::from_ymd_opt(2000, 1, 1).unwrap())); @@ -2924,6 +2976,9 @@ mod tests { assert_eq!(d.with_ordinal(61), Some(NaiveDate::from_ymd_opt(2000, 3, 1).unwrap())); assert_eq!(d.with_ordinal(366), Some(NaiveDate::from_ymd_opt(2000, 12, 31).unwrap())); assert_eq!(d.with_ordinal(367), None); + assert_eq!(d.with_ordinal(1 << 28 | 60), None); + let d = NaiveDate::from_ymd_opt(1999, 5, 5).unwrap(); + assert_eq!(d.with_ordinal(366), None); assert_eq!(d.with_ordinal(u32::MAX), None); } @@ -3328,6 +3383,71 @@ 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); + } + } + + #[test] + fn test_date_to_mdf_to_date() { + for (year, year_flags, _) in YEAR_FLAGS { + for ordinal in 1..=year_flags.ndays() { + let date = NaiveDate::from_yo_opt(year, ordinal).unwrap(); + assert_eq!(date, NaiveDate::from_mdf(date.year(), date.mdf()).unwrap()); + } + } + } + + // 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 c6d7536a0c..34f6f84f4a 100644 --- a/src/naive/internals.rs +++ b/src/naive/internals.rs @@ -15,22 +15,8 @@ #![cfg_attr(feature = "__internal_bench", allow(missing_docs))] -use crate::Weekday; use core::fmt; -/// The internal date representation: `year << 13 | Of` -pub(super) type DateImpl = i32; - -/// MAX_YEAR is one year less than the type is capable of representing. Internally we may sometimes -/// use the headroom, notably to handle cases where the offset of a `DateTime` constructed with -/// `NaiveDate::MAX` pushes it beyond the valid, representable range. -pub(super) const MAX_YEAR: DateImpl = (i32::MAX >> 13) - 1; - -/// MIN_YEAR is one year more than the type is capable of representing. Internally we may sometimes -/// use the headroom, notably to handle cases where the offset of a `DateTime` constructed with -/// `NaiveDate::MIN` pushes it beyond the valid, representable range. -pub(super) const MIN_YEAR: DateImpl = (i32::MIN >> 13) + 1; - /// The year flags (aka the dominical letter). /// /// There are 14 possible classes of year in the Gregorian calendar: @@ -180,7 +166,6 @@ impl fmt::Debug for YearFlags { } // OL: (ordinal << 1) | leap year flag -pub(super) const MIN_OL: u32 = 1 << 1; pub(super) const MAX_OL: u32 = 366 << 1; // `(366 << 1) | 1` would be day 366 in a non-leap year pub(super) const MAX_MDL: u32 = (12 << 6) | (31 << 1) | 1; @@ -269,127 +254,8 @@ const OL_TO_MDL: &[u8; MAX_OL as usize + 1] = &[ 98, // 12 ]; -/// Ordinal (day of year) and year flags: `(ordinal << 4) | flags`. -/// -/// The whole bits except for the least 3 bits are referred as `Ol` (ordinal and leap flag), -/// which is an index to the `OL_TO_MDL` lookup table. -/// -/// The methods implemented on `Of` always return a valid value. -#[derive(PartialEq, PartialOrd, Copy, Clone)] -pub(super) struct Of(u32); - -impl Of { - #[inline] - 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: DateImpl) -> Of { - // We assume the value in the `DateImpl` is valid. - Of((date_impl & 0b1_1111_1111_1111) as u32) - } - - #[inline] - pub(super) const fn from_mdf(Mdf(mdf): Mdf) -> Option { - let mdl = mdf >> 3; - if mdl > MAX_MDL { - // Panicking on out-of-bounds indexing would be reasonable, but just return `None`. - return None; - } - // Array is indexed from `[1..=MAX_MDL]`, with a `0` index having a meaningless value. - let v = MDL_TO_OL[mdl as usize]; - let of = Of(mdf.wrapping_sub((v as i32 as u32 & 0x3ff) << 3)); - of.validate() - } - - #[inline] - pub(super) const fn inner(&self) -> u32 { - self.0 - } - - /// Returns `(ordinal << 1) | leap-year-flag`. - #[inline] - const fn ol(&self) -> u32 { - self.0 >> 3 - } - - #[inline] - const fn validate(self) -> Option { - let ol = self.ol(); - match ol >= MIN_OL && ol <= MAX_OL { - true => Some(self), - false => None, - } - } - - #[inline] - pub(super) const fn ordinal(&self) -> u32 { - self.0 >> 4 - } - - #[inline] - pub(super) const fn with_ordinal(&self, ordinal: u32) -> Option { - let of = Of((ordinal << 4) | (self.0 & 0b1111)); - of.validate() - } - - #[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 { - Mdf::from_of(*self) - } - - /// Returns an `Of` with the next day, or `None` if this is the last day of the year. - #[inline] - pub(super) const fn succ(&self) -> Option { - let of = Of(self.0 + (1 << 4)); - of.validate() - } - - /// Returns an `Of` with the previous day, or `None` if this is the first day of the year. - #[inline] - pub(super) const fn pred(&self) -> Option { - match self.ordinal() { - 1 => None, - _ => Some(Of(self.0 - (1 << 4))), - } - } -} - -impl fmt::Debug for Of { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let Of(of) = *self; - write!( - f, - "Of(({} << 4) | {:#04o} /*{:?}*/)", - of >> 4, - of & 0b1111, - YearFlags((of & 0b1111) as u8) - ) - } -} - /// Month, day of month and year flags: `(month << 9) | (day << 4) | flags` +/// `M_MMMD_DDDD_LFFF` /// /// The whole bits except for the least 3 bits are referred as `Mdl` /// (month, day of month and leap flag), @@ -411,15 +277,10 @@ impl Mdf { } #[inline] - pub(super) const fn from_of(Of(of): Of) -> Mdf { - let ol = of >> 3; - if ol <= MAX_OL { - // Array is indexed from `[1..=MAX_OL]`, with a `0` index having a meaningless value. - Mdf(of + ((OL_TO_MDL[ol as usize] as u32) << 3)) - } else { - // Panicking here would be reasonable, but we are just going on with a safe value. - Mdf(0) - } + pub(super) const fn from_ol(ol: i32, YearFlags(flags): YearFlags) -> Mdf { + debug_assert!(ol > 1 && ol <= MAX_OL as i32); + // Array is indexed from `[2..=MAX_OL]`, with a `0` index having a meaningless value. + Mdf(((ol as u32 + OL_TO_MDL[ol as usize] as u32) << 3) | flags as u32) } #[cfg(test)] @@ -473,10 +334,18 @@ impl Mdf { Mdf((mdf & !0b1111) | flags as u32) } - #[cfg_attr(feature = "cargo-clippy", allow(clippy::wrong_self_convention))] #[inline] - pub(super) const fn to_of(&self) -> Option { - Of::from_mdf(*self) + pub(super) const fn ordinal(&self) -> Option { + let mdl = self.0 >> 3; + match MDL_TO_OL[mdl as usize] { + XX => None, + v => Some((mdl - v as i32 as u32) >> 1), + } + } + + #[inline] + pub(super) const fn year_flags(&self) -> YearFlags { + YearFlags((self.0 & 0b1111) as u8) } } @@ -494,27 +363,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::Mdf; 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]; @@ -555,42 +407,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) { @@ -682,69 +498,6 @@ mod tests { } } - #[test] - fn test_of_fields() { - for &flags in FLAGS.iter() { - for ordinal in 1u32..=366 { - if let Some(of) = Of::new(ordinal, flags) { - assert_eq!(of.ordinal(), ordinal); - } - } - } - } - - #[test] - fn test_of_with_fields() { - fn check(flags: YearFlags, ordinal: u32) { - let of = Of::new(ordinal, flags).unwrap(); - - for ordinal in 0u32..=1024 { - let of = of.with_ordinal(ordinal); - assert_eq!(of, Of::new(ordinal, flags)); - if let Some(of) = of { - assert_eq!(of.ordinal(), ordinal); - } - } - } - - for &flags in NONLEAP_FLAGS.iter() { - check(flags, 1); - check(flags, 365); - } - for &flags in LEAP_FLAGS.iter() { - check(flags, 1); - check(flags, 366); - } - } - - #[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() { @@ -814,85 +567,13 @@ 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 { - if let Some(of) = Of(i).validate() { - assert!(of.to_mdf().valid()); - } - } - } - - #[test] - fn test_mdf_to_of() { - for i in 0u32..=8192 { - let mdf = Mdf(i); - assert_eq!(mdf.valid(), mdf.to_of().is_some()); - } - } - - #[test] - fn test_of_to_mdf_to_of() { - for i in 0u32..=8192 { - if let Some(of) = Of(i).validate() { - assert_eq!(of, of.to_mdf().to_of().unwrap()); - } - } - } - - #[test] - fn test_mdf_to_of_to_mdf() { - for i in 0u32..=8192 { - let mdf = Mdf(i); - if mdf.valid() { - assert_eq!(mdf, mdf.to_of().unwrap().to_mdf()); - } - } - } - #[test] 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()); - assert!(Mdf::new(1, 32, regular_year).is_none()); - assert!(Mdf::new(2, 31, regular_year).is_some()); - - assert!(Of::from_mdf(Mdf::new(2, 30, regular_year).unwrap()).is_none()); - assert!(Of::from_mdf(Mdf::new(2, 30, leap_year).unwrap()).is_none()); - assert!(Of::from_mdf(Mdf::new(2, 29, regular_year).unwrap()).is_none()); - 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()); - - assert!(Of::new(365, regular_year).unwrap().succ().is_none()); - assert!(Of::new(365, leap_year).unwrap().succ().is_some()); - assert!(Of::new(366, leap_year).unwrap().succ().is_none()); - - assert!(Of::new(1, regular_year).unwrap().pred().is_none()); - assert!(Of::new(1, leap_year).unwrap().pred().is_none()); - } - - #[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); + let flags = YearFlags::from_year(2023); + assert!(Mdf::new(0, 1, flags).is_none()); + assert!(Mdf::new(13, 1, flags).is_none()); + assert!(Mdf::new(1, 0, flags).is_none()); + assert!(Mdf::new(1, 32, flags).is_none()); + assert!(Mdf::new(2, 31, flags).is_some()); } } diff --git a/src/naive/isoweek.rs b/src/naive/isoweek.rs index 4b3d8d9d85..1d0ddfba36 100644 --- a/src/naive/isoweek.rs +++ b/src/naive/isoweek.rs @@ -5,7 +5,7 @@ use core::fmt; -use super::internals::{DateImpl, Of, YearFlags}; +use super::internals::YearFlags; #[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] use rkyv::{Archive, Deserialize, Serialize}; @@ -28,35 +28,35 @@ pub struct IsoWeek { // note that this allows for larger year range than `NaiveDate`. // this is crucial because we have an edge case for the first and last week supported, // which year number might not match the calendar year number. - ywf: DateImpl, // (year << 10) | (week << 4) | flag + 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 DateImpl | DateImpl::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 @@ -155,7 +155,7 @@ impl fmt::Debug for IsoWeek { mod tests { #[cfg(feature = "rkyv-validation")] use super::IsoWeek; - use crate::naive::{internals, NaiveDate}; + use crate::naive::date::{self, NaiveDate}; use crate::Datelike; #[test] @@ -163,13 +163,13 @@ mod tests { let minweek = NaiveDate::MIN.iso_week(); let maxweek = NaiveDate::MAX.iso_week(); - assert_eq!(minweek.year(), internals::MIN_YEAR); + assert_eq!(minweek.year(), date::MIN_YEAR); assert_eq!(minweek.week(), 1); assert_eq!(minweek.week0(), 0); #[cfg(feature = "alloc")] assert_eq!(format!("{:?}", minweek), NaiveDate::MIN.format("%G-W%V").to_string()); - assert_eq!(maxweek.year(), internals::MAX_YEAR + 1); + assert_eq!(maxweek.year(), date::MAX_YEAR + 1); assert_eq!(maxweek.week(), 1); assert_eq!(maxweek.week0(), 0); #[cfg(feature = "alloc")]