diff --git a/src/date.rs b/src/date.rs index 0c9b1eef4f..4f08c33887 100644 --- a/src/date.rs +++ b/src/date.rs @@ -6,7 +6,7 @@ #[cfg(any(feature = "alloc", feature = "std", test))] use core::borrow::Borrow; use core::cmp::Ordering; -use core::ops::{Add, Sub}; +use core::ops::{Add, AddAssign, Sub, SubAssign}; use core::{fmt, hash}; #[cfg(feature = "rkyv")] @@ -479,6 +479,13 @@ impl Add for Date { } } +impl AddAssign for Date { + #[inline] + fn add_assign(&mut self, rhs: OldDuration) { + self.date = self.date.checked_add_signed(rhs).expect("`Date + Duration` overflowed"); + } +} + impl Sub for Date { type Output = Date; @@ -488,6 +495,13 @@ impl Sub for Date { } } +impl SubAssign for Date { + #[inline] + fn sub_assign(&mut self, rhs: OldDuration) { + self.date = self.date.checked_sub_signed(rhs).expect("`Date - Duration` overflowed"); + } +} + impl Sub> for Date { type Output = OldDuration; @@ -514,7 +528,13 @@ where #[cfg(test)] mod tests { - use crate::{Duration, Utc}; + use super::Date; + + use crate::oldtime::Duration; + use crate::{FixedOffset, NaiveDate, Utc}; + + #[cfg(feature = "clock")] + use crate::offset::{Local, TimeZone}; #[test] #[cfg(feature = "clock")] @@ -533,4 +553,72 @@ mod tests { let future = Utc::today() + Duration::weeks(12); assert_eq!(Utc::today().years_since(future), None); } + + #[test] + fn test_date_add_assign() { + let naivedate = NaiveDate::from_ymd(2000, 1, 1); + let date = Date::::from_utc(naivedate, Utc); + let mut date_add = date; + + date_add += Duration::days(5); + assert_eq!(date_add, date + Duration::days(5)); + + let timezone = FixedOffset::east(60 * 60); + let date = date.with_timezone(&timezone); + let date_add = date_add.with_timezone(&timezone); + + assert_eq!(date_add, date + Duration::days(5)); + + let timezone = FixedOffset::west(2 * 60 * 60); + let date = date.with_timezone(&timezone); + let date_add = date_add.with_timezone(&timezone); + + assert_eq!(date_add, date + Duration::days(5)); + } + + #[test] + #[cfg(feature = "clock")] + fn test_date_add_assign_local() { + let naivedate = NaiveDate::from_ymd(2000, 1, 1); + + let date = Local.from_utc_date(&naivedate); + let mut date_add = date; + + date_add += Duration::days(5); + assert_eq!(date_add, date + Duration::days(5)); + } + + #[test] + fn test_date_sub_assign() { + let naivedate = NaiveDate::from_ymd(2000, 1, 1); + let date = Date::::from_utc(naivedate, Utc); + let mut date_sub = date; + + date_sub -= Duration::days(5); + assert_eq!(date_sub, date - Duration::days(5)); + + let timezone = FixedOffset::east(60 * 60); + let date = date.with_timezone(&timezone); + let date_sub = date_sub.with_timezone(&timezone); + + assert_eq!(date_sub, date - Duration::days(5)); + + let timezone = FixedOffset::west(2 * 60 * 60); + let date = date.with_timezone(&timezone); + let date_sub = date_sub.with_timezone(&timezone); + + assert_eq!(date_sub, date - Duration::days(5)); + } + + #[test] + #[cfg(feature = "clock")] + fn test_date_sub_assign_local() { + let naivedate = NaiveDate::from_ymd(2000, 1, 1); + + let date = Local.from_utc_date(&naivedate); + let mut date_sub = date; + + date_sub -= Duration::days(5); + assert_eq!(date_sub, date - Duration::days(5)); + } } diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index cc902646e9..6af2abb64a 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -11,7 +11,7 @@ use alloc::string::{String, ToString}; #[cfg(any(feature = "alloc", feature = "std", test))] use core::borrow::Borrow; use core::cmp::Ordering; -use core::ops::{Add, Sub}; +use core::ops::{Add, AddAssign, Sub, SubAssign}; use core::{fmt, hash, str}; #[cfg(feature = "std")] use std::string::ToString; @@ -852,6 +852,16 @@ impl Add for DateTime { } } +impl AddAssign for DateTime { + #[inline] + fn add_assign(&mut self, rhs: OldDuration) { + let datetime = + self.datetime.checked_add_signed(rhs).expect("`DateTime + Duration` overflowed"); + let tz = self.timezone(); + *self = tz.from_utc_datetime(&datetime); + } +} + impl Sub for DateTime { type Output = DateTime; @@ -861,6 +871,16 @@ impl Sub for DateTime { } } +impl SubAssign for DateTime { + #[inline] + fn sub_assign(&mut self, rhs: OldDuration) { + let datetime = + self.datetime.checked_sub_signed(rhs).expect("`DateTime - Duration` overflowed"); + let tz = self.timezone(); + *self = tz.from_utc_datetime(&datetime) + } +} + impl Sub> for DateTime { type Output = OldDuration; diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index fbf959e10d..d9b11757c8 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -426,3 +426,77 @@ fn test_years_elapsed() { let future = Utc::today() + Duration::weeks(12); assert_eq!(Utc::today().years_since(future), None); } + +#[test] +fn test_datetime_add_assign() { + let naivedatetime = NaiveDate::from_ymd(2000, 1, 1).and_hms(0, 0, 0); + let datetime = DateTime::::from_utc(naivedatetime, Utc); + let mut datetime_add = datetime; + + datetime_add += Duration::seconds(60); + assert_eq!(datetime_add, datetime + Duration::seconds(60)); + + let timezone = FixedOffset::east(60 * 60); + let datetime = datetime.with_timezone(&timezone); + let datetime_add = datetime_add.with_timezone(&timezone); + + assert_eq!(datetime_add, datetime + Duration::seconds(60)); + + let timezone = FixedOffset::west(2 * 60 * 60); + let datetime = datetime.with_timezone(&timezone); + let datetime_add = datetime_add.with_timezone(&timezone); + + assert_eq!(datetime_add, datetime + Duration::seconds(60)); +} + +#[test] +#[cfg(feature = "clock")] +fn test_datetime_add_assign_local() { + let naivedatetime = NaiveDate::from_ymd(2022, 1, 1).and_hms(0, 0, 0); + + let datetime = Local.from_utc_datetime(&naivedatetime); + let mut datetime_add = Local.from_utc_datetime(&naivedatetime); + + // ensure we cross a DST transition + for i in 1..=365 { + datetime_add += Duration::days(1); + assert_eq!(datetime_add, datetime + Duration::days(i)) + } +} + +#[test] +fn test_datetime_sub_assign() { + let naivedatetime = NaiveDate::from_ymd(2000, 1, 1).and_hms(12, 0, 0); + let datetime = DateTime::::from_utc(naivedatetime, Utc); + let mut datetime_sub = datetime; + + datetime_sub -= Duration::minutes(90); + assert_eq!(datetime_sub, datetime - Duration::minutes(90)); + + let timezone = FixedOffset::east(60 * 60); + let datetime = datetime.with_timezone(&timezone); + let datetime_sub = datetime_sub.with_timezone(&timezone); + + assert_eq!(datetime_sub, datetime - Duration::minutes(90)); + + let timezone = FixedOffset::west(2 * 60 * 60); + let datetime = datetime.with_timezone(&timezone); + let datetime_sub = datetime_sub.with_timezone(&timezone); + + assert_eq!(datetime_sub, datetime - Duration::minutes(90)); +} + +#[test] +#[cfg(feature = "clock")] +fn test_datetime_sub_assign_local() { + let naivedatetime = NaiveDate::from_ymd(2022, 1, 1).and_hms(0, 0, 0); + + let datetime = Local.from_utc_datetime(&naivedatetime); + let mut datetime_sub = Local.from_utc_datetime(&naivedatetime); + + // ensure we cross a DST transition + for i in 1..=365 { + datetime_sub -= Duration::days(1); + assert_eq!(datetime_sub, datetime - Duration::days(i)) + } +}