mirror of
https://github.com/chronotope/chrono.git
synced 2025-09-26 20:40:51 +00:00
Implement Add<Months>
and Sub<Months>
for NaiveDate
(#731)
* Add add_months() * Months struct with Add and Sub impls * Update changelog
This commit is contained in:
parent
782f904375
commit
ab688c384f
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,3 +1,6 @@
|
|||||||
target
|
target
|
||||||
Cargo.lock
|
Cargo.lock
|
||||||
.tool-versions
|
.tool-versions
|
||||||
|
|
||||||
|
# for jetbrains users
|
||||||
|
.idea/
|
||||||
|
@ -36,6 +36,7 @@ Versions with only mechanical changes will be omitted from the following list.
|
|||||||
* Fix the behavior of `Duration::abs()` for negative durations with non-zero nanos
|
* Fix the behavior of `Duration::abs()` for negative durations with non-zero nanos
|
||||||
* Add compatibility with rfc2822 comments (#733)
|
* Add compatibility with rfc2822 comments (#733)
|
||||||
* Make `js-sys` and `wasm-bindgen` enabled by default when target is `wasm32-unknown-unknown` for ease of API discovery
|
* Make `js-sys` and `wasm-bindgen` enabled by default when target is `wasm32-unknown-unknown` for ease of API discovery
|
||||||
|
* Add the `Months` struct and associated `Add` and `Sub` impls
|
||||||
|
|
||||||
## 0.4.19
|
## 0.4.19
|
||||||
|
|
||||||
|
@ -507,7 +507,7 @@ mod weekday;
|
|||||||
pub use weekday::{ParseWeekdayError, Weekday};
|
pub use weekday::{ParseWeekdayError, Weekday};
|
||||||
|
|
||||||
mod month;
|
mod month;
|
||||||
pub use month::{Month, ParseMonthError};
|
pub use month::{Month, Months, ParseMonthError};
|
||||||
|
|
||||||
mod traits;
|
mod traits;
|
||||||
pub use traits::{Datelike, Timelike};
|
pub use traits::{Datelike, Timelike};
|
||||||
|
@ -189,6 +189,10 @@ impl num_traits::FromPrimitive for Month {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A duration in calendar months
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct Months(pub usize);
|
||||||
|
|
||||||
/// An error resulting from reading `<Month>` value with `FromStr`.
|
/// An error resulting from reading `<Month>` value with `FromStr`.
|
||||||
#[derive(Clone, PartialEq)]
|
#[derive(Clone, PartialEq)]
|
||||||
pub struct ParseMonthError {
|
pub struct ParseMonthError {
|
||||||
|
@ -17,6 +17,7 @@ use rkyv::{Archive, Deserialize, Serialize};
|
|||||||
use crate::format::DelayedFormat;
|
use crate::format::DelayedFormat;
|
||||||
use crate::format::{parse, ParseError, ParseResult, Parsed, StrftimeItems};
|
use crate::format::{parse, ParseError, ParseResult, Parsed, StrftimeItems};
|
||||||
use crate::format::{Item, Numeric, Pad};
|
use crate::format::{Item, Numeric, Pad};
|
||||||
|
use crate::month::Months;
|
||||||
use crate::naive::{IsoWeek, NaiveDateTime, NaiveTime};
|
use crate::naive::{IsoWeek, NaiveDateTime, NaiveTime};
|
||||||
use crate::oldtime::Duration as OldDuration;
|
use crate::oldtime::Duration as OldDuration;
|
||||||
use crate::{Datelike, Duration, Weekday};
|
use crate::{Datelike, Duration, Weekday};
|
||||||
@ -596,6 +597,33 @@ impl NaiveDate {
|
|||||||
parsed.to_naive_date()
|
parsed.to_naive_date()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Private function to calculate necessary primitives for `Add<Month>`
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `delta` - Number of months (+/-) to add
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// A new NaiveDate on the first day of the resulting year & month
|
||||||
|
fn add_months_get_first_day(&self, delta: i32) -> NaiveDate {
|
||||||
|
let zeroed_months = self.month() as i32 - 1; // zero-based for modulo operations
|
||||||
|
let res_months = zeroed_months + delta;
|
||||||
|
let delta_years = if res_months < 0 {
|
||||||
|
if (-res_months) % 12 > 0 {
|
||||||
|
res_months / 12 - 1
|
||||||
|
} else {
|
||||||
|
res_months / 12
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
res_months / 12
|
||||||
|
};
|
||||||
|
let res_years = self.year() + delta_years;
|
||||||
|
let res_months = res_months % 12;
|
||||||
|
let res_months = if res_months < 0 { res_months + 12 } else { res_months };
|
||||||
|
NaiveDate::from_ymd(res_years, res_months as u32 + 1, 1)
|
||||||
|
}
|
||||||
|
|
||||||
/// Makes a new `NaiveDateTime` from the current date and given `NaiveTime`.
|
/// Makes a new `NaiveDateTime` from the current date and given `NaiveTime`.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
@ -1561,6 +1589,59 @@ impl AddAssign<OldDuration> for NaiveDate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Add<Months> for NaiveDate {
|
||||||
|
type Output = NaiveDate;
|
||||||
|
|
||||||
|
/// An addition of months to `NaiveDate` clamped to valid days in resulting month.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use chrono::{Duration, NaiveDate, Months};
|
||||||
|
///
|
||||||
|
/// let from_ymd = NaiveDate::from_ymd;
|
||||||
|
///
|
||||||
|
/// assert_eq!(from_ymd(2014, 1, 1) + Months(1), from_ymd(2014, 2, 1));
|
||||||
|
/// assert_eq!(from_ymd(2014, 1, 1) + Months(11), from_ymd(2014, 12, 1));
|
||||||
|
/// assert_eq!(from_ymd(2014, 1, 1) + Months(12), from_ymd(2015, 1, 1));
|
||||||
|
/// assert_eq!(from_ymd(2014, 1, 1) + Months(13), from_ymd(2015, 2, 1));
|
||||||
|
/// assert_eq!(from_ymd(2014, 1, 31) + Months(1), from_ymd(2014, 2, 28));
|
||||||
|
/// assert_eq!(from_ymd(2020, 1, 31) + Months(1), from_ymd(2020, 2, 29));
|
||||||
|
/// ```
|
||||||
|
fn add(self, months: Months) -> Self::Output {
|
||||||
|
let target = self.add_months_get_first_day(months.0 as i32);
|
||||||
|
let target_plus = target.add_months_get_first_day(1);
|
||||||
|
let last_day = target_plus.sub(Duration::days(1));
|
||||||
|
let day = core::cmp::min(self.day(), last_day.day());
|
||||||
|
NaiveDate::from_ymd(target.year(), target.month(), day)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sub<Months> for NaiveDate {
|
||||||
|
type Output = NaiveDate;
|
||||||
|
|
||||||
|
/// A subtraction of Months from `NaiveDate` clamped to valid days in resulting month.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use chrono::{Duration, NaiveDate, Months};
|
||||||
|
///
|
||||||
|
/// let from_ymd = NaiveDate::from_ymd;
|
||||||
|
///
|
||||||
|
/// assert_eq!(from_ymd(2014, 1, 1) - Months(11), from_ymd(2013, 2, 1));
|
||||||
|
/// assert_eq!(from_ymd(2014, 1, 1) - Months(12), from_ymd(2013, 1, 1));
|
||||||
|
/// assert_eq!(from_ymd(2014, 1, 1) - Months(13), from_ymd(2012, 12, 1));
|
||||||
|
/// ```
|
||||||
|
fn sub(self, months: Months) -> Self::Output {
|
||||||
|
let target = self.add_months_get_first_day(-(months.0 as i32));
|
||||||
|
let target_plus = target.add_months_get_first_day(1);
|
||||||
|
let last_day = target_plus.sub(Duration::days(1));
|
||||||
|
let day = core::cmp::min(self.day(), last_day.day());
|
||||||
|
NaiveDate::from_ymd(target.year(), target.month(), day)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A subtraction of `Duration` from `NaiveDate` discards the fractional days,
|
/// A subtraction of `Duration` from `NaiveDate` discards the fractional days,
|
||||||
/// rounding to the closest integral number of days towards `Duration::zero()`.
|
/// rounding to the closest integral number of days towards `Duration::zero()`.
|
||||||
/// It is the same as the addition with a negated `Duration`.
|
/// It is the same as the addition with a negated `Duration`.
|
||||||
@ -2002,6 +2083,46 @@ mod tests {
|
|||||||
use crate::{Datelike, Weekday};
|
use crate::{Datelike, Weekday};
|
||||||
use std::{i32, u32};
|
use std::{i32, u32};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_add_months_get_first_day() {
|
||||||
|
assert_eq!(
|
||||||
|
NaiveDate::from_ymd(2014, 1, 1).add_months_get_first_day(1),
|
||||||
|
NaiveDate::from_ymd(2014, 2, 1)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
NaiveDate::from_ymd(2014, 1, 31).add_months_get_first_day(1),
|
||||||
|
NaiveDate::from_ymd(2014, 2, 1)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
NaiveDate::from_ymd(2020, 1, 10).add_months_get_first_day(1),
|
||||||
|
NaiveDate::from_ymd(2020, 2, 1)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
NaiveDate::from_ymd(2014, 1, 1).add_months_get_first_day(-1),
|
||||||
|
NaiveDate::from_ymd(2013, 12, 1)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
NaiveDate::from_ymd(2014, 1, 31).add_months_get_first_day(-1),
|
||||||
|
NaiveDate::from_ymd(2013, 12, 1)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
NaiveDate::from_ymd(2020, 1, 10).add_months_get_first_day(-1),
|
||||||
|
NaiveDate::from_ymd(2019, 12, 1)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
NaiveDate::from_ymd(2014, 1, 10).add_months_get_first_day(-11),
|
||||||
|
NaiveDate::from_ymd(2013, 2, 1)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
NaiveDate::from_ymd(2014, 1, 10).add_months_get_first_day(-12),
|
||||||
|
NaiveDate::from_ymd(2013, 1, 1)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
NaiveDate::from_ymd(2014, 1, 10).add_months_get_first_day(-13),
|
||||||
|
NaiveDate::from_ymd(2012, 12, 1)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_readme_doomsday() {
|
fn test_readme_doomsday() {
|
||||||
use num_iter::range_inclusive;
|
use num_iter::range_inclusive;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user