Skip to content

Commit

Permalink
Add NaiveDateTime::checked_(add|sub)_offset
Browse files Browse the repository at this point in the history
  • Loading branch information
pitdicker committed Aug 6, 2023
1 parent d30ccbc commit 7efcf31
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 2 deletions.
25 changes: 23 additions & 2 deletions src/naive/datetime/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ use crate::format::{Fixed, Item, Numeric, Pad};
use crate::naive::{Days, IsoWeek, NaiveDate, NaiveTime};
use crate::offset::Utc;
use crate::oldtime::Duration as OldDuration;
use crate::{DateTime, Datelike, LocalResult, Months, TimeZone, Timelike, Weekday};

use crate::{
try_opt, DateTime, Datelike, FixedOffset, LocalResult, Months, TimeZone, Timelike, Weekday,
};
#[cfg(feature = "rustc-serialize")]
pub(super) mod rustc_serialize;

Expand Down Expand Up @@ -663,6 +664,16 @@ 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`], but preserves leap seconds.
#[must_use]
pub(crate) const fn checked_add_offset(self, rhs: FixedOffset) -> Option<NaiveDateTime> {
let (time, days) = self.time.overflowing_add_offset(rhs);
Some(NaiveDateTime { date: try_opt!(self.date.add_days(days)), 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),
Expand Down Expand Up @@ -739,6 +750,16 @@ impl NaiveDateTime {
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`], but preserves leap seconds.
#[must_use]
pub(crate) const fn checked_sub_offset(self, rhs: FixedOffset) -> Option<NaiveDateTime> {
let (time, days) = self.time.overflowing_sub_offset(rhs);
Some(NaiveDateTime { date: try_opt!(self.date.add_days(days)), time })
}

/// Subtracts given `Months` from the current date and time.
///
/// Uses the last day of the month if the day does not exist in the resulting month.
Expand Down
54 changes: 54 additions & 0 deletions src/naive/datetime/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -377,3 +377,57 @@ 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());
}

0 comments on commit 7efcf31

Please sign in to comment.