diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index c5c3c0b231..67e12c71f7 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -20,8 +20,10 @@ use crate::format::{parse, parse_and_remainder, ParseError, ParseResult, Parsed, use crate::format::{Fixed, Item, Numeric, Pad}; use crate::naive::{Days, IsoWeek, NaiveDate, NaiveTime}; use crate::offset::Utc; -use crate::{expect, DateTime, Datelike, LocalResult, Months, TimeZone, Timelike, Weekday}; - +use crate::{ + expect, try_opt, DateTime, Datelike, FixedOffset, LocalResult, Months, TimeZone, Timelike, + Weekday, +}; #[cfg(feature = "rustc-serialize")] pub(super) mod rustc_serialize; @@ -705,6 +707,37 @@ impl NaiveDateTime { Some(Self { date: self.date.checked_add_months(rhs)?, time: self.time }) } + /// Adds given `FixedOffset` to the current datetime. + /// Returns `None` if the result would be outside the valid range for [`NaiveDateTime`]. + /// + /// This method is similar to [`checked_add_signed`](#method.checked_add_offset), but preserves + /// leap seconds. + #[must_use] + pub const fn checked_add_offset(self, rhs: FixedOffset) -> Option { + let (time, days) = self.time.overflowing_add_offset(rhs); + let date = match days { + -1 => try_opt!(self.date.pred_opt()), + 1 => try_opt!(self.date.succ_opt()), + _ => self.date, + }; + Some(NaiveDateTime { date, time }) + } + + /// Subtracts given `FixedOffset` from the current datetime. + /// Returns `None` if the result would be outside the valid range for [`NaiveDateTime`]. + /// + /// This method is similar to [`checked_sub_signed`](#method.checked_sub_signed), but preserves + /// leap seconds. + pub const fn checked_sub_offset(self, rhs: FixedOffset) -> Option { + let (time, days) = self.time.overflowing_sub_offset(rhs); + let date = match days { + -1 => try_opt!(self.date.pred_opt()), + 1 => try_opt!(self.date.succ_opt()), + _ => self.date, + }; + Some(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), @@ -1586,6 +1619,15 @@ impl AddAssign for NaiveDateTime { } } +impl Add for NaiveDateTime { + type Output = NaiveDateTime; + + #[inline] + fn add(self, rhs: FixedOffset) -> NaiveDateTime { + self.checked_add_offset(rhs).unwrap() + } +} + impl Add for NaiveDateTime { type Output = NaiveDateTime; @@ -1713,6 +1755,15 @@ impl SubAssign for NaiveDateTime { } } +impl Sub for NaiveDateTime { + type Output = NaiveDateTime; + + #[inline] + fn sub(self, rhs: FixedOffset) -> NaiveDateTime { + self.checked_sub_offset(rhs).unwrap() + } +} + /// A subtraction of Months from `NaiveDateTime` clamped to valid days in resulting month. /// /// # Panics diff --git a/src/naive/datetime/tests.rs b/src/naive/datetime/tests.rs index 9ee22dfde6..4558bf7f42 100644 --- a/src/naive/datetime/tests.rs +++ b/src/naive/datetime/tests.rs @@ -433,3 +433,60 @@ fn test_and_utc() { assert_eq!(dt_utc.naive_local(), ndt); assert_eq!(dt_utc.timezone(), Utc); } + +#[test] +fn test_checked_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) + }; + + let positive_offset = FixedOffset::east_opt(2 * 60 * 60).unwrap(); + // regular date + let dt = ymdhmsm(2023, 5, 5, 20, 10, 0, 0).unwrap(); + assert_eq!(dt.checked_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).unwrap(); + assert_eq!(dt.checked_add_offset(positive_offset), ymdhmsm(2023, 7, 1, 1, 59, 59, 1_000)); + // out of range + assert!(NaiveDateTime::MAX.checked_add_offset(positive_offset).is_none()); + + let negative_offset = FixedOffset::west_opt(2 * 60 * 60).unwrap(); + // regular date + let dt = ymdhmsm(2023, 5, 5, 20, 10, 0, 0).unwrap(); + assert_eq!(dt.checked_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).unwrap(); + assert_eq!(dt.checked_add_offset(negative_offset), ymdhmsm(2023, 6, 30, 21, 59, 59, 1_000)); + // out of range + assert!(NaiveDateTime::MIN.checked_add_offset(negative_offset).is_none()); +} + +#[test] +fn test_checked_sub_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) + }; + + let positive_offset = FixedOffset::east_opt(2 * 60 * 60).unwrap(); + // regular date + let dt = ymdhmsm(2023, 5, 5, 20, 10, 0, 0).unwrap(); + assert_eq!(dt.checked_sub_offset(positive_offset), ymdhmsm(2023, 5, 5, 18, 10, 0, 0)); + // leap second is preserved + let dt = ymdhmsm(2023, 6, 30, 23, 59, 59, 1_000).unwrap(); + assert_eq!(dt.checked_sub_offset(positive_offset), ymdhmsm(2023, 6, 30, 21, 59, 59, 1_000)); + // out of range + assert!(NaiveDateTime::MIN.checked_sub_offset(positive_offset).is_none()); + + let negative_offset = FixedOffset::west_opt(2 * 60 * 60).unwrap(); + // regular date + let dt = ymdhmsm(2023, 5, 5, 20, 10, 0, 0).unwrap(); + assert_eq!(dt.checked_sub_offset(negative_offset), ymdhmsm(2023, 5, 5, 22, 10, 0, 0)); + // leap second is preserved + let dt = ymdhmsm(2023, 6, 30, 23, 59, 59, 1_000).unwrap(); + assert_eq!(dt.checked_sub_offset(negative_offset), ymdhmsm(2023, 7, 1, 1, 59, 59, 1_000)); + // out of range + assert!(NaiveDateTime::MAX.checked_sub_offset(negative_offset).is_none()); + + assert_eq!(dt.checked_add_offset(positive_offset), Some(dt + positive_offset)); + assert_eq!(dt.checked_sub_offset(positive_offset), Some(dt - positive_offset)); +} diff --git a/src/offset/fixed.rs b/src/offset/fixed.rs index 8038e1a5e5..013f822479 100644 --- a/src/offset/fixed.rs +++ b/src/offset/fixed.rs @@ -199,24 +199,6 @@ where (lhs + OldDuration::seconds(i64::from(rhs))).with_nanosecond(nanos).unwrap() } -impl Add for NaiveDateTime { - type Output = NaiveDateTime; - - #[inline] - fn add(self, rhs: FixedOffset) -> NaiveDateTime { - add_with_leapsecond(&self, rhs.local_minus_utc) - } -} - -impl Sub for NaiveDateTime { - type Output = NaiveDateTime; - - #[inline] - fn sub(self, rhs: FixedOffset) -> NaiveDateTime { - add_with_leapsecond(&self, -rhs.local_minus_utc) - } -} - impl Add for DateTime { type Output = DateTime;