From a9fea9f02d494e7bee90eb172e54c980638c199d Mon Sep 17 00:00:00 2001 From: Jacob Pratt Date: Sat, 29 Jul 2023 06:23:24 -0400 Subject: [PATCH] Use ranged integers internally for `Duration` --- time/src/duration.rs | 211 ++++++++++++++++++++++++++++------------- time/src/quickcheck.rs | 17 ++-- time/src/rand.rs | 4 +- time/src/tests.rs | 8 -- time/src/time.rs | 10 +- 5 files changed, 164 insertions(+), 86 deletions(-) diff --git a/time/src/duration.rs b/time/src/duration.rs index a2e588ad6..62d6f1d41 100644 --- a/time/src/duration.rs +++ b/time/src/duration.rs @@ -6,6 +6,8 @@ use core::iter::Sum; use core::ops::{Add, AddAssign, Div, Mul, Neg, Sub, SubAssign}; use core::time::Duration as StdDuration; +use deranged::RangedI32; + use crate::convert::*; use crate::error; #[cfg(feature = "std")] @@ -20,11 +22,16 @@ pub(crate) enum Padding { Optimize, } -impl Default for Padding { - fn default() -> Self { - Self::Optimize - } -} +/// The type of the `nanosecond` field of `Duration`. +type Nanoseconds = + RangedI32<{ -(Nanosecond.per(Second) as i32 - 1) }, { Nanosecond.per(Second) as i32 - 1 }>; + +/// A value for the `nanoseconds` field that is exactly zero. +/// +/// This is used in a few locations, so centralizing the `unsafe` makes sense. +// Safety: Zero is in range. +#[allow(clippy::undocumented_unsafe_blocks)] // rust-lang/rust-clippy#11246 +const ZERO: Nanoseconds = unsafe { Nanoseconds::new_unchecked(0) }; /// A span of time with nanosecond precision. /// @@ -32,12 +39,13 @@ impl Default for Padding { /// nanoseconds. /// /// This implementation allows for negative durations, unlike [`core::time::Duration`]. -#[derive(Clone, Copy, Default, PartialEq, Eq, Hash, PartialOrd, Ord)] +#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct Duration { /// Number of whole seconds. seconds: i64, /// Number of nanoseconds within the second. The sign always matches the `seconds` field. - nanoseconds: i32, // always -10^9 < nanoseconds < 10^9 + // Sign must match that of `seconds` (though this is not a safety requirement). + nanoseconds: Nanoseconds, #[allow(clippy::missing_docs_in_private_items)] padding: Padding, } @@ -51,6 +59,16 @@ impl fmt::Debug for Duration { } } +impl Default for Duration { + fn default() -> Self { + Self { + seconds: 0, + nanoseconds: ZERO, + padding: Padding::Optimize, + } + } +} + /// This is adapted from the [`std` implementation][std], which uses mostly bit /// operations to ensure the highest precision: /// @@ -146,7 +164,7 @@ macro_rules! try_from_secs { // following numbers -128..=127. The check above (exp < 63) // doesn't cover i64::MIN as that is -2^63, so we have this // additional case to handle the asymmetry of iN::MIN. - break 'value Self::new_unchecked(i64::MIN, 0); + break 'value Self::new_ranged_unchecked(i64::MIN, ZERO); } else if $secs.is_nan() { // Change from std: std doesn't differentiate between the error // cases. @@ -166,7 +184,8 @@ macro_rules! try_from_secs { let secs_signed = ((secs as i64) ^ (mask as i64)) - (mask as i64); #[allow(trivial_numeric_casts)] let nanos_signed = ((nanos as i32) ^ (mask as i32)) - (mask as i32); - Self::new_unchecked(secs_signed, nanos_signed) + // Safety: `nanos_signed` is in range. + unsafe { Self::new_unchecked(secs_signed, nanos_signed) } } }}; } @@ -246,10 +265,10 @@ impl Duration { pub const WEEK: Self = Self::weeks(1); /// The minimum possible duration. Adding any negative duration to this will cause an overflow. - pub const MIN: Self = Self::new_unchecked(i64::MIN, -((Nanosecond.per(Second) - 1) as i32)); + pub const MIN: Self = Self::new_ranged(i64::MIN, Nanoseconds::MIN); /// The maximum possible duration. Adding any positive duration to this will cause an overflow. - pub const MAX: Self = Self::new_unchecked(i64::MAX, (Nanosecond.per(Second) - 1) as _); + pub const MAX: Self = Self::new_ranged(i64::MAX, Nanoseconds::MAX); // endregion constants // region: is_{sign} @@ -261,7 +280,7 @@ impl Duration { /// assert!(!1.nanoseconds().is_zero()); /// ``` pub const fn is_zero(self) -> bool { - self.seconds == 0 && self.nanoseconds == 0 + self.seconds == 0 && self.nanoseconds.get() == 0 } /// Check if a duration is negative. @@ -273,7 +292,7 @@ impl Duration { /// assert!(!1.seconds().is_negative()); /// ``` pub const fn is_negative(self) -> bool { - self.seconds < 0 || self.nanoseconds < 0 + self.seconds < 0 || self.nanoseconds.get() < 0 } /// Check if a duration is positive. @@ -285,7 +304,7 @@ impl Duration { /// assert!(!(-1).seconds().is_positive()); /// ``` pub const fn is_positive(self) -> bool { - self.seconds > 0 || self.nanoseconds > 0 + self.seconds > 0 || self.nanoseconds.get() > 0 } // endregion is_{sign} @@ -302,7 +321,7 @@ impl Duration { /// ``` pub const fn abs(self) -> Self { match self.seconds.checked_abs() { - Some(seconds) => Self::new_unchecked(seconds, self.nanoseconds.abs()), + Some(seconds) => Self::new_ranged_unchecked(seconds, self.nanoseconds.abs()), None => Self::MAX, } } @@ -317,21 +336,36 @@ impl Duration { /// assert_eq!((-1).seconds().unsigned_abs(), 1.std_seconds()); /// ``` pub const fn unsigned_abs(self) -> StdDuration { - StdDuration::new(self.seconds.unsigned_abs(), self.nanoseconds.unsigned_abs()) + StdDuration::new( + self.seconds.unsigned_abs(), + self.nanoseconds.get().unsigned_abs(), + ) } // endregion abs // region: constructors /// Create a new `Duration` without checking the validity of the components. - pub(crate) const fn new_unchecked(seconds: i64, nanoseconds: i32) -> Self { + /// + /// # Safety + /// + /// - `nanoseconds` must be in the range `-999_999_999..=999_999_999`. + /// + /// While the sign of `nanoseconds` is required to be the same as the sign of `seconds`, this is + /// not a safety invariant. + pub(crate) const unsafe fn new_unchecked(seconds: i64, nanoseconds: i32) -> Self { + Self::new_ranged_unchecked( + seconds, + // Safety: The caller must uphold the safety invariants. + unsafe { Nanoseconds::new_unchecked(nanoseconds) }, + ) + } + + /// Create a new `Duration` without checking the validity of the components. + pub(crate) const fn new_ranged_unchecked(seconds: i64, nanoseconds: Nanoseconds) -> Self { if seconds < 0 { - debug_assert!(nanoseconds <= 0); - debug_assert!(nanoseconds > -(Nanosecond.per(Second) as i32)); + debug_assert!(nanoseconds.get() <= 0); } else if seconds > 0 { - debug_assert!(nanoseconds >= 0); - debug_assert!(nanoseconds < Nanosecond.per(Second) as _); - } else { - debug_assert!(nanoseconds.unsigned_abs() < Nanosecond.per(Second)); + debug_assert!(nanoseconds.get() >= 0); } Self { @@ -367,7 +401,31 @@ impl Duration { nanoseconds -= Nanosecond.per(Second) as i32; } - Self::new_unchecked(seconds, nanoseconds) + // Safety: `nanoseconds` is in range due to the modulus above. + unsafe { Self::new_unchecked(seconds, nanoseconds) } + } + + /// Create a new `Duration` with the provided seconds and nanoseconds. + pub(crate) const fn new_ranged(mut seconds: i64, mut nanoseconds: Nanoseconds) -> Self { + if seconds > 0 && nanoseconds.get() < 0 { + // `seconds` cannot overflow here because it is positive. + seconds -= 1; + // Safety: `nanoseconds` is negative with a maximum of 999,999,999, so adding a billion + // to it is guaranteed to result in an in-range value. + nanoseconds = unsafe { + Nanoseconds::new_unchecked(nanoseconds.get() + Nanosecond.per(Second) as i32) + }; + } else if seconds < 0 && nanoseconds.get() > 0 { + // `seconds` cannot overflow here because it is negative. + seconds += 1; + // Safety: `nanoseconds` is positive with a minimum of -999,999,999, so subtracting a + // billion from it is guaranteed to result in an in-range value. + nanoseconds = unsafe { + Nanoseconds::new_unchecked(nanoseconds.get() - Nanosecond.per(Second) as i32) + }; + } + + Self::new_ranged_unchecked(seconds, nanoseconds) } /// Create a new `Duration` with the given number of weeks. Equivalent to @@ -433,7 +491,7 @@ impl Duration { /// assert_eq!(Duration::seconds(1), 1_000.milliseconds()); /// ``` pub const fn seconds(seconds: i64) -> Self { - Self::new_unchecked(seconds, 0) + Self::new_ranged_unchecked(seconds, ZERO) } /// Creates a new `Duration` from the specified number of seconds represented as `f64`. @@ -616,11 +674,14 @@ impl Duration { /// assert_eq!(Duration::milliseconds(-1), (-1_000).microseconds()); /// ``` pub const fn milliseconds(milliseconds: i64) -> Self { - Self::new_unchecked( - milliseconds / Millisecond.per(Second) as i64, - (milliseconds % Millisecond.per(Second) as i64 * Nanosecond.per(Millisecond) as i64) - as _, - ) + // Safety: `nanoseconds` is guaranteed to be in range because of the modulus. + unsafe { + Self::new_unchecked( + milliseconds / Millisecond.per(Second) as i64, + (milliseconds % Millisecond.per(Second) as i64 * Nanosecond.per(Millisecond) as i64) + as _, + ) + } } /// Create a new `Duration` with the given number of microseconds. @@ -631,11 +692,14 @@ impl Duration { /// assert_eq!(Duration::microseconds(-1), (-1_000).nanoseconds()); /// ``` pub const fn microseconds(microseconds: i64) -> Self { - Self::new_unchecked( - microseconds / Microsecond.per(Second) as i64, - (microseconds % Microsecond.per(Second) as i64 * Nanosecond.per(Microsecond) as i64) - as _, - ) + // Safety: `nanoseconds` is guaranteed to be in range because of the modulus. + unsafe { + Self::new_unchecked( + microseconds / Microsecond.per(Second) as i64, + (microseconds % Microsecond.per(Second) as i64 * Nanosecond.per(Microsecond) as i64) + as _, + ) + } } /// Create a new `Duration` with the given number of nanoseconds. @@ -646,10 +710,13 @@ impl Duration { /// assert_eq!(Duration::nanoseconds(-1), (-1).microseconds() / 1_000); /// ``` pub const fn nanoseconds(nanoseconds: i64) -> Self { - Self::new_unchecked( - nanoseconds / Nanosecond.per(Second) as i64, - (nanoseconds % Nanosecond.per(Second) as i64) as _, - ) + // Safety: `nanoseconds` is guaranteed to be in range because of the modulus. + unsafe { + Self::new_unchecked( + nanoseconds / Nanosecond.per(Second) as i64, + (nanoseconds % Nanosecond.per(Second) as i64) as _, + ) + } } /// Create a new `Duration` with the given number of nanoseconds. @@ -664,7 +731,8 @@ impl Duration { crate::expect_failed("overflow constructing `time::Duration`"); } - Self::new_unchecked(seconds as _, nanoseconds as _) + // Safety: `nanoseconds` is guaranteed to be in range because of the modulus above. + unsafe { Self::new_unchecked(seconds as _, nanoseconds as _) } } // endregion constructors @@ -742,7 +810,7 @@ impl Duration { /// assert_eq!((-1.5).seconds().as_seconds_f64(), -1.5); /// ``` pub fn as_seconds_f64(self) -> f64 { - self.seconds as f64 + self.nanoseconds as f64 / Nanosecond.per(Second) as f64 + self.seconds as f64 + self.nanoseconds.get() as f64 / Nanosecond.per(Second) as f64 } /// Get the number of fractional seconds in the duration. @@ -753,7 +821,7 @@ impl Duration { /// assert_eq!((-1.5).seconds().as_seconds_f32(), -1.5); /// ``` pub fn as_seconds_f32(self) -> f32 { - self.seconds as f32 + self.nanoseconds as f32 / Nanosecond.per(Second) as f32 + self.seconds as f32 + self.nanoseconds.get() as f32 / Nanosecond.per(Second) as f32 } /// Get the number of whole milliseconds in the duration. @@ -767,7 +835,7 @@ impl Duration { /// ``` pub const fn whole_milliseconds(self) -> i128 { self.seconds as i128 * Millisecond.per(Second) as i128 - + self.nanoseconds as i128 / Nanosecond.per(Millisecond) as i128 + + self.nanoseconds.get() as i128 / Nanosecond.per(Millisecond) as i128 } /// Get the number of milliseconds past the number of whole seconds. @@ -781,7 +849,7 @@ impl Duration { /// ``` // Allow the lint, as the value is guaranteed to be less than 1000. pub const fn subsec_milliseconds(self) -> i16 { - (self.nanoseconds / Nanosecond.per(Millisecond) as i32) as _ + (self.nanoseconds.get() / Nanosecond.per(Millisecond) as i32) as _ } /// Get the number of whole microseconds in the duration. @@ -795,7 +863,7 @@ impl Duration { /// ``` pub const fn whole_microseconds(self) -> i128 { self.seconds as i128 * Microsecond.per(Second) as i128 - + self.nanoseconds as i128 / Nanosecond.per(Microsecond) as i128 + + self.nanoseconds.get() as i128 / Nanosecond.per(Microsecond) as i128 } /// Get the number of microseconds past the number of whole seconds. @@ -808,7 +876,7 @@ impl Duration { /// assert_eq!((-1.0004).seconds().subsec_microseconds(), -400); /// ``` pub const fn subsec_microseconds(self) -> i32 { - self.nanoseconds / Nanosecond.per(Microsecond) as i32 + self.nanoseconds.get() / Nanosecond.per(Microsecond) as i32 } /// Get the number of nanoseconds in the duration. @@ -821,7 +889,7 @@ impl Duration { /// assert_eq!((-1).nanoseconds().whole_nanoseconds(), -1); /// ``` pub const fn whole_nanoseconds(self) -> i128 { - self.seconds as i128 * Nanosecond.per(Second) as i128 + self.nanoseconds as i128 + self.seconds as i128 * Nanosecond.per(Second) as i128 + self.nanoseconds.get() as i128 } /// Get the number of nanoseconds past the number of whole seconds. @@ -834,6 +902,11 @@ impl Duration { /// assert_eq!((-1.000_000_400).seconds().subsec_nanoseconds(), -400); /// ``` pub const fn subsec_nanoseconds(self) -> i32 { + self.nanoseconds.get() + } + + /// Get the number of nanoseconds past the number of whole seconds. + pub(crate) const fn subsec_nanoseconds_ranged(self) -> Nanoseconds { self.nanoseconds } // endregion getters @@ -849,7 +922,7 @@ impl Duration { /// ``` pub const fn checked_add(self, rhs: Self) -> Option { let mut seconds = const_try_opt!(self.seconds.checked_add(rhs.seconds)); - let mut nanoseconds = self.nanoseconds + rhs.nanoseconds; + let mut nanoseconds = self.nanoseconds.get() + rhs.nanoseconds.get(); if nanoseconds >= Nanosecond.per(Second) as _ || seconds < 0 && nanoseconds > 0 { nanoseconds -= Nanosecond.per(Second) as i32; @@ -860,7 +933,8 @@ impl Duration { seconds = const_try_opt!(seconds.checked_sub(1)); } - Some(Self::new_unchecked(seconds, nanoseconds)) + // Safety: `nanoseconds` is guaranteed to be in range because of the overflow handling. + unsafe { Some(Self::new_unchecked(seconds, nanoseconds)) } } /// Computes `self - rhs`, returning `None` if an overflow occurred. @@ -873,7 +947,7 @@ impl Duration { /// ``` pub const fn checked_sub(self, rhs: Self) -> Option { let mut seconds = const_try_opt!(self.seconds.checked_sub(rhs.seconds)); - let mut nanoseconds = self.nanoseconds - rhs.nanoseconds; + let mut nanoseconds = self.nanoseconds.get() - rhs.nanoseconds.get(); if nanoseconds >= Nanosecond.per(Second) as _ || seconds < 0 && nanoseconds > 0 { nanoseconds -= Nanosecond.per(Second) as i32; @@ -884,7 +958,8 @@ impl Duration { seconds = const_try_opt!(seconds.checked_sub(1)); } - Some(Self::new_unchecked(seconds, nanoseconds)) + // Safety: `nanoseconds` is guaranteed to be in range because of the overflow handling. + unsafe { Some(Self::new_unchecked(seconds, nanoseconds)) } } /// Computes `self * rhs`, returning `None` if an overflow occurred. @@ -899,14 +974,15 @@ impl Duration { /// ``` pub const fn checked_mul(self, rhs: i32) -> Option { // Multiply nanoseconds as i64, because it cannot overflow that way. - let total_nanos = self.nanoseconds as i64 * rhs as i64; + let total_nanos = self.nanoseconds.get() as i64 * rhs as i64; let extra_secs = total_nanos / Nanosecond.per(Second) as i64; let nanoseconds = (total_nanos % Nanosecond.per(Second) as i64) as _; let seconds = const_try_opt!( const_try_opt!(self.seconds.checked_mul(rhs as _)).checked_add(extra_secs) ); - Some(Self::new_unchecked(seconds, nanoseconds)) + // Safety: `nanoseconds` is guaranteed to be in range because of the modulus above. + unsafe { Some(Self::new_unchecked(seconds, nanoseconds)) } } /// Computes `self / rhs`, returning `None` if `rhs == 0` or if the result would overflow. @@ -922,9 +998,12 @@ impl Duration { let carry = self.seconds - seconds * (rhs as i64); let extra_nanos = const_try_opt!((carry * Nanosecond.per(Second) as i64).checked_div(rhs as i64)); - let nanoseconds = const_try_opt!(self.nanoseconds.checked_div(rhs)) + (extra_nanos as i32); + let nanoseconds = + const_try_opt!(self.nanoseconds.get().checked_div(rhs)) + (extra_nanos as i32); - Some(Self::new_unchecked(seconds, nanoseconds)) + // Safety: TODO may not be safe? `nanoseconds` could overflow past a billion on final + // addition? + unsafe { Some(Self::new_unchecked(seconds, nanoseconds)) } } // endregion checked arithmetic @@ -949,7 +1028,7 @@ impl Duration { } return Self::MIN; } - let mut nanoseconds = self.nanoseconds + rhs.nanoseconds; + let mut nanoseconds = self.nanoseconds.get() + rhs.nanoseconds.get(); if nanoseconds >= Nanosecond.per(Second) as _ || seconds < 0 && nanoseconds > 0 { nanoseconds -= Nanosecond.per(Second) as i32; @@ -966,7 +1045,8 @@ impl Duration { }; } - Self::new_unchecked(seconds, nanoseconds) + // Safety: `nanoseconds` is guaranteed to be in range because of the overflow handling. + unsafe { Self::new_unchecked(seconds, nanoseconds) } } /// Computes `self - rhs`, saturating if an overflow occurred. @@ -989,7 +1069,7 @@ impl Duration { } return Self::MIN; } - let mut nanoseconds = self.nanoseconds - rhs.nanoseconds; + let mut nanoseconds = self.nanoseconds.get() - rhs.nanoseconds.get(); if nanoseconds >= Nanosecond.per(Second) as _ || seconds < 0 && nanoseconds > 0 { nanoseconds -= Nanosecond.per(Second) as i32; @@ -1006,7 +1086,8 @@ impl Duration { }; } - Self::new_unchecked(seconds, nanoseconds) + // Safety: `nanoseconds` is guaranteed to be in range because of the overflow handling. + unsafe { Self::new_unchecked(seconds, nanoseconds) } } /// Computes `self * rhs`, saturating if an overflow occurred. @@ -1023,7 +1104,7 @@ impl Duration { /// ``` pub const fn saturating_mul(self, rhs: i32) -> Self { // Multiply nanoseconds as i64, because it cannot overflow that way. - let total_nanos = self.nanoseconds as i64 * rhs as i64; + let total_nanos = self.nanoseconds.get() as i64 * rhs as i64; let extra_secs = total_nanos / Nanosecond.per(Second) as i64; let nanoseconds = (total_nanos % Nanosecond.per(Second) as i64) as _; let (seconds, overflow1) = self.seconds.overflowing_mul(rhs as _); @@ -1041,7 +1122,8 @@ impl Duration { return Self::MIN; } - Self::new_unchecked(seconds, nanoseconds) + // Safety: `nanoseconds` is guaranteed to be in range because of to the modulus above. + unsafe { Self::new_unchecked(seconds, nanoseconds) } } // endregion saturating arithmetic @@ -1124,7 +1206,7 @@ impl fmt::Display for Duration { } let seconds = self.seconds.unsigned_abs(); - let nanoseconds = self.nanoseconds.unsigned_abs(); + let nanoseconds = self.nanoseconds.get().unsigned_abs(); item!("d", seconds / Second.per(Day) as u64)?; item!( @@ -1174,6 +1256,7 @@ impl TryFrom for StdDuration { .map_err(|_| error::ConversionRange)?, duration .nanoseconds + .get() .try_into() .map_err(|_| error::ConversionRange)?, )) @@ -1221,7 +1304,7 @@ impl Neg for Duration { type Output = Self; fn neg(self) -> Self::Output { - Self::new_unchecked(-self.seconds, -self.nanoseconds) + Self::new_ranged_unchecked(-self.seconds, self.nanoseconds.neg()) } } @@ -1395,7 +1478,7 @@ impl PartialOrd for Duration { Some( self.seconds .cmp(&(rhs.as_secs() as _)) - .then_with(|| self.nanoseconds.cmp(&(rhs.subsec_nanos() as _))), + .then_with(|| self.nanoseconds.get().cmp(&(rhs.subsec_nanos() as _))), ) } } diff --git a/time/src/quickcheck.rs b/time/src/quickcheck.rs index 756a67cc3..791491e44 100644 --- a/time/src/quickcheck.rs +++ b/time/src/quickcheck.rs @@ -72,25 +72,22 @@ impl Arbitrary for Date { impl Arbitrary for Duration { fn arbitrary(g: &mut Gen) -> Self { - Self::nanoseconds_i128(arbitrary_between!( - i128; - g, - Self::MIN.whole_nanoseconds(), - Self::MAX.whole_nanoseconds() - )) + Self::new_ranged(<_>::arbitrary(g), <_>::arbitrary(g)) } fn shrink(&self) -> Box> { Box::new( - (self.subsec_nanoseconds(), self.whole_seconds()) + (self.subsec_nanoseconds_ranged(), self.whole_seconds()) .shrink() .map(|(mut nanoseconds, seconds)| { // Coerce the sign if necessary. - if (seconds > 0 && nanoseconds < 0) || (seconds < 0 && nanoseconds > 0) { - nanoseconds *= -1; + if (seconds > 0 && nanoseconds.get() < 0) + || (seconds < 0 && nanoseconds.get() > 0) + { + nanoseconds = nanoseconds.neg(); } - Self::new_unchecked(seconds, nanoseconds) + Self::new_ranged_unchecked(seconds, nanoseconds) }), ) } diff --git a/time/src/rand.rs b/time/src/rand.rs index db6d42439..eac6fe66d 100644 --- a/time/src/rand.rs +++ b/time/src/rand.rs @@ -40,9 +40,7 @@ impl Distribution for Standard { impl Distribution for Standard { fn sample(&self, rng: &mut R) -> Duration { - Duration::nanoseconds_i128( - rng.gen_range(Duration::MIN.whole_nanoseconds()..=Duration::MAX.whole_nanoseconds()), - ) + Duration::new_ranged(rng.gen(), rng.gen()) } } diff --git a/time/src/tests.rs b/time/src/tests.rs index 030c473be..e637ba03d 100644 --- a/time/src/tests.rs +++ b/time/src/tests.rs @@ -71,14 +71,6 @@ fn digit_count() { assert_eq!(1_000_000_000_u32.num_digits(), 10); } -#[test] -fn default() { - assert_eq!( - duration::Padding::Optimize.clone(), - duration::Padding::default() - ); -} - #[test] fn debug() { let _ = format!("{:?}", duration::Padding::Optimize); diff --git a/time/src/time.rs b/time/src/time.rs index 13f8ab00c..477dcf528 100644 --- a/time/src/time.rs +++ b/time/src/time.rs @@ -160,6 +160,13 @@ impl Time { // region: constructors /// Create a `Time` from its components. + /// + /// # Safety + /// + /// - `hours` must be in the range `0..=23`. + /// - `minutes` must be in the range `0..=59`. + /// - `seconds` must be in the range `0..=59`. + /// - `nanoseconds` must be in the range `0..=999_999_999`. #[doc(hidden)] pub const unsafe fn __from_hms_nanos_unchecked( hour: u8, @@ -875,7 +882,8 @@ impl Sub for Time { (seconds, nanosecond_diff) }; - Duration::new_unchecked(seconds, nanoseconds) + // Safety: `nanoseconds` is in range due to the overflow handling. + unsafe { Duration::new_unchecked(seconds, nanoseconds) } } } // endregion trait impls