From 752024d1522021a2a8c2950221ecd555d6deadb2 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 29 Sep 2023 12:21:08 +0200 Subject: [PATCH 01/11] Remove duplicate `try_opt` macro --- src/duration.rs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/duration.rs b/src/duration.rs index eb985e7f42..7e987e7117 100644 --- a/src/duration.rs +++ b/src/duration.rs @@ -16,6 +16,8 @@ use core::{fmt, i64}; #[cfg(feature = "std")] use std::error::Error; +use crate::try_opt; + #[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] use rkyv::{Archive, Deserialize, Serialize}; @@ -38,15 +40,6 @@ const SECS_PER_DAY: i64 = 86_400; /// 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. From 5bea5c5a1f914e784ab378daff809cfddd54ed06 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 29 Sep 2023 12:43:38 +0200 Subject: [PATCH 02/11] Add `Duration::new` --- src/duration.rs | 17 +++++++++++++++++ src/naive/time/mod.rs | 4 ++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/duration.rs b/src/duration.rs index 7e987e7117..4aa86cd4c8 100644 --- a/src/duration.rs +++ b/src/duration.rs @@ -75,6 +75,23 @@ pub(crate) const MAX: Duration = Duration { }; 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 { + 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 the given number of weeks. /// /// Equivalent to `Duration::seconds(weeks * 7 * 24 * 60 * 60)` with diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index 94aee99168..bcbd557809 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -1196,7 +1196,7 @@ impl Add 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 } } @@ -1306,7 +1306,7 @@ impl Sub 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 } } From 650895f1d57b3b6300d983f334440b61500db56f Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 27 Jul 2023 16:21:20 +0200 Subject: [PATCH 03/11] Make all methods on `Duration` const --- src/duration.rs | 103 +++++++++++++++++++++--------------------------- 1 file changed, 44 insertions(+), 59 deletions(-) diff --git a/src/duration.rs b/src/duration.rs index 4aa86cd4c8..96ad89e2d0 100644 --- a/src/duration.rs +++ b/src/duration.rs @@ -16,7 +16,7 @@ use core::{fmt, i64}; #[cfg(feature = "std")] use std::error::Error; -use crate::try_opt; +use crate::{expect, try_opt}; #[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] use rkyv::{Archive, Deserialize, Serialize}; @@ -102,8 +102,8 @@ impl Duration { /// 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. @@ -115,8 +115,8 @@ impl Duration { /// /// 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) + pub const fn try_weeks(weeks: i64) -> Option { + Duration::try_seconds(try_opt!(weeks.checked_mul(SECS_PER_WEEK))) } /// Makes a new `Duration` with the given number of days. @@ -129,8 +129,8 @@ impl Duration { /// 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. @@ -142,8 +142,8 @@ impl Duration { /// /// 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) + pub const fn try_days(days: i64) -> Option { + Duration::try_seconds(try_opt!(days.checked_mul(SECS_PER_DAY))) } /// Makes a new `Duration` with the given number of hours. @@ -155,8 +155,8 @@ impl Duration { /// 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. @@ -167,8 +167,8 @@ impl Duration { /// /// 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) + pub const fn try_hours(hours: i64) -> Option { + Duration::try_seconds(try_opt!(hours.checked_mul(SECS_PER_HOUR))) } /// Makes a new `Duration` with the given number of minutes. @@ -180,8 +180,8 @@ impl Duration { /// 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. @@ -192,8 +192,8 @@ impl Duration { /// /// 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) + pub const fn try_minutes(minutes: i64) -> Option { + Duration::try_seconds(try_opt!(minutes.checked_mul(SECS_PER_MINUTE))) } /// Makes a new `Duration` with the given number of seconds. @@ -206,8 +206,8 @@ impl Duration { /// 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. @@ -218,12 +218,8 @@ impl Duration { /// 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 }; - if d < MIN || d > MAX { - return None; - } - Some(d) + pub const fn try_seconds(seconds: i64) -> Option { + Duration::new(seconds, 0) } /// Makes a new `Duration` with the given number of milliseconds. @@ -234,8 +230,8 @@ impl Duration { /// 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. @@ -246,14 +242,14 @@ impl Duration { /// 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 d = Duration { secs, nanos: millis as i32 * NANOS_PER_MILLI }; + pub const fn try_milliseconds(milliseconds: i64) -> Option { // 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) } @@ -354,40 +350,30 @@ impl Duration { /// 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)); + pub const fn checked_add(&self, rhs: &Duration) -> Option { + // 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; } + 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 { - let mut secs = try_opt!(self.secs.checked_sub(rhs.secs)); + pub const fn checked_sub(&self, rhs: &Duration) -> Option { + // 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. @@ -428,23 +414,22 @@ impl Duration { /// /// This function errors when original duration is larger than the maximum /// value supported for this type. - pub fn from_std(duration: StdDuration) -> Result { + pub const fn from_std(duration: StdDuration) -> Result { // 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 { + pub const fn to_std(&self) -> Result { if self.secs < 0 { return Err(OutOfRangeError(())); } From 0019f34a6ccbbf6a9934b7d652ec960841ed71c5 Mon Sep 17 00:00:00 2001 From: Scott Driggers Date: Wed, 22 Dec 2021 14:15:39 -0500 Subject: [PATCH 04/11] Add `test_duration_const()` --- src/duration.rs | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/duration.rs b/src/duration.rs index 96ad89e2d0..be3dc0ceb5 100644 --- a/src/duration.rs +++ b/src/duration.rs @@ -1143,6 +1143,40 @@ mod tests { ); } + #[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() { From 23e10f9fe506ce183fc1b7539fac400aca7d8997 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 1 Oct 2023 21:57:54 +0200 Subject: [PATCH 05/11] Make `DateTime::from_timestamp` const --- src/datetime/mod.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 967f6a5f72..7b0fa842c4 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -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}; @@ -626,8 +627,11 @@ impl DateTime { /// ``` #[inline] #[must_use] - pub fn from_timestamp(secs: i64, nsecs: u32) -> Option { - NaiveDateTime::from_timestamp_opt(secs, nsecs).as_ref().map(NaiveDateTime::and_utc) + pub const fn from_timestamp(secs: i64, nsecs: u32) -> Option { + Some(DateTime { + datetime: try_opt!(NaiveDateTime::from_timestamp_opt(secs, nsecs)), + offset: Utc, + }) } /// Makes a new [`DateTime`] from the number of non-leap milliseconds From 86452c3ba517f93714673dc3985b6490e3ff3d89 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 1 Oct 2023 21:58:30 +0200 Subject: [PATCH 06/11] Make methods on `NaiveWeek` const --- src/naive/date.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index ca11efdca1..0b3fafc2ad 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -61,14 +61,14 @@ impl NaiveWeek { /// ``` #[inline] #[must_use] - pub fn first_day(&self) -> NaiveDate { + pub const fn first_day(&self) -> NaiveDate { let start = self.start.num_days_from_monday() as i32; let ref_day = self.date.weekday().num_days_from_monday() as i32; // Calculate the number of days to subtract from `self.date`. // Do not construct an intermediate date beyond `self.date`, because that may be out of // range if `date` is close to `NaiveDate::MAX`. let days = start - ref_day - if start > ref_day { 7 } else { 0 }; - self.date.add_days(days).unwrap() + expect!(self.date.add_days(days), "first weekday out of range for `NaiveDate`") } /// Returns a date representing the last day of the week. @@ -89,14 +89,14 @@ impl NaiveWeek { /// ``` #[inline] #[must_use] - pub fn last_day(&self) -> NaiveDate { + pub const fn last_day(&self) -> NaiveDate { let end = self.start.pred().num_days_from_monday() as i32; let ref_day = self.date.weekday().num_days_from_monday() as i32; // Calculate the number of days to add to `self.date`. // Do not construct an intermediate date before `self.date` (like with `first_day()`), // because that may be out of range if `date` is close to `NaiveDate::MIN`. let days = end - ref_day + if end < ref_day { 7 } else { 0 }; - self.date.add_days(days).unwrap() + expect!(self.date.add_days(days), "last weekday out of range for `NaiveDate`") } /// Returns a [`RangeInclusive`] representing the whole week bounded by @@ -119,7 +119,7 @@ impl NaiveWeek { /// ``` #[inline] #[must_use] - pub fn days(&self) -> RangeInclusive { + pub const fn days(&self) -> RangeInclusive { self.first_day()..=self.last_day() } } From 6caf1e7fedad9a88a1535a1fdecca76d68c349f0 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 1 Oct 2023 22:03:46 +0200 Subject: [PATCH 07/11] Make remaining methods on `NaiveDate` const --- src/naive/date.rs | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index 0b3fafc2ad..c4fe5209d1 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -1155,9 +1155,12 @@ impl NaiveDate { /// assert_eq!(NaiveDate::MAX.checked_add_signed(Duration::days(1)), None); /// ``` #[must_use] - pub fn checked_add_signed(self, rhs: OldDuration) -> Option { - let days = i32::try_from(rhs.num_days()).ok()?; - self.add_days(days) + pub const fn checked_add_signed(self, rhs: OldDuration) -> Option { + let days = rhs.num_days(); + if days < i32::MIN as i64 || days > i32::MAX as i64 { + return None; + } + self.add_days(days as i32) } /// Subtracts the number of whole days in the given `Duration` from the current date. @@ -1181,9 +1184,12 @@ impl NaiveDate { /// assert_eq!(NaiveDate::MIN.checked_sub_signed(Duration::days(1)), None); /// ``` #[must_use] - pub fn checked_sub_signed(self, rhs: OldDuration) -> Option { - let days = i32::try_from(-rhs.num_days()).ok()?; - self.add_days(days) + pub const fn checked_sub_signed(self, rhs: OldDuration) -> Option { + let days = -rhs.num_days(); + if days < i32::MIN as i64 || days > i32::MAX as i64 { + return None; + } + self.add_days(days as i32) } /// Subtracts another `NaiveDate` from the current date. @@ -1209,7 +1215,7 @@ impl NaiveDate { /// assert_eq!(since(from_ymd(2014, 1, 1), from_ymd(1614, 1, 1)), Duration::days(365*400 + 97)); /// ``` #[must_use] - pub fn signed_duration_since(self, rhs: NaiveDate) -> OldDuration { + pub const fn signed_duration_since(self, rhs: NaiveDate) -> OldDuration { let year1 = self.year(); let year2 = rhs.year(); let (year1_div_400, year1_mod_400) = div_mod_floor(year1, 400); From be91e22d4e289966f0c7528d038f8bafb50fc050 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 1 Oct 2023 22:29:58 +0200 Subject: [PATCH 08/11] Make `NaiveTime::signed_duration_since` const --- src/naive/time/mod.rs | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index bcbd557809..7d87280c3d 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -724,7 +724,7 @@ impl NaiveTime { /// Duration::seconds(61)); /// ``` #[must_use] - pub fn signed_duration_since(self, rhs: NaiveTime) -> OldDuration { + pub const fn signed_duration_since(self, rhs: NaiveTime) -> OldDuration { // | | :leap| | | | | | | :leap| | // | | : | | | | | | | : | | // ----+----+-----*---+----+----+----+----+----+----+-------*-+----+---- @@ -735,25 +735,20 @@ impl NaiveTime { // `rhs.frac`|========================================>| // | | | `self - rhs` | | - use core::cmp::Ordering; - - let secs = i64::from(self.secs) - i64::from(rhs.secs); - let frac = i64::from(self.frac) - i64::from(rhs.frac); + let mut secs = self.secs as i64 - rhs.secs as i64; + let frac = self.frac as i64 - rhs.frac as i64; // `secs` may contain a leap second yet to be counted - let adjust = match self.secs.cmp(&rhs.secs) { - Ordering::Greater => i64::from(rhs.frac >= 1_000_000_000), - Ordering::Equal => 0, - Ordering::Less => { - if self.frac >= 1_000_000_000 { - -1 - } else { - 0 - } - } - }; + if self.secs > rhs.secs && rhs.frac >= 1_000_000_000 { + secs += 1; + } else if self.secs < rhs.secs && self.frac >= 1_000_000_000 { + secs -= 1; + } + + let secs_from_frac = frac.div_euclid(1_000_000_000); + let frac = frac.rem_euclid(1_000_000_000) as u32; - OldDuration::seconds(secs + adjust) + OldDuration::nanoseconds(frac) + expect!(OldDuration::new(secs + secs_from_frac, frac), "must be in range") } /// Adds given `FixedOffset` to the current time, and returns the number of days that should be From 037a25c450d7385ac6cf0d4c4b754ccbb17e8f8c Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 2 Oct 2023 07:20:20 +0200 Subject: [PATCH 09/11] Simplify `NaiveTime::overflowing_add_signed` and make const --- src/naive/time/mod.rs | 69 ++++++++++++++----------------------------- 1 file changed, 22 insertions(+), 47 deletions(-) diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index 7d87280c3d..f6861c0385 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -582,46 +582,30 @@ impl NaiveTime { /// (from_hms(20, 4, 5), -86_400)); /// ``` #[must_use] - pub fn overflowing_add_signed(&self, mut rhs: OldDuration) -> (NaiveTime, i64) { - let mut secs = self.secs; - let mut frac = self.frac; - - // check if `self` is a leap second and adding `rhs` would escape that leap second. - // if it's the case, update `self` and `rhs` to involve no leap second; - // otherwise the addition immediately finishes. + pub const fn overflowing_add_signed(&self, rhs: OldDuration) -> (NaiveTime, i64) { + let mut secs = self.secs as i64; + let mut frac = self.frac as i32; + let secs_to_add = rhs.num_seconds(); + let frac_to_add = rhs.subsec_nanos(); + + // Check if `self` is a leap second and adding `rhs` would escape that leap second. + // If that is the case, update `frac` and `secs` to involve no leap second. + // If it stays within the leap second or the second before, and only adds a fractional + // second, just do that and return (this way the rest of the code can ignore leap seconds). if frac >= 1_000_000_000 { - let rfrac = 2_000_000_000 - frac; - if rhs >= OldDuration::nanoseconds(i64::from(rfrac)) { - rhs -= OldDuration::nanoseconds(i64::from(rfrac)); + // check below is adjusted to not overflow an i32: `frac + frac_to_add >= 2_000_000_000` + if secs_to_add > 0 || (frac_to_add > 0 && frac >= 2_000_000_000 - frac_to_add) { + frac -= 1_000_000_000; + } else if secs_to_add < 0 { + frac -= 1_000_000_000; secs += 1; - frac = 0; - } else if 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; - debug_assert!(frac < 2_000_000_000); - return (NaiveTime { secs, frac }, 0); + return (NaiveTime { secs: self.secs, frac: (frac + frac_to_add) as u32 }, 0); } } - debug_assert!(secs <= 86_400); - debug_assert!(frac < 1_000_000_000); - - let rhssecs = rhs.num_seconds(); - let rhsfrac = (rhs - OldDuration::seconds(rhssecs)).num_nanoseconds().unwrap(); - debug_assert_eq!(OldDuration::seconds(rhssecs) + OldDuration::nanoseconds(rhsfrac), rhs); - let rhssecsinday = rhssecs % 86_400; - let mut morerhssecs = rhssecs - rhssecsinday; - let rhssecs = rhssecsinday as i32; - let rhsfrac = rhsfrac as i32; - debug_assert!(-86_400 < rhssecs && rhssecs < 86_400); - debug_assert_eq!(morerhssecs % 86_400, 0); - debug_assert!(-1_000_000_000 < rhsfrac && rhsfrac < 1_000_000_000); - - let mut secs = secs as i32 + rhssecs; - let mut frac = frac as i32 + rhsfrac; - debug_assert!(-86_400 < secs && secs < 2 * 86_400); - debug_assert!(-1_000_000_000 < frac && frac < 2_000_000_000); + + let mut secs = secs + secs_to_add; + frac += frac_to_add; if frac < 0 { frac += 1_000_000_000; @@ -630,19 +614,10 @@ impl NaiveTime { frac -= 1_000_000_000; secs += 1; } - debug_assert!((-86_400..2 * 86_400).contains(&secs)); - debug_assert!((0..1_000_000_000).contains(&frac)); - - if secs < 0 { - secs += 86_400; - morerhssecs -= 86_400; - } else if secs >= 86_400 { - secs -= 86_400; - morerhssecs += 86_400; - } - debug_assert!((0..86_400).contains(&secs)); - (NaiveTime { secs: secs as u32, frac: frac as u32 }, morerhssecs) + let secs_in_day = secs.rem_euclid(86_400); + let remaining = secs - secs_in_day; + (NaiveTime { secs: secs_in_day as u32, frac: frac as u32 }, remaining) } /// Subtracts given `Duration` from the current time, and also returns the number of *seconds* From e7ecdfcb25dd871dc286e8932af63a5edba52e7b Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 2 Oct 2023 13:17:55 +0200 Subject: [PATCH 10/11] Make `NaiveTime::overflowing_sub_signed` const --- src/duration.rs | 19 ++++++++++++++----- src/naive/time/mod.rs | 4 ++-- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/duration.rs b/src/duration.rs index be3dc0ceb5..7c371b1740 100644 --- a/src/duration.rs +++ b/src/duration.rs @@ -435,6 +435,15 @@ impl Duration { } 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 { @@ -442,11 +451,11 @@ impl Neg for 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 } } } diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index f6861c0385..b47639ce0d 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -639,8 +639,8 @@ impl NaiveTime { /// ``` #[inline] #[must_use] - pub fn overflowing_sub_signed(&self, rhs: OldDuration) -> (NaiveTime, i64) { - let (time, rhs) = self.overflowing_add_signed(-rhs); + pub const fn overflowing_sub_signed(&self, rhs: OldDuration) -> (NaiveTime, i64) { + let (time, rhs) = self.overflowing_add_signed(rhs.neg()); (time, -rhs) // safe to negate, rhs is within +/- (2^63 / 1000) } From addcf663c0a6732b06516c43098b675431990ced Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 1 Oct 2023 22:15:59 +0200 Subject: [PATCH 11/11] Make remaining methods on `NaiveDateTime` const --- src/naive/datetime/mod.rs | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index de9976de9e..69fa52a824 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -504,9 +504,11 @@ impl NaiveDateTime { #[deprecated(since = "0.4.31", note = "use `timestamp_nanos_opt()` instead")] #[inline] #[must_use] - pub fn timestamp_nanos(&self) -> i64 { - self.timestamp_nanos_opt() - .expect("value can not be represented in a timestamp with nanosecond precision.") + pub const fn timestamp_nanos(&self) -> i64 { + expect!( + self.timestamp_nanos_opt(), + "value can not be represented in a timestamp with nanosecond precision." + ) } /// Returns the number of non-leap *nanoseconds* since midnight on January 1, 1970. @@ -542,9 +544,9 @@ impl NaiveDateTime { /// ``` #[inline] #[must_use] - pub fn timestamp_nanos_opt(&self) -> Option { + pub const fn timestamp_nanos_opt(&self) -> Option { let mut timestamp = self.timestamp(); - let mut timestamp_subsec_nanos = i64::from(self.timestamp_subsec_nanos()); + let mut timestamp_subsec_nanos = self.timestamp_subsec_nanos() as i64; // subsec nanos are always non-negative, however the timestamp itself (both in seconds and in nanos) can be // negative. Now i64::MIN is NOT dividable by 1_000_000_000, so @@ -562,7 +564,7 @@ impl NaiveDateTime { timestamp += 1; } - timestamp.checked_mul(1_000_000_000).and_then(|ns| ns.checked_add(timestamp_subsec_nanos)) + try_opt!(timestamp.checked_mul(1_000_000_000)).checked_add(timestamp_subsec_nanos) } /// Returns the number of milliseconds since the last whole non-leap second. @@ -699,7 +701,7 @@ impl NaiveDateTime { /// Some(from_ymd(2016, 7, 9).and_hms_milli_opt(3, 5, 59, 300).unwrap())); /// ``` #[must_use] - pub fn checked_add_signed(self, rhs: OldDuration) -> Option { + pub const fn checked_add_signed(self, rhs: OldDuration) -> Option { let (time, rhs) = self.time.overflowing_add_signed(rhs); // early checking to avoid overflow in OldDuration::seconds @@ -852,7 +854,7 @@ impl NaiveDateTime { /// Some(from_ymd(2016, 7, 7).and_hms_milli_opt(3, 6, 0, 300).unwrap())); /// ``` #[must_use] - pub fn checked_sub_signed(self, rhs: OldDuration) -> Option { + pub const fn checked_sub_signed(self, rhs: OldDuration) -> Option { let (time, rhs) = self.time.overflowing_sub_signed(rhs); // early checking to avoid overflow in OldDuration::seconds @@ -860,7 +862,7 @@ impl NaiveDateTime { return None; } - let date = self.date.checked_sub_signed(OldDuration::seconds(rhs))?; + let date = try_opt!(self.date.checked_sub_signed(OldDuration::seconds(rhs))); Some(NaiveDateTime { date, time }) } @@ -949,8 +951,13 @@ impl NaiveDateTime { /// Duration::seconds(3600) - Duration::milliseconds(500)); /// ``` #[must_use] - pub fn signed_duration_since(self, rhs: NaiveDateTime) -> OldDuration { - self.date.signed_duration_since(rhs.date) + self.time.signed_duration_since(rhs.time) + pub const fn signed_duration_since(self, rhs: NaiveDateTime) -> OldDuration { + expect!( + self.date + .signed_duration_since(rhs.date) + .checked_add(&self.time.signed_duration_since(rhs.time)), + "always in range" + ) } /// Formats the combined date and time with the specified formatting items.