From 150a6435b192243c724497c5b8584387c6f01840 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 11 Aug 2023 13:59:34 +0200 Subject: [PATCH 01/17] Remove `DateImpl` type alias --- src/naive/date.rs | 10 +++++----- src/naive/internals.rs | 11 ++++------- src/naive/isoweek.rs | 6 +++--- 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index 190a6bac60..668c183c63 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -27,7 +27,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::internals::{self, Mdf, Of, YearFlags}; use super::isoweek; const MAX_YEAR: i32 = internals::MAX_YEAR; @@ -196,7 +196,7 @@ impl Days { )] #[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] pub struct NaiveDate { - ymdf: DateImpl, // (year << 13) | of + ymdf: i32, // (year << 13) | of } /// The minimum possible `NaiveDate` (January 1, 262145 BCE). @@ -233,7 +233,7 @@ impl NaiveDate { } 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) }), + Some(of) => Some(NaiveDate { ymdf: (year << 13) | (of.inner() as i32) }), None => None, // Invalid: Ordinal outside of the nr of days in a year with those flags. } } @@ -245,7 +245,7 @@ impl NaiveDate { return None; // Out-of-range } match mdf.to_of() { - Some(of) => Some(NaiveDate { ymdf: (year << 13) | (of.inner() as DateImpl) }), + Some(of) => Some(NaiveDate { ymdf: (year << 13) | (of.inner() as i32) }), None => None, // Non-existing date } } @@ -1058,7 +1058,7 @@ impl NaiveDate { /// 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 } + NaiveDate { ymdf: (self.ymdf & !0b1_1111_1111_1111) | of.inner() as i32 } } /// Makes a new `NaiveDate` for the next calendar date. diff --git a/src/naive/internals.rs b/src/naive/internals.rs index c6d7536a0c..a570d468cd 100644 --- a/src/naive/internals.rs +++ b/src/naive/internals.rs @@ -18,18 +18,15 @@ 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; +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: DateImpl = (i32::MIN >> 13) + 1; +pub(super) const MIN_YEAR: i32 = (i32::MIN >> 13) + 1; /// The year flags (aka the dominical letter). /// @@ -285,8 +282,8 @@ impl Of { of.validate() } - pub(super) const fn from_date_impl(date_impl: DateImpl) -> Of { - // We assume the value in the `DateImpl` is valid. + 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) } diff --git a/src/naive/isoweek.rs b/src/naive/isoweek.rs index 4b3d8d9d85..795359d8d6 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::{Of, YearFlags}; #[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] use rkyv::{Archive, Deserialize, Serialize}; @@ -28,7 +28,7 @@ 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. @@ -53,7 +53,7 @@ pub(super) fn iso_week_from_yof(year: i32, of: Of) -> IsoWeek { } }; let flags = YearFlags::from_year(year); - IsoWeek { ywf: (year << 10) | (week << 4) as DateImpl | DateImpl::from(flags.0) } + IsoWeek { ywf: (year << 10) | (week << 4) as i32 | i32::from(flags.0) } } impl IsoWeek { From 630f01f88ae7ac412cdb0462ff489ca625032e0c Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 30 Jul 2023 08:07:26 +0200 Subject: [PATCH 02/17] Rename `ymdf` field to `yof` --- src/naive/date.rs | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index 668c183c63..07a9761ceb 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -196,7 +196,7 @@ impl Days { )] #[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] pub struct NaiveDate { - ymdf: i32, // (year << 13) | of + yof: i32, // (year << 13) | of } /// The minimum possible `NaiveDate` (January 1, 262145 BCE). @@ -233,7 +233,7 @@ impl NaiveDate { } debug_assert!(YearFlags::from_year(year).0 == flags.0); match Of::new(ordinal, flags) { - Some(of) => Some(NaiveDate { ymdf: (year << 13) | (of.inner() as i32) }), + Some(of) => Some(NaiveDate { yof: (year << 13) | (of.inner() as i32) }), None => None, // Invalid: Ordinal outside of the nr of days in a year with those flags. } } @@ -245,7 +245,7 @@ impl NaiveDate { return None; // Out-of-range } match mdf.to_of() { - Some(of) => Some(NaiveDate { ymdf: (year << 13) | (of.inner() as i32) }), + Some(of) => Some(NaiveDate { yof: (year << 13) | (of.inner() as i32) }), None => None, // Non-existing date } } @@ -787,10 +787,10 @@ 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 @@ -1041,7 +1041,7 @@ impl NaiveDate { /// Returns the packed ordinal-flags. #[inline] const fn of(&self) -> Of { - Of::from_date_impl(self.ymdf) + Of::from_date_impl(self.yof) } /// Makes a new `NaiveDate` with the packed month-day-flags changed. @@ -1058,7 +1058,7 @@ impl NaiveDate { /// 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 i32 } + NaiveDate { yof: (self.yof & !0b1_1111_1111_1111) | of.inner() as i32 } } /// Makes a new `NaiveDate` for the next calendar date. @@ -1433,13 +1433,13 @@ 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. @@ -1484,16 +1484,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 { From 16ad3f312df26cc145e63a9614a509b18a0737b0 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 30 Jul 2023 08:21:05 +0200 Subject: [PATCH 03/17] Add description of `NaiveDate` internal format --- src/naive/date.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/naive/date.rs b/src/naive/date.rs index 07a9761ceb..67dfa2941a 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; From 5443d7abf5b77a5fa621004b47950a4191cd04a3 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 30 Jul 2023 12:19:29 +0200 Subject: [PATCH 04/17] Move `MIN_YEAR` and `MAX_YEAR` constants --- src/naive/date.rs | 13 ++++++++++--- src/naive/internals.rs | 10 ---------- src/naive/isoweek.rs | 6 +++--- 3 files changed, 13 insertions(+), 16 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index 67dfa2941a..e6aacf9973 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -40,9 +40,6 @@ use crate::{Datelike, TimeDelta, Weekday}; use super::internals::{self, Mdf, Of, YearFlags}; use super::isoweek; -const MAX_YEAR: i32 = internals::MAX_YEAR; -const MIN_YEAR: i32 = internals::MIN_YEAR; - /// A week represented by a [`NaiveDate`] and a [`Weekday`] which is the first /// day of the week. #[derive(Debug)] @@ -2302,6 +2299,16 @@ 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; + #[cfg(all(test, any(feature = "rustc-serialize", feature = "serde")))] fn test_encodable_json(to_string: F) where diff --git a/src/naive/internals.rs b/src/naive/internals.rs index a570d468cd..1f9482cba7 100644 --- a/src/naive/internals.rs +++ b/src/naive/internals.rs @@ -18,16 +18,6 @@ use crate::Weekday; use core::fmt; -/// 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; - /// The year flags (aka the dominical letter). /// /// There are 14 possible classes of year in the Gregorian calendar: diff --git a/src/naive/isoweek.rs b/src/naive/isoweek.rs index 795359d8d6..c4649db557 100644 --- a/src/naive/isoweek.rs +++ b/src/naive/isoweek.rs @@ -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")] From 55c47afa3d733bafffde2450cd440c623bb824e7 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 10 Aug 2023 16:51:31 +0200 Subject: [PATCH 05/17] Remove `Of::succ` --- src/naive/date.rs | 18 +++++++++++++++--- src/naive/internals.rs | 11 ----------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index e6aacf9973..ee2a83df8e 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -1098,9 +1098,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), } } @@ -2309,6 +2310,17 @@ pub(super) const MAX_YEAR: i32 = (i32::MAX >> 13) - 1; /// `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; + #[cfg(all(test, any(feature = "rustc-serialize", feature = "serde")))] fn test_encodable_json(to_string: F) where diff --git a/src/naive/internals.rs b/src/naive/internals.rs index 1f9482cba7..1a957cd727 100644 --- a/src/naive/internals.rs +++ b/src/naive/internals.rs @@ -346,13 +346,6 @@ impl Of { 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 { @@ -867,10 +860,6 @@ 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()); - 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()); } From 86539300faa7da300c190230a6f14430533c90cb Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 10 Aug 2023 16:58:09 +0200 Subject: [PATCH 06/17] Remove `Of::pred` --- src/naive/date.rs | 7 ++++--- src/naive/internals.rs | 12 ------------ 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index ee2a83df8e..74528d1a9f 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -1135,9 +1135,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), } } diff --git a/src/naive/internals.rs b/src/naive/internals.rs index 1a957cd727..fb22aa4f6e 100644 --- a/src/naive/internals.rs +++ b/src/naive/internals.rs @@ -345,15 +345,6 @@ impl Of { pub(super) const fn to_mdf(&self) -> Mdf { Mdf::from_of(*self) } - - /// 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 { @@ -859,9 +850,6 @@ mod tests { 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(1, regular_year).unwrap().pred().is_none()); - assert!(Of::new(1, leap_year).unwrap().pred().is_none()); } #[test] From f4f30186015358450d7d8723ed2db3ec2dd3370a Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 10 Aug 2023 17:01:44 +0200 Subject: [PATCH 07/17] Remove `Of::with_ordinal` --- src/naive/date.rs | 15 ++++++++++++++- src/naive/internals.rs | 30 ------------------------------ 2 files changed, 14 insertions(+), 31 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index 74528d1a9f..4e9fa4779a 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -1836,7 +1836,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. @@ -2946,7 +2953,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())); @@ -2954,6 +2964,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); } diff --git a/src/naive/internals.rs b/src/naive/internals.rs index fb22aa4f6e..acb7b2741f 100644 --- a/src/naive/internals.rs +++ b/src/naive/internals.rs @@ -315,12 +315,6 @@ impl Of { 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) @@ -664,30 +658,6 @@ mod tests { } } - #[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); From 253a77c461fe8363ebcf5f32616a9725bd7d4642 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 30 Jul 2023 12:19:37 +0200 Subject: [PATCH 08/17] Remove `Of::ordinal` --- src/naive/date.rs | 25 +++++++++++++++---------- src/naive/internals.rs | 16 ---------------- 2 files changed, 15 insertions(+), 26 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index 4e9fa4779a..6b5c9f559f 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -238,10 +238,14 @@ 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 { yof: (year << 13) | (of.inner() as i32) }), - 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. } } @@ -803,7 +807,7 @@ 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.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; @@ -1228,8 +1232,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)) } @@ -1455,7 +1459,7 @@ impl NaiveDate { // 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. @@ -1648,7 +1652,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. @@ -1665,7 +1669,7 @@ impl Datelike for NaiveDate { /// ``` #[inline] fn ordinal0(&self) -> u32 { - self.of().ordinal() - 1 + self.ordinal() - 1 } /// Returns the day of week. @@ -2531,7 +2535,7 @@ mod tests { NaiveDate::MAX == calculated_max, "`NaiveDate::MAX` should have year flag {:?} and ordinal {}", calculated_max.of().flags(), - calculated_max.of().ordinal() + calculated_max.ordinal() ); // let's also check that the entire range do not exceed 2^44 seconds @@ -2695,6 +2699,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))); diff --git a/src/naive/internals.rs b/src/naive/internals.rs index acb7b2741f..d418ad5e7d 100644 --- a/src/naive/internals.rs +++ b/src/naive/internals.rs @@ -310,11 +310,6 @@ impl Of { } } - #[inline] - pub(super) const fn ordinal(&self) -> u32 { - self.0 >> 4 - } - #[inline] pub(super) const fn flags(&self) -> YearFlags { YearFlags((self.0 & 0b1111) as u8) @@ -647,17 +642,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_weekday() { assert_eq!(Of::new(1, A).unwrap().weekday(), Weekday::Sun); From 41e083f59a9c27a48dd6a625174c105ac376e43b Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 11 Aug 2023 14:36:25 +0200 Subject: [PATCH 09/17] Move `iso_week_from_yof` to `IsoWeek::from_yof` --- src/naive/date.rs | 3 +-- src/naive/isoweek.rs | 48 ++++++++++++++++++++++---------------------- 2 files changed, 25 insertions(+), 26 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index 6b5c9f559f..c4daae1370 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. @@ -1689,7 +1688,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.of()) } /// Makes a new `NaiveDate` with the year number changed, while keeping the same month and day. diff --git a/src/naive/isoweek.rs b/src/naive/isoweek.rs index c4649db557..45a142a0dc 100644 --- a/src/naive/isoweek.rs +++ b/src/naive/isoweek.rs @@ -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, of: Of) -> Self { + 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 { - (year, rawweek) - } - }; - let flags = YearFlags::from_year(year); - IsoWeek { ywf: (year << 10) | (week << 4) as i32 | i32::from(flags.0) } -} + let lastweek = of.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 From 6e09bff1fb9c5868a940d15331b49f3d8a2ee668 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 30 Jul 2023 13:14:45 +0200 Subject: [PATCH 10/17] Change `IsoWeek::from_yof` to consuming `ordinal` and `year_flags` remove `Of::isoweekdate_raw` --- src/naive/date.rs | 34 ++++++++++++++++++++++++++++++++-- src/naive/internals.rs | 17 ----------------- src/naive/isoweek.rs | 8 ++++---- 3 files changed, 36 insertions(+), 23 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index c4daae1370..4b965d2711 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -1688,7 +1688,7 @@ impl Datelike for NaiveDate { #[inline] fn iso_week(&self) -> IsoWeek { - IsoWeek::from_yof(self.year(), self.of()) + IsoWeek::from_yof(self.year(), self.ordinal(), self.of().flags()) } /// Makes a new `NaiveDate` with the year number changed, while keeping the same month and day. @@ -2516,7 +2516,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`, @@ -3375,6 +3375,36 @@ mod tests { } } + #[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.of().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 d418ad5e7d..f18141760e 100644 --- a/src/naive/internals.rs +++ b/src/naive/internals.rs @@ -321,14 +321,6 @@ impl Of { 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 { @@ -739,15 +731,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 { diff --git a/src/naive/isoweek.rs b/src/naive/isoweek.rs index 45a142a0dc..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}; @@ -38,14 +38,14 @@ impl IsoWeek { // 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, of: Of) -> Self { - let (rawweek, _) = of.isoweekdate_raw(); + 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 { - let lastweek = of.flags().nisoweeks(); + let lastweek = year_flags.nisoweeks(); if rawweek > lastweek { // next year (year + 1, 1) From d3b6b6ea1927e111fef2f5078a8e683ca5aed1ae Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 30 Jul 2023 13:08:04 +0200 Subject: [PATCH 11/17] Remove `Of::weekday` --- src/naive/date.rs | 33 ++++++++++++++++++++++- src/naive/internals.rs | 60 ------------------------------------------ 2 files changed, 32 insertions(+), 61 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index 4b965d2711..22a486b7e3 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -1473,10 +1473,19 @@ 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, + } } /// Counts the days in the proleptic Gregorian calendar, with January 1, Year 1 (CE) as day 1. @@ -2332,6 +2341,10 @@ 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; + #[cfg(all(test, any(feature = "rustc-serialize", feature = "serde")))] fn test_encodable_json(to_string: F) where @@ -3375,6 +3388,24 @@ mod tests { } } + #[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.of().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 { diff --git a/src/naive/internals.rs b/src/naive/internals.rs index f18141760e..884010db7c 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). @@ -315,12 +314,6 @@ impl Of { 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)) - } - #[cfg_attr(feature = "cargo-clippy", allow(clippy::wrong_self_convention))] #[inline] pub(super) const fn to_mdf(&self) -> Mdf { @@ -446,27 +439,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]; @@ -634,34 +610,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() { @@ -788,12 +736,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); - } } From 016e9759efc2091ca46e44d9608115c7b2a243ee Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 11 Aug 2023 14:19:19 +0200 Subject: [PATCH 12/17] Remove `Of::new` --- src/naive/internals.rs | 47 ------------------------------------------ 1 file changed, 47 deletions(-) diff --git a/src/naive/internals.rs b/src/naive/internals.rs index 884010db7c..5ba10fe975 100644 --- a/src/naive/internals.rs +++ b/src/naive/internals.rs @@ -265,12 +265,6 @@ const OL_TO_MDL: &[u8; MAX_OL as usize + 1] = &[ 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: i32) -> Of { // We assume the value in `date_impl` is valid. Of((date_impl & 0b1_1111_1111_1111) as u32) @@ -483,42 +477,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) { @@ -719,11 +677,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()); From 7f8a3557a18a3d0ada07e29aabcbdaae3cb71066 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 10 Aug 2023 22:06:18 +0200 Subject: [PATCH 13/17] Remove `Of::flags` --- src/naive/date.rs | 28 +++++++++++++++++++++------- src/naive/internals.rs | 5 ----- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index 22a486b7e3..b8aafdee87 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -1488,6 +1488,11 @@ impl NaiveDate { } } + #[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. // This duplicates `Datelike::num_days_from_ce()`, because trait methods can't be const yet. pub(crate) const fn num_days_from_ce(&self) -> i32 { @@ -1697,7 +1702,7 @@ impl Datelike for NaiveDate { #[inline] fn iso_week(&self) -> IsoWeek { - IsoWeek::from_yof(self.year(), self.ordinal(), self.of().flags()) + 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. @@ -2345,6 +2350,8 @@ const MAX_OL: i32 = 366 << 4; // 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 @@ -2541,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() ); @@ -2561,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)); } @@ -3388,12 +3395,19 @@ 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.of().flags(), year_flags); + 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(); @@ -3412,7 +3426,7 @@ mod tests { // 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.of().flags(), year_flags); + assert_eq!(jan4.year_flags(), year_flags); assert_eq!(iso_week.week(), 1); } } diff --git a/src/naive/internals.rs b/src/naive/internals.rs index 5ba10fe975..95690d0364 100644 --- a/src/naive/internals.rs +++ b/src/naive/internals.rs @@ -303,11 +303,6 @@ impl Of { } } - #[inline] - pub(super) const fn flags(&self) -> YearFlags { - YearFlags((self.0 & 0b1111) as u8) - } - #[cfg_attr(feature = "cargo-clippy", allow(clippy::wrong_self_convention))] #[inline] pub(super) const fn to_mdf(&self) -> Mdf { From 50185eb7e1850b9827de9164f281cb7a8f137d39 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 30 Jul 2023 16:25:34 +0200 Subject: [PATCH 14/17] Change `Mdf::from_of` to `Mdf::from_ol` --- src/naive/date.rs | 8 +------- src/naive/internals.rs | 20 +++++--------------- 2 files changed, 6 insertions(+), 22 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index b8aafdee87..0bb6343160 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -1045,13 +1045,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. diff --git a/src/naive/internals.rs b/src/naive/internals.rs index 95690d0364..3dd2e01e91 100644 --- a/src/naive/internals.rs +++ b/src/naive/internals.rs @@ -265,11 +265,6 @@ const OL_TO_MDL: &[u8; MAX_OL as usize + 1] = &[ 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; @@ -306,7 +301,7 @@ impl Of { #[cfg_attr(feature = "cargo-clippy", allow(clippy::wrong_self_convention))] #[inline] pub(super) const fn to_mdf(&self) -> Mdf { - Mdf::from_of(*self) + Mdf::from_ol(self.0 as i32 >> 3, YearFlags((self.0 & 0b1111) as u8)) } } @@ -345,15 +340,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)] From c25bdf73498d880dc585c8717c5abca4fc0a6a99 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 11 Aug 2023 07:18:06 +0200 Subject: [PATCH 15/17] Add `Mdf::ordinal` and `Mdf::year_flags` --- src/naive/internals.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/naive/internals.rs b/src/naive/internals.rs index 3dd2e01e91..bed31aef49 100644 --- a/src/naive/internals.rs +++ b/src/naive/internals.rs @@ -402,6 +402,20 @@ impl Mdf { pub(super) const fn to_of(&self) -> Option { Of::from_mdf(*self) } + + #[inline] + 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) + } } impl fmt::Debug for Mdf { From 562d7fd0398d6a822e08e49dfcfe585f79f8f316 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 11 Aug 2023 07:33:20 +0200 Subject: [PATCH 16/17] Remove `NaiveDate::with_of` --- src/naive/date.rs | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index 0bb6343160..729e42349c 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::{self, Mdf, YearFlags}; /// A week represented by a [`NaiveDate`] and a [`Weekday`] which is the first /// day of the week. @@ -1053,16 +1053,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. From e28f06387e43503dad5e95aeee514c4387b41a2a Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 11 Aug 2023 07:49:52 +0200 Subject: [PATCH 17/17] Remove `Mdf::to_of` --- src/naive/date.rs | 17 ++++-- src/naive/internals.rs | 128 +++-------------------------------------- 2 files changed, 20 insertions(+), 125 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index 729e42349c..990164fb35 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -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 } } @@ -3422,6 +3419,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 bed31aef49..34f6f84f4a 100644 --- a/src/naive/internals.rs +++ b/src/naive/internals.rs @@ -166,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; @@ -255,70 +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 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_ol(self.0 as i32 >> 3, YearFlags((self.0 & 0b1111) as u8)) - } -} - -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), @@ -397,12 +334,6 @@ 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) - } - #[inline] pub(super) const fn ordinal(&self) -> Option { let mdl = self.0 >> 3; @@ -434,7 +365,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]; @@ -636,56 +567,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()); } }