Skip to content

Commit

Permalink
add/sub Days
Browse files Browse the repository at this point in the history
docs fixes

improve checked impls
  • Loading branch information
esheppa committed Aug 22, 2022
1 parent cd6d42b commit 3caf9f6
Show file tree
Hide file tree
Showing 5 changed files with 149 additions and 4 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
79 changes: 79 additions & 0 deletions src/naive/date.rs
Expand Up @@ -5,6 +5,7 @@

#[cfg(any(feature = "alloc", feature = "std", test))]
use core::borrow::Borrow;
use core::convert::TryFrom;
use core::ops::{Add, AddAssign, RangeInclusive, Sub, SubAssign};
use core::{fmt, str};

Expand Down Expand Up @@ -115,6 +116,22 @@ impl NaiveWeek {
}
}

/// A duration in calendar days.
///
/// This is useful becuase when using `Duration` it is possible
/// that adding `Duration::days(1)` doesn't increment the day value as expected due to it being a
/// fixed number of seconds. This difference applies only when dealing with `DateTime<TimeZone>` data types
/// and in other cases `Duration::days(n)` and `Days::new(n)` are equivalent.
#[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 @@ -698,6 +715,52 @@ impl NaiveDate {
NaiveDate::from_mdf(year, Mdf::new(month as u32, day, flags))
}

/// 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);
}

i64::try_from(days.0).ok().and_then(|d| self.diff_days(d))
}

/// 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);
}

i64::try_from(days.0).ok().and_then(|d| self.diff_days(-d))
}

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 @@ -1716,6 +1779,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
32 changes: 31 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, Months, TimeZone, Timelike, Weekday};

Expand Down Expand Up @@ -662,6 +662,20 @@ impl NaiveDateTime {
Some(Self { date: self.date.checked_sub_months(rhs)?, time: self.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> {
Some(Self { date: self.date.checked_add_days(days)?, ..self })
}

/// 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> {
Some(Self { date: self.date.checked_sub_days(days)?, ..self })
}

/// Subtracts another `NaiveDateTime` from the current date and time.
/// This does not overflow or underflow at all.
///
Expand Down Expand Up @@ -1535,6 +1549,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 3caf9f6

Please sign in to comment.