Skip to content

Commit

Permalink
add/sub Days
Browse files Browse the repository at this point in the history
  • Loading branch information
esheppa committed Aug 17, 2022
1 parent a383abf commit e5d5e64
Show file tree
Hide file tree
Showing 5 changed files with 180 additions and 29 deletions.
38 changes: 37 additions & 1 deletion src/datetime/mod.rs
Expand Up @@ -24,7 +24,7 @@ use crate::format::DelayedFormat;
use crate::format::Locale;
use crate::format::{parse, ParseError, ParseResult, Parsed, StrftimeItems};
use crate::format::{Fixed, Item};
use crate::naive::{IsoWeek, NaiveDate, NaiveDateTime, NaiveTime};
use crate::naive::{Days, IsoWeek, NaiveDate, NaiveDateTime, NaiveTime};
#[cfg(feature = "clock")]
use crate::offset::Local;
use crate::offset::{FixedOffset, Offset, TimeZone, Utc};
Expand Down Expand Up @@ -339,6 +339,26 @@ impl<Tz: TimeZone> DateTime<Tz> {
Some(tz.from_utc_datetime(&datetime))
}

/// Add a duration in [`Days`] to the date part of the `DateTime`
///
/// Returns `None` if the resulting date would be out of range.
pub fn checked_add_days(self, days: Days) -> Option<Self> {
self.datetime
.checked_add_days(days)?
.and_local_timezone(TimeZone::from_offset(&self.offset))
.single()
}

/// Subtract a duration in [`Days`] to the date part of the `DateTime`
///
/// Returns `None` if the resulting date would be out of range.
pub fn checked_sub_days(self, days: Days) -> Option<Self> {
self.datetime
.checked_sub_days(days)?
.and_local_timezone(TimeZone::from_offset(&self.offset))
.single()
}

/// Subtracts another `DateTime` from the current date and time.
/// This does not overflow or underflow at all.
#[inline]
Expand Down Expand Up @@ -897,6 +917,22 @@ impl<Tz: TimeZone> Sub<DateTime<Tz>> for DateTime<Tz> {
}
}

impl<Tz: TimeZone> Add<Days> for DateTime<Tz> {
type Output = DateTime<Tz>;

fn add(self, days: Days) -> Self::Output {
self.checked_add_days(days).unwrap()
}
}

impl<Tz: TimeZone> Sub<Days> for DateTime<Tz> {
type Output = DateTime<Tz>;

fn sub(self, days: Days) -> Self::Output {
self.checked_sub_days(days).unwrap()
}
}

impl<Tz: TimeZone> fmt::Debug for DateTime<Tz> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}{:?}", self.naive_local(), self.offset)
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Expand Up @@ -481,7 +481,7 @@ pub use format::{ParseError, ParseResult};

pub mod naive;
#[doc(no_inline)]
pub use naive::{IsoWeek, NaiveDate, NaiveDateTime, NaiveTime, NaiveWeek};
pub use naive::{Days, IsoWeek, NaiveDate, NaiveDateTime, NaiveTime, NaiveWeek};

pub mod offset;
#[cfg(feature = "clock")]
Expand Down
131 changes: 106 additions & 25 deletions src/naive/date.rs
Expand Up @@ -115,6 +115,19 @@ impl NaiveWeek {
}
}

/// A duration in calendar days. This is useful becuase when using `TimeDelta` it is possible
/// that adding TimeDelta::days(1) doesn't increment the day value as expected due to it being a
/// fixed number of seconds.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd)]
pub struct Days(pub(crate) u64);

impl Days {
/// Construct a new `Days` from a number of months
pub fn new(num: u64) -> Self {
Self(num)
}
}

/// ISO 8601 calendar date without timezone.
/// Allows for every [proleptic Gregorian date](#calendar-date)
/// from Jan 1, 262145 BCE to Dec 31, 262143 CE.
Expand Down Expand Up @@ -625,31 +638,6 @@ impl NaiveDate {
}
}

/// Subtract a duration in [`Months`] from the date
///
/// If the day would be out of range for the resulting month, use the last day for that month.
///
/// Returns `None` if the resulting date would be out of range.
///
/// ```
/// # use chrono::{NaiveDate, Months};
/// assert_eq!(
/// NaiveDate::from_ymd(2022, 2, 20).checked_sub_months(Months::new(6)),
/// Some(NaiveDate::from_ymd(2021, 8, 20))
/// );
/// ```
pub fn checked_sub_months(self, months: Months) -> Option<Self> {
if months.0 == 0 {
return Some(self);
}

// Copy `i32::MIN` here so we don't have to do a complicated cast
match months.0 <= 2_147_483_648 {
true => self.diff_months(-(months.0 as i32)),
false => None,
}
}

fn diff_months(self, months: i32) -> Option<Self> {
let (years, left) = ((months / 12), (months % 12));

Expand Down Expand Up @@ -692,6 +680,83 @@ impl NaiveDate {
NaiveDate::from_mdf(year, Mdf::new(month as u32, day, flags))
}

/// Subtract a duration in [`Months`] from the date
///
/// If the day would be out of range for the resulting month, use the last day for that month.
///
/// Returns `None` if the resulting date would be out of range.
///
/// ```
/// # use chrono::{NaiveDate, Months};
/// assert_eq!(
/// NaiveDate::from_ymd(2022, 2, 20).checked_sub_months(Months::new(6)),
/// Some(NaiveDate::from_ymd(2021, 8, 20))
/// );
/// ```
pub fn checked_sub_months(self, months: Months) -> Option<Self> {
if months.0 == 0 {
return Some(self);
}

// Copy `i32::MIN` here so we don't have to do a complicated cast
match months.0 <= 2_147_483_648 {
true => self.diff_months(-(months.0 as i32)),
false => None,
}
}

/// Add a duration in [`Days`] to the date\
///
/// Returns `None` if the resulting date would be out of range.
///
/// ```
/// # use chrono::{NaiveDate, Days};
/// assert_eq!(
/// NaiveDate::from_ymd(2022, 2, 20).checked_add_days(Days::new(9)),
/// Some(NaiveDate::from_ymd(2022, 3, 1))
/// );
/// assert_eq!(
/// NaiveDate::from_ymd(2022, 7, 31).checked_add_days(Days::new(2)),
/// Some(NaiveDate::from_ymd(2022, 8, 2))
/// );
/// ```
pub fn checked_add_days(self, days: Days) -> Option<Self> {
if days.0 == 0 {
return Some(self);
}

match days.0 <= core::i64::MAX as u64 {
true => self.diff_days(days.0 as i64),
false => None,
}
}

/// Subtract a duration in [`Days`] from the date
///
/// Returns `None` if the resulting date would be out of range.
///
/// ```
/// # use chrono::{NaiveDate, Days};
/// assert_eq!(
/// NaiveDate::from_ymd(2022, 2, 20).checked_sub_days(Days::new(6)),
/// Some(NaiveDate::from_ymd(2022, 2, 14))
/// );
/// ```
pub fn checked_sub_days(self, days: Days) -> Option<Self> {
if days.0 == 0 {
return Some(self);
}

match days.0 <= core::i64::MAX as u64 {
true => self.diff_days(-(days.0 as i64)),
false => None,
}
}

fn diff_days(self, days: i64) -> Option<Self> {
self.checked_add_signed(Duration::days(days))
}

/// Makes a new `NaiveDateTime` from the current date and given `NaiveTime`.
///
/// # Example
Expand Down Expand Up @@ -1710,6 +1775,22 @@ impl Sub<Months> for NaiveDate {
}
}

impl Add<Days> for NaiveDate {
type Output = NaiveDate;

fn add(self, days: Days) -> Self::Output {
self.checked_add_days(days).unwrap()
}
}

impl Sub<Days> for NaiveDate {
type Output = NaiveDate;

fn sub(self, days: Days) -> Self::Output {
self.checked_sub_days(days).unwrap()
}
}

/// A subtraction of `Duration` from `NaiveDate` discards the fractional days,
/// rounding to the closest integral number of days towards `Duration::zero()`.
/// It is the same as the addition with a negated `Duration`.
Expand Down
36 changes: 35 additions & 1 deletion src/naive/datetime/mod.rs
Expand Up @@ -17,7 +17,7 @@ use rkyv::{Archive, Deserialize, Serialize};
use crate::format::DelayedFormat;
use crate::format::{parse, ParseError, ParseResult, Parsed, StrftimeItems};
use crate::format::{Fixed, Item, Numeric, Pad};
use crate::naive::{IsoWeek, NaiveDate, NaiveTime};
use crate::naive::{Days, IsoWeek, NaiveDate, NaiveTime};
use crate::oldtime::Duration as OldDuration;
use crate::{DateTime, Datelike, LocalResult, TimeZone, Timelike, Weekday};

Expand Down Expand Up @@ -606,6 +606,24 @@ impl NaiveDateTime {
Some(NaiveDateTime { date, time })
}

/// Add a duration in [`Days`] to the date part of the `NaiveDateTime`
///
/// Returns `None` if the resulting date would be out of range.
pub fn checked_add_days(self, days: Days) -> Option<Self> {
let new_date = self.date().checked_add_days(days)?;

Some(new_date.and_time(self.time()))
}

/// Subtract a duration in [`Days`] from the date part of the `NaiveDateTime`
///
/// Returns `None` if the resulting date would be out of range.
pub fn checked_sub_days(self, days: Days) -> Option<Self> {
let new_date = self.date().checked_sub_days(days)?;

Some(new_date.and_time(self.time()))
}

/// Subtracts another `NaiveDateTime` from the current date and time.
/// This does not overflow or underflow at all.
///
Expand Down Expand Up @@ -1401,6 +1419,22 @@ impl Sub<NaiveDateTime> for NaiveDateTime {
}
}

impl Add<Days> for NaiveDateTime {
type Output = NaiveDateTime;

fn add(self, days: Days) -> Self::Output {
self.checked_add_days(days).unwrap()
}
}

impl Sub<Days> for NaiveDateTime {
type Output = NaiveDateTime;

fn sub(self, days: Days) -> Self::Output {
self.checked_sub_days(days).unwrap()
}
}

/// The `Debug` output of the naive date and time `dt` is the same as
/// [`dt.format("%Y-%m-%dT%H:%M:%S%.f")`](crate::format::strftime).
///
Expand Down
2 changes: 1 addition & 1 deletion src/naive/mod.rs
Expand Up @@ -11,7 +11,7 @@ mod isoweek;
mod time;

#[allow(deprecated)]
pub use self::date::{NaiveDate, NaiveWeek, MAX_DATE, MIN_DATE};
pub use self::date::{Days, NaiveDate, NaiveWeek, MAX_DATE, MIN_DATE};
#[cfg(feature = "rustc-serialize")]
#[allow(deprecated)]
pub use self::datetime::rustc_serialize::TsSeconds;
Expand Down

0 comments on commit e5d5e64

Please sign in to comment.