From 6c238f3018d00f933270aa078a4f171027acafeb Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 27 Jul 2023 20:44:46 +0200 Subject: [PATCH 1/6] Add `NaiveDateTime::timestamp_nanos_opt` --- src/naive/datetime/mod.rs | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 09268aeffd..9c5a9c8b2e 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -460,6 +460,25 @@ impl NaiveDateTime { /// /// The dates that can be represented as nanoseconds are between 1677-09-21T00:12:44.0 and /// 2262-04-11T23:47:16.854775804. + #[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.") + } + + /// Returns the number of non-leap *nanoseconds* since midnight on January 1, 1970. + /// + /// Note that this does *not* account for the timezone! + /// The true "UNIX timestamp" would count seconds since the midnight *UTC* on the epoch. + /// + /// # Errors + /// + /// An `i64` with nanosecond precision can span a range of ~584 years. This function returns + /// `None` on an out of range `NaiveDateTime`. + /// + /// The dates that can be represented as nanoseconds are between 1677-09-21T00:12:44.0 and + /// 2262-04-11T23:47:16.854775804. /// /// # Example /// @@ -467,12 +486,12 @@ impl NaiveDateTime { /// use chrono::{NaiveDate, NaiveDateTime}; /// /// let dt = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().and_hms_nano_opt(0, 0, 1, 444).unwrap(); - /// assert_eq!(dt.timestamp_nanos(), 1_000_000_444); + /// assert_eq!(dt.timestamp_nanos_opt(), Some(1_000_000_444)); /// /// let dt = NaiveDate::from_ymd_opt(2001, 9, 9).unwrap().and_hms_nano_opt(1, 46, 40, 555).unwrap(); /// /// const A_BILLION: i64 = 1_000_000_000; - /// let nanos = dt.timestamp_nanos(); + /// let nanos = dt.timestamp_nanos_opt().unwrap(); /// assert_eq!(nanos, 1_000_000_000_000_000_555); /// assert_eq!( /// Some(dt), @@ -481,11 +500,8 @@ impl NaiveDateTime { /// ``` #[inline] #[must_use] - pub fn timestamp_nanos(&self) -> i64 { - self.timestamp() - .checked_mul(1_000_000_000) - .and_then(|ns| ns.checked_add(i64::from(self.timestamp_subsec_nanos()))) - .expect("value can not be represented in a timestamp with nanosecond precision.") + pub fn timestamp_nanos_opt(&self) -> Option { + self.timestamp().checked_mul(1_000_000_000)?.checked_add(self.time.nanosecond() as i64) } /// Returns the number of milliseconds since the last whole non-leap second. From 0c2f073624ea5fbed2b6b2fb30e3f365e3596333 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 8 Sep 2023 21:54:27 +0200 Subject: [PATCH 2/6] Add `DateTime::timestamp_nanos_opt` --- src/datetime/mod.rs | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 2701cd1776..69bee1419a 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -253,6 +253,22 @@ impl DateTime { self.datetime.timestamp_micros() } + /// Returns the number of non-leap-nanoseconds since January 1, 1970 UTC. + /// + /// # Panics + /// + /// An `i64` with nanosecond precision can span a range of ~584 years. This function panics on + /// an out of range `DateTime`. + /// + /// The dates that can be represented as nanoseconds are between 1677-09-21T00:12:44.0 and + /// 2262-04-11T23:47:16.854775804. + #[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.") + } + /// Returns the number of non-leap-nanoseconds since January 1, 1970 UTC. /// /// # Panics @@ -269,15 +285,15 @@ impl DateTime { /// use chrono::{Utc, NaiveDate}; /// /// let dt = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().and_hms_nano_opt(0, 0, 1, 444).unwrap().and_local_timezone(Utc).unwrap(); - /// assert_eq!(dt.timestamp_nanos(), 1_000_000_444); + /// assert_eq!(dt.timestamp_nanos_opt(), Some(1_000_000_444)); /// /// let dt = NaiveDate::from_ymd_opt(2001, 9, 9).unwrap().and_hms_nano_opt(1, 46, 40, 555).unwrap().and_local_timezone(Utc).unwrap(); - /// assert_eq!(dt.timestamp_nanos(), 1_000_000_000_000_000_555); + /// assert_eq!(dt.timestamp_nanos_opt(), Some(1_000_000_000_000_000_555)); /// ``` #[inline] #[must_use] - pub fn timestamp_nanos(&self) -> i64 { - self.datetime.timestamp_nanos() + pub fn timestamp_nanos_opt(&self) -> Option { + self.datetime.timestamp_nanos_opt() } /// Returns the number of milliseconds since the last second boundary. From a0d05569955d709c42a7351323d520dc76d1edfc Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 8 Sep 2023 21:55:30 +0200 Subject: [PATCH 3/6] Use `timestamp_nanos_opt` in `duration_round` and `duration_trunc` --- src/round.rs | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/src/round.rs b/src/round.rs index 9271353b77..c8e7b18f23 100644 --- a/src/round.rs +++ b/src/round.rs @@ -142,9 +142,6 @@ pub trait DurationRound: Sized { fn duration_trunc(self, duration: Duration) -> Result; } -/// The maximum number of seconds a DateTime can be to be represented as nanoseconds -const MAX_SECONDS_TIMESTAMP_FOR_NANOS: i64 = 9_223_372_036; - impl DurationRound for DateTime { type Err = RoundingError; @@ -181,10 +178,7 @@ where if span < 0 { return Err(RoundingError::DurationExceedsLimit); } - if naive.timestamp().abs() > MAX_SECONDS_TIMESTAMP_FOR_NANOS { - return Err(RoundingError::TimestampExceedsLimit); - } - let stamp = naive.timestamp_nanos(); + let stamp = naive.timestamp_nanos_opt().ok_or(RoundingError::TimestampExceedsLimit)?; if span > stamp.abs() { return Err(RoundingError::DurationExceedsTimestamp); } @@ -223,10 +217,7 @@ where if span < 0 { return Err(RoundingError::DurationExceedsLimit); } - if naive.timestamp().abs() > MAX_SECONDS_TIMESTAMP_FOR_NANOS { - return Err(RoundingError::TimestampExceedsLimit); - } - let stamp = naive.timestamp_nanos(); + let stamp = naive.timestamp_nanos_opt().ok_or(RoundingError::TimestampExceedsLimit)?; if span > stamp.abs() { return Err(RoundingError::DurationExceedsTimestamp); } From 240433df11719406c09704c44d3c42f921504ba9 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 8 Sep 2023 22:27:47 +0200 Subject: [PATCH 4/6] Use `timestamp_nanos_opt` in serialization; add panic warning --- src/datetime/serde.rs | 24 ++++++++++++++++++++++-- src/naive/datetime/serde.rs | 24 ++++++++++++++++++++++-- 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/src/datetime/serde.rs b/src/datetime/serde.rs index 61f7f68840..4044eb586b 100644 --- a/src/datetime/serde.rs +++ b/src/datetime/serde.rs @@ -157,6 +157,14 @@ pub mod ts_nanoseconds { /// /// Intended for use with `serde`s `serialize_with` attribute. /// + /// # Errors + /// + /// An `i64` with nanosecond precision can span a range of ~584 years. This function returns an + /// error on an out of range `DateTime`. + /// + /// The dates that can be represented as nanoseconds are between 1677-09-21T00:12:44.0 and + /// 2262-04-11T23:47:16.854775804. + /// /// # Example: /// /// ```rust @@ -180,7 +188,9 @@ pub mod ts_nanoseconds { where S: ser::Serializer, { - serializer.serialize_i64(dt.timestamp_nanos()) + serializer.serialize_i64(dt.timestamp_nanos_opt().ok_or(ser::Error::custom( + "value out of range for a timestamp with nanosecond precision", + ))?) } /// Deserialize a [`DateTime`] from a nanosecond timestamp @@ -286,6 +296,14 @@ pub mod ts_nanoseconds_option { /// /// Intended for use with `serde`s `serialize_with` attribute. /// + /// # Errors + /// + /// An `i64` with nanosecond precision can span a range of ~584 years. This function returns an + /// error on an out of range `DateTime`. + /// + /// The dates that can be represented as nanoseconds are between 1677-09-21T00:12:44.0 and + /// 2262-04-11T23:47:16.854775804. + /// /// # Example: /// /// ```rust @@ -310,7 +328,9 @@ pub mod ts_nanoseconds_option { S: ser::Serializer, { match *opt { - Some(ref dt) => serializer.serialize_some(&dt.timestamp_nanos()), + Some(ref dt) => serializer.serialize_some(&dt.timestamp_nanos_opt().ok_or( + ser::Error::custom("value out of range for a timestamp with nanosecond precision"), + )?), None => serializer.serialize_none(), } } diff --git a/src/naive/datetime/serde.rs b/src/naive/datetime/serde.rs index 49dfb8390d..86d8b20f4c 100644 --- a/src/naive/datetime/serde.rs +++ b/src/naive/datetime/serde.rs @@ -91,6 +91,14 @@ pub mod ts_nanoseconds { /// /// Intended for use with `serde`s `serialize_with` attribute. /// + /// # Errors + /// + /// An `i64` with nanosecond precision can span a range of ~584 years. This function returns an + /// error on an out of range `DateTime`. + /// + /// The dates that can be represented as nanoseconds are between 1677-09-21T00:12:44.0 and + /// 2262-04-11T23:47:16.854775804. + /// /// # Example: /// /// ```rust @@ -114,7 +122,9 @@ pub mod ts_nanoseconds { where S: ser::Serializer, { - serializer.serialize_i64(dt.timestamp_nanos()) + serializer.serialize_i64(dt.timestamp_nanos_opt().ok_or(ser::Error::custom( + "value out of range for a timestamp with nanosecond precision", + ))?) } /// Deserialize a `NaiveDateTime` from a nanoseconds timestamp @@ -218,6 +228,14 @@ pub mod ts_nanoseconds_option { /// /// Intended for use with `serde`s `serialize_with` attribute. /// + /// # Errors + /// + /// An `i64` with nanosecond precision can span a range of ~584 years. This function returns an + /// error on an out of range `DateTime`. + /// + /// The dates that can be represented as nanoseconds are between 1677-09-21T00:12:44.0 and + /// 2262-04-11T23:47:16.854775804. + /// /// # Example: /// /// ```rust @@ -242,7 +260,9 @@ pub mod ts_nanoseconds_option { S: ser::Serializer, { match *opt { - Some(ref dt) => serializer.serialize_some(&dt.timestamp_nanos()), + Some(ref dt) => serializer.serialize_some(&dt.timestamp_nanos_opt().ok_or( + ser::Error::custom("value out of range for a timestamp with nanosecond precision"), + )?), None => serializer.serialize_none(), } } From fb5a57ae60a8a5278276414c4ba927d40f296f3b Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 8 Sep 2023 22:00:04 +0200 Subject: [PATCH 5/6] Switch tests to `timestamp_nanos_opt` --- src/naive/datetime/tests.rs | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/src/naive/datetime/tests.rs b/src/naive/datetime/tests.rs index 5ded98f385..39187de67e 100644 --- a/src/naive/datetime/tests.rs +++ b/src/naive/datetime/tests.rs @@ -386,7 +386,7 @@ fn test_nanosecond_range() { const A_BILLION: i64 = 1_000_000_000; let maximum = "2262-04-11T23:47:16.854775804"; let parsed: NaiveDateTime = maximum.parse().unwrap(); - let nanos = parsed.timestamp_nanos(); + let nanos = parsed.timestamp_nanos_opt().unwrap(); assert_eq!( parsed, NaiveDateTime::from_timestamp_opt(nanos / A_BILLION, (nanos % A_BILLION) as u32).unwrap() @@ -394,29 +394,23 @@ fn test_nanosecond_range() { let minimum = "1677-09-21T00:12:44.000000000"; let parsed: NaiveDateTime = minimum.parse().unwrap(); - let nanos = parsed.timestamp_nanos(); + let nanos = parsed.timestamp_nanos_opt().unwrap(); assert_eq!( parsed, NaiveDateTime::from_timestamp_opt(nanos / A_BILLION, (nanos % A_BILLION) as u32).unwrap() ); -} -#[test] -#[should_panic] -fn test_nanosecond_just_beyond_range() { + // Just beyond range let maximum = "2262-04-11T23:47:16.854775804"; let parsed: NaiveDateTime = maximum.parse().unwrap(); let beyond_max = parsed + OldDuration::milliseconds(300); - let _ = beyond_max.timestamp_nanos(); -} + assert!(beyond_max.timestamp_nanos_opt().is_none()); -#[test] -#[should_panic] -fn test_nanosecond_far_beyond_range() { + // Far beyond range let maximum = "2262-04-11T23:47:16.854775804"; let parsed: NaiveDateTime = maximum.parse().unwrap(); let beyond_max = parsed + OldDuration::days(365); - let _ = beyond_max.timestamp_nanos(); + assert!(beyond_max.timestamp_nanos_opt().is_none()); } #[test] From 7d20d22b6dc394e3afab671577af995da4c7e140 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 8 Sep 2023 21:55:45 +0200 Subject: [PATCH 6/6] Deprecate `timestamp_nanos` --- src/datetime/mod.rs | 1 + src/naive/datetime/mod.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 69bee1419a..24e7e9f16a 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -262,6 +262,7 @@ impl DateTime { /// /// The dates that can be represented as nanoseconds are between 1677-09-21T00:12:44.0 and /// 2262-04-11T23:47:16.854775804. + #[deprecated(since = "0.4.31", note = "use `timestamp_nanos_opt()` instead")] #[inline] #[must_use] pub fn timestamp_nanos(&self) -> i64 { diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 9c5a9c8b2e..b2f7d63bf0 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -460,6 +460,7 @@ impl NaiveDateTime { /// /// The dates that can be represented as nanoseconds are between 1677-09-21T00:12:44.0 and /// 2262-04-11T23:47:16.854775804. + #[deprecated(since = "0.4.31", note = "use `timestamp_nanos_opt()` instead")] #[inline] #[must_use] pub fn timestamp_nanos(&self) -> i64 {