Skip to content

Commit

Permalink
Add NaiveDate::diff_months_days
Browse files Browse the repository at this point in the history
  • Loading branch information
pitdicker committed Sep 2, 2023
1 parent b59b34d commit 6dbdf56
Showing 1 changed file with 73 additions and 0 deletions.
73 changes: 73 additions & 0 deletions src/naive/date.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1427,6 +1427,79 @@ impl NaiveDate {
self.ymdf & (0b1000) == 0
}

/// Difference between two dates expressed as whole months and the remaining days.
/// This method takes into account the correct number of days of each passing month.
///
/// The order of the arguments is (`self`, `other`), not (first, last).
/// `self` can be seen as the reference date.
/// - When *counting down* from the reference date to `other`, the number of remaining days may
/// depend on the number of days in the first month.
/// - When *counting the elapsed period* from `self` to `other`, the number of remaining days
/// may depend on the number of days in the last month before `other`.
///
/// Returns `(months, days)`.
///
/// ```
/// # use chrono::NaiveDate;
/// let ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap();
///
/// // Elapsed months and days since a reference date:
/// assert_eq!(ymd(2022, 2, 27).diff_months_days(ymd(2022, 5, 26)), (2, 29));
/// assert_eq!(ymd(2022, 2, 27).diff_months_days(ymd(2022, 5, 27)), (3, 0));
/// assert_eq!(ymd(2022, 2, 27).diff_months_days(ymd(2022, 5, 28)), (3, 1));
/// assert_eq!(ymd(2022, 2, 27).diff_months_days(ymd(2022, 5, 29)), (3, 2));
/// assert_eq!(ymd(2022, 2, 27).diff_months_days(ymd(2022, 5, 30)), (3, 3));
/// assert_eq!(ymd(2022, 2, 27).diff_months_days(ymd(2022, 5, 31)), (3, 4));
/// assert_eq!(ymd(2022, 2, 27).diff_months_days(ymd(2022, 6, 1)), (3, 5));
/// assert_eq!(ymd(2022, 2, 27).diff_months_days(ymd(2022, 6, 2)), (3, 6));
///
/// // Remaining months and days until the reference date:
/// assert_eq!(ymd(2022, 6, 2).diff_months_days(ymd(2022, 2, 27)), (3, 3)); // <- 3 days less!
/// assert_eq!(ymd(2022, 6, 2).diff_months_days(ymd(2022, 2, 28)), (3, 2));
/// assert_eq!(ymd(2022, 6, 2).diff_months_days(ymd(2022, 3, 1)), (3, 1));
/// assert_eq!(ymd(2022, 6, 2).diff_months_days(ymd(2022, 3, 2)), (3, 0));
/// assert_eq!(ymd(2022, 6, 2).diff_months_days(ymd(2022, 3, 3)), (2, 30));
/// ```
pub const fn diff_months_days(self: NaiveDate, other: NaiveDate) -> (u32, u32) {
const fn days_in_month(month: u32, leap_year: bool) -> u32 {
match month {
0 => 31, // december
1 => 31, // januari
2 => 28 + leap_year as u32,
3 => 31,
4 => 30,
5 => 31,
6 => 30,
7 => 31,
8 => 31,
9 => 30,
10 => 31,
11 => 30,
12 => 31, // december
_ => panic!("invalid month number"),
}
}

let (first, last) = if self.ymdf <= other.ymdf { (self, other) } else { (other, self) };

let mut months = 12 * (last.year() - first.year()) as u32 + last.month() - first.month();
let days = if first.day() <= last.day() {
last.day() - first.day()
} else {
months -= 1;
if other.ymdf < self.ymdf {
let days_remaining_in_first_month =
days_in_month(first.month(), self.leap_year()) - first.day();
days_remaining_in_first_month + last.day()
} else {
let days_remaining_in_forelast_month =
days_in_month(last.month() - 1, self.leap_year()) - first.day();
days_remaining_in_forelast_month + last.day()
}
};
(months, days)
}

// This duplicates `Datelike::year()`, because trait methods can't be const yet.
#[inline]
const fn year(&self) -> i32 {
Expand Down

0 comments on commit 6dbdf56

Please sign in to comment.