Skip to content

Commit

Permalink
Eliminate more panics in edge cases
Browse files Browse the repository at this point in the history
  • Loading branch information
jhpratt committed Oct 25, 2021
1 parent ea8e71c commit bcde313
Show file tree
Hide file tree
Showing 6 changed files with 68 additions and 8 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]

Expand Down
3 changes: 2 additions & 1 deletion src/date.rs
Original file line number Diff line number Diff line change
Expand Up @@ -742,7 +742,8 @@ impl Sub<Duration> 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")
}
}

Expand Down
17 changes: 15 additions & 2 deletions src/instant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
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

Expand Down Expand Up @@ -186,7 +193,13 @@ impl Sub<Duration> 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
}
}
}

Expand Down
16 changes: 15 additions & 1 deletion src/primitive_date_time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -605,7 +605,21 @@ impl Sub<Duration> 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,
}
}
}

Expand Down
37 changes: 34 additions & 3 deletions src/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -524,7 +555,7 @@ impl Sub<Duration> 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
}
}

Expand Down
1 change: 1 addition & 0 deletions tests/integration/instant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down

0 comments on commit bcde313

Please sign in to comment.