diff --git a/src/naive/date.rs b/src/naive/date.rs index b8aafdee87..3d64812184 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -37,7 +37,7 @@ use crate::naive::{IsoWeek, NaiveDateTime, NaiveTime}; use crate::{expect, try_opt}; use crate::{Datelike, TimeDelta, Weekday}; -use super::internals::{self, Mdf, Of, YearFlags}; +use super::internals::{Mdf, YearFlags}; /// A week represented by a [`NaiveDate`] and a [`Weekday`] which is the first /// day of the week. @@ -251,11 +251,8 @@ impl NaiveDate { /// 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 { yof: (year << 13) | (of.inner() as i32) }), + match mdf.ordinal() { + Some(ordinal) => NaiveDate::from_ordinal_and_flags(year, ordinal, mdf.year_flags()), None => None, // Non-existing date } } @@ -488,7 +485,7 @@ impl NaiveDate { let days = try_opt!(days.checked_add(365)); // make December 31, 1 BCE equal to day 0 let year_div_400 = days.div_euclid(146_097); let cycle = days.rem_euclid(146_097); - let (year_mod_400, ordinal) = internals::cycle_to_yo(cycle as u32); + let (year_mod_400, ordinal) = cycle_to_yo(cycle as u32); let flags = YearFlags::from_year_mod_400(year_mod_400 as i32); NaiveDate::from_ordinal_and_flags(year_div_400 * 400 + year_mod_400 as i32, ordinal, flags) } @@ -806,12 +803,12 @@ impl NaiveDate { // 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.ordinal()); + let cycle = 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; - let (year_mod_400, ordinal) = internals::cycle_to_yo(cycle as u32); + let (year_mod_400, ordinal) = cycle_to_yo(cycle as u32); let flags = YearFlags::from_year_mod_400(year_mod_400 as i32); NaiveDate::from_ordinal_and_flags(year_div_400 * 400 + year_mod_400 as i32, ordinal, flags) } @@ -1045,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.yof) + Mdf::from_ol((self.yof & OL_MASK) >> 3, self.year_flags()) } /// Makes a new `NaiveDate` with the packed month-day-flags changed. @@ -1059,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 { yof: (self.yof & !0b1_1111_1111_1111) | of.inner() as i32 } + 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. @@ -1231,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.ordinal()) as i64; - let cycle2 = internals::yo_to_cycle(year2_mod_400 as u32, rhs.ordinal()) as i64; + let cycle1 = yo_to_cycle(year1_mod_400 as u32, self.ordinal()) as i64; + let cycle2 = 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)) } @@ -2321,6 +2309,23 @@ impl Default for NaiveDate { } } +const fn cycle_to_yo(cycle: u32) -> (u32, u32) { + let mut year_mod_400 = cycle / 365; + let mut ordinal0 = cycle % 365; + let delta = YEAR_DELTAS[year_mod_400 as usize] as u32; + if ordinal0 < delta { + year_mod_400 -= 1; + ordinal0 += 365 - YEAR_DELTAS[year_mod_400 as usize] as u32; + } else { + ordinal0 -= delta; + } + (year_mod_400, ordinal0 + 1) +} + +const fn yo_to_cycle(year_mod_400: u32, ordinal: u32) -> u32 { + year_mod_400 * 365 + YEAR_DELTAS[year_mod_400 as usize] as u32 + ordinal - 1 +} + const fn div_mod_floor(val: i32, div: i32) -> (i32, i32) { (val.div_euclid(div), val.rem_euclid(div)) } @@ -2352,6 +2357,28 @@ const WEEKDAY_FLAGS_MASK: i32 = 0b111; const YEAR_FLAGS_MASK: i32 = LEAP_YEAR_MASK | WEEKDAY_FLAGS_MASK; +const YEAR_DELTAS: &[u8; 401] = &[ + 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, + 8, 9, 9, 9, 9, 10, 10, 10, 10, 11, 11, 11, 11, 12, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 14, + 15, 15, 15, 15, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18, 19, 19, 19, 19, 20, 20, 20, 20, + 21, 21, 21, 21, 22, 22, 22, 22, 23, 23, 23, 23, 24, 24, 24, 24, 25, 25, 25, // 100 + 25, 25, 25, 25, 25, 26, 26, 26, 26, 27, 27, 27, 27, 28, 28, 28, 28, 29, 29, 29, 29, 30, 30, 30, + 30, 31, 31, 31, 31, 32, 32, 32, 32, 33, 33, 33, 33, 34, 34, 34, 34, 35, 35, 35, 35, 36, 36, 36, + 36, 37, 37, 37, 37, 38, 38, 38, 38, 39, 39, 39, 39, 40, 40, 40, 40, 41, 41, 41, 41, 42, 42, 42, + 42, 43, 43, 43, 43, 44, 44, 44, 44, 45, 45, 45, 45, 46, 46, 46, 46, 47, 47, 47, 47, 48, 48, 48, + 48, 49, 49, 49, // 200 + 49, 49, 49, 49, 49, 50, 50, 50, 50, 51, 51, 51, 51, 52, 52, 52, 52, 53, 53, 53, 53, 54, 54, 54, + 54, 55, 55, 55, 55, 56, 56, 56, 56, 57, 57, 57, 57, 58, 58, 58, 58, 59, 59, 59, 59, 60, 60, 60, + 60, 61, 61, 61, 61, 62, 62, 62, 62, 63, 63, 63, 63, 64, 64, 64, 64, 65, 65, 65, 65, 66, 66, 66, + 66, 67, 67, 67, 67, 68, 68, 68, 68, 69, 69, 69, 69, 70, 70, 70, 70, 71, 71, 71, 71, 72, 72, 72, + 72, 73, 73, 73, // 300 + 73, 73, 73, 73, 73, 74, 74, 74, 74, 75, 75, 75, 75, 76, 76, 76, 76, 77, 77, 77, 77, 78, 78, 78, + 78, 79, 79, 79, 79, 80, 80, 80, 80, 81, 81, 81, 81, 82, 82, 82, 82, 83, 83, 83, 83, 84, 84, 84, + 84, 85, 85, 85, 85, 86, 86, 86, 86, 87, 87, 87, 87, 88, 88, 88, 88, 89, 89, 89, 89, 90, 90, 90, + 90, 91, 91, 91, 91, 92, 92, 92, 92, 93, 93, 93, 93, 94, 94, 94, 94, 95, 95, 95, 95, 96, 96, 96, + 96, 97, 97, 97, 97, // 400+1 +]; + #[cfg(all(test, any(feature = "rustc-serialize", feature = "serde")))] fn test_encodable_json(to_string: F) where @@ -3431,6 +3458,16 @@ mod tests { } } + #[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] = [ diff --git a/src/naive/internals.rs b/src/naive/internals.rs index 95690d0364..26576d865e 100644 --- a/src/naive/internals.rs +++ b/src/naive/internals.rs @@ -63,45 +63,6 @@ const YEAR_TO_FLAGS: &[YearFlags; 400] = &[ D, CB, A, G, F, ED, C, B, A, GF, E, D, C, // 400 ]; -const YEAR_DELTAS: &[u8; 401] = &[ - 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, - 8, 9, 9, 9, 9, 10, 10, 10, 10, 11, 11, 11, 11, 12, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 14, - 15, 15, 15, 15, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18, 19, 19, 19, 19, 20, 20, 20, 20, - 21, 21, 21, 21, 22, 22, 22, 22, 23, 23, 23, 23, 24, 24, 24, 24, 25, 25, 25, // 100 - 25, 25, 25, 25, 25, 26, 26, 26, 26, 27, 27, 27, 27, 28, 28, 28, 28, 29, 29, 29, 29, 30, 30, 30, - 30, 31, 31, 31, 31, 32, 32, 32, 32, 33, 33, 33, 33, 34, 34, 34, 34, 35, 35, 35, 35, 36, 36, 36, - 36, 37, 37, 37, 37, 38, 38, 38, 38, 39, 39, 39, 39, 40, 40, 40, 40, 41, 41, 41, 41, 42, 42, 42, - 42, 43, 43, 43, 43, 44, 44, 44, 44, 45, 45, 45, 45, 46, 46, 46, 46, 47, 47, 47, 47, 48, 48, 48, - 48, 49, 49, 49, // 200 - 49, 49, 49, 49, 49, 50, 50, 50, 50, 51, 51, 51, 51, 52, 52, 52, 52, 53, 53, 53, 53, 54, 54, 54, - 54, 55, 55, 55, 55, 56, 56, 56, 56, 57, 57, 57, 57, 58, 58, 58, 58, 59, 59, 59, 59, 60, 60, 60, - 60, 61, 61, 61, 61, 62, 62, 62, 62, 63, 63, 63, 63, 64, 64, 64, 64, 65, 65, 65, 65, 66, 66, 66, - 66, 67, 67, 67, 67, 68, 68, 68, 68, 69, 69, 69, 69, 70, 70, 70, 70, 71, 71, 71, 71, 72, 72, 72, - 72, 73, 73, 73, // 300 - 73, 73, 73, 73, 73, 74, 74, 74, 74, 75, 75, 75, 75, 76, 76, 76, 76, 77, 77, 77, 77, 78, 78, 78, - 78, 79, 79, 79, 79, 80, 80, 80, 80, 81, 81, 81, 81, 82, 82, 82, 82, 83, 83, 83, 83, 84, 84, 84, - 84, 85, 85, 85, 85, 86, 86, 86, 86, 87, 87, 87, 87, 88, 88, 88, 88, 89, 89, 89, 89, 90, 90, 90, - 90, 91, 91, 91, 91, 92, 92, 92, 92, 93, 93, 93, 93, 94, 94, 94, 94, 95, 95, 95, 95, 96, 96, 96, - 96, 97, 97, 97, 97, // 400+1 -]; - -pub(super) const fn cycle_to_yo(cycle: u32) -> (u32, u32) { - let mut year_mod_400 = cycle / 365; - let mut ordinal0 = cycle % 365; - let delta = YEAR_DELTAS[year_mod_400 as usize] as u32; - if ordinal0 < delta { - year_mod_400 -= 1; - ordinal0 += 365 - YEAR_DELTAS[year_mod_400 as usize] as u32; - } else { - ordinal0 -= delta; - } - (year_mod_400, ordinal0 + 1) -} - -pub(super) const fn yo_to_cycle(year_mod_400: u32, ordinal: u32) -> u32 { - year_mod_400 * 365 + YEAR_DELTAS[year_mod_400 as usize] as u32 + ordinal - 1 -} - impl YearFlags { #[allow(unreachable_pub)] // public as an alias for benchmarks only #[doc(hidden)] // for benchmarks only @@ -166,7 +127,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; @@ -255,75 +215,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 { - 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) - } - - #[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, - } - } - - #[cfg_attr(feature = "cargo-clippy", allow(clippy::wrong_self_convention))] - #[inline] - pub(super) const fn to_mdf(&self) -> Mdf { - Mdf::from_of(*self) - } -} - -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), @@ -345,15 +238,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)] @@ -407,10 +295,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) } } @@ -430,7 +326,7 @@ impl fmt::Debug for Mdf { #[cfg(test)] mod tests { - use super::{Mdf, Of}; + use super::Mdf; use super::{YearFlags, A, AG, B, BA, C, CB, D, DC, E, ED, F, FE, G, GF}; const NONLEAP_FLAGS: [YearFlags; 7] = [A, B, C, D, E, F, G]; @@ -632,56 +528,13 @@ mod tests { } } - #[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!(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()); + 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 1d0ddfba36..7776b928b7 100644 --- a/src/naive/isoweek.rs +++ b/src/naive/isoweek.rs @@ -25,8 +25,8 @@ use rkyv::{Archive, Deserialize, Serialize}; )] #[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] 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, + // 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: i32, // (year << 10) | (week << 4) | flag } @@ -34,10 +34,10 @@ pub struct IsoWeek { 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 + // 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`. + // 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 {