diff --git a/src/duration.rs b/src/duration.rs index 63fa66fd49..3bfdc66fe9 100644 --- a/src/duration.rs +++ b/src/duration.rs @@ -49,7 +49,13 @@ macro_rules! try_opt { /// ISO 8601 time duration with nanosecond precision. /// -/// This also allows for the negative duration; see individual methods for details. +/// This also allows for negative durations; see individual methods for details. +/// +/// A `Duration` is represented internally as a complement of seconds and +/// nanoseconds. The range is restricted to that of `i64` milliseconds, with the +/// minimum value notably being set to `-i64::MAX` rather than allowing the full +/// range of `i64::MIN`. This is to allow easy flipping of sign, so that for +/// instance `abs()` can be called without any checks. #[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] #[cfg_attr( @@ -76,8 +82,13 @@ pub(crate) const MAX: Duration = Duration { }; impl Duration { - /// Makes a new `Duration` with given number of weeks. - /// Equivalent to `Duration::seconds(weeks * 7 * 24 * 60 * 60)` with overflow checks. + /// Makes a new `Duration` with the given number of weeks. + /// + /// Equivalent to `Duration::seconds(weeks * 7 * 24 * 60 * 60)` with + /// overflow checks. + /// + /// # Panics + /// /// Panics when the duration is out of bounds. #[inline] #[must_use] @@ -85,16 +96,26 @@ impl Duration { 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. + /// Makes a new `Duration` with the given number of weeks. + /// + /// Equivalent to `Duration::seconds(weeks * 7 * 24 * 60 * 60)` with + /// overflow checks. + /// + /// # Errors + /// /// 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. - /// Equivalent to `Duration::seconds(days * 24 * 60 * 60)` with overflow checks. + /// Makes a new `Duration` with the given number of days. + /// + /// Equivalent to `Duration::seconds(days * 24 * 60 * 60)` with overflow + /// checks. + /// + /// # Panics + /// /// Panics when the duration is out of bounds. #[inline] #[must_use] @@ -102,16 +123,25 @@ impl Duration { 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. + /// Makes a new `Duration` with the given number of days. + /// + /// Equivalent to `Duration::seconds(days * 24 * 60 * 60)` with overflow + /// checks. + /// + /// # Errors + /// /// 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. + /// Makes a new `Duration` with the given number of hours. + /// /// Equivalent to `Duration::seconds(hours * 60 * 60)` with overflow checks. + /// + /// # Panics + /// /// Panics when the duration is out of bounds. #[inline] #[must_use] @@ -119,16 +149,24 @@ impl Duration { Duration::try_hours(hours).expect("Duration::hours ouf of bounds") } - /// Makes a new `Duration` with given number of hours. + /// Makes a new `Duration` with the given number of hours. + /// /// Equivalent to `Duration::seconds(hours * 60 * 60)` with overflow checks. + /// + /// # Errors + /// /// 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. + /// Makes a new `Duration` with the given number of minutes. + /// /// Equivalent to `Duration::seconds(minutes * 60)` with overflow checks. + /// + /// # Panics + /// /// Panics when the duration is out of bounds. #[inline] #[must_use] @@ -136,26 +174,39 @@ impl Duration { Duration::try_minutes(minutes).expect("Duration::minutes out of bounds") } - /// Makes a new `Duration` with given number of minutes. + /// Makes a new `Duration` with the given number of minutes. + /// /// Equivalent to `Duration::seconds(minutes * 60)` with overflow checks. + /// + /// # Errors + /// /// 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. - /// Panics when the duration is more than `i64::MAX` milliseconds - /// or less than `-i64::MAX` milliseconds. + /// Makes a new `Duration` with the given number of seconds. + /// + /// # Panics + /// + /// Panics when the duration is out of bounds, i.e. when the value is more + /// than `i64::MAX / 1_000` seconds or less than `-i64::MAX / 1_000` seconds + /// (in this context, this is the same as `i64::MIN / 1_000` due to + /// rounding). #[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::MAX` milliseconds. + /// Makes a new `Duration` with the given number of seconds. + /// + /// # Errors + /// + /// Returns `None` when the duration is more than `i64::MAX / 1_000` seconds + /// or less than `-i64::MAX / 1_000` seconds (in this context, this is the + /// same as `i64::MIN / 1_000` due to rounding). #[inline] pub fn try_seconds(seconds: i64) -> Option { let d = Duration { secs: seconds, nanos: 0 }; @@ -165,15 +216,43 @@ impl Duration { Some(d) } - /// Makes a new `Duration` with given number of milliseconds. + /// Makes a new `Duration` with the given number of milliseconds. + /// + /// # Panics + /// + /// Panics when the duration is out of bounds, i.e. when the duration is + /// more than `i64::MAX` milliseconds or less than `-i64::MAX` milliseconds. + /// Notably, this is not the same as `i64::MIN`. #[inline] - pub const fn milliseconds(milliseconds: i64) -> Duration { + pub fn milliseconds(milliseconds: i64) -> Duration { + Duration::try_milliseconds(milliseconds).expect("Duration::milliseconds out of bounds") + } + + /// Makes a new `Duration` with the given number of milliseconds. + /// + /// # Errors + /// + /// Returns `None` when the duration is more than `i64::MAX` milliseconds or + /// less than `-i64::MAX` milliseconds. Notably, this is not the same as + /// `i64::MIN`. + #[inline] + pub fn try_milliseconds(milliseconds: i64) -> Option { let (secs, millis) = div_mod_floor_64(milliseconds, MILLIS_PER_SEC); - let nanos = millis as i32 * NANOS_PER_MILLI; - Duration { secs, nanos } + let d = Duration { secs, nanos: millis as i32 * NANOS_PER_MILLI }; + // We don't need to compare against MAX, as this function accepts an + // i64, and MAX is aligned to i64::MAX milliseconds. + if d < MIN { + return None; + } + Some(d) } - /// Makes a new `Duration` with given number of microseconds. + /// Makes a new `Duration` with the given number of microseconds. + /// + /// The number of microseconds acceptable by this constructor is less than + /// the total number that can actually be stored in a `Duration`, so it is + /// not possible to specify a value that would be out of bounds. This + /// function is therefore infallible. #[inline] pub const fn microseconds(microseconds: i64) -> Duration { let (secs, micros) = div_mod_floor_64(microseconds, MICROS_PER_SEC); @@ -181,37 +260,42 @@ impl Duration { Duration { secs, nanos } } - /// Makes a new `Duration` with given number of nanoseconds. + /// Makes a new `Duration` with the given number of nanoseconds. + /// + /// The number of nanoseconds acceptable by this constructor is less than + /// the total number that can actually be stored in a `Duration`, so it is + /// not possible to specify a value that would be out of bounds. This + /// function is therefore infallible. #[inline] pub const fn nanoseconds(nanos: i64) -> Duration { let (secs, nanos) = div_mod_floor_64(nanos, NANOS_PER_SEC as i64); Duration { secs, nanos: nanos as i32 } } - /// Returns the total number of whole weeks in the duration. + /// Returns the total number of whole weeks in the `Duration`. #[inline] pub const fn num_weeks(&self) -> i64 { self.num_days() / 7 } - /// Returns the total number of whole days in the duration. + /// Returns the total number of whole days in the `Duration`. pub const fn num_days(&self) -> i64 { self.num_seconds() / SECS_PER_DAY } - /// Returns the total number of whole hours in the duration. + /// Returns the total number of whole hours in the `Duration`. #[inline] pub const fn num_hours(&self) -> i64 { self.num_seconds() / SECS_PER_HOUR } - /// Returns the total number of whole minutes in the duration. + /// Returns the total number of whole minutes in the `Duration`. #[inline] pub const fn num_minutes(&self) -> i64 { self.num_seconds() / SECS_PER_MINUTE } - /// Returns the total number of whole seconds in the duration. + /// Returns the total number of whole seconds in the `Duration`. pub const fn num_seconds(&self) -> i64 { // If secs is negative, nanos should be subtracted from the duration. if self.secs < 0 && self.nanos > 0 { @@ -223,7 +307,7 @@ impl Duration { /// Returns the number of nanoseconds such that /// `subsec_nanos() + num_seconds() * NANOS_PER_SEC` is the total number of - /// nanoseconds in the duration. + /// nanoseconds in the `Duration`. pub const fn subsec_nanos(&self) -> i32 { if self.secs < 0 && self.nanos > 0 { self.nanos - NANOS_PER_SEC @@ -232,16 +316,17 @@ impl Duration { } } - /// Returns the total number of whole milliseconds in the duration, + /// Returns the total number of whole milliseconds in the `Duration`. pub const fn num_milliseconds(&self) -> i64 { - // A proper Duration will not overflow, because MIN and MAX are defined - // such that the range is exactly i64 milliseconds. + // A proper Duration will not overflow, because MIN and MAX are defined such + // that the range is within the bounds of an i64, from -i64::MAX through to + // +i64::MAX inclusive. Notably, i64::MIN is excluded from this range. let secs_part = self.num_seconds() * MILLIS_PER_SEC; let nanos_part = self.subsec_nanos() / NANOS_PER_MILLI; secs_part + nanos_part as i64 } - /// Returns the total number of whole microseconds in the duration, + /// Returns the total number of whole microseconds in the `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)); @@ -249,7 +334,7 @@ impl Duration { secs_part.checked_add(nanos_part as i64) } - /// Returns the total number of whole nanoseconds in the duration, + /// Returns the total number of whole nanoseconds in the `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)); @@ -257,7 +342,7 @@ impl Duration { secs_part.checked_add(nanos_part as i64) } - /// Add two durations, returning `None` if overflow occurred. + /// Add two `Duration`s, returning `None` if overflow occurred. #[must_use] pub fn checked_add(&self, rhs: &Duration) -> Option { let mut secs = try_opt!(self.secs.checked_add(rhs.secs)); @@ -276,7 +361,7 @@ impl Duration { } } - /// Subtract two durations, returning `None` if overflow occurred. + /// Subtract two `Duration`s, returning `None` if overflow occurred. #[must_use] pub fn checked_sub(&self, rhs: &Duration) -> Option { let mut secs = try_opt!(self.secs.checked_sub(rhs.secs)); @@ -295,7 +380,7 @@ impl Duration { } } - /// Returns the duration as an absolute (non-negative) value. + /// Returns the `Duration` as an absolute (non-negative) value. #[inline] pub const fn abs(&self) -> Duration { if self.secs < 0 && self.nanos != 0 { @@ -317,13 +402,13 @@ impl Duration { MAX } - /// A duration where the stored seconds and nanoseconds are equal to zero. + /// A `Duration` where the stored seconds and nanoseconds are equal to zero. #[inline] pub const fn zero() -> Duration { Duration { secs: 0, nanos: 0 } } - /// Returns `true` if the duration equals `Duration::zero()`. + /// Returns `true` if the `Duration` equals `Duration::zero()`. #[inline] pub const fn is_zero(&self) -> bool { self.secs == 0 && self.nanos == 0 @@ -579,6 +664,42 @@ mod tests { assert_eq!(Duration::milliseconds(-999).num_seconds(), 0); assert_eq!(Duration::milliseconds(-1001).num_seconds(), -1); } + #[test] + fn test_duration_seconds_max_allowed() { + let duration = Duration::seconds(i64::MAX / 1_000); + assert_eq!(duration.num_seconds(), i64::MAX / 1_000); + assert_eq!( + duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, + i64::MAX as i128 / 1_000 * 1_000_000_000 + ); + } + #[test] + fn test_duration_seconds_max_overflow() { + assert!(Duration::try_seconds(i64::MAX / 1_000 + 1).is_none()); + } + #[test] + #[should_panic(expected = "Duration::seconds out of bounds")] + fn test_duration_seconds_max_overflow_panic() { + let _ = Duration::seconds(i64::MAX / 1_000 + 1); + } + #[test] + fn test_duration_seconds_min_allowed() { + let duration = Duration::seconds(i64::MIN / 1_000); // Same as -i64::MAX / 1_000 due to rounding + assert_eq!(duration.num_seconds(), i64::MIN / 1_000); // Same as -i64::MAX / 1_000 due to rounding + assert_eq!( + duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, + -i64::MAX as i128 / 1_000 * 1_000_000_000 + ); + } + #[test] + fn test_duration_seconds_min_underflow() { + assert!(Duration::try_seconds(-i64::MAX / 1_000 - 1).is_none()); + } + #[test] + #[should_panic(expected = "Duration::seconds out of bounds")] + fn test_duration_seconds_min_underflow_panic() { + let _ = Duration::seconds(-i64::MAX / 1_000 - 1); + } #[test] fn test_duration_num_milliseconds() { @@ -589,10 +710,52 @@ mod tests { assert_eq!(Duration::microseconds(1001).num_milliseconds(), 1); assert_eq!(Duration::microseconds(-999).num_milliseconds(), 0); assert_eq!(Duration::microseconds(-1001).num_milliseconds(), -1); - assert_eq!(Duration::milliseconds(i64::MAX).num_milliseconds(), i64::MAX); - assert_eq!(Duration::milliseconds(-i64::MAX).num_milliseconds(), -i64::MAX); - assert_eq!(MAX.num_milliseconds(), i64::MAX); - assert_eq!(MIN.num_milliseconds(), -i64::MAX); + } + #[test] + fn test_duration_milliseconds_max_allowed() { + // The maximum number of milliseconds acceptable through the constructor is + // equal to the number that can be stored in a Duration. + let duration = Duration::milliseconds(i64::MAX); + assert_eq!(duration.num_milliseconds(), i64::MAX); + assert_eq!( + duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, + i64::MAX as i128 * 1_000_000 + ); + } + #[test] + fn test_duration_milliseconds_max_overflow() { + // Here we ensure that trying to add one millisecond to the maximum storable + // value will fail. + assert!(Duration::milliseconds(i64::MAX).checked_add(&Duration::milliseconds(1)).is_none()); + } + #[test] + fn test_duration_milliseconds_min_allowed() { + // The minimum number of milliseconds acceptable through the constructor is + // not equal to the number that can be stored in a Duration - there is a + // difference of one (i64::MIN vs -i64::MAX). + let duration = Duration::milliseconds(-i64::MAX); + assert_eq!(duration.num_milliseconds(), -i64::MAX); + assert_eq!( + duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, + -i64::MAX as i128 * 1_000_000 + ); + } + #[test] + fn test_duration_milliseconds_min_underflow() { + // Here we ensure that trying to subtract one millisecond from the minimum + // storable value will fail. + assert!(Duration::milliseconds(-i64::MAX) + .checked_sub(&Duration::milliseconds(1)) + .is_none()); + } + #[test] + #[should_panic(expected = "Duration::milliseconds out of bounds")] + fn test_duration_milliseconds_min_underflow_panic() { + // Here we ensure that trying to create a value one millisecond below the + // minimum storable value will fail. This test is necessary because the + // storable range is -i64::MAX, but the constructor type of i64 will allow + // i64::MIN, which is one value below. + let _ = Duration::milliseconds(i64::MIN); // Same as -i64::MAX - 1 } #[test] @@ -604,10 +767,6 @@ mod tests { assert_eq!(Duration::nanoseconds(1001).num_microseconds(), Some(1)); assert_eq!(Duration::nanoseconds(-999).num_microseconds(), Some(0)); assert_eq!(Duration::nanoseconds(-1001).num_microseconds(), Some(-1)); - assert_eq!(Duration::microseconds(i64::MAX).num_microseconds(), Some(i64::MAX)); - assert_eq!(Duration::microseconds(-i64::MAX).num_microseconds(), Some(-i64::MAX)); - assert_eq!(MAX.num_microseconds(), None); - assert_eq!(MIN.num_microseconds(), None); // overflow checks const MICROS_PER_DAY: i64 = 86_400_000_000; @@ -622,16 +781,86 @@ mod tests { assert_eq!(Duration::days(i64::MAX / MICROS_PER_DAY + 1).num_microseconds(), None); assert_eq!(Duration::days(-i64::MAX / MICROS_PER_DAY - 1).num_microseconds(), None); } + #[test] + fn test_duration_microseconds_max_allowed() { + // The number of microseconds acceptable through the constructor is far + // fewer than the number that can actually be stored in a Duration, so this + // is not a particular insightful test. + let duration = Duration::microseconds(i64::MAX); + assert_eq!(duration.num_microseconds(), Some(i64::MAX)); + assert_eq!( + duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, + i64::MAX as i128 * 1_000 + ); + // Here we create a Duration with the maximum possible number of + // microseconds by creating a Duration with the maximum number of + // milliseconds and then checking that the number of microseconds matches + // the storage limit. + let duration = Duration::milliseconds(i64::MAX); + assert!(duration.num_microseconds().is_none()); + assert_eq!( + duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, + i64::MAX as i128 * 1_000_000 + ); + } + #[test] + fn test_duration_microseconds_max_overflow() { + // This test establishes that a Duration can store more microseconds than + // are representable through the return of duration.num_microseconds(). + let duration = Duration::microseconds(i64::MAX) + Duration::microseconds(1); + assert!(duration.num_microseconds().is_none()); + assert_eq!( + duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, + (i64::MAX as i128 + 1) * 1_000 + ); + // Here we ensure that trying to add one microsecond to the maximum storable + // value will fail. + assert!(Duration::milliseconds(i64::MAX).checked_add(&Duration::microseconds(1)).is_none()); + } + #[test] + fn test_duration_microseconds_min_allowed() { + // The number of microseconds acceptable through the constructor is far + // fewer than the number that can actually be stored in a Duration, so this + // is not a particular insightful test. + let duration = Duration::microseconds(i64::MIN); + assert_eq!(duration.num_microseconds(), Some(i64::MIN)); + assert_eq!( + duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, + i64::MIN as i128 * 1_000 + ); + // Here we create a Duration with the minimum possible number of + // microseconds by creating a Duration with the minimum number of + // milliseconds and then checking that the number of microseconds matches + // the storage limit. + let duration = Duration::milliseconds(-i64::MAX); + assert!(duration.num_microseconds().is_none()); + assert_eq!( + duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, + -i64::MAX as i128 * 1_000_000 + ); + } + #[test] + fn test_duration_microseconds_min_underflow() { + // This test establishes that a Duration can store more microseconds than + // are representable through the return of duration.num_microseconds(). + let duration = Duration::microseconds(i64::MIN) - Duration::microseconds(1); + assert!(duration.num_microseconds().is_none()); + assert_eq!( + duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, + (i64::MIN as i128 - 1) * 1_000 + ); + // Here we ensure that trying to subtract one microsecond from the minimum + // storable value will fail. + assert!(Duration::milliseconds(-i64::MAX) + .checked_sub(&Duration::microseconds(1)) + .is_none()); + } #[test] fn test_duration_num_nanoseconds() { assert_eq!(Duration::zero().num_nanoseconds(), Some(0)); assert_eq!(Duration::nanoseconds(1).num_nanoseconds(), Some(1)); assert_eq!(Duration::nanoseconds(-1).num_nanoseconds(), Some(-1)); - assert_eq!(Duration::nanoseconds(i64::MAX).num_nanoseconds(), Some(i64::MAX)); - assert_eq!(Duration::nanoseconds(-i64::MAX).num_nanoseconds(), Some(-i64::MAX)); - assert_eq!(MAX.num_nanoseconds(), None); - assert_eq!(MIN.num_nanoseconds(), None); // overflow checks const NANOS_PER_DAY: i64 = 86_400_000_000_000; @@ -646,9 +875,124 @@ mod tests { assert_eq!(Duration::days(i64::MAX / NANOS_PER_DAY + 1).num_nanoseconds(), None); assert_eq!(Duration::days(-i64::MAX / NANOS_PER_DAY - 1).num_nanoseconds(), None); } + #[test] + fn test_duration_nanoseconds_max_allowed() { + // The number of nanoseconds acceptable through the constructor is far fewer + // than the number that can actually be stored in a Duration, so this is not + // a particular insightful test. + let duration = Duration::nanoseconds(i64::MAX); + assert_eq!(duration.num_nanoseconds(), Some(i64::MAX)); + assert_eq!( + duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, + i64::MAX as i128 + ); + // Here we create a Duration with the maximum possible number of nanoseconds + // by creating a Duration with the maximum number of milliseconds and then + // checking that the number of nanoseconds matches the storage limit. + let duration = Duration::milliseconds(i64::MAX); + assert!(duration.num_nanoseconds().is_none()); + assert_eq!( + duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, + i64::MAX as i128 * 1_000_000 + ); + } + #[test] + fn test_duration_nanoseconds_max_overflow() { + // This test establishes that a Duration can store more nanoseconds than are + // representable through the return of duration.num_nanoseconds(). + let duration = Duration::nanoseconds(i64::MAX) + Duration::nanoseconds(1); + assert!(duration.num_nanoseconds().is_none()); + assert_eq!( + duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, + i64::MAX as i128 + 1 + ); + // Here we ensure that trying to add one nanosecond to the maximum storable + // value will fail. + assert!(Duration::milliseconds(i64::MAX).checked_add(&Duration::nanoseconds(1)).is_none()); + } + #[test] + fn test_duration_nanoseconds_min_allowed() { + // The number of nanoseconds acceptable through the constructor is far fewer + // than the number that can actually be stored in a Duration, so this is not + // a particular insightful test. + let duration = Duration::nanoseconds(i64::MIN); + assert_eq!(duration.num_nanoseconds(), Some(i64::MIN)); + assert_eq!( + duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, + i64::MIN as i128 + ); + // Here we create a Duration with the minimum possible number of nanoseconds + // by creating a Duration with the minimum number of milliseconds and then + // checking that the number of nanoseconds matches the storage limit. + let duration = Duration::milliseconds(-i64::MAX); + assert!(duration.num_nanoseconds().is_none()); + assert_eq!( + duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, + -i64::MAX as i128 * 1_000_000 + ); + } + #[test] + fn test_duration_nanoseconds_min_underflow() { + // This test establishes that a Duration can store more nanoseconds than are + // representable through the return of duration.num_nanoseconds(). + let duration = Duration::nanoseconds(i64::MIN) - Duration::nanoseconds(1); + assert!(duration.num_nanoseconds().is_none()); + assert_eq!( + duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, + i64::MIN as i128 - 1 + ); + // Here we ensure that trying to subtract one nanosecond from the minimum + // storable value will fail. + assert!(Duration::milliseconds(-i64::MAX).checked_sub(&Duration::nanoseconds(1)).is_none()); + } + + #[test] + fn test_max() { + assert_eq!( + MAX.secs as i128 * 1_000_000_000 + MAX.nanos as i128, + i64::MAX as i128 * 1_000_000 + ); + assert_eq!(MAX, Duration::milliseconds(i64::MAX)); + assert_eq!(MAX.num_milliseconds(), i64::MAX); + assert_eq!(MAX.num_microseconds(), None); + assert_eq!(MAX.num_nanoseconds(), None); + } + #[test] + fn test_min() { + assert_eq!( + MIN.secs as i128 * 1_000_000_000 + MIN.nanos as i128, + -i64::MAX as i128 * 1_000_000 + ); + assert_eq!(MIN, Duration::milliseconds(-i64::MAX)); + assert_eq!(MIN.num_milliseconds(), -i64::MAX); + assert_eq!(MIN.num_microseconds(), None); + assert_eq!(MIN.num_nanoseconds(), None); + } + + #[test] + fn test_duration_ord() { + assert!(Duration::milliseconds(1) < Duration::milliseconds(2)); + assert!(Duration::milliseconds(2) > Duration::milliseconds(1)); + assert!(Duration::milliseconds(-1) > Duration::milliseconds(-2)); + assert!(Duration::milliseconds(-2) < Duration::milliseconds(-1)); + assert!(Duration::milliseconds(-1) < Duration::milliseconds(1)); + assert!(Duration::milliseconds(1) > Duration::milliseconds(-1)); + assert!(Duration::milliseconds(0) < Duration::milliseconds(1)); + assert!(Duration::milliseconds(0) > Duration::milliseconds(-1)); + assert!(Duration::milliseconds(1_001) < Duration::milliseconds(1_002)); + assert!(Duration::milliseconds(-1_001) > Duration::milliseconds(-1_002)); + assert!(Duration::nanoseconds(1_234_567_890) < Duration::nanoseconds(1_234_567_891)); + assert!(Duration::nanoseconds(-1_234_567_890) > Duration::nanoseconds(-1_234_567_891)); + assert!(Duration::milliseconds(i64::MAX) > Duration::milliseconds(i64::MAX - 1)); + assert!(Duration::milliseconds(-i64::MAX) < Duration::milliseconds(-i64::MAX + 1)); + } #[test] fn test_duration_checked_ops() { + assert_eq!( + Duration::milliseconds(i64::MAX).checked_add(&Duration::milliseconds(0)), + Some(Duration::milliseconds(i64::MAX)) + ); assert_eq!( Duration::milliseconds(i64::MAX - 1).checked_add(&Duration::microseconds(999)), Some(Duration::milliseconds(i64::MAX - 2) + Duration::microseconds(1999)) @@ -656,14 +1000,20 @@ mod tests { assert!(Duration::milliseconds(i64::MAX) .checked_add(&Duration::microseconds(1000)) .is_none()); + assert!(Duration::milliseconds(i64::MAX).checked_add(&Duration::nanoseconds(1)).is_none()); assert_eq!( Duration::milliseconds(-i64::MAX).checked_sub(&Duration::milliseconds(0)), Some(Duration::milliseconds(-i64::MAX)) ); + assert_eq!( + Duration::milliseconds(-i64::MAX + 1).checked_sub(&Duration::microseconds(999)), + Some(Duration::milliseconds(-i64::MAX + 2) - Duration::microseconds(1999)) + ); assert!(Duration::milliseconds(-i64::MAX) .checked_sub(&Duration::milliseconds(1)) .is_none()); + assert!(Duration::milliseconds(-i64::MAX).checked_sub(&Duration::nanoseconds(1)).is_none()); } #[test]