Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Duration features #1137

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
150 changes: 90 additions & 60 deletions src/duration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
#[cfg(feature = "std")]
use std::error::Error;

use crate::{expect, try_opt};

#[cfg(feature = "rkyv")]
use rkyv::{Archive, Deserialize, Serialize};

Expand All @@ -38,15 +40,6 @@
/// The number of (non-leap) seconds in a week.
const SECS_PER_WEEK: i64 = 604_800;

macro_rules! try_opt {
($e:expr) => {
match $e {
Some(v) => v,
None => return None,
}
};
}

/// ISO 8601 time duration with nanosecond precision.
///
/// This also allows for the negative duration; see individual methods for details.
Expand Down Expand Up @@ -74,93 +67,106 @@
};

impl Duration {
/// Makes a new `Duration` with given number of seconds and nanoseconds.
///
/// # Errors
///
/// Returns `None` when the duration is out of bounds, or if `nanos` ≥ 1,000,000,000.
pub(crate) const fn new(secs: i64, nanos: u32) -> Option<Duration> {
if secs < MIN.secs
|| secs > MAX.secs
|| nanos > 1_000_000_000
|| (secs == MAX.secs && nanos > MAX.nanos as u32)
|| (secs == MIN.secs && nanos < MIN.nanos as u32)
{
return None;
}
Some(Duration { secs, nanos: nanos as i32 })
}

/// Makes a new `Duration` with given number of weeks.
/// Equivalent to `Duration::seconds(weeks * 7 * 24 * 60 * 60)` with overflow checks.
/// Panics when the duration is out of bounds.
#[inline]
#[must_use]
pub fn weeks(weeks: i64) -> Duration {
Duration::try_weeks(weeks).expect("Duration::weeks out of bounds")
pub const fn weeks(weeks: i64) -> Duration {
expect!(Duration::try_weeks(weeks), "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<Duration> {
weeks.checked_mul(SECS_PER_WEEK).and_then(Duration::try_seconds)
pub const fn try_weeks(weeks: i64) -> Option<Duration> {
Duration::try_seconds(try_opt!(weeks.checked_mul(SECS_PER_WEEK)))
}

/// Makes a new `Duration` with given number of days.
/// Equivalent to `Duration::seconds(days * 24 * 60 * 60)` with overflow checks.
/// Panics when the duration is out of bounds.
#[inline]
#[must_use]
pub fn days(days: i64) -> Duration {
Duration::try_days(days).expect("Duration::days out of bounds")
pub const fn days(days: i64) -> Duration {
expect!(Duration::try_days(days), "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<Duration> {
days.checked_mul(SECS_PER_DAY).and_then(Duration::try_seconds)
pub const fn try_days(days: i64) -> Option<Duration> {
Duration::try_seconds(try_opt!(days.checked_mul(SECS_PER_DAY)))
}

/// Makes a new `Duration` with given number of hours.
/// Equivalent to `Duration::seconds(hours * 60 * 60)` with overflow checks.
/// Panics when the duration is out of bounds.
#[inline]
#[must_use]
pub fn hours(hours: i64) -> Duration {
Duration::try_hours(hours).expect("Duration::hours ouf of bounds")
pub const fn hours(hours: i64) -> Duration {
expect!(Duration::try_hours(hours), "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<Duration> {
hours.checked_mul(SECS_PER_HOUR).and_then(Duration::try_seconds)
pub const fn try_hours(hours: i64) -> Option<Duration> {
Duration::try_seconds(try_opt!(hours.checked_mul(SECS_PER_HOUR)))
}

/// Makes a new `Duration` with given number of minutes.
/// Equivalent to `Duration::seconds(minutes * 60)` with overflow checks.
/// Panics when the duration is out of bounds.
#[inline]
#[must_use]
pub fn minutes(minutes: i64) -> Duration {
Duration::try_minutes(minutes).expect("Duration::minutes out of bounds")
pub const fn minutes(minutes: i64) -> Duration {
expect!(Duration::try_minutes(minutes), "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<Duration> {
minutes.checked_mul(SECS_PER_MINUTE).and_then(Duration::try_seconds)
pub const fn try_minutes(minutes: i64) -> Option<Duration> {
Duration::try_seconds(try_opt!(minutes.checked_mul(SECS_PER_MINUTE)))
}

/// 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.
#[inline]
#[must_use]
pub fn seconds(seconds: i64) -> Duration {
Duration::try_seconds(seconds).expect("Duration::seconds out of bounds")
pub const fn seconds(seconds: i64) -> Duration {
expect!(Duration::try_seconds(seconds), "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.
#[inline]
pub fn try_seconds(seconds: i64) -> Option<Duration> {
let d = Duration { secs: seconds, nanos: 0 };
if d < MIN || d > MAX {
return None;
}
Some(d)
pub const fn try_seconds(seconds: i64) -> Option<Duration> {
Duration::new(seconds, 0)
}

/// Makes a new `Duration` with given number of milliseconds.
Expand Down Expand Up @@ -257,40 +263,30 @@

/// Add two durations, returning `None` if overflow occurred.
#[must_use]
pub fn checked_add(&self, rhs: &Duration) -> Option<Duration> {
let mut secs = try_opt!(self.secs.checked_add(rhs.secs));
pub const fn checked_add(&self, rhs: &Duration) -> Option<Duration> {
// No overflow checks here because we stay comfortably wihtin the range of an `i64`.
// Range checks happen in `Duration::new`.
let mut secs = self.secs + rhs.secs;
let mut nanos = self.nanos + rhs.nanos;
if nanos >= NANOS_PER_SEC {
nanos -= NANOS_PER_SEC;
secs = try_opt!(secs.checked_add(1));
}
let d = Duration { secs, nanos };
// Even if d is within the bounds of i64 seconds,
// it might still overflow i64 milliseconds.
if d < MIN || d > MAX {
None
} else {
Some(d)
secs += 1;

Check warning on line 273 in src/duration.rs

View check run for this annotation

Codecov / codecov/patch

src/duration.rs#L273

Added line #L273 was not covered by tests
}
Duration::new(secs, nanos as u32)
}

/// Subtract two durations, returning `None` if overflow occurred.
#[must_use]
pub fn checked_sub(&self, rhs: &Duration) -> Option<Duration> {
let mut secs = try_opt!(self.secs.checked_sub(rhs.secs));
pub const fn checked_sub(&self, rhs: &Duration) -> Option<Duration> {
// No overflow checks here because we stay comfortably wihtin the range of an `i64`.
// Range checks happen in `Duration::new`.
let mut secs = self.secs - rhs.secs;
let mut nanos = self.nanos - rhs.nanos;
if nanos < 0 {
nanos += NANOS_PER_SEC;
secs = try_opt!(secs.checked_sub(1));
}
let d = Duration { secs, nanos };
// Even if d is within the bounds of i64 seconds,
// it might still overflow i64 milliseconds.
if d < MIN || d > MAX {
None
} else {
Some(d)
secs -= 1;
}
Duration::new(secs, nanos as u32)
}

/// Returns the duration as an absolute (non-negative) value.
Expand Down Expand Up @@ -331,22 +327,22 @@
///
/// This function errors when original duration is larger than the maximum
/// value supported for this type.
pub fn from_std(duration: StdDuration) -> Result<Duration, OutOfRangeError> {
pub const fn from_std(duration: StdDuration) -> Result<Duration, OutOfRangeError> {
// We need to check secs as u64 before coercing to i64
if duration.as_secs() > MAX.secs as u64 {
return Err(OutOfRangeError(()));
}
let d = Duration { secs: duration.as_secs() as i64, nanos: duration.subsec_nanos() as i32 };
if d > MAX {
return Err(OutOfRangeError(()));
match Duration::new(duration.as_secs() as i64, duration.subsec_nanos()) {
Some(d) => Ok(d),
None => Err(OutOfRangeError(())),
}
Ok(d)
}

/// Creates a `std::time::Duration` object from `time::Duration`
///
/// This function errors when duration is less than zero. As standard
/// library implementation is limited to non-negative values.
// TODO: make this method const once our MSRV is 1.58+
pub fn to_std(&self) -> Result<StdDuration, OutOfRangeError> {
if self.secs < 0 {
return Err(OutOfRangeError(()));
Expand Down Expand Up @@ -795,4 +791,38 @@
Err(OutOfRangeError(()))
);
}

#[test]
fn test_duration_const() {
const ONE_WEEK: Duration = Duration::weeks(1);
const ONE_DAY: Duration = Duration::days(1);
const ONE_HOUR: Duration = Duration::hours(1);
const ONE_MINUTE: Duration = Duration::minutes(1);
const ONE_SECOND: Duration = Duration::seconds(1);
const ONE_MILLI: Duration = Duration::milliseconds(1);
const ONE_MICRO: Duration = Duration::microseconds(1);
const ONE_NANO: Duration = Duration::nanoseconds(1);
let combo: Duration = ONE_WEEK
+ ONE_DAY
+ ONE_HOUR
+ ONE_MINUTE
+ ONE_SECOND
+ ONE_MILLI
+ ONE_MICRO
+ ONE_NANO;

assert!(ONE_WEEK != Duration::zero());
assert!(ONE_DAY != Duration::zero());
assert!(ONE_HOUR != Duration::zero());
assert!(ONE_MINUTE != Duration::zero());
assert!(ONE_SECOND != Duration::zero());
assert!(ONE_MILLI != Duration::zero());
assert!(ONE_MICRO != Duration::zero());
assert!(ONE_NANO != Duration::zero());
assert_eq!(
combo,
Duration::seconds(86400 * 7 + 86400 + 3600 + 60 + 1)
+ Duration::nanoseconds(1 + 1_000 + 1_000_000)
);
}
}
4 changes: 2 additions & 2 deletions src/naive/time/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1194,7 +1194,7 @@ impl Add<Duration> for NaiveTime {
// overflow during the conversion to `chrono::Duration`.
// But we limit to double that just in case `self` is a leap-second.
let secs = rhs.as_secs() % (2 * 24 * 60 * 60);
let d = OldDuration::from_std(Duration::new(secs, rhs.subsec_nanos())).unwrap();
let d = OldDuration::new(secs as i64, rhs.subsec_nanos()).unwrap();
self.overflowing_add_signed(d).0
}
}
Expand Down Expand Up @@ -1304,7 +1304,7 @@ impl Sub<Duration> for NaiveTime {
// overflow during the conversion to `chrono::Duration`.
// But we limit to double that just in case `self` is a leap-second.
let secs = rhs.as_secs() % (2 * 24 * 60 * 60);
let d = OldDuration::from_std(Duration::new(secs, rhs.subsec_nanos())).unwrap();
let d = OldDuration::new(secs as i64, rhs.subsec_nanos()).unwrap();
self.overflowing_sub_signed(d).0
}
}
Expand Down