From 7f89920c0caa19ddc97206755a5318f0d90f7587 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 5 May 2023 15:37:33 +0200 Subject: [PATCH] Add `overflowing_naive_local` and `NaiveDateTime::unchecked_add_offset` --- src/datetime/mod.rs | 23 ++++++++++++++++++++++- src/naive/datetime/mod.rs | 12 ++++++++++++ src/naive/datetime/tests.rs | 26 ++++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 1 deletion(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 898b9f92f9..e96228642d 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -185,6 +185,14 @@ impl DateTime { /// /// # Example /// + /// # Panics + /// + /// [`DateTime`] internally stores the date and time in UTC with a [`NaiveDateTime`]. This + /// method will panic if the offset from UTC would push the local datetime outside of the + /// representable range of a [`DateTime`]. + /// + /// # Example + /// /// ``` /// use chrono::prelude::*; /// @@ -463,7 +471,20 @@ impl DateTime { #[inline] #[must_use] pub fn naive_local(&self) -> NaiveDateTime { - self.datetime + self.offset.fix() + self.datetime + .checked_add_offset(self.offset.fix()) + .expect("Local time out of range for `NaiveDateTime`") + } + + /// Returns a view to the naive local datetime. + /// + /// This makes use of the buffer space outside of the representable range of values of + /// `NaiveDateTime`. The result can be used as intermediate value, but should never be exposed + /// outside chrono. + #[inline] + #[must_use] + pub(crate) fn overflowing_naive_local(&self) -> NaiveDateTime { + self.datetime.unchecked_add_offset(self.offset.fix()) } /// Retrieve the elapsed years from now to the given [`DateTime`]. diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 926247d9a3..e081767f89 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -672,6 +672,18 @@ impl NaiveDateTime { self.date.add_days(days, false).map(|date| NaiveDateTime { date, time }) } + /// Adds given `FixedOffset` to the current datetime. + /// The resulting value may be outside the valid range of [`NaiveDateTime`]. + /// + /// This can be useful for intermediate values, but the resulting out-of-range `NaiveDate` + /// should not be exposed to library users. + #[must_use] + pub(crate) fn unchecked_add_offset(self, rhs: FixedOffset) -> NaiveDateTime { + let (time, days) = self.time.overflowing_add_offset(rhs); + let date = self.date.add_days(days, true).unwrap(); + NaiveDateTime { date, time } + } + /// Subtracts given `Duration` from the current date and time. /// /// As a part of Chrono's [leap second handling](./struct.NaiveTime.html#leap-second-handling), diff --git a/src/naive/datetime/tests.rs b/src/naive/datetime/tests.rs index 759a5830f2..f202118005 100644 --- a/src/naive/datetime/tests.rs +++ b/src/naive/datetime/tests.rs @@ -441,3 +441,29 @@ fn test_and_timezone_min_max_dates() { } } } + +#[test] +fn test_unchecked_add_offset() { + let ymdhmsm = |y, m, d, h, mn, s, mi| { + NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_milli_opt(h, mn, s, mi).unwrap() + }; + let positive_offset = FixedOffset::east_opt(2 * 60 * 60).unwrap(); + // regular date + let dt = ymdhmsm(2023, 5, 5, 20, 10, 0, 0); + assert_eq!(dt.unchecked_add_offset(positive_offset), ymdhmsm(2023, 5, 5, 22, 10, 0, 0)); + // leap second is preserved + let dt = ymdhmsm(2023, 6, 30, 23, 59, 59, 1_000); + assert_eq!(dt.unchecked_add_offset(positive_offset), ymdhmsm(2023, 7, 1, 1, 59, 59, 1_000)); + // out of range + assert!(NaiveDateTime::MAX.unchecked_add_offset(positive_offset) > NaiveDateTime::MAX); + + let negative_offset = FixedOffset::west_opt(2 * 60 * 60).unwrap(); + // regular date + let dt = ymdhmsm(2023, 5, 5, 20, 10, 0, 0); + assert_eq!(dt.unchecked_add_offset(negative_offset), ymdhmsm(2023, 5, 5, 18, 10, 0, 0)); + // leap second is preserved + let dt = ymdhmsm(2023, 6, 30, 23, 59, 59, 1_000); + assert_eq!(dt.unchecked_add_offset(negative_offset), ymdhmsm(2023, 6, 30, 21, 59, 59, 1_000)); + // out of range + assert!(NaiveDateTime::MIN.unchecked_add_offset(negative_offset) < NaiveDateTime::MIN); +}