diff options
Diffstat (limited to 'vendor/time/src/date.rs')
-rw-r--r-- | vendor/time/src/date.rs | 449 |
1 files changed, 389 insertions, 60 deletions
diff --git a/vendor/time/src/date.rs b/vendor/time/src/date.rs index 9f194b434..3f76adb2d 100644 --- a/vendor/time/src/date.rs +++ b/vendor/time/src/date.rs @@ -1,19 +1,28 @@ //! The [`Date`] struct and its associated `impl`s. use core::fmt; +use core::num::NonZeroI32; use core::ops::{Add, Sub}; use core::time::Duration as StdDuration; #[cfg(feature = "formatting")] use std::io; +use deranged::RangedI32; + use crate::convert::*; #[cfg(feature = "formatting")] use crate::formatting::Formattable; +use crate::internal_macros::{ + cascade, const_try, const_try_opt, div_floor, ensure_ranged, expect_opt, impl_add_assign, + impl_sub_assign, +}; #[cfg(feature = "parsing")] use crate::parsing::Parsable; use crate::util::{days_in_year, days_in_year_month, is_leap_year, weeks_in_year}; use crate::{error, Duration, Month, PrimitiveDateTime, Time, Weekday}; +type Year = RangedI32<MIN_YEAR, MAX_YEAR>; + /// The minimum valid year. pub(crate) const MIN_YEAR: i32 = if cfg!(feature = "large-dates") { -999_999 @@ -39,32 +48,43 @@ pub struct Date { // | 2 bits | 21 bits | 9 bits | // | unassigned | year | ordinal | // The year is 15 bits when `large-dates` is not enabled. - value: i32, + value: NonZeroI32, } impl Date { /// The minimum valid `Date`. /// /// The value of this may vary depending on the feature flags enabled. - pub const MIN: Self = Self::__from_ordinal_date_unchecked(MIN_YEAR, 1); + // Safety: `ordinal` is not zero. + #[allow(clippy::undocumented_unsafe_blocks)] + pub const MIN: Self = unsafe { Self::__from_ordinal_date_unchecked(MIN_YEAR, 1) }; /// The maximum valid `Date`. /// /// The value of this may vary depending on the feature flags enabled. - pub const MAX: Self = Self::__from_ordinal_date_unchecked(MAX_YEAR, days_in_year(MAX_YEAR)); + // Safety: `ordinal` is not zero. + #[allow(clippy::undocumented_unsafe_blocks)] + pub const MAX: Self = + unsafe { Self::__from_ordinal_date_unchecked(MAX_YEAR, days_in_year(MAX_YEAR)) }; // region: constructors /// Construct a `Date` from the year and ordinal values, the validity of which must be /// guaranteed by the caller. + /// + /// # Safety + /// + /// `ordinal` must not be zero. `year` should be in the range `MIN_YEAR..=MAX_YEAR`, but this + /// is not a safety invariant. #[doc(hidden)] - pub const fn __from_ordinal_date_unchecked(year: i32, ordinal: u16) -> Self { + pub const unsafe fn __from_ordinal_date_unchecked(year: i32, ordinal: u16) -> Self { debug_assert!(year >= MIN_YEAR); debug_assert!(year <= MAX_YEAR); debug_assert!(ordinal != 0); debug_assert!(ordinal <= days_in_year(year)); Self { - value: (year << 9) | ordinal as i32, + // Safety: The caller must guarantee that `ordinal` is not zero. + value: unsafe { NonZeroI32::new_unchecked((year << 9) | ordinal as i32) }, } } @@ -91,14 +111,29 @@ impl Date { [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335], ]; - ensure_value_in_range!(year in MIN_YEAR => MAX_YEAR); - ensure_value_in_range!(day conditionally in 1 => days_in_year_month(year, month)); + ensure_ranged!(Year: year); + match day { + 1..=28 => {} + 29..=31 if day <= days_in_year_month(year, month) => {} + _ => { + return Err(crate::error::ComponentRange { + name: "day", + minimum: 1, + maximum: days_in_year_month(year, month) as _, + value: day as _, + conditional_range: true, + }); + } + } - Ok(Self::__from_ordinal_date_unchecked( - year, - DAYS_CUMULATIVE_COMMON_LEAP[is_leap_year(year) as usize][month as usize - 1] - + day as u16, - )) + // Safety: `ordinal` is not zero. + Ok(unsafe { + Self::__from_ordinal_date_unchecked( + year, + DAYS_CUMULATIVE_COMMON_LEAP[is_leap_year(year) as usize][month as usize - 1] + + day as u16, + ) + }) } /// Attempt to create a `Date` from the year and ordinal day number. @@ -114,9 +149,23 @@ impl Date { /// assert!(Date::from_ordinal_date(2019, 366).is_err()); // 2019 isn't a leap year. /// ``` pub const fn from_ordinal_date(year: i32, ordinal: u16) -> Result<Self, error::ComponentRange> { - ensure_value_in_range!(year in MIN_YEAR => MAX_YEAR); - ensure_value_in_range!(ordinal conditionally in 1 => days_in_year(year)); - Ok(Self::__from_ordinal_date_unchecked(year, ordinal)) + ensure_ranged!(Year: year); + match ordinal { + 1..=365 => {} + 366 if is_leap_year(year) => {} + _ => { + return Err(crate::error::ComponentRange { + name: "ordinal", + minimum: 1, + maximum: days_in_year(year) as _, + value: ordinal as _, + conditional_range: true, + }); + } + } + + // Safety: `ordinal` is not zero. + Ok(unsafe { Self::__from_ordinal_date_unchecked(year, ordinal) }) } /// Attempt to create a `Date` from the ISO year, week, and weekday. @@ -137,8 +186,20 @@ impl Date { week: u8, weekday: Weekday, ) -> Result<Self, error::ComponentRange> { - ensure_value_in_range!(year in MIN_YEAR => MAX_YEAR); - ensure_value_in_range!(week conditionally in 1 => weeks_in_year(year)); + ensure_ranged!(Year: year); + match week { + 1..=52 => {} + 53 if week <= weeks_in_year(year) => {} + _ => { + return Err(crate::error::ComponentRange { + name: "week", + minimum: 1, + maximum: weeks_in_year(year) as _, + value: week as _, + conditional_range: true, + }); + } + } let adj_year = year - 1; let raw = 365 * adj_year + div_floor!(adj_year, 4) - div_floor!(adj_year, 100) @@ -155,14 +216,21 @@ impl Date { let ordinal = week as i16 * 7 + weekday.number_from_monday() as i16 - jan_4; Ok(if ordinal <= 0 { - Self::__from_ordinal_date_unchecked( - year - 1, - (ordinal as u16).wrapping_add(days_in_year(year - 1)), - ) + // Safety: `ordinal` is not zero. + unsafe { + Self::__from_ordinal_date_unchecked( + year - 1, + (ordinal as u16).wrapping_add(days_in_year(year - 1)), + ) + } } else if ordinal > days_in_year(year) as i16 { - Self::__from_ordinal_date_unchecked(year + 1, ordinal as u16 - days_in_year(year)) + // Safety: `ordinal` is not zero. + unsafe { + Self::__from_ordinal_date_unchecked(year + 1, ordinal as u16 - days_in_year(year)) + } } else { - Self::__from_ordinal_date_unchecked(year, ordinal as _) + // Safety: `ordinal` is not zero. + unsafe { Self::__from_ordinal_date_unchecked(year, ordinal as _) } }) } @@ -181,9 +249,8 @@ impl Date { /// ``` #[doc(alias = "from_julian_date")] pub const fn from_julian_day(julian_day: i32) -> Result<Self, error::ComponentRange> { - ensure_value_in_range!( - julian_day in Self::MIN.to_julian_day() => Self::MAX.to_julian_day() - ); + type JulianDay = RangedI32<{ Date::MIN.to_julian_day() }, { Date::MAX.to_julian_day() }>; + ensure_ranged!(JulianDay: julian_day); Ok(Self::from_julian_day_unchecked(julian_day)) } @@ -223,7 +290,8 @@ impl Date { cascade!(ordinal in 1..366 => year); } - Self::__from_ordinal_date_unchecked(year, ordinal) + // Safety: `ordinal` is not zero. + unsafe { Self::__from_ordinal_date_unchecked(year, ordinal) } } // endregion constructors @@ -237,7 +305,7 @@ impl Date { /// assert_eq!(date!(2020 - 01 - 01).year(), 2020); /// ``` pub const fn year(self) -> i32 { - self.value >> 9 + self.value.get() >> 9 } /// Get the month. @@ -316,7 +384,7 @@ impl Date { /// assert_eq!(date!(2019 - 12 - 31).ordinal(), 365); /// ``` pub const fn ordinal(self) -> u16 { - (self.value & 0x1FF) as _ + (self.value.get() & 0x1FF) as _ } /// Get the ISO 8601 year and week number. @@ -483,14 +551,16 @@ impl Date { /// ``` pub const fn next_day(self) -> Option<Self> { if self.ordinal() == 366 || (self.ordinal() == 365 && !is_leap_year(self.year())) { - if self.value == Self::MAX.value { + if self.value.get() == Self::MAX.value.get() { None } else { - Some(Self::__from_ordinal_date_unchecked(self.year() + 1, 1)) + // Safety: `ordinal` is not zero. + unsafe { Some(Self::__from_ordinal_date_unchecked(self.year() + 1, 1)) } } } else { Some(Self { - value: self.value + 1, + // Safety: `ordinal` is not zero. + value: unsafe { NonZeroI32::new_unchecked(self.value.get() + 1) }, }) } } @@ -517,18 +587,119 @@ impl Date { pub const fn previous_day(self) -> Option<Self> { if self.ordinal() != 1 { Some(Self { - value: self.value - 1, + // Safety: `ordinal` is not zero. + value: unsafe { NonZeroI32::new_unchecked(self.value.get() - 1) }, }) - } else if self.value == Self::MIN.value { + } else if self.value.get() == Self::MIN.value.get() { None } else { - Some(Self::__from_ordinal_date_unchecked( - self.year() - 1, - days_in_year(self.year() - 1), - )) + // Safety: `ordinal` is not zero. + Some(unsafe { + Self::__from_ordinal_date_unchecked(self.year() - 1, days_in_year(self.year() - 1)) + }) } } + /// Calculates the first occurrence of a weekday that is strictly later than a given `Date`. + /// + /// # Panics + /// Panics if an overflow occurred. + /// + /// # Examples + /// ``` + /// # use time::Weekday; + /// # use time_macros::date; + /// assert_eq!( + /// date!(2023 - 06 - 28).next_occurrence(Weekday::Monday), + /// date!(2023 - 07 - 03) + /// ); + /// assert_eq!( + /// date!(2023 - 06 - 19).next_occurrence(Weekday::Monday), + /// date!(2023 - 06 - 26) + /// ); + /// ``` + pub const fn next_occurrence(self, weekday: Weekday) -> Self { + expect_opt!( + self.checked_next_occurrence(weekday), + "overflow calculating the next occurrence of a weekday" + ) + } + + /// Calculates the first occurrence of a weekday that is strictly earlier than a given `Date`. + /// + /// # Panics + /// Panics if an overflow occurred. + /// + /// # Examples + /// ``` + /// # use time::Weekday; + /// # use time_macros::date; + /// assert_eq!( + /// date!(2023 - 06 - 28).prev_occurrence(Weekday::Monday), + /// date!(2023 - 06 - 26) + /// ); + /// assert_eq!( + /// date!(2023 - 06 - 19).prev_occurrence(Weekday::Monday), + /// date!(2023 - 06 - 12) + /// ); + /// ``` + pub const fn prev_occurrence(self, weekday: Weekday) -> Self { + expect_opt!( + self.checked_prev_occurrence(weekday), + "overflow calculating the previous occurrence of a weekday" + ) + } + + /// Calculates the `n`th occurrence of a weekday that is strictly later than a given `Date`. + /// + /// # Panics + /// Panics if an overflow occurred or if `n == 0`. + /// + /// # Examples + /// ``` + /// # use time::Weekday; + /// # use time_macros::date; + /// assert_eq!( + /// date!(2023 - 06 - 25).nth_next_occurrence(Weekday::Monday, 5), + /// date!(2023 - 07 - 24) + /// ); + /// assert_eq!( + /// date!(2023 - 06 - 26).nth_next_occurrence(Weekday::Monday, 5), + /// date!(2023 - 07 - 31) + /// ); + /// ``` + pub const fn nth_next_occurrence(self, weekday: Weekday, n: u8) -> Self { + expect_opt!( + self.checked_nth_next_occurrence(weekday, n), + "overflow calculating the next occurrence of a weekday" + ) + } + + /// Calculates the `n`th occurrence of a weekday that is strictly earlier than a given `Date`. + /// + /// # Panics + /// Panics if an overflow occurred or if `n == 0`. + /// + /// # Examples + /// ``` + /// # use time::Weekday; + /// # use time_macros::date; + /// assert_eq!( + /// date!(2023 - 06 - 27).nth_prev_occurrence(Weekday::Monday, 3), + /// date!(2023 - 06 - 12) + /// ); + /// assert_eq!( + /// date!(2023 - 06 - 26).nth_prev_occurrence(Weekday::Monday, 3), + /// date!(2023 - 06 - 05) + /// ); + /// ``` + pub const fn nth_prev_occurrence(self, weekday: Weekday, n: u8) -> Self { + expect_opt!( + self.checked_nth_prev_occurrence(weekday, n), + "overflow calculating the previous occurrence of a weekday" + ) + } + /// Get the Julian day for the date. /// /// The algorithm to perform this conversion is derived from one provided by Peter Baum; it is @@ -597,6 +768,49 @@ impl Date { } } + /// Computes `self + duration`, returning `None` if an overflow occurred. + /// + /// ```rust + /// # use time::{Date, ext::NumericalStdDuration}; + /// # use time_macros::date; + /// assert_eq!(Date::MAX.checked_add_std(1.std_days()), None); + /// assert_eq!( + /// date!(2020 - 12 - 31).checked_add_std(2.std_days()), + /// Some(date!(2021 - 01 - 02)) + /// ); + /// ``` + /// + /// # Note + /// + /// This function only takes whole days into account. + /// + /// ```rust + /// # use time::{Date, ext::NumericalStdDuration}; + /// # use time_macros::date; + /// assert_eq!(Date::MAX.checked_add_std(23.std_hours()), Some(Date::MAX)); + /// assert_eq!( + /// date!(2020 - 12 - 31).checked_add_std(23.std_hours()), + /// Some(date!(2020 - 12 - 31)) + /// ); + /// assert_eq!( + /// date!(2020 - 12 - 31).checked_add_std(47.std_hours()), + /// Some(date!(2021 - 01 - 01)) + /// ); + /// ``` + pub const fn checked_add_std(self, duration: StdDuration) -> Option<Self> { + let whole_days = duration.as_secs() / Second::per(Day) as u64; + if whole_days > i32::MAX as u64 { + return None; + } + + let julian_day = const_try_opt!(self.to_julian_day().checked_add(whole_days as _)); + if let Ok(date) = Self::from_julian_day(julian_day) { + Some(date) + } else { + None + } + } + /// Computes `self - duration`, returning `None` if an overflow occurred. /// /// ``` @@ -641,6 +855,109 @@ impl Date { None } } + + /// Computes `self - duration`, returning `None` if an overflow occurred. + /// + /// ``` + /// # use time::{Date, ext::NumericalStdDuration}; + /// # use time_macros::date; + /// assert_eq!(Date::MIN.checked_sub_std(1.std_days()), None); + /// assert_eq!( + /// date!(2020 - 12 - 31).checked_sub_std(2.std_days()), + /// Some(date!(2020 - 12 - 29)) + /// ); + /// ``` + /// + /// # Note + /// + /// This function only takes whole days into account. + /// + /// ``` + /// # use time::{Date, ext::NumericalStdDuration}; + /// # use time_macros::date; + /// assert_eq!(Date::MIN.checked_sub_std(23.std_hours()), Some(Date::MIN)); + /// assert_eq!( + /// date!(2020 - 12 - 31).checked_sub_std(23.std_hours()), + /// Some(date!(2020 - 12 - 31)) + /// ); + /// assert_eq!( + /// date!(2020 - 12 - 31).checked_sub_std(47.std_hours()), + /// Some(date!(2020 - 12 - 30)) + /// ); + /// ``` + pub const fn checked_sub_std(self, duration: StdDuration) -> Option<Self> { + let whole_days = duration.as_secs() / Second::per(Day) as u64; + if whole_days > i32::MAX as u64 { + return None; + } + + let julian_day = const_try_opt!(self.to_julian_day().checked_sub(whole_days as _)); + if let Ok(date) = Self::from_julian_day(julian_day) { + Some(date) + } else { + None + } + } + + /// Calculates the first occurrence of a weekday that is strictly later than a given `Date`. + /// Returns `None` if an overflow occurred. + pub(crate) const fn checked_next_occurrence(self, weekday: Weekday) -> Option<Self> { + let day_diff = match weekday as i8 - self.weekday() as i8 { + 1 | -6 => 1, + 2 | -5 => 2, + 3 | -4 => 3, + 4 | -3 => 4, + 5 | -2 => 5, + 6 | -1 => 6, + val => { + debug_assert!(val == 0); + 7 + } + }; + + self.checked_add(Duration::days(day_diff)) + } + + /// Calculates the first occurrence of a weekday that is strictly earlier than a given `Date`. + /// Returns `None` if an overflow occurred. + pub(crate) const fn checked_prev_occurrence(self, weekday: Weekday) -> Option<Self> { + let day_diff = match weekday as i8 - self.weekday() as i8 { + 1 | -6 => 6, + 2 | -5 => 5, + 3 | -4 => 4, + 4 | -3 => 3, + 5 | -2 => 2, + 6 | -1 => 1, + val => { + debug_assert!(val == 0); + 7 + } + }; + + self.checked_sub(Duration::days(day_diff)) + } + + /// Calculates the `n`th occurrence of a weekday that is strictly later than a given `Date`. + /// Returns `None` if an overflow occurred or if `n == 0`. + pub(crate) const fn checked_nth_next_occurrence(self, weekday: Weekday, n: u8) -> Option<Self> { + if n == 0 { + return None; + } + + const_try_opt!(self.checked_next_occurrence(weekday)) + .checked_add(Duration::weeks(n as i64 - 1)) + } + + /// Calculates the `n`th occurrence of a weekday that is strictly earlier than a given `Date`. + /// Returns `None` if an overflow occurred or if `n == 0`. + pub(crate) const fn checked_nth_prev_occurrence(self, weekday: Weekday, n: u8) -> Option<Self> { + if n == 0 { + return None; + } + + const_try_opt!(self.checked_prev_occurrence(weekday)) + .checked_sub(Duration::weeks(n as i64 - 1)) + } // endregion: checked arithmetic // region: saturating arithmetic @@ -739,17 +1056,21 @@ impl Date { /// ``` #[must_use = "This method does not mutate the original `Date`."] pub const fn replace_year(self, year: i32) -> Result<Self, error::ComponentRange> { - ensure_value_in_range!(year in MIN_YEAR => MAX_YEAR); + ensure_ranged!(Year: year); let ordinal = self.ordinal(); // Dates in January and February are unaffected by leap years. if ordinal <= 59 { - return Ok(Self::__from_ordinal_date_unchecked(year, ordinal)); + // Safety: `ordinal` is not zero. + return Ok(unsafe { Self::__from_ordinal_date_unchecked(year, ordinal) }); } match (is_leap_year(self.year()), is_leap_year(year)) { - (false, false) | (true, true) => Ok(Self::__from_ordinal_date_unchecked(year, ordinal)), + (false, false) | (true, true) => { + // Safety: `ordinal` is not zero. + Ok(unsafe { Self::__from_ordinal_date_unchecked(year, ordinal) }) + } // February 29 does not exist in common years. (true, false) if ordinal == 60 => Err(error::ComponentRange { name: "day", @@ -760,10 +1081,12 @@ impl Date { }), // We're going from a common year to a leap year. Shift dates in March and later by // one day. - (false, true) => Ok(Self::__from_ordinal_date_unchecked(year, ordinal + 1)), + // Safety: `ordinal` is not zero. + (false, true) => Ok(unsafe { Self::__from_ordinal_date_unchecked(year, ordinal + 1) }), // We're going from a leap year to a common year. Shift dates in January and // February by one day. - (true, false) => Ok(Self::__from_ordinal_date_unchecked(year, ordinal - 1)), + // Safety: `ordinal` is not zero. + (true, false) => Ok(unsafe { Self::__from_ordinal_date_unchecked(year, ordinal - 1) }), } } @@ -801,17 +1124,27 @@ impl Date { /// ``` #[must_use = "This method does not mutate the original `Date`."] pub const fn replace_day(self, day: u8) -> Result<Self, error::ComponentRange> { - // Days 1-28 are present in every month, so we can skip checking. - if day == 0 || day >= 29 { - ensure_value_in_range!( - day conditionally in 1 => days_in_year_month(self.year(), self.month()) - ); + match day { + 1..=28 => {} + 29..=31 if day <= days_in_year_month(self.year(), self.month()) => {} + _ => { + return Err(crate::error::ComponentRange { + name: "day", + minimum: 1, + maximum: days_in_year_month(self.year(), self.month()) as _, + value: day as _, + conditional_range: true, + }); + } } - Ok(Self::__from_ordinal_date_unchecked( - self.year(), - (self.ordinal() as i16 - self.day() as i16 + day as i16) as _, - )) + // Safety: `ordinal` is not zero. + Ok(unsafe { + Self::__from_ordinal_date_unchecked( + self.year(), + (self.ordinal() as i16 - self.day() as i16 + day as i16) as _, + ) + }) } // endregion replacement } @@ -1014,10 +1347,8 @@ impl Add<StdDuration> for Date { type Output = Self; fn add(self, duration: StdDuration) -> Self::Output { - Self::from_julian_day( - self.to_julian_day() + (duration.as_secs() / Second.per(Day) as u64) as i32, - ) - .expect("overflow adding duration to date") + self.checked_add_std(duration) + .expect("overflow adding duration to date") } } @@ -1036,10 +1367,8 @@ impl Sub<StdDuration> for Date { type Output = Self; fn sub(self, duration: StdDuration) -> Self::Output { - Self::from_julian_day( - self.to_julian_day() - (duration.as_secs() / Second.per(Day) as u64) as i32, - ) - .expect("overflow subtracting duration from date") + self.checked_sub_std(duration) + .expect("overflow subtracting duration from date") } } |