Skip to content

Commit

Permalink
Convert NaiveDate::checked_(add|add)_months to return Result
Browse files Browse the repository at this point in the history
  • Loading branch information
pitdicker committed Mar 22, 2024
1 parent 21ee9b7 commit 45cad27
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 89 deletions.
48 changes: 25 additions & 23 deletions src/naive/date/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ use crate::format::{
};
use crate::month::Months;
use crate::naive::{Days, IsoWeek, NaiveDateTime, NaiveTime, NaiveWeek};
use crate::{ok, try_err, try_ok_or, try_opt};
use crate::{try_err, try_ok_or};
use crate::{Datelike, Error, TimeDelta, Weekday};

use super::internals::{Mdf, YearFlags};
Expand Down Expand Up @@ -449,30 +449,31 @@ impl NaiveDate {
///
/// # 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, Months};
/// assert_eq!(
/// NaiveDate::from_ymd(2022, 2, 20).unwrap().checked_add_months(Months::new(6)),
/// Some(NaiveDate::from_ymd(2022, 8, 20).unwrap())
/// NaiveDate::from_ymd(2022, 2, 20)?.checked_add_months(Months::new(6)),
/// NaiveDate::from_ymd(2022, 8, 20)
/// );
/// assert_eq!(
/// NaiveDate::from_ymd(2022, 7, 31).unwrap().checked_add_months(Months::new(2)),
/// Some(NaiveDate::from_ymd(2022, 9, 30).unwrap())
/// NaiveDate::from_ymd(2022, 7, 31)?.checked_add_months(Months::new(2)),
/// NaiveDate::from_ymd(2022, 9, 30)
/// );
/// # Ok::<(), chrono::Error>(())
/// ```
#[must_use]
pub const fn checked_add_months(self, months: Months) -> Option<Self> {
pub const fn checked_add_months(self, months: Months) -> Result<Self, Error> {
if months.0 == 0 {
return Some(self);
return Ok(self);
}

match months.0 <= i32::MAX as u32 {
true => self.diff_months(months.0 as i32),
false => None,
false => Err(Error::OutOfRange),
}
}

Expand All @@ -482,38 +483,39 @@ impl NaiveDate {
///
/// # 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, Months};
/// assert_eq!(
/// NaiveDate::from_ymd(2022, 2, 20).unwrap().checked_sub_months(Months::new(6)),
/// Some(NaiveDate::from_ymd(2021, 8, 20).unwrap())
/// NaiveDate::from_ymd(2022, 2, 20)?.checked_sub_months(Months::new(6)),
/// NaiveDate::from_ymd(2021, 8, 20)
/// );
///
/// assert_eq!(
/// NaiveDate::from_ymd(2014, 1, 1)
/// .unwrap()
/// .checked_sub_months(Months::new(core::i32::MAX as u32 + 1)),
/// None
/// NaiveDate::from_ymd(2022, 7, 31)?.checked_sub_months(Months::new(3)),
/// NaiveDate::from_ymd(2022, 4, 30)
/// );
/// # Ok::<(), chrono::Error>(())
/// ```
#[must_use]
pub const fn checked_sub_months(self, months: Months) -> Option<Self> {
pub const fn checked_sub_months(self, months: Months) -> Result<Self, Error> {
if months.0 == 0 {
return Some(self);
return Ok(self);
}

match months.0 <= i32::MAX as u32 {
true => self.diff_months(-(months.0 as i32)),
false => None,
false => Err(Error::OutOfRange),
}
}

const fn diff_months(self, months: i32) -> Option<Self> {
let months = try_opt!((self.year() * 12 + self.month() as i32 - 1).checked_add(months));
const fn diff_months(self, months: i32) -> Result<Self, Error> {
let months = try_ok_or!(
(self.year() * 12 + self.month() as i32 - 1).checked_add(months),
Error::OutOfRange
);
let year = months.div_euclid(12);
let month = months.rem_euclid(12) as u32 + 1;

Expand All @@ -527,7 +529,7 @@ impl NaiveDate {
day = day_max;
};

ok!(NaiveDate::from_ymd(year, month, day))
NaiveDate::from_ymd(year, month, day)
}

/// Add a duration in [`Days`] to the date
Expand Down
83 changes: 19 additions & 64 deletions src/naive/date/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,88 +40,43 @@ fn test_date_bounds() {
}

#[test]
fn diff_months() {
fn diff_months() -> Result<(), Error> {
let ymd = NaiveDate::from_ymd;
// identity
assert_eq!(
NaiveDate::from_ymd(2022, 8, 3).unwrap().checked_add_months(Months::new(0)),
Some(NaiveDate::from_ymd(2022, 8, 3).unwrap())
);

assert_eq!(ymd(2022, 8, 3)?.checked_add_months(Months::new(0)), ymd(2022, 8, 3));
// add with months exceeding `i32::MAX`
assert_eq!(
NaiveDate::from_ymd(2022, 8, 3)
.unwrap()
.checked_add_months(Months::new(i32::MAX as u32 + 1)),
None
ymd(2022, 8, 3)?.checked_add_months(Months::new(i32::MAX as u32 + 1)),
Err(Error::OutOfRange)
);

// sub with months exceeding `i32::MIN`
assert_eq!(
NaiveDate::from_ymd(2022, 8, 3)
.unwrap()
.checked_sub_months(Months::new(i32::MIN.unsigned_abs() + 1)),
None
ymd(2022, 8, 3)?.checked_sub_months(Months::new(i32::MIN.unsigned_abs() + 1)),
Err(Error::OutOfRange)
);

// add overflowing year
assert_eq!(NaiveDate::MAX.checked_add_months(Months::new(1)), None);

assert_eq!(NaiveDate::MAX.checked_add_months(Months::new(1)), Err(Error::OutOfRange));
// add underflowing year
assert_eq!(NaiveDate::MIN.checked_sub_months(Months::new(1)), None);

assert_eq!(NaiveDate::MIN.checked_sub_months(Months::new(1)), Err(Error::OutOfRange));
// sub crossing year 0 boundary
assert_eq!(
NaiveDate::from_ymd(2022, 8, 3).unwrap().checked_sub_months(Months::new(2050 * 12)),
Some(NaiveDate::from_ymd(-28, 8, 3).unwrap())
);

assert_eq!(ymd(2022, 8, 3)?.checked_sub_months(Months::new(2050 * 12)), ymd(-28, 8, 3));
// add crossing year boundary
assert_eq!(
NaiveDate::from_ymd(2022, 8, 3).unwrap().checked_add_months(Months::new(6)),
Some(NaiveDate::from_ymd(2023, 2, 3).unwrap())
);

assert_eq!(ymd(2022, 8, 3)?.checked_add_months(Months::new(6)), ymd(2023, 2, 3));
// sub crossing year boundary
assert_eq!(
NaiveDate::from_ymd(2022, 8, 3).unwrap().checked_sub_months(Months::new(10)),
Some(NaiveDate::from_ymd(2021, 10, 3).unwrap())
);

assert_eq!(ymd(2022, 8, 3)?.checked_sub_months(Months::new(10)), ymd(2021, 10, 3));
// add clamping day, non-leap year
assert_eq!(
NaiveDate::from_ymd(2022, 1, 29).unwrap().checked_add_months(Months::new(1)),
Some(NaiveDate::from_ymd(2022, 2, 28).unwrap())
);

assert_eq!(ymd(2022, 1, 29)?.checked_add_months(Months::new(1)), ymd(2022, 2, 28));
// add to leap day
assert_eq!(
NaiveDate::from_ymd(2022, 10, 29).unwrap().checked_add_months(Months::new(16)),
Some(NaiveDate::from_ymd(2024, 2, 29).unwrap())
);

assert_eq!(ymd(2022, 10, 29)?.checked_add_months(Months::new(16)), ymd(2024, 2, 29));
// add into december
assert_eq!(
NaiveDate::from_ymd(2022, 10, 31).unwrap().checked_add_months(Months::new(2)),
Some(NaiveDate::from_ymd(2022, 12, 31).unwrap())
);

assert_eq!(ymd(2022, 10, 31)?.checked_add_months(Months::new(2)), ymd(2022, 12, 31));
// sub into december
assert_eq!(
NaiveDate::from_ymd(2022, 10, 31).unwrap().checked_sub_months(Months::new(10)),
Some(NaiveDate::from_ymd(2021, 12, 31).unwrap())
);

assert_eq!(ymd(2022, 10, 31)?.checked_sub_months(Months::new(10)), ymd(2021, 12, 31));
// add into january
assert_eq!(
NaiveDate::from_ymd(2022, 8, 3).unwrap().checked_add_months(Months::new(5)),
Some(NaiveDate::from_ymd(2023, 1, 3).unwrap())
);

assert_eq!(ymd(2022, 8, 3)?.checked_add_months(Months::new(5)), ymd(2023, 1, 3));
// sub into january
assert_eq!(
NaiveDate::from_ymd(2022, 8, 3).unwrap().checked_sub_months(Months::new(7)),
Some(NaiveDate::from_ymd(2022, 1, 3).unwrap())
);
assert_eq!(ymd(2022, 8, 3)?.checked_sub_months(Months::new(7)), ymd(2022, 1, 3));
Ok(())
}

#[test]
Expand Down
4 changes: 2 additions & 2 deletions src/naive/datetime/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,7 @@ impl NaiveDateTime {
/// ```
#[must_use]
pub const fn checked_add_months(self, rhs: Months) -> Option<NaiveDateTime> {
Some(Self { date: try_opt!(self.date.checked_add_months(rhs)), time: self.time })
Some(Self { date: try_opt!(ok!(self.date.checked_add_months(rhs))), time: self.time })
}

/// Adds given `FixedOffset` to the current datetime.
Expand Down Expand Up @@ -530,7 +530,7 @@ impl NaiveDateTime {
/// ```
#[must_use]
pub const fn checked_sub_months(self, rhs: Months) -> Option<NaiveDateTime> {
Some(Self { date: try_opt!(self.date.checked_sub_months(rhs)), time: self.time })
Some(Self { date: try_opt!(ok!(self.date.checked_sub_months(rhs))), time: self.time })
}

/// Add a duration in [`Days`] to the date part of the `NaiveDateTime`
Expand Down

0 comments on commit 45cad27

Please sign in to comment.