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

Make remaining methods const #1337

Merged
merged 11 commits into from
Feb 1, 2024
8 changes: 6 additions & 2 deletions src/datetime/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ use crate::naive::{Days, IsoWeek, NaiveDate, NaiveDateTime, NaiveTime};
#[cfg(feature = "clock")]
use crate::offset::Local;
use crate::offset::{FixedOffset, Offset, TimeZone, Utc};
use crate::try_opt;
#[allow(deprecated)]
use crate::Date;
use crate::{Datelike, Months, Timelike, Weekday};
Expand Down Expand Up @@ -626,8 +627,11 @@ impl DateTime<Utc> {
/// ```
#[inline]
#[must_use]
pub fn from_timestamp(secs: i64, nsecs: u32) -> Option<Self> {
NaiveDateTime::from_timestamp_opt(secs, nsecs).as_ref().map(NaiveDateTime::and_utc)
pub const fn from_timestamp(secs: i64, nsecs: u32) -> Option<Self> {
Some(DateTime {
datetime: try_opt!(NaiveDateTime::from_timestamp_opt(secs, nsecs)),
offset: Utc,
})
}

/// Makes a new [`DateTime<Utc>`] from the number of non-leap milliseconds
Expand Down
182 changes: 110 additions & 72 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(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))]
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 negative durations; see individual methods for details.
Expand Down Expand Up @@ -82,6 +75,23 @@
};

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
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The functions documentation says >= 1_000_000_000, but here > 1_000_000_000 is used in the check?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for catching that!

|| (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 the given number of weeks.
///
/// Equivalent to `Duration::seconds(weeks * 7 * 24 * 60 * 60)` with
Expand All @@ -92,8 +102,8 @@
/// 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 the given number of weeks.
Expand All @@ -105,8 +115,8 @@
///
/// 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 the given number of days.
Expand All @@ -119,8 +129,8 @@
/// 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 the given number of days.
Expand All @@ -132,8 +142,8 @@
///
/// 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 the given number of hours.
Expand All @@ -145,8 +155,8 @@
/// Panics when the duration is out of bounds.
#[inline]
#[must_use]
pub fn hours(hours: i64) -> Duration {
Duration::try_hours(hours).expect("Duration::hours out of bounds")
pub const fn hours(hours: i64) -> Duration {
expect!(Duration::try_hours(hours), "Duration::hours out of bounds")
}

/// Makes a new `Duration` with the given number of hours.
Expand All @@ -157,8 +167,8 @@
///
/// 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 the given number of minutes.
Expand All @@ -170,8 +180,8 @@
/// 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 the given number of minutes.
Expand All @@ -182,8 +192,8 @@
///
/// 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 the given number of seconds.
Expand All @@ -196,8 +206,8 @@
/// rounding).
#[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 the given number of seconds.
Expand All @@ -208,12 +218,8 @@
/// 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<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 the given number of milliseconds.
Expand All @@ -224,8 +230,8 @@
/// more than `i64::MAX` milliseconds or less than `-i64::MAX` milliseconds.
/// Notably, this is not the same as `i64::MIN`.
#[inline]
pub fn milliseconds(milliseconds: i64) -> Duration {
Duration::try_milliseconds(milliseconds).expect("Duration::milliseconds out of bounds")
pub const fn milliseconds(milliseconds: i64) -> Duration {
expect!(Duration::try_milliseconds(milliseconds), "Duration::milliseconds out of bounds")
}

/// Makes a new `Duration` with the given number of milliseconds.
Expand All @@ -236,14 +242,14 @@
/// less than `-i64::MAX` milliseconds. Notably, this is not the same as
/// `i64::MIN`.
#[inline]
pub fn try_milliseconds(milliseconds: i64) -> Option<Duration> {
let (secs, millis) = div_mod_floor_64(milliseconds, MILLIS_PER_SEC);
let d = Duration { secs, nanos: millis as i32 * NANOS_PER_MILLI };
pub const fn try_milliseconds(milliseconds: i64) -> Option<Duration> {
// 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 {
if milliseconds < -i64::MAX {
return None;
}
let (secs, millis) = div_mod_floor_64(milliseconds, MILLIS_PER_SEC);
let d = Duration { secs, nanos: millis as i32 * NANOS_PER_MILLI };
Some(d)
}

Expand Down Expand Up @@ -344,40 +350,30 @@

/// Add two `Duration`s, 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 within 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 360 in src/duration.rs

View check run for this annotation

Codecov / codecov/patch

src/duration.rs#L360

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

/// Subtract two `Duration`s, 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 within 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 @@ -418,40 +414,48 @@
///
/// 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.
pub fn to_std(&self) -> Result<StdDuration, OutOfRangeError> {
pub const fn to_std(&self) -> Result<StdDuration, OutOfRangeError> {
if self.secs < 0 {
return Err(OutOfRangeError(()));
}
Ok(StdDuration::new(self.secs as u64, self.nanos as u32))
}

/// This duplicates `Neg::neg` because trait methods can't be const yet.
pub(crate) const fn neg(self) -> Duration {
let (secs_diff, nanos) = match self.nanos {
0 => (0, 0),
nanos => (1, NANOS_PER_SEC - nanos),
};
Duration { secs: -self.secs - secs_diff, nanos }
}
}

impl Neg for Duration {
type Output = Duration;

#[inline]
fn neg(self) -> Duration {
if self.nanos == 0 {
Duration { secs: -self.secs, nanos: 0 }
} else {
Duration { secs: -self.secs - 1, nanos: NANOS_PER_SEC - self.nanos }
}
let (secs_diff, nanos) = match self.nanos {
0 => (0, 0),
nanos => (1, NANOS_PER_SEC - nanos),
};
Duration { secs: -self.secs - secs_diff, nanos }
}
}

Expand Down Expand Up @@ -1148,6 +1152,40 @@
);
}

#[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)
);
}

#[test]
#[cfg(feature = "rkyv-validation")]
fn test_rkyv_validation() {
Expand Down
Loading
Loading