Use overflowing_naive_local in checked_(add|sub)_months

This commit is contained in:
Paul Dicker 2023-09-28 18:16:47 +02:00 committed by Paul Dicker
parent e292d9bc90
commit dd201649b5
2 changed files with 41 additions and 8 deletions

View File

@ -436,13 +436,17 @@ impl<Tz: TimeZone> DateTime<Tz> {
/// # Errors
///
/// Returns `None` if:
/// - The resulting date would be out of range.
/// - The local time at the resulting date does not exist or is ambiguous, for example during a
/// daylight saving time transition.
/// - The resulting UTC datetime would be out of range.
/// - The resulting local datetime would be out of range (unless `months` is zero).
#[must_use]
pub fn checked_add_months(self, rhs: Months) -> Option<DateTime<Tz>> {
self.naive_local()
.checked_add_months(rhs)?
pub fn checked_add_months(self, months: Months) -> Option<DateTime<Tz>> {
// `NaiveDate::checked_add_months` has a fast path for `Months(0)` that does not validate
// the resulting date, with which we can return `Some` even for an out of range local
// datetime.
self.overflowing_naive_local()
.checked_add_months(months)?
.and_local_timezone(Tz::from_offset(&self.offset))
.single()
}
@ -469,13 +473,17 @@ impl<Tz: TimeZone> DateTime<Tz> {
/// # Errors
///
/// Returns `None` if:
/// - The resulting date would be out of range.
/// - The local time at the resulting date does not exist or is ambiguous, for example during a
/// daylight saving time transition.
/// - The resulting UTC datetime would be out of range.
/// - The resulting local datetime would be out of range (unless `months` is zero).
#[must_use]
pub fn checked_sub_months(self, rhs: Months) -> Option<DateTime<Tz>> {
self.naive_local()
.checked_sub_months(rhs)?
pub fn checked_sub_months(self, months: Months) -> Option<DateTime<Tz>> {
// `NaiveDate::checked_sub_months` has a fast path for `Months(0)` that does not validate
// the resulting date, with which we can return `Some` even for an out of range local
// datetime.
self.overflowing_naive_local()
.checked_sub_months(months)?
.and_local_timezone(Tz::from_offset(&self.offset))
.single()
}

View File

@ -1461,6 +1461,31 @@ fn test_min_max_setters() {
assert_eq!(beyond_max.with_nanosecond(beyond_max.nanosecond()), Some(beyond_max));
}
#[test]
fn test_min_max_add_months() {
let offset_min = FixedOffset::west_opt(2 * 60 * 60).unwrap();
let beyond_min = offset_min.from_utc_datetime(&NaiveDateTime::MIN);
let offset_max = FixedOffset::east_opt(2 * 60 * 60).unwrap();
let beyond_max = offset_max.from_utc_datetime(&NaiveDateTime::MAX);
let max_time = NaiveTime::from_hms_nano_opt(23, 59, 59, 999_999_999).unwrap();
assert_eq!(beyond_min.checked_add_months(Months::new(0)), Some(beyond_min));
assert_eq!(
beyond_min.checked_add_months(Months::new(1)),
Some(offset_min.from_utc_datetime(&(NaiveDate::MIN + Months(1)).and_time(NaiveTime::MIN)))
);
assert_eq!(beyond_min.checked_sub_months(Months::new(0)), Some(beyond_min));
assert_eq!(beyond_min.checked_sub_months(Months::new(1)), None);
assert_eq!(beyond_max.checked_add_months(Months::new(0)), Some(beyond_max));
assert_eq!(beyond_max.checked_add_months(Months::new(1)), None);
assert_eq!(beyond_max.checked_sub_months(Months::new(0)), Some(beyond_max));
assert_eq!(
beyond_max.checked_sub_months(Months::new(1)),
Some(offset_max.from_utc_datetime(&(NaiveDate::MAX - Months(1)).and_time(max_time)))
);
}
#[test]
#[should_panic]
fn test_local_beyond_min_datetime() {