diff --git a/src/duration.rs b/src/duration.rs index f00b03663e..63fa66fd49 100644 --- a/src/duration.rs +++ b/src/duration.rs @@ -24,7 +24,7 @@ const NANOS_PER_MICRO: i32 = 1000; /// The number of nanoseconds in a millisecond. const NANOS_PER_MILLI: i32 = 1_000_000; /// The number of nanoseconds in seconds. -const NANOS_PER_SEC: i32 = 1_000_000_000; +pub(crate) const NANOS_PER_SEC: i32 = 1_000_000_000; /// The number of microseconds per second. const MICROS_PER_SEC: i64 = 1_000_000; /// The number of milliseconds per second. diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 51b9988a75..764a1743a8 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -13,7 +13,7 @@ use core::{fmt, str}; #[cfg(feature = "rkyv")] use rkyv::{Archive, Deserialize, Serialize}; -use crate::duration::Duration as OldDuration; +use crate::duration::{Duration as OldDuration, NANOS_PER_SEC}; #[cfg(feature = "alloc")] use crate::format::DelayedFormat; use crate::format::{parse, parse_and_remainder, ParseError, ParseResult, Parsed, StrftimeItems}; @@ -197,6 +197,39 @@ impl NaiveDateTime { NaiveDateTime::from_timestamp_opt(secs, nsecs) } + /// Creates a new [NaiveDateTime] from nanoseconds since the UNIX epoch. + /// + /// The UNIX epoch starts on midnight, January 1, 1970, UTC. + /// + /// # Errors + /// + /// Returns `None` if the number of nanoseconds would be out of range for a `NaiveDateTime` + /// (more than ca. 262,000 years away from common era) + /// + /// # Example + /// + /// ``` + /// use chrono::NaiveDateTime; + /// let timestamp_nanos: i64 = 1662921288_000_000_000; //Sunday, September 11, 2022 6:34:48 PM + /// let naive_datetime = NaiveDateTime::from_timestamp_nanos(timestamp_nanos); + /// assert!(naive_datetime.is_some()); + /// assert_eq!(timestamp_nanos, naive_datetime.unwrap().timestamp_nanos_opt().unwrap()); + /// + /// // Negative timestamps (before the UNIX epoch) are supported as well. + /// let timestamp_nanos: i64 = -2208936075_000_000_000; //Mon Jan 01 1900 14:38:45 GMT+0000 + /// let naive_datetime = NaiveDateTime::from_timestamp_nanos(timestamp_nanos); + /// assert!(naive_datetime.is_some()); + /// assert_eq!(timestamp_nanos, naive_datetime.unwrap().timestamp_nanos_opt().unwrap()); + /// ``` + #[inline] + #[must_use] + pub const fn from_timestamp_nanos(nanos: i64) -> Option { + let secs = nanos.div_euclid(NANOS_PER_SEC as i64); + let nsecs = nanos.rem_euclid(NANOS_PER_SEC as i64) as u32; + + NaiveDateTime::from_timestamp_opt(secs, nsecs) + } + /// Makes a new `NaiveDateTime` corresponding to a UTC date and time, /// from the number of non-leap seconds /// since the midnight UTC on January 1, 1970 (aka "UNIX timestamp") diff --git a/src/naive/datetime/tests.rs b/src/naive/datetime/tests.rs index 0dbbe7bc5e..6375a9d351 100644 --- a/src/naive/datetime/tests.rs +++ b/src/naive/datetime/tests.rs @@ -78,6 +78,57 @@ fn test_datetime_from_timestamp_micros() { } } +#[test] +fn test_datetime_from_timestamp_nanos() { + let valid_map = [ + (1662921288000000000, "2022-09-11 18:34:48.000000000"), + (1662921288123456000, "2022-09-11 18:34:48.123456000"), + (1662921288123456789, "2022-09-11 18:34:48.123456789"), + (1662921287890000000, "2022-09-11 18:34:47.890000000"), + (-2208936075000000000, "1900-01-01 14:38:45.000000000"), + (-5337182663000000000, "1800-11-15 01:15:37.000000000"), + (0, "1970-01-01 00:00:00.000000000"), + (119731017000000000, "1973-10-17 18:36:57.000000000"), + (1234567890000000000, "2009-02-13 23:31:30.000000000"), + (2034061609000000000, "2034-06-16 09:06:49.000000000"), + ]; + + for (timestamp_nanos, _formatted) in valid_map.iter().copied() { + let naive_datetime = NaiveDateTime::from_timestamp_nanos(timestamp_nanos).unwrap(); + assert_eq!(timestamp_nanos, naive_datetime.timestamp_nanos_opt().unwrap()); + #[cfg(feature = "alloc")] + assert_eq!(naive_datetime.format("%F %T%.9f").to_string(), _formatted); + } + + const A_BILLION: i64 = 1_000_000_000; + // Maximum datetime in nanoseconds + let maximum = "2262-04-11T23:47:16.854775804"; + let parsed: NaiveDateTime = maximum.parse().unwrap(); + let nanos = parsed.timestamp_nanos_opt().unwrap(); + assert_eq!( + NaiveDateTime::from_timestamp_nanos(nanos).unwrap(), + NaiveDateTime::from_timestamp_opt(nanos / A_BILLION, (nanos % A_BILLION) as u32).unwrap() + ); + // Minimum datetime in nanoseconds + let minimum = "1677-09-21T00:12:44.000000000"; + let parsed: NaiveDateTime = minimum.parse().unwrap(); + let nanos = parsed.timestamp_nanos_opt().unwrap(); + assert_eq!( + NaiveDateTime::from_timestamp_nanos(nanos).unwrap(), + NaiveDateTime::from_timestamp_opt(nanos / A_BILLION, (nanos % A_BILLION) as u32).unwrap() + ); + + // Test that the result of `from_timestamp_nanos` compares equal to + // that of `from_timestamp_opt`. + let secs_test = [0, 1, 2, 1000, 1234, 12345678, -1, -2, -1000, -12345678]; + for secs in secs_test.iter().copied() { + assert_eq!( + NaiveDateTime::from_timestamp_nanos(secs * 1_000_000_000), + NaiveDateTime::from_timestamp_opt(secs, 0) + ); + } +} + #[test] fn test_datetime_from_timestamp() { let from_timestamp = |secs| NaiveDateTime::from_timestamp_opt(secs, 0);