Skip to content

Commit

Permalink
Convert NaiveDateTime::{checked_add_signed, checked_sub_signed}
Browse files Browse the repository at this point in the history
  • Loading branch information
pitdicker committed Mar 13, 2024
1 parent 16e2719 commit c904446
Show file tree
Hide file tree
Showing 3 changed files with 137 additions and 110 deletions.
4 changes: 2 additions & 2 deletions src/datetime/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ impl<Tz: TimeZone> DateTime<Tz> {
#[inline]
#[must_use]
pub fn checked_add_signed(self, rhs: TimeDelta) -> Option<DateTime<Tz>> {
let datetime = self.datetime.checked_add_signed(rhs)?;
let datetime = self.datetime.checked_add_signed(rhs).ok()?;
let tz = self.timezone();
Some(tz.from_utc_datetime(&datetime))
}
Expand Down Expand Up @@ -349,7 +349,7 @@ impl<Tz: TimeZone> DateTime<Tz> {
#[inline]
#[must_use]
pub fn checked_sub_signed(self, rhs: TimeDelta) -> Option<DateTime<Tz>> {
let datetime = self.datetime.checked_sub_signed(rhs)?;
let datetime = self.datetime.checked_sub_signed(rhs).ok()?;
let tz = self.timezone();
Some(tz.from_utc_datetime(&datetime))
}
Expand Down
193 changes: 109 additions & 84 deletions src/naive/datetime/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ use crate::format::{Fixed, Item, Numeric, Pad};
use crate::naive::{Days, IsoWeek, NaiveDate, NaiveTime};
use crate::offset::Utc;
use crate::{
expect, ok, try_err, try_opt, DateTime, Datelike, Error, FixedOffset, LocalResult, Months,
TimeDelta, TimeZone, Timelike, Weekday,
expect, ok, try_err, try_ok_or, try_opt, DateTime, Datelike, Error, FixedOffset, LocalResult,
Months, TimeDelta, TimeZone, Timelike, Weekday,
};

/// Tools to help serializing/deserializing `NaiveDateTime`s
Expand Down Expand Up @@ -242,76 +242,89 @@ impl NaiveDateTime {
///
/// # Errors
///
/// Returns `None` if the resulting date would be out of range.
/// Returns [`Error::OutOfRange`] if the resulting date would be out of range.
///
/// # Example
///
/// ```
/// use chrono::{NaiveDate, TimeDelta};
///
/// let from_ymd = |y, m, d| NaiveDate::from_ymd(y, m, d).unwrap();
///
/// let d = from_ymd(2016, 7, 8);
/// let hms = |h, m, s| d.and_hms(h, m, s).unwrap();
/// assert_eq!(hms(3, 5, 7).checked_add_signed(TimeDelta::zero()), Some(hms(3, 5, 7)));
/// assert_eq!(hms(3, 5, 7).checked_add_signed(TimeDelta::seconds(1).unwrap()), Some(hms(3, 5, 8)));
/// assert_eq!(
/// hms(3, 5, 7).checked_add_signed(TimeDelta::seconds(-1).unwrap()),
/// Some(hms(3, 5, 6))
/// );
/// let d = NaiveDate::from_ymd(2016, 7, 8)?;
/// let hms = |h, m, s| d.and_hms(h, m, s);
/// assert_eq!(hms(3, 5, 7)?.checked_add_signed(TimeDelta::zero()), hms(3, 5, 7));
/// assert_eq!(hms(3, 5, 7)?.checked_add_signed(TimeDelta::seconds(1).unwrap()), hms(3, 5, 8));
/// assert_eq!(hms(3, 5, 7)?.checked_add_signed(TimeDelta::seconds(-1).unwrap()), hms(3, 5, 6));
/// assert_eq!(
/// hms(3, 5, 7).checked_add_signed(TimeDelta::seconds(3600 + 60).unwrap()),
/// Some(hms(4, 6, 7))
/// hms(3, 5, 7)?.checked_add_signed(TimeDelta::seconds(3600 + 60).unwrap()),
/// hms(4, 6, 7)
/// );
/// assert_eq!(
/// hms(3, 5, 7).checked_add_signed(TimeDelta::seconds(86_400).unwrap()),
/// Some(from_ymd(2016, 7, 9).and_hms(3, 5, 7).unwrap())
/// hms(3, 5, 7)?.checked_add_signed(TimeDelta::seconds(86_400).unwrap()),
/// NaiveDate::from_ymd(2016, 7, 9)?.and_hms(3, 5, 7)
/// );
///
/// let hmsm = |h, m, s, milli| d.and_hms_milli(h, m, s, milli).unwrap();
/// let hmsm = |h, m, s, milli| d.and_hms_milli(h, m, s, milli);
/// assert_eq!(
/// hmsm(3, 5, 7, 980).checked_add_signed(TimeDelta::milliseconds(450).unwrap()),
/// Some(hmsm(3, 5, 8, 430))
/// hmsm(3, 5, 7, 980)?.checked_add_signed(TimeDelta::milliseconds(450).unwrap()),
/// hmsm(3, 5, 8, 430)
/// );
/// # Ok::<(), chrono::Error>(())
/// ```
///
/// Overflow returns `None`.
/// Overflow returns [`Error::OutOfRange`].
///
/// ```
/// # use chrono::{TimeDelta, NaiveDate};
/// # let hms = |h, m, s| NaiveDate::from_ymd(2016, 7, 8).unwrap().and_hms(h, m, s).unwrap();
/// assert_eq!(hms(3, 5, 7).checked_add_signed(TimeDelta::days(1_000_000_000).unwrap()), None);
/// # use chrono::{Error, NaiveDate, TimeDelta};
/// # let d = NaiveDate::from_ymd(2016, 7, 8)?;
/// # let hms = |h, m, s| d.and_hms(h, m, s);
/// assert_eq!(
/// hms(3, 5, 7)?.checked_add_signed(TimeDelta::days(1_000_000_000).unwrap()),
/// Err(Error::OutOfRange)
/// );
/// # Ok::<(), Error>(())
/// ```
///
/// Leap seconds are handled,
/// but the addition assumes that it is the only leap second happened.
///
/// ```
/// # use chrono::{TimeDelta, NaiveDate};
/// # let from_ymd = |y, m, d| NaiveDate::from_ymd(y, m, d).unwrap();
/// # let hmsm = |h, m, s, milli| from_ymd(2016, 7, 8).and_hms_milli(h, m, s, milli).unwrap();
/// let leap = hmsm(3, 5, 59, 1_300);
/// assert_eq!(leap.checked_add_signed(TimeDelta::zero()),
/// Some(hmsm(3, 5, 59, 1_300)));
/// assert_eq!(leap.checked_add_signed(TimeDelta::milliseconds(-500).unwrap()),
/// Some(hmsm(3, 5, 59, 800)));
/// assert_eq!(leap.checked_add_signed(TimeDelta::milliseconds(500).unwrap()),
/// Some(hmsm(3, 5, 59, 1_800)));
/// assert_eq!(leap.checked_add_signed(TimeDelta::milliseconds(800).unwrap()),
/// Some(hmsm(3, 6, 0, 100)));
/// assert_eq!(leap.checked_add_signed(TimeDelta::seconds(10).unwrap()),
/// Some(hmsm(3, 6, 9, 300)));
/// assert_eq!(leap.checked_add_signed(TimeDelta::seconds(-10).unwrap()),
/// Some(hmsm(3, 5, 50, 300)));
/// assert_eq!(leap.checked_add_signed(TimeDelta::days(1).unwrap()),
/// Some(from_ymd(2016, 7, 9).and_hms_milli(3, 5, 59, 300).unwrap()));
/// # use chrono::{NaiveDate, TimeDelta};
/// # let d = NaiveDate::from_ymd(2016, 7, 8)?;
/// # let hmsm = |h, m, s, milli| d.and_hms_milli(h, m, s, milli);
/// let leap = hmsm(3, 5, 59, 1_300)?;
/// assert_eq!(leap.checked_add_signed(TimeDelta::zero()), hmsm(3, 5, 59, 1_300));
/// assert_eq!(
/// leap.checked_add_signed(TimeDelta::milliseconds(-500).unwrap()),
/// hmsm(3, 5, 59, 800)
/// );
/// assert_eq!(
/// leap.checked_add_signed(TimeDelta::milliseconds(500).unwrap()),
/// hmsm(3, 5, 59, 1_800)
/// );
/// assert_eq!(
/// leap.checked_add_signed(TimeDelta::milliseconds(800).unwrap()),
/// hmsm(3, 6, 0, 100)
/// );
/// assert_eq!(
/// leap.checked_add_signed(TimeDelta::seconds(10).unwrap()),
/// hmsm(3, 6, 9, 300)
/// );
/// assert_eq!(
/// leap.checked_add_signed(TimeDelta::seconds(-10).unwrap()),
/// hmsm(3, 5, 50, 300)
/// );
/// assert_eq!(
/// leap.checked_add_signed(TimeDelta::days(1).unwrap()),
/// NaiveDate::from_ymd(2016, 7, 9)?.and_hms_milli(3, 5, 59, 300)
/// );
/// # Ok::<(), chrono::Error>(())
/// ```
#[must_use]
pub const fn checked_add_signed(self, rhs: TimeDelta) -> Option<NaiveDateTime> {
pub const fn checked_add_signed(self, rhs: TimeDelta) -> Result<NaiveDateTime, Error> {
let (time, remainder) = self.time.overflowing_add_signed(rhs);
let remainder = try_opt!(TimeDelta::seconds(remainder));
let date = try_opt!(ok!(self.date.checked_add_signed(remainder)));
Some(NaiveDateTime { date, time })
let remainder = try_ok_or!(TimeDelta::seconds(remainder), Error::OutOfRange);
let date = try_err!(self.date.checked_add_signed(remainder));
Ok(NaiveDateTime { date, time })
}

/// Adds given `Months` to the current date and time.
Expand Down Expand Up @@ -423,72 +436,84 @@ impl NaiveDateTime {
///
/// # Errors
///
/// Returns `None` if the resulting date would be out of range.
/// Returns `[`Error::OutOfRange`] if the resulting date would be out of range.
///
/// # Example
///
/// ```
/// use chrono::{NaiveDate, TimeDelta};
///
/// let from_ymd = |y, m, d| NaiveDate::from_ymd(y, m, d).unwrap();
///
/// let d = from_ymd(2016, 7, 8);
/// let hms = |h, m, s| d.and_hms(h, m, s).unwrap();
/// assert_eq!(hms(3, 5, 7).checked_sub_signed(TimeDelta::zero()), Some(hms(3, 5, 7)));
/// assert_eq!(hms(3, 5, 7).checked_sub_signed(TimeDelta::seconds(1).unwrap()), Some(hms(3, 5, 6)));
/// let d = NaiveDate::from_ymd(2016, 7, 8)?;
/// let hms = |h, m, s| d.and_hms(h, m, s);
/// assert_eq!(hms(3, 5, 7)?.checked_sub_signed(TimeDelta::zero()), hms(3, 5, 7));
/// assert_eq!(hms(3, 5, 7)?.checked_sub_signed(TimeDelta::seconds(1).unwrap()), hms(3, 5, 6));
/// assert_eq!(
/// hms(3, 5, 7).checked_sub_signed(TimeDelta::seconds(-1).unwrap()),
/// Some(hms(3, 5, 8))
/// hms(3, 5, 7)?.checked_sub_signed(TimeDelta::seconds(-1).unwrap()),
/// hms(3, 5, 8)
/// );
/// assert_eq!(
/// hms(3, 5, 7).checked_sub_signed(TimeDelta::seconds(3600 + 60).unwrap()),
/// Some(hms(2, 4, 7))
/// hms(3, 5, 7)?.checked_sub_signed(TimeDelta::seconds(3600 + 60).unwrap()),
/// hms(2, 4, 7)
/// );
/// assert_eq!(
/// hms(3, 5, 7).checked_sub_signed(TimeDelta::seconds(86_400).unwrap()),
/// Some(from_ymd(2016, 7, 7).and_hms(3, 5, 7).unwrap())
/// hms(3, 5, 7)?.checked_sub_signed(TimeDelta::seconds(86_400).unwrap()),
/// NaiveDate::from_ymd(2016, 7, 7)?.and_hms(3, 5, 7)
/// );
///
/// let hmsm = |h, m, s, milli| d.and_hms_milli(h, m, s, milli).unwrap();
/// let hmsm = |h, m, s, milli| d.and_hms_milli(h, m, s, milli);
/// assert_eq!(
/// hmsm(3, 5, 7, 450).checked_sub_signed(TimeDelta::milliseconds(670).unwrap()),
/// Some(hmsm(3, 5, 6, 780))
/// hmsm(3, 5, 7, 450)?.checked_sub_signed(TimeDelta::milliseconds(670).unwrap()),
/// hmsm(3, 5, 6, 780)
/// );
/// # Ok::<(), chrono::Error>(())
/// ```
///
/// Overflow returns `None`.
/// Overflow returns [`Error::OutOfRange`].
///
/// ```
/// # use chrono::{TimeDelta, NaiveDate};
/// # let hms = |h, m, s| NaiveDate::from_ymd(2016, 7, 8).unwrap().and_hms(h, m, s).unwrap();
/// assert_eq!(hms(3, 5, 7).checked_sub_signed(TimeDelta::days(1_000_000_000).unwrap()), None);
/// # use chrono::{Error, NaiveDate, TimeDelta};
/// # let d = NaiveDate::from_ymd(2016, 7, 8)?;
/// # let hms = |h, m, s| d.and_hms(h, m, s);
/// assert_eq!(
/// hms(3, 5, 7)?.checked_sub_signed(TimeDelta::days(1_000_000_000).unwrap()),
/// Err(Error::OutOfRange)
/// );
/// # Ok::<(), Error>(())
/// ```
///
/// Leap seconds are handled,
/// but the subtraction assumes that it is the only leap second happened.
///
/// ```
/// # use chrono::{TimeDelta, NaiveDate};
/// # let from_ymd = |y, m, d| NaiveDate::from_ymd(y, m, d).unwrap();
/// # let hmsm = |h, m, s, milli| from_ymd(2016, 7, 8).and_hms_milli(h, m, s, milli).unwrap();
/// let leap = hmsm(3, 5, 59, 1_300);
/// assert_eq!(leap.checked_sub_signed(TimeDelta::zero()),
/// Some(hmsm(3, 5, 59, 1_300)));
/// assert_eq!(leap.checked_sub_signed(TimeDelta::milliseconds(200).unwrap()),
/// Some(hmsm(3, 5, 59, 1_100)));
/// assert_eq!(leap.checked_sub_signed(TimeDelta::milliseconds(500).unwrap()),
/// Some(hmsm(3, 5, 59, 800)));
/// assert_eq!(leap.checked_sub_signed(TimeDelta::seconds(60).unwrap()),
/// Some(hmsm(3, 5, 0, 300)));
/// assert_eq!(leap.checked_sub_signed(TimeDelta::days(1).unwrap()),
/// Some(from_ymd(2016, 7, 7).and_hms_milli(3, 6, 0, 300).unwrap()));
/// # use chrono::{NaiveDate, TimeDelta};
/// # let d = NaiveDate::from_ymd(2016, 7, 8)?;
/// # let hmsm = |h, m, s, milli| d.and_hms_milli(h, m, s, milli);
/// let leap = hmsm(3, 5, 59, 1_300)?;
/// assert_eq!(leap.checked_sub_signed(TimeDelta::zero()), hmsm(3, 5, 59, 1_300));
/// assert_eq!(
/// leap.checked_sub_signed(TimeDelta::milliseconds(200).unwrap()),
/// hmsm(3, 5, 59, 1_100)
/// );
/// assert_eq!(
/// leap.checked_sub_signed(TimeDelta::milliseconds(500).unwrap()),
/// hmsm(3, 5, 59, 800)
/// );
/// assert_eq!(
/// leap.checked_sub_signed(TimeDelta::seconds(60).unwrap()),
/// hmsm(3, 5, 0, 300)
/// );
/// assert_eq!(
/// leap.checked_sub_signed(TimeDelta::days(1).unwrap()),
/// NaiveDate::from_ymd(2016, 7, 7)?.and_hms_milli(3, 6, 0, 300)
/// );
/// # Ok::<(), chrono::Error>(())
/// ```
#[must_use]
pub const fn checked_sub_signed(self, rhs: TimeDelta) -> Option<NaiveDateTime> {
pub const fn checked_sub_signed(self, rhs: TimeDelta) -> Result<NaiveDateTime, Error> {
let (time, remainder) = self.time.overflowing_sub_signed(rhs);
let remainder = try_opt!(TimeDelta::seconds(remainder));
let date = try_opt!(ok!(self.date.checked_sub_signed(remainder)));
Some(NaiveDateTime { date, time })
let remainder = try_ok_or!(TimeDelta::seconds(remainder), Error::OutOfRange);
let date = try_err!(self.date.checked_sub_signed(remainder));
Ok(NaiveDateTime { date, time })
}

/// Subtracts given `Months` from the current date and time.
Expand Down
50 changes: 26 additions & 24 deletions src/naive/datetime/tests.rs
Original file line number Diff line number Diff line change
@@ -1,48 +1,50 @@
use super::NaiveDateTime;
use crate::{Datelike, FixedOffset, LocalResult, NaiveDate, TimeDelta, Utc};
use crate::{Datelike, Error, FixedOffset, LocalResult, NaiveDate, TimeDelta, Utc};

#[test]
fn test_datetime_add() {
fn test_datetime_add() -> Result<(), Error> {
fn check(
(y, m, d, h, n, s): (i32, u32, u32, u32, u32, u32),
rhs: TimeDelta,
result: Option<(i32, u32, u32, u32, u32, u32)>,
) {
let lhs = NaiveDate::from_ymd(y, m, d).unwrap().and_hms(h, n, s).unwrap();
let sum = result.map(|(y, m, d, h, n, s)| {
NaiveDate::from_ymd(y, m, d).unwrap().and_hms(h, n, s).unwrap()
});
result: Result<(i32, u32, u32, u32, u32, u32), Error>,
) -> Result<(), Error> {
let lhs = NaiveDate::from_ymd(y, m, d)?.and_hms(h, n, s)?;
let sum = match result {
Ok((y, m, d, h, n, s)) => NaiveDate::from_ymd(y, m, d)?.and_hms(h, n, s),
Err(e) => Err(e),
};
assert_eq!(lhs.checked_add_signed(rhs), sum);
assert_eq!(lhs.checked_sub_signed(-rhs), sum);
Ok(())
}
let seconds = |s| TimeDelta::seconds(s).unwrap();

check((2014, 5, 6, 7, 8, 9), seconds(3600 + 60 + 1), Some((2014, 5, 6, 8, 9, 10)));
check((2014, 5, 6, 7, 8, 9), seconds(-(3600 + 60 + 1)), Some((2014, 5, 6, 6, 7, 8)));
check((2014, 5, 6, 7, 8, 9), seconds(86399), Some((2014, 5, 7, 7, 8, 8)));
check((2014, 5, 6, 7, 8, 9), seconds(86_400 * 10), Some((2014, 5, 16, 7, 8, 9)));
check((2014, 5, 6, 7, 8, 9), seconds(-86_400 * 10), Some((2014, 4, 26, 7, 8, 9)));
check((2014, 5, 6, 7, 8, 9), seconds(86_400 * 10), Some((2014, 5, 16, 7, 8, 9)));
check((2014, 5, 6, 7, 8, 9), seconds(3600 + 60 + 1), Ok((2014, 5, 6, 8, 9, 10)))?;
check((2014, 5, 6, 7, 8, 9), seconds(-(3600 + 60 + 1)), Ok((2014, 5, 6, 6, 7, 8)))?;
check((2014, 5, 6, 7, 8, 9), seconds(86399), Ok((2014, 5, 7, 7, 8, 8)))?;
check((2014, 5, 6, 7, 8, 9), seconds(86_400 * 10), Ok((2014, 5, 16, 7, 8, 9)))?;
check((2014, 5, 6, 7, 8, 9), seconds(-86_400 * 10), Ok((2014, 4, 26, 7, 8, 9)))?;
check((2014, 5, 6, 7, 8, 9), seconds(86_400 * 10), Ok((2014, 5, 16, 7, 8, 9)))?;

// overflow check
// assumes that we have correct values for MAX/MIN_DAYS_FROM_YEAR_0 from `naive::date`.
// (they are private constants, but the equivalence is tested in that module.)
let max_days_from_year_0 =
NaiveDate::MAX.signed_duration_since(NaiveDate::from_ymd(0, 1, 1).unwrap());
check((0, 1, 1, 0, 0, 0), max_days_from_year_0, Some((NaiveDate::MAX.year(), 12, 31, 0, 0, 0)));
let max_days_from_year_0 = NaiveDate::MAX.signed_duration_since(NaiveDate::from_ymd(0, 1, 1)?);
check((0, 1, 1, 0, 0, 0), max_days_from_year_0, Ok((NaiveDate::MAX.year(), 12, 31, 0, 0, 0)))?;
check(
(0, 1, 1, 0, 0, 0),
max_days_from_year_0 + seconds(86399),
Some((NaiveDate::MAX.year(), 12, 31, 23, 59, 59)),
);
check((0, 1, 1, 0, 0, 0), max_days_from_year_0 + seconds(86_400), None);
check((0, 1, 1, 0, 0, 0), TimeDelta::max_value(), None);
Ok((NaiveDate::MAX.year(), 12, 31, 23, 59, 59)),
)?;
check((0, 1, 1, 0, 0, 0), max_days_from_year_0 + seconds(86_400), Err(Error::OutOfRange))?;
check((0, 1, 1, 0, 0, 0), TimeDelta::max_value(), Err(Error::OutOfRange))?;

let min_days_from_year_0 =
NaiveDate::MIN.signed_duration_since(NaiveDate::from_ymd(0, 1, 1).unwrap());
check((0, 1, 1, 0, 0, 0), min_days_from_year_0, Some((NaiveDate::MIN.year(), 1, 1, 0, 0, 0)));
check((0, 1, 1, 0, 0, 0), min_days_from_year_0 - seconds(1), None);
check((0, 1, 1, 0, 0, 0), TimeDelta::min_value(), None);
check((0, 1, 1, 0, 0, 0), min_days_from_year_0, Ok((NaiveDate::MIN.year(), 1, 1, 0, 0, 0)))?;
check((0, 1, 1, 0, 0, 0), min_days_from_year_0 - seconds(1), Err(Error::OutOfRange))?;
check((0, 1, 1, 0, 0, 0), TimeDelta::min_value(), Err(Error::OutOfRange))?;
Ok(())
}

#[test]
Expand Down

0 comments on commit c904446

Please sign in to comment.