From 1fc6d8b821dfe71ce916b12fe41c62bb806e4717 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sat, 6 May 2023 18:24:15 +0200 Subject: [PATCH] Add `NaiveDate::diff_months_days` --- src/naive/date.rs | 73 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/src/naive/date.rs b/src/naive/date.rs index 53d698e3b1..f5ed021a27 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -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 {