diff --git a/CHANGELOG.md b/CHANGELOG.md index fe2ce0756..f04823150 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,7 +43,7 @@ The format is based on [Keep a Changelog]. This project adheres to [Semantic Ver - Cross-building to Windows now succeeds. - A parse error on a `UtcOffset` component now indicates the error comes from the offset. -- `Duration::checked_sub` and `Duration::saturating_sub` no longer panic in edge cases. +- Some arithmetic no longer panics in edge cases. ## 0.3.3 [2021-09-25] diff --git a/src/date.rs b/src/date.rs index df8e206fe..1bf7e14ae 100644 --- a/src/date.rs +++ b/src/date.rs @@ -742,7 +742,8 @@ impl Sub for Date { type Output = Self; fn sub(self, duration: Duration) -> Self::Output { - self + -duration + Self::from_julian_day(self.to_julian_day() - duration.whole_days() as i32) + .expect("overflow subtracting duration to date") } } diff --git a/src/instant.rs b/src/instant.rs index a1f79a152..9b422fb22 100644 --- a/src/instant.rs +++ b/src/instant.rs @@ -89,7 +89,14 @@ impl Instant { /// assert_eq!(now.checked_sub((-5).seconds()), Some(now - (-5).seconds())); /// ``` pub fn checked_sub(self, duration: Duration) -> Option { - self.checked_add(-duration) + if duration.is_zero() { + Some(self) + } else if duration.is_positive() { + self.0.checked_sub(duration.abs_std()).map(Self) + } else { + debug_assert!(duration.is_negative()); + self.0.checked_add(duration.abs_std()).map(Self) + } } // endregion checked arithmetic @@ -186,7 +193,13 @@ impl Sub for Instant { type Output = Self; fn sub(self, duration: Duration) -> Self::Output { - self + -duration + if duration.is_positive() { + Self(self.0 - duration.abs_std()) + } else if duration.is_negative() { + Self(self.0 + duration.abs_std()) + } else { + self + } } } diff --git a/src/primitive_date_time.rs b/src/primitive_date_time.rs index 8ade2b16f..ac0281a4e 100644 --- a/src/primitive_date_time.rs +++ b/src/primitive_date_time.rs @@ -605,7 +605,21 @@ impl Sub for PrimitiveDateTime { type Output = Self; fn sub(self, duration: Duration) -> Self::Output { - self + -duration + let (date_adjustment, time) = self.time.adjusting_sub(duration); + let date = self.date - duration; + + Self { + date: match date_adjustment { + util::DateAdjustment::Previous => date + .previous_day() + .expect("resulting value is out of range"), + util::DateAdjustment::Next => { + date.next_day().expect("resulting value is out of range") + } + util::DateAdjustment::None => date, + }, + time, + } } } diff --git a/src/time.rs b/src/time.rs index aa7952b42..494ea9c53 100644 --- a/src/time.rs +++ b/src/time.rs @@ -328,8 +328,8 @@ impl Time { // endregion getters // region: arithmetic helpers - /// Add the sub-day time of the [`Duration`] to the `Time`. Wraps on overflow, returning the - /// necessary whether the date is the following day. + /// Add the sub-day time of the [`Duration`] to the `Time`. Wraps on overflow, returning whether + /// the date is different. pub(crate) const fn adjusting_add(self, duration: Duration) -> (DateAdjustment, Self) { let mut nanoseconds = self.nanosecond as i32 + duration.subsec_nanoseconds(); let mut seconds = self.second as i8 + (duration.whole_seconds() % 60) as i8; @@ -359,6 +359,37 @@ impl Time { ) } + /// Subtract the sub-day time of the [`Duration`] to the `Time`. Wraps on overflow, returning + /// whether the date is different. + pub(crate) const fn adjusting_sub(self, duration: Duration) -> (DateAdjustment, Self) { + let mut nanoseconds = self.nanosecond as i32 - duration.subsec_nanoseconds(); + let mut seconds = self.second as i8 - (duration.whole_seconds() % 60) as i8; + let mut minutes = self.minute as i8 - (duration.whole_minutes() % 60) as i8; + let mut hours = self.hour as i8 - (duration.whole_hours() % 24) as i8; + let mut date_adjustment = DateAdjustment::None; + + cascade!(nanoseconds in 0..1_000_000_000 => seconds); + cascade!(seconds in 0..60 => minutes); + cascade!(minutes in 0..60 => hours); + if hours >= 24 { + hours -= 24; + date_adjustment = DateAdjustment::Next; + } else if hours < 0 { + hours += 24; + date_adjustment = DateAdjustment::Previous; + } + + ( + date_adjustment, + Self::__from_hms_nanos_unchecked( + hours as _, + minutes as _, + seconds as _, + nanoseconds as _, + ), + ) + } + /// Add the sub-day time of the [`std::time::Duration`] to the `Time`. Wraps on overflow, /// returning whether the date is the previous date as the first element of the tuple. pub(crate) const fn adjusting_add_std(self, duration: StdDuration) -> (bool, Self) { @@ -524,7 +555,7 @@ impl Sub for Time { /// assert_eq!(time!(23:59:59) - (-2).seconds(), time!(0:00:01)); /// ``` fn sub(self, duration: Duration) -> Self::Output { - self + -duration + self.adjusting_sub(duration).1 } } diff --git a/tests/integration/instant.rs b/tests/integration/instant.rs index b0a2dee03..cc993e3e7 100644 --- a/tests/integration/instant.rs +++ b/tests/integration/instant.rs @@ -121,6 +121,7 @@ fn add_assign_std_duration() { fn sub_duration() { let instant = Instant::now(); assert!(instant - 100.milliseconds() <= Instant::now()); + assert_eq!(instant - Duration::ZERO, instant); } #[test]