diff --git a/src/naive/date.rs b/src/naive/date.rs index 3fa2884406..c87a765a29 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; use core::ops::{Add, AddAssign, RangeInclusive, Sub, SubAssign}; use core::{fmt, str}; @@ -81,7 +82,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 - Duration::days(days.into()) + self.date - Days::from_u32(days) } /// Returns a date representing the last day of the week. @@ -97,7 +98,7 @@ impl NaiveWeek { /// ``` #[inline] pub fn last_day(&self) -> NaiveDate { - self.first_day() + Duration::days(6) + self.first_day() + Days::from_u32(6) } /// Returns a [`RangeInclusive`] representing the whole week bounded by @@ -125,17 +126,29 @@ impl NaiveWeek { /// 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` 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); +/// and in other cases `Duration::days(n)` and `Days::from_u32(n)` are equivalent. +#[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 days + #[deprecated(since = "0.4.23", note = "Use Days::from_u32 instead")] pub fn new(num: u64) -> Self { + Self(u32::try_from(num).unwrap()) + } + + /// Construct a new `Days` from a number of days + pub fn from_u32(num: u32) -> Self { Self(num) } } +impl From for Result { + fn from(s: Days) -> Self { + Ok(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. @@ -226,7 +239,8 @@ fn test_date_bounds() { // let's also check that the entire range do not exceed 2^44 seconds // (sometimes used for bounding `Duration` against overflow) - let maxsecs = NaiveDate::MAX.signed_duration_since(NaiveDate::MIN).num_seconds(); + let days = NaiveDate::MAX.days_since(NaiveDate::MIN).ok().unwrap(); + let maxsecs = u64::from(days.0) * 24 * 60 * 60; let maxsecs = maxsecs + 86401; // also take care of DateTime assert!( maxsecs < (1 << MAX_BITS), @@ -639,11 +653,11 @@ impl NaiveDate { /// ``` /// # use chrono::{NaiveDate, Days}; /// assert_eq!( - /// NaiveDate::from_ymd_opt(2022, 2, 20).unwrap().checked_add_days(Days::new(9)), + /// NaiveDate::from_ymd_opt(2022, 2, 20).unwrap().checked_add_days(Days::from_u32(9)), /// Some(NaiveDate::from_ymd_opt(2022, 3, 1).unwrap()) /// ); /// assert_eq!( - /// NaiveDate::from_ymd_opt(2022, 7, 31).unwrap().checked_add_days(Days::new(2)), + /// NaiveDate::from_ymd_opt(2022, 7, 31).unwrap().checked_add_days(Days::from_u32(2)), /// Some(NaiveDate::from_ymd_opt(2022, 8, 2).unwrap()) /// ); /// ``` @@ -662,7 +676,7 @@ impl NaiveDate { /// ``` /// # use chrono::{NaiveDate, Days}; /// assert_eq!( - /// NaiveDate::from_ymd_opt(2022, 2, 20).unwrap().checked_sub_days(Days::new(6)), + /// NaiveDate::from_ymd_opt(2022, 2, 20).unwrap().checked_sub_days(Days::from_u32(6)), /// Some(NaiveDate::from_ymd_opt(2022, 2, 14).unwrap()) /// ); /// ``` @@ -1071,6 +1085,34 @@ impl NaiveDate { } } + /// Subtracts another `NaiveDate` from the current date. + /// + /// Returns a `Result` where `Ok` means the `self` was later than + /// the `rhs`, and the `Err` variant means the inverse. + /// + /// The `Ok` variant will be returned when `self` and `rhs` are equal + /// days between the two dates. + /// + /// note: we explicitly don't use an implementor of the `Error` + /// type for the `E` parameter of the `Result`. This is because + /// this isn't intended to be handled with the `?` operator + pub fn days_since(self, rhs: NaiveDate) -> Result { + let diff = self.signed_duration_since(rhs).num_days(); + + if diff >= 0 { + Ok(Days::from_u32(diff.try_into().unwrap())) + } else { + Err(Days::from_u32(diff.abs().try_into().unwrap())) + } + } + + /// Finds the absolute difference in days between two `NaiveDate`s + pub fn abs_diff(self, rhs: NaiveDate) -> Days { + let diff = self.signed_duration_since(rhs).num_days(); + + Days::from_u32(diff.abs().try_into().unwrap()) + } + /// Formats the date with the specified formatting items. /// Otherwise it is the same as the ordinary `format` method. /// @@ -1207,7 +1249,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. @@ -1238,7 +1280,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`] @@ -1695,6 +1737,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; @@ -1703,6 +1751,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 `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`. @@ -1778,26 +1832,26 @@ impl Sub for NaiveDate { /// 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; - } - // current < NaiveDate::MAX from here on: let current = self.value; - // This can't panic because current is < NaiveDate::MAX: - self.value = current.succ_opt().unwrap(); - 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).ok().unwrap().0; + (exact_size as usize, Some(exact_size as usize)) + } + None => (0, Some(0)), + } } } @@ -1805,35 +1859,34 @@ 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_opt().unwrap(); - 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 < OldDuration::weeks(1) { - return None; - } let current = self.value; - self.value = current + OldDuration::weeks(1); - Some(current) + self.value = current?.checked_add_days(Days::from_u32(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).err().unwrap().0 / 7; + (exact_size as usize, Some(exact_size as usize)) + } + None => (0, Some(0)), + } } } @@ -1841,12 +1894,10 @@ impl ExactSizeIterator for NaiveDateWeeksIterator {} impl DoubleEndedIterator for NaiveDateWeeksIterator { fn next_back(&mut self) -> Option { - if self.value - NaiveDate::MIN < OldDuration::weeks(1) { - return None; - } + // NaiveDate::MAX.days_since(self.value?).forwards()?; let current = self.value; - self.value = current - OldDuration::weeks(1); - Some(current) + self.value = current?.checked_sub_days(Days::from_u32(7)); + current } } @@ -2673,22 +2724,22 @@ mod tests { assert_eq!(lhs.checked_add_days(rhs), sum); } - check((2014, 1, 1), Days::new(0), Some((2014, 1, 1))); + check((2014, 1, 1), Days::from_u32(0), Some((2014, 1, 1))); // always round towards zero - check((2014, 1, 1), Days::new(1), Some((2014, 1, 2))); - check((2014, 1, 1), Days::new(364), Some((2014, 12, 31))); - check((2014, 1, 1), Days::new(365 * 4 + 1), Some((2018, 1, 1))); - check((2014, 1, 1), Days::new(365 * 400 + 97), Some((2414, 1, 1))); + check((2014, 1, 1), Days::from_u32(1), Some((2014, 1, 2))); + check((2014, 1, 1), Days::from_u32(364), Some((2014, 12, 31))); + check((2014, 1, 1), Days::from_u32(365 * 4 + 1), Some((2018, 1, 1))); + check((2014, 1, 1), Days::from_u32(365 * 400 + 97), Some((2414, 1, 1))); - check((-7, 1, 1), Days::new(365 * 12 + 3), Some((5, 1, 1))); + check((-7, 1, 1), Days::from_u32(365 * 12 + 3), Some((5, 1, 1))); // overflow check check( (0, 1, 1), - Days::new(MAX_DAYS_FROM_YEAR_0.try_into().unwrap()), + Days::from_u32(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::from_u32(u32::try_from(MAX_DAYS_FROM_YEAR_0).unwrap() + 1), None); } #[test] @@ -2699,15 +2750,23 @@ mod tests { assert_eq!(lhs - diff, rhs); } - check((2014, 1, 1), (2014, 1, 1), Days::new(0)); - check((2014, 1, 2), (2014, 1, 1), Days::new(1)); - check((2014, 12, 31), (2014, 1, 1), Days::new(364)); - check((2015, 1, 3), (2014, 1, 1), Days::new(365 + 2)); - check((2018, 1, 1), (2014, 1, 1), Days::new(365 * 4 + 1)); - check((2414, 1, 1), (2014, 1, 1), Days::new(365 * 400 + 97)); + check((2014, 1, 1), (2014, 1, 1), Days::from_u32(0)); + check((2014, 1, 2), (2014, 1, 1), Days::from_u32(1)); + check((2014, 12, 31), (2014, 1, 1), Days::from_u32(364)); + check((2015, 1, 3), (2014, 1, 1), Days::from_u32(365 + 2)); + check((2018, 1, 1), (2014, 1, 1), Days::from_u32(365 * 4 + 1)); + check((2414, 1, 1), (2014, 1, 1), Days::from_u32(365 * 400 + 97)); - check((MAX_YEAR, 12, 31), (0, 1, 1), Days::new(MAX_DAYS_FROM_YEAR_0.try_into().unwrap())); - check((0, 1, 1), (MIN_YEAR, 1, 1), Days::new((-MIN_DAYS_FROM_YEAR_0).try_into().unwrap())); + check( + (MAX_YEAR, 12, 31), + (0, 1, 1), + Days::from_u32(MAX_DAYS_FROM_YEAR_0.try_into().unwrap()), + ); + check( + (0, 1, 1), + (MIN_YEAR, 1, 1), + Days::from_u32((-MIN_DAYS_FROM_YEAR_0).try_into().unwrap()), + ); } #[test] @@ -2730,6 +2789,26 @@ mod tests { assert_eq!(date, ymd(2016, 9, 29)); } + #[test] + fn test_days_addassignment() { + let ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); + let mut date = ymd(2016, 10, 1); + date += Days::from_u32(10); + assert_eq!(date, ymd(2016, 10, 11)); + date += Days::from_u32(30); + assert_eq!(date, ymd(2016, 11, 10)); + } + + #[test] + fn test_days_subassignment() { + let ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); + let mut date = ymd(2016, 10, 11); + date -= Days::from_u32(10); + assert_eq!(date, ymd(2016, 10, 1)); + date -= Days::from_u32(2); + assert_eq!(date, ymd(2016, 9, 29)); + } + #[test] fn test_date_fmt() { assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt(2012, 3, 4).unwrap()), "2012-03-04"); @@ -2867,10 +2946,10 @@ mod tests { #[test] fn test_day_iterator_limit() { - assert_eq!(NaiveDate::from_ymd_opt(262143, 12, 29).unwrap().iter_days().take(4).count(), 2); + assert_eq!(NaiveDate::from_ymd_opt(262143, 12, 29).unwrap().iter_days().take(4).count(), 3); assert_eq!( NaiveDate::from_ymd_opt(-262144, 1, 3).unwrap().iter_days().rev().take(4).count(), - 2 + 3 ); } @@ -2878,11 +2957,11 @@ mod tests { fn test_week_iterator_limit() { assert_eq!( NaiveDate::from_ymd_opt(262143, 12, 12).unwrap().iter_weeks().take(4).count(), - 2 + 3 ); assert_eq!( NaiveDate::from_ymd_opt(-262144, 1, 15).unwrap().iter_weeks().rev().take(4).count(), - 2 + 3 ); }