Skip to content

Commit ffc75e5

Browse files
Zomtirpitdicker
authored andcommitted
Add TimeDelta::checked_mul and TimeDelta::checked_div
1 parent f8cecbe commit ffc75e5

File tree

1 file changed

+41
-18
lines changed

1 file changed

+41
-18
lines changed

src/time_delta.rs

+41-18
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,40 @@ impl TimeDelta {
372372
TimeDelta::new(secs, nanos as u32)
373373
}
374374

375+
/// Multiply a `TimeDelta` with a i32, returning `None` if overflow occurred.
376+
#[must_use]
377+
pub const fn checked_mul(&self, rhs: i32) -> Option<TimeDelta> {
378+
// Multiply nanoseconds as i64, because it cannot overflow that way.
379+
let total_nanos = self.nanos as i64 * rhs as i64;
380+
let (extra_secs, nanos) = div_mod_floor_64(total_nanos, NANOS_PER_SEC as i64);
381+
// Multiply seconds as i128 to prevent overflow
382+
let secs: i128 = self.secs as i128 * rhs as i128 + extra_secs as i128;
383+
if secs <= i64::MIN as i128 || secs >= i64::MAX as i128 {
384+
return None;
385+
};
386+
Some(TimeDelta { secs: secs as i64, nanos: nanos as i32 })
387+
}
388+
389+
/// Divide a `TimeDelta` with a i32, returning `None` if dividing by 0.
390+
#[must_use]
391+
pub const fn checked_div(&self, rhs: i32) -> Option<TimeDelta> {
392+
if rhs == 0 {
393+
return None;
394+
}
395+
let secs = self.secs / rhs as i64;
396+
let carry = self.secs % rhs as i64;
397+
let extra_nanos = carry * NANOS_PER_SEC as i64 / rhs as i64;
398+
let nanos = self.nanos / rhs + extra_nanos as i32;
399+
400+
let (secs, nanos) = match nanos {
401+
i32::MIN..=-1 => (secs - 1, nanos + NANOS_PER_SEC),
402+
NANOS_PER_SEC..=i32::MAX => (secs + 1, nanos - NANOS_PER_SEC),
403+
_ => (secs, nanos),
404+
};
405+
406+
Some(TimeDelta { secs, nanos })
407+
}
408+
375409
/// Returns the `TimeDelta` as an absolute (non-negative) value.
376410
#[inline]
377411
pub const fn abs(&self) -> TimeDelta {
@@ -489,31 +523,15 @@ impl Mul<i32> for TimeDelta {
489523
type Output = TimeDelta;
490524

491525
fn mul(self, rhs: i32) -> TimeDelta {
492-
// Multiply nanoseconds as i64, because it cannot overflow that way.
493-
let total_nanos = self.nanos as i64 * rhs as i64;
494-
let (extra_secs, nanos) = div_mod_floor_64(total_nanos, NANOS_PER_SEC as i64);
495-
let secs = self.secs * rhs as i64 + extra_secs;
496-
TimeDelta { secs, nanos: nanos as i32 }
526+
self.checked_mul(rhs).expect("`TimeDelta * i32` overflowed")
497527
}
498528
}
499529

500530
impl Div<i32> for TimeDelta {
501531
type Output = TimeDelta;
502532

503533
fn div(self, rhs: i32) -> TimeDelta {
504-
let mut secs = self.secs / rhs as i64;
505-
let carry = self.secs - secs * rhs as i64;
506-
let extra_nanos = carry * NANOS_PER_SEC as i64 / rhs as i64;
507-
let mut nanos = self.nanos / rhs + extra_nanos as i32;
508-
if nanos >= NANOS_PER_SEC {
509-
nanos -= NANOS_PER_SEC;
510-
secs += 1;
511-
}
512-
if nanos < 0 {
513-
nanos += NANOS_PER_SEC;
514-
secs -= 1;
515-
}
516-
TimeDelta { secs, nanos }
534+
self.checked_div(rhs).expect("`i32` is zero")
517535
}
518536
}
519537

@@ -1034,6 +1052,7 @@ mod tests {
10341052
#[test]
10351053
fn test_duration_checked_ops() {
10361054
let milliseconds = |ms| TimeDelta::try_milliseconds(ms).unwrap();
1055+
let seconds = |s| TimeDelta::try_seconds(s).unwrap();
10371056

10381057
assert_eq!(
10391058
milliseconds(i64::MAX).checked_add(&milliseconds(0)),
@@ -1056,6 +1075,10 @@ mod tests {
10561075
);
10571076
assert!(milliseconds(-i64::MAX).checked_sub(&milliseconds(1)).is_none());
10581077
assert!(milliseconds(-i64::MAX).checked_sub(&TimeDelta::nanoseconds(1)).is_none());
1078+
1079+
assert!(seconds(i64::MAX / 1000).checked_mul(2000).is_none());
1080+
assert!(seconds(i64::MIN / 1000).checked_mul(2000).is_none());
1081+
assert!(seconds(1).checked_div(0).is_none());
10591082
}
10601083

10611084
#[test]

0 commit comments

Comments
 (0)