diff --git a/src/lib.rs b/src/lib.rs index 3c79b56a4b..b98425e9ff 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -449,7 +449,7 @@ pub use format::{ParseError, ParseResult}; pub mod naive; #[doc(no_inline)] -pub use naive::{Days, IsoWeek, NaiveDate, NaiveDateTime, NaiveTime, NaiveWeek}; +pub use naive::{Days, DaysDelta, IsoWeek, NaiveDate, NaiveDateTime, NaiveTime, NaiveWeek}; pub mod offset; #[cfg(feature = "clock")] diff --git a/src/naive/date.rs b/src/naive/date.rs index 305f96b8ae..8e1bd8b68a 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -5,7 +5,8 @@ #[cfg(any(feature = "alloc", feature = "std", test))] use core::borrow::Borrow; -use core::convert::TryFrom; +use core::convert::{TryFrom, TryInto}; +use core::hash::{Hash, Hasher}; use core::ops::{Add, AddAssign, RangeInclusive, Sub, SubAssign}; use core::{fmt, str}; @@ -75,7 +76,7 @@ impl NaiveWeek { let start = self.start.num_days_from_monday(); let end = self.date.weekday().num_days_from_monday(); let days = if start > end { 7 - start + end } else { end - start }; - self.date - OldTimeDelta::days(days.into()) + self.date - Days::new(days) } /// Returns a date representing the last day of the week. @@ -91,7 +92,7 @@ impl NaiveWeek { /// ``` #[inline] pub fn last_day(&self) -> NaiveDate { - self.first_day() + OldTimeDelta::days(6) + self.first_day() + Days::new(6) } /// Returns a [`RangeInclusive`] representing the whole week bounded by @@ -120,16 +121,99 @@ impl NaiveWeek { /// that adding `OldTimeDelta::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` data types /// and in other cases `OldTimeDelta::days(n)` and `Days::new(n)` are equivalent. -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd)] -pub struct Days(pub(crate) u64); +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] +pub struct Days(pub(crate) u32); impl Days { /// Construct a new `Days` from a number of months - pub fn new(num: u64) -> Self { + pub fn new(num: u32) -> Self { Self(num) } } +/// A difference in a number of days, either forwards or backwards. +/// +/// This type is often returned from fuctions but is generally not used as a parameter. +/// Instead the inner `Days` must first be extracted and then used. There are helper methods +/// which can assist with this. +#[derive(Clone, Copy, Debug, PartialOrd, Ord)] +pub enum DaysDelta { + /// the forwards direction + Forwards(Days), + /// the backwards direction + Backwards(Days), +} + +impl DaysDelta { + /// Assert that the direction is forwards and throw away the `Days` otherwise. + pub fn forwards(self) -> Option { + match self { + DaysDelta::Forwards(d) => Some(d), + DaysDelta::Backwards(_) => None, + } + } + + /// Assert that the direction is backwards and throw away the `Days` otherwise. + pub fn backwards(self) -> Option { + match self { + DaysDelta::Forwards(_) => None, + DaysDelta::Backwards(d) => Some(d), + } + } + + /// Get the contained `Days`, no matter which direction + pub fn abs(self) -> Days { + match self { + DaysDelta::Backwards(d) => d, + DaysDelta::Forwards(d) => d, + } + } +} + +impl Hash for DaysDelta { + fn hash(&self, state: &mut H) { + // the `Hash` impl must match the `PartialEq` impl + // in that when the Days is 0, forwards and backwards are + // equal and have the same hash. + // otherwise, we have to hash something extra to account + // for whether the delta is forwards or backwards. + match self { + DaysDelta::Forwards(d) | DaysDelta::Backwards(d) if *d == Days::new(0) => { + Days::new(0).hash(state) + } + DaysDelta::Forwards(d) => { + d.hash(state); + "Forwards".hash(state); + } + DaysDelta::Backwards(d) => { + d.hash(state); + "Backwards".hash(state); + } + } + } +} + +impl PartialEq for DaysDelta { + fn eq(&self, other: &DaysDelta) -> bool { + match (self, other) { + (DaysDelta::Forwards(f1), DaysDelta::Forwards(f2)) => f1 == f2, + (DaysDelta::Backwards(b1), DaysDelta::Backwards(b2)) => b1 == b2, + (DaysDelta::Forwards(lhs), DaysDelta::Backwards(rhs)) + | (DaysDelta::Backwards(lhs), DaysDelta::Forwards(rhs)) => { + *lhs == Days(0) && *rhs == Days(0) + } + } + } +} + +impl Eq for DaysDelta {} + +impl From for DaysDelta { + fn from(s: Days) -> Self { + DaysDelta::Forwards(s) + } +} + /// ISO 8601 calendar date without timezone. /// Allows for every [proleptic Gregorian date](#calendar-date) /// from Jan 1, 262145 BCE to Dec 31, 262143 CE. @@ -210,7 +294,8 @@ fn test_date_bounds() { // let's also check that the entire range do not exceed 2^44 seconds // (sometimes used for bounding `OldTimeDelta` against overflow) - let maxsecs = NaiveDate::MAX.signed_duration_since(NaiveDate::MIN).num_seconds(); + let days = NaiveDate::MAX.days_since(NaiveDate::MIN).forwards().unwrap(); + let maxsecs = u64::from(days.0) * 24 * 60 * 60; let maxsecs = maxsecs + 86401; // also take care of DateTime assert!( maxsecs < (1 << MAX_BITS), @@ -1194,6 +1279,19 @@ impl NaiveDate { ) } + /// Subtracts another `NaiveDate` from the current date. + /// Returns a `DaysDelta` of the direction and number of + /// days between the two dates. + pub fn days_since(self, rhs: NaiveDate) -> DaysDelta { + let diff = self.signed_duration_since(rhs).num_days(); + + if diff >= 0 { + DaysDelta::Forwards(Days::new(diff.try_into().unwrap())) + } else { + DaysDelta::Backwards(Days::new(diff.abs().try_into().unwrap())) + } + } + /// Formats the date with the specified formatting items. /// Otherwise it is the same as the ordinary `format` method. /// @@ -1299,7 +1397,7 @@ impl NaiveDate { /// ``` #[inline] pub fn iter_days(&self) -> NaiveDateDaysIterator { - NaiveDateDaysIterator { value: *self } + NaiveDateDaysIterator { value: Some(*self) } } /// Returns an iterator that steps by weeks across all representable dates. @@ -1330,7 +1428,7 @@ impl NaiveDate { /// ``` #[inline] pub fn iter_weeks(&self) -> NaiveDateWeeksIterator { - NaiveDateWeeksIterator { value: *self } + NaiveDateWeeksIterator { value: Some(*self) } } /// Returns the [`NaiveWeek`] that the date belongs to, starting with the [`Weekday`] @@ -1787,6 +1885,12 @@ impl Add for NaiveDate { } } +impl AddAssign for NaiveDate { + fn add_assign(&mut self, days: Days) { + *self = self.checked_add_days(days).unwrap(); + } +} + impl Sub for NaiveDate { type Output = NaiveDate; @@ -1795,6 +1899,12 @@ impl Sub for NaiveDate { } } +impl SubAssign for NaiveDate { + fn sub_assign(&mut self, days: Days) { + *self = self.checked_sub_days(days).unwrap(); + } +} + /// A subtraction of `OldTimeDelta` from `NaiveDate` discards the fractional days, /// rounding to the closest integral number of days towards `OldTimeDelta::zero()`. /// It is the same as the addition with a negated `OldTimeDelta`. @@ -1835,10 +1945,10 @@ impl SubAssign for NaiveDate { } /// Subtracts another `NaiveDate` from the current date. -/// Returns a `OldTimeDelta` of integral numbers. +/// Returns a `DaysDelta` of integral numbers. /// /// This does not overflow or underflow at all, -/// as all possible output fits in the range of `OldTimeDelta`. +/// as all possible output fits in the range of `TimeDelta`. /// /// The implementation is a wrapper around /// [`NaiveDate::signed_duration_since`](#method.signed_duration_since). @@ -1846,50 +1956,56 @@ impl SubAssign for NaiveDate { /// # Example /// /// ``` -/// use chrono::{OldTimeDelta, NaiveDate}; +/// use chrono::{DaysDelta, NaiveDate, Days}; /// /// let from_ymd = NaiveDate::from_ymd; /// -/// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2014, 1, 1), OldTimeDelta::zero()); -/// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2013, 12, 31), OldTimeDelta::days(1)); -/// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2014, 1, 2), OldTimeDelta::days(-1)); -/// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2013, 9, 23), OldTimeDelta::days(100)); -/// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2013, 1, 1), OldTimeDelta::days(365)); -/// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2010, 1, 1), OldTimeDelta::days(365*4 + 1)); -/// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(1614, 1, 1), OldTimeDelta::days(365*400 + 97)); +/// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2014, 1, 1), DaysDelta::Forwards(Days::new(0))); +/// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2013, 12, 31), DaysDelta::Forwards(Days::new(1))); +/// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2014, 1, 2), DaysDelta::Backwards(Days::new(1))); +/// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2013, 9, 23), DaysDelta::Forwards(Days::new(100))); +/// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2013, 1, 1), DaysDelta::Forwards(Days::new(365))); +/// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2010, 1, 1), DaysDelta::Forwards(Days::new(365*4 + 1))); +/// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(1614, 1, 1), DaysDelta::Forwards(Days::new(365*400 + 97))); /// ``` impl Sub for NaiveDate { - type Output = OldTimeDelta; + type Output = DaysDelta; #[inline] - fn sub(self, rhs: NaiveDate) -> OldTimeDelta { - self.signed_duration_since(rhs) + fn sub(self, rhs: NaiveDate) -> DaysDelta { + self.days_since(rhs) } } /// Iterator over `NaiveDate` with a step size of one day. #[derive(Debug, Copy, Clone, Hash, PartialEq, PartialOrd, Eq, Ord)] pub struct NaiveDateDaysIterator { - value: NaiveDate, + value: Option, } impl Iterator for NaiveDateDaysIterator { type Item = NaiveDate; fn next(&mut self) -> Option { - if self.value == NaiveDate::MAX { - return None; - } + // if self.value == NaiveDate::MAX { + // return None; + // } // current < NaiveDate::MAX from here on: let current = self.value; // This can't panic because current is < NaiveDate::MAX: - self.value = current.succ(); - Some(current) + self.value = current?.succ_opt(); + + current } fn size_hint(&self) -> (usize, Option) { - let exact_size = NaiveDate::MAX.signed_duration_since(self.value).num_days(); - (exact_size as usize, Some(exact_size as usize)) + match self.value { + Some(v) => { + let exact_size = NaiveDate::MAX.days_since(v).forwards().unwrap().0; + (exact_size as usize, Some(exact_size as usize)) + } + None => (0, Some(0)), + } } } @@ -1897,35 +2013,35 @@ impl ExactSizeIterator for NaiveDateDaysIterator {} impl DoubleEndedIterator for NaiveDateDaysIterator { fn next_back(&mut self) -> Option { - if self.value == NaiveDate::MIN { - return None; - } let current = self.value; - self.value = current.pred(); - Some(current) + self.value = current?.pred_opt(); + current } } #[derive(Debug, Copy, Clone, Hash, PartialEq, PartialOrd, Eq, Ord)] pub struct NaiveDateWeeksIterator { - value: NaiveDate, + value: Option, } impl Iterator for NaiveDateWeeksIterator { type Item = NaiveDate; fn next(&mut self) -> Option { - if NaiveDate::MAX - self.value < OldTimeDelta::weeks(1) { - return None; - } + // NaiveDate::MAX.days_since(self.value?).forwards()?; let current = self.value; - self.value = current + OldTimeDelta::weeks(1); - Some(current) + self.value = current?.checked_add_days(Days::new(7)); + current } fn size_hint(&self) -> (usize, Option) { - let exact_size = NaiveDate::MAX.signed_duration_since(self.value).num_weeks(); - (exact_size as usize, Some(exact_size as usize)) + match self.value { + Some(v) => { + let exact_size = NaiveDate::MAX.days_since(v).forwards().unwrap().0 / 7; + (exact_size as usize, Some(exact_size as usize)) + } + None => (0, Some(0)), + } } } @@ -1933,12 +2049,10 @@ impl ExactSizeIterator for NaiveDateWeeksIterator {} impl DoubleEndedIterator for NaiveDateWeeksIterator { fn next_back(&mut self) -> Option { - if self.value - NaiveDate::MIN < OldTimeDelta::weeks(1) { - return None; - } + // NaiveDate::MAX.days_since(self.value?).forwards()?; let current = self.value; - self.value = current - OldTimeDelta::weeks(1); - Some(current) + self.value = current?.checked_sub_days(Days::new(7)); + current } } @@ -2673,7 +2787,7 @@ mod tests { Days::new(MAX_DAYS_FROM_YEAR_0.try_into().unwrap()), Some((MAX_YEAR, 12, 31)), ); - check((0, 1, 1), Days::new(u64::try_from(MAX_DAYS_FROM_YEAR_0).unwrap() + 1), None); + check((0, 1, 1), Days::new(u32::try_from(MAX_DAYS_FROM_YEAR_0).unwrap() + 1), None); } #[test] @@ -2715,6 +2829,26 @@ mod tests { assert_eq!(date, ymd(2016, 9, 29)); } + #[test] + fn test_days_addassignment() { + let ymd = NaiveDate::from_ymd; + let mut date = ymd(2016, 10, 1); + date += Days::new(10); + assert_eq!(date, ymd(2016, 10, 11)); + date += Days::new(30); + assert_eq!(date, ymd(2016, 11, 10)); + } + + #[test] + fn test_days_subassignment() { + let ymd = NaiveDate::from_ymd; + let mut date = ymd(2016, 10, 11); + date -= Days::new(10); + assert_eq!(date, ymd(2016, 10, 1)); + date -= Days::new(2); + assert_eq!(date, ymd(2016, 9, 29)); + } + #[test] fn test_date_fmt() { assert_eq!(format!("{:?}", NaiveDate::from_ymd(2012, 3, 4)), "2012-03-04"); @@ -2843,14 +2977,14 @@ mod tests { #[test] fn test_day_iterator_limit() { - assert_eq!(NaiveDate::from_ymd(262143, 12, 29).iter_days().take(4).count(), 2); - assert_eq!(NaiveDate::from_ymd(-262144, 1, 3).iter_days().rev().take(4).count(), 2); + assert_eq!(NaiveDate::from_ymd(262143, 12, 29).iter_days().take(4).count(), 3); + assert_eq!(NaiveDate::from_ymd(-262144, 1, 3).iter_days().rev().take(4).count(), 3); } #[test] fn test_week_iterator_limit() { - assert_eq!(NaiveDate::from_ymd(262143, 12, 12).iter_weeks().take(4).count(), 2); - assert_eq!(NaiveDate::from_ymd(-262144, 1, 15).iter_weeks().rev().take(4).count(), 2); + assert_eq!(NaiveDate::from_ymd(262143, 12, 12).iter_weeks().take(4).count(), 3); + assert_eq!(NaiveDate::from_ymd(-262144, 1, 15).iter_weeks().rev().take(4).count(), 3); } #[test] diff --git a/src/naive/mod.rs b/src/naive/mod.rs index f1bd6d6cbc..6055c49c84 100644 --- a/src/naive/mod.rs +++ b/src/naive/mod.rs @@ -11,7 +11,7 @@ mod isoweek; mod time; #[allow(deprecated)] -pub use self::date::{Days, NaiveDate, NaiveWeek, MAX_DATE, MIN_DATE}; +pub use self::date::{Days, DaysDelta, NaiveDate, NaiveWeek, MAX_DATE, MIN_DATE}; #[allow(deprecated)] pub use self::datetime::{NaiveDateTime, MAX_DATETIME, MIN_DATETIME}; pub use self::isoweek::IsoWeek;