diff --git a/tests/offset_date_time.rs b/tests/offset_date_time.rs index 767214b0c..86db74a89 100644 --- a/tests/offset_date_time.rs +++ b/tests/offset_date_time.rs @@ -1270,3 +1270,9 @@ fn saturating_sub_duration() { datetime!(+999999 - 12 - 31 23:59:59.999_999_999 +10) ); } + +#[test] +#[should_panic = "overflow adding duration to date"] +fn issue_621() { + let _ = OffsetDateTime::UNIX_EPOCH + StdDuration::from_secs(18_157_382_926_370_278_155); +} diff --git a/time/src/date.rs b/time/src/date.rs index 96a5d8b98..81de793e3 100644 --- a/time/src/date.rs +++ b/time/src/date.rs @@ -741,6 +741,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 { + 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. /// /// ``` @@ -786,6 +829,49 @@ impl Date { } } + /// 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 { + 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 { @@ -1225,10 +1311,8 @@ impl Add 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") } } @@ -1247,10 +1331,8 @@ impl Sub 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") } }