From 6203516241a95aa6e1d53a788ad5e06e04dae6f8 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 7 Jun 2023 09:43:36 +0200 Subject: [PATCH 1/5] Derive `Default` for `Duration`. --- src/duration.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/duration.rs b/src/duration.rs index 570c6ce5f3..7673ede27a 100644 --- a/src/duration.rs +++ b/src/duration.rs @@ -50,7 +50,7 @@ macro_rules! try_opt { /// ISO 8601 time duration with nanosecond precision. /// /// This also allows for the negative duration; see individual methods for details. -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] +#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] #[cfg_attr( feature = "rkyv", From 805729bff1d18b3477ace53d89a56dd7a8e3a64d Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 8 Jun 2023 08:49:56 +0200 Subject: [PATCH 2/5] Rename `Duration::nanos_mod_sec` to `subsec_nanos`, make public --- src/duration.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/duration.rs b/src/duration.rs index 7673ede27a..b46f1705b6 100644 --- a/src/duration.rs +++ b/src/duration.rs @@ -184,9 +184,9 @@ impl Duration { } /// Returns the number of nanoseconds such that - /// `nanos_mod_sec() + num_seconds() * NANOS_PER_SEC` is the total number of + /// `subsec_nanos() + num_seconds() * NANOS_PER_SEC` is the total number of /// nanoseconds in the duration. - const fn nanos_mod_sec(&self) -> i32 { + pub const fn subsec_nanos(&self) -> i32 { if self.secs < 0 && self.nanos > 0 { self.nanos - NANOS_PER_SEC } else { @@ -199,7 +199,7 @@ impl Duration { // A proper Duration will not overflow, because MIN and MAX are defined // such that the range is exactly i64 milliseconds. let secs_part = self.num_seconds() * MILLIS_PER_SEC; - let nanos_part = self.nanos_mod_sec() / NANOS_PER_MILLI; + let nanos_part = self.subsec_nanos() / NANOS_PER_MILLI; secs_part + nanos_part as i64 } @@ -207,7 +207,7 @@ impl Duration { /// or `None` on overflow (exceeding 2^63 microseconds in either direction). pub const fn num_microseconds(&self) -> Option { let secs_part = try_opt!(self.num_seconds().checked_mul(MICROS_PER_SEC)); - let nanos_part = self.nanos_mod_sec() / NANOS_PER_MICRO; + let nanos_part = self.subsec_nanos() / NANOS_PER_MICRO; secs_part.checked_add(nanos_part as i64) } @@ -215,7 +215,7 @@ impl Duration { /// or `None` on overflow (exceeding 2^63 nanoseconds in either direction). pub const fn num_nanoseconds(&self) -> Option { let secs_part = try_opt!(self.num_seconds().checked_mul(NANOS_PER_SEC as i64)); - let nanos_part = self.nanos_mod_sec(); + let nanos_part = self.subsec_nanos(); secs_part.checked_add(nanos_part as i64) } From f272a06623a5bb6fb63841ef1d3403a4e238fb4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Gaspard?= Date: Sun, 22 Jan 2023 19:59:13 +0100 Subject: [PATCH 3/5] Add Duration::try_* builders --- src/duration.rs | 56 ++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 46 insertions(+), 10 deletions(-) diff --git a/src/duration.rs b/src/duration.rs index b46f1705b6..c7958f3b36 100644 --- a/src/duration.rs +++ b/src/duration.rs @@ -80,8 +80,15 @@ impl Duration { #[inline] #[must_use] pub fn weeks(weeks: i64) -> Duration { - let secs = weeks.checked_mul(SECS_PER_WEEK).expect("Duration::weeks out of bounds"); - Duration::seconds(secs) + Duration::try_weeks(weeks).expect("Duration::weeks out of bounds") + } + + /// Makes a new `Duration` with given number of weeks. + /// Equivalent to `Duration::seconds(weeks * 7 * 24 * 60 * 60)` with overflow checks. + /// Returns `None` when the duration is out of bounds. + #[inline] + pub fn try_weeks(weeks: i64) -> Option { + weeks.checked_mul(SECS_PER_WEEK).and_then(Duration::try_seconds) } /// Makes a new `Duration` with given number of days. @@ -90,8 +97,15 @@ impl Duration { #[inline] #[must_use] pub fn days(days: i64) -> Duration { - let secs = days.checked_mul(SECS_PER_DAY).expect("Duration::days out of bounds"); - Duration::seconds(secs) + Duration::try_days(days).expect("Duration::days out of bounds") + } + + /// Makes a new `Duration` with given number of days. + /// Equivalent to `Duration::seconds(days * 24 * 60 * 60)` with overflow checks. + /// Returns `None` when the duration is out of bounds. + #[inline] + pub fn try_days(days: i64) -> Option { + days.checked_mul(SECS_PER_DAY).and_then(Duration::try_seconds) } /// Makes a new `Duration` with given number of hours. @@ -100,8 +114,15 @@ impl Duration { #[inline] #[must_use] pub fn hours(hours: i64) -> Duration { - let secs = hours.checked_mul(SECS_PER_HOUR).expect("Duration::hours ouf of bounds"); - Duration::seconds(secs) + Duration::try_hours(hours).expect("Duration::hours ouf of bounds") + } + + /// Makes a new `Duration` with given number of hours. + /// Equivalent to `Duration::seconds(hours * 60 * 60)` with overflow checks. + /// Returns `None` when the duration is out of bounds. + #[inline] + pub fn try_hours(hours: i64) -> Option { + hours.checked_mul(SECS_PER_HOUR).and_then(Duration::try_seconds) } /// Makes a new `Duration` with given number of minutes. @@ -110,8 +131,15 @@ impl Duration { #[inline] #[must_use] pub fn minutes(minutes: i64) -> Duration { - let secs = minutes.checked_mul(SECS_PER_MINUTE).expect("Duration::minutes out of bounds"); - Duration::seconds(secs) + Duration::try_minutes(minutes).expect("Duration::minutes out of bounds") + } + + /// Makes a new `Duration` with given number of minutes. + /// Equivalent to `Duration::seconds(minutes * 60)` with overflow checks. + /// Returns `None` when the duration is out of bounds. + #[inline] + pub fn try_minutes(minutes: i64) -> Option { + minutes.checked_mul(SECS_PER_MINUTE).and_then(Duration::try_seconds) } /// Makes a new `Duration` with given number of seconds. @@ -120,11 +148,19 @@ impl Duration { #[inline] #[must_use] pub fn seconds(seconds: i64) -> Duration { + Duration::try_seconds(seconds).expect("Duration::seconds out of bounds") + } + + /// Makes a new `Duration` with given number of seconds. + /// Returns `None` when the duration is more than `i64::MAX` milliseconds + /// or less than `i64::MIN` milliseconds. + #[inline] + pub fn try_seconds(seconds: i64) -> Option { let d = Duration { secs: seconds, nanos: 0 }; if d < MIN || d > MAX { - panic!("Duration::seconds out of bounds"); + return None; } - d + Some(d) } /// Makes a new `Duration` with given number of milliseconds. From 5f9a733cd3c10446624f12ba8265395e48ac43d9 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 8 Jun 2023 09:19:38 +0200 Subject: [PATCH 4/5] Use `checked_add` and `checked_sub` in `Add` and `Sub` impls --- src/duration.rs | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/src/duration.rs b/src/duration.rs index c7958f3b36..add0333872 100644 --- a/src/duration.rs +++ b/src/duration.rs @@ -372,13 +372,7 @@ impl Add for Duration { type Output = Duration; fn add(self, rhs: Duration) -> Duration { - let mut secs = self.secs + rhs.secs; - let mut nanos = self.nanos + rhs.nanos; - if nanos >= NANOS_PER_SEC { - nanos -= NANOS_PER_SEC; - secs += 1; - } - Duration { secs, nanos } + self.checked_add(&rhs).expect("`Duration + Duration` overflowed") } } @@ -386,13 +380,7 @@ impl Sub for Duration { type Output = Duration; fn sub(self, rhs: Duration) -> Duration { - let mut secs = self.secs - rhs.secs; - let mut nanos = self.nanos - rhs.nanos; - if nanos < 0 { - nanos += NANOS_PER_SEC; - secs -= 1; - } - Duration { secs, nanos } + self.checked_sub(&rhs).expect("`Duration - Duration` overflowed") } } From 14d466951baddadf44015bde2eda9ec796938251 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 8 Jun 2023 09:41:37 +0200 Subject: [PATCH 5/5] Implement `AddAssign` and `SubAssign` --- src/duration.rs | 21 ++++++++++++++++++++- src/naive/time/mod.rs | 4 ++-- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/duration.rs b/src/duration.rs index add0333872..fb64591b80 100644 --- a/src/duration.rs +++ b/src/duration.rs @@ -10,7 +10,7 @@ //! Temporal quantification -use core::ops::{Add, Div, Mul, Neg, Sub}; +use core::ops::{Add, AddAssign, Div, Mul, Neg, Sub, SubAssign}; use core::time::Duration as StdDuration; use core::{fmt, i64}; #[cfg(feature = "std")] @@ -384,6 +384,20 @@ impl Sub for Duration { } } +impl AddAssign for Duration { + fn add_assign(&mut self, rhs: Duration) { + let new = self.checked_add(&rhs).expect("`Duration + Duration` overflowed"); + *self = new; + } +} + +impl SubAssign for Duration { + fn sub_assign(&mut self, rhs: Duration) { + let new = self.checked_sub(&rhs).expect("`Duration - Duration` overflowed"); + *self = new; + } +} + impl Mul for Duration { type Output = Duration; @@ -533,6 +547,11 @@ mod tests { -(Duration::days(3) + Duration::seconds(70)), Duration::days(-4) + Duration::seconds(86_400 - 70) ); + + let mut d = Duration::default(); + d += Duration::minutes(1); + d -= Duration::seconds(30); + assert_eq!(d, Duration::seconds(30)); } #[test] diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index 4882bb69b0..965834ce95 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -590,11 +590,11 @@ impl NaiveTime { if frac >= 1_000_000_000 { let rfrac = 2_000_000_000 - frac; if rhs >= OldDuration::nanoseconds(i64::from(rfrac)) { - rhs = rhs - OldDuration::nanoseconds(i64::from(rfrac)); + rhs -= OldDuration::nanoseconds(i64::from(rfrac)); secs += 1; frac = 0; } else if rhs < OldDuration::nanoseconds(-i64::from(frac)) { - rhs = rhs + OldDuration::nanoseconds(i64::from(frac)); + rhs += OldDuration::nanoseconds(i64::from(frac)); frac = 0; } else { frac = (i64::from(frac) + rhs.num_nanoseconds().unwrap()) as u32;