Skip to content

Commit 7e358c3

Browse files
committed
Convert NaiveDate/NaiveDateTime::checked_(add/sub)_days to return Result
1 parent 45d22e8 commit 7e358c3

File tree

5 files changed

+55
-66
lines changed

5 files changed

+55
-66
lines changed

src/datetime/mod.rs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,7 @@ use crate::offset::Local;
2828
use crate::offset::{FixedOffset, Offset, TimeZone, Utc};
2929
#[cfg(any(feature = "clock", feature = "std"))]
3030
use crate::OutOfRange;
31-
use crate::{try_err, try_ok_or};
32-
use crate::{Datelike, Error, Months, TimeDelta, Timelike, Weekday};
31+
use crate::{try_err, try_ok_or, Datelike, Error, Months, TimeDelta, Timelike, Weekday};
3332

3433
#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))]
3534
use rkyv::{Archive, Deserialize, Serialize};
@@ -396,9 +395,10 @@ impl<Tz: TimeZone> DateTime<Tz> {
396395
// `NaiveDate::add_days` has a fast path if the result remains within the same year, that
397396
// does not validate the resulting date. This allows us to return `Some` even for an out of
398397
// range local datetime when adding `Days(0)`.
399-
self.overflowing_naive_local()
400-
.checked_add_days(days)
401-
.and_then(|dt| self.timezone().from_local_datetime(&dt).single())
398+
let naive = self.overflowing_naive_local().checked_add_days(days).ok()?;
399+
self.timezone()
400+
.from_local_datetime(&naive)
401+
.single()
402402
.filter(|dt| dt <= &DateTime::<Utc>::MAX_UTC)
403403
}
404404

@@ -416,9 +416,10 @@ impl<Tz: TimeZone> DateTime<Tz> {
416416
// `NaiveDate::add_days` has a fast path if the result remains within the same year, that
417417
// does not validate the resulting date. This allows us to return `Some` even for an out of
418418
// range local datetime when adding `Days(0)`.
419-
self.overflowing_naive_local()
420-
.checked_sub_days(days)
421-
.and_then(|dt| self.timezone().from_local_datetime(&dt).single())
419+
let naive = self.overflowing_naive_local().checked_sub_days(days).ok()?;
420+
self.timezone()
421+
.from_local_datetime(&naive)
422+
.single()
422423
.filter(|dt| dt >= &DateTime::<Utc>::MIN_UTC)
423424
}
424425

src/naive/date/mod.rs

Lines changed: 29 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -557,89 +557,83 @@ impl NaiveDate {
557557
///
558558
/// # Errors
559559
///
560-
/// Returns `None` if the resulting date would be out of range.
560+
/// Returns [`Error::OutOfRange`] if the resulting date would be out of range.
561561
///
562562
/// # Example
563563
///
564564
/// ```
565-
/// # use chrono::{NaiveDate, Days};
566-
/// assert_eq!(
567-
/// NaiveDate::from_ymd(2022, 2, 20).unwrap().checked_add_days(Days::new(9)),
568-
/// Some(NaiveDate::from_ymd(2022, 3, 1).unwrap())
569-
/// );
565+
/// # use chrono::{NaiveDate, Days, Error};
570566
/// assert_eq!(
571-
/// NaiveDate::from_ymd(2022, 7, 31).unwrap().checked_add_days(Days::new(2)),
572-
/// Some(NaiveDate::from_ymd(2022, 8, 2).unwrap())
567+
/// NaiveDate::from_ymd(2022, 2, 20)?.checked_add_days(Days::new(9)),
568+
/// NaiveDate::from_ymd(2022, 3, 1)
573569
/// );
574570
/// assert_eq!(
575-
/// NaiveDate::from_ymd(2022, 7, 31).unwrap().checked_add_days(Days::new(1000000000000)),
576-
/// None
571+
/// NaiveDate::from_ymd(2022, 7, 31)?.checked_add_days(Days::new(1000000000000)),
572+
/// Err(Error::OutOfRange)
577573
/// );
574+
/// # Ok::<(), Error>(())
578575
/// ```
579-
#[must_use]
580-
pub const fn checked_add_days(self, days: Days) -> Option<Self> {
576+
pub const fn checked_add_days(self, days: Days) -> Result<Self, Error> {
581577
match days.0 <= i32::MAX as u64 {
582578
true => self.add_days(days.0 as i32),
583-
false => None,
579+
false => Err(Error::OutOfRange),
584580
}
585581
}
586582

587583
/// Subtract a duration in [`Days`] from the date
588584
///
589585
/// # Errors
590586
///
591-
/// Returns `None` if the resulting date would be out of range.
587+
/// Returns [`Error::OutOfRange`] if the resulting date would be out of range.
592588
///
593589
/// # Example
594590
///
595591
/// ```
596-
/// # use chrono::{NaiveDate, Days};
592+
/// # use chrono::{NaiveDate, Days, Error};
597593
/// assert_eq!(
598-
/// NaiveDate::from_ymd(2022, 2, 20).unwrap().checked_sub_days(Days::new(6)),
599-
/// Some(NaiveDate::from_ymd(2022, 2, 14).unwrap())
594+
/// NaiveDate::from_ymd(2022, 2, 20)?.checked_sub_days(Days::new(6)),
595+
/// NaiveDate::from_ymd(2022, 2, 14)
600596
/// );
601597
/// assert_eq!(
602-
/// NaiveDate::from_ymd(2022, 2, 20).unwrap().checked_sub_days(Days::new(1000000000000)),
603-
/// None
598+
/// NaiveDate::from_ymd(2022, 2, 20)?.checked_sub_days(Days::new(1000000000000)),
599+
/// Err(Error::OutOfRange)
604600
/// );
601+
/// # Ok::<(), Error>(())
605602
/// ```
606-
#[must_use]
607-
pub const fn checked_sub_days(self, days: Days) -> Option<Self> {
603+
pub const fn checked_sub_days(self, days: Days) -> Result<Self, Error> {
608604
match days.0 <= i32::MAX as u64 {
609605
true => self.add_days(-(days.0 as i32)),
610-
false => None,
606+
false => Err(Error::OutOfRange),
611607
}
612608
}
613609

614610
/// Add a duration of `i32` days to the date.
615-
pub(crate) const fn add_days(self, days: i32) -> Option<Self> {
611+
pub(crate) const fn add_days(self, days: i32) -> Result<Self, Error> {
616612
// Fast path if the result is within the same year.
617613
// Also `DateTime::checked_(add|sub)_days` relies on this path, because if the value remains
618614
// within the year it doesn't do a check if the year is in range.
619615
// This way `DateTime:checked_(add|sub)_days(Days::new(0))` can be a no-op on dates were the
620616
// local datetime is beyond `NaiveDate::{MIN, MAX}.
621617
const ORDINAL_MASK: i32 = 0b1_1111_1111_0000;
622-
if let Some(ordinal) = ((self.yof() & ORDINAL_MASK) >> 4).checked_add(days) {
618+
let ordinal =
619+
try_ok_or!(((self.yof() & ORDINAL_MASK) >> 4).checked_add(days), Error::OutOfRange);
620+
{
623621
if ordinal > 0 && ordinal <= (365 + self.leap_year() as i32) {
624622
let year_and_flags = self.yof() & !ORDINAL_MASK;
625-
return Some(NaiveDate::from_yof(year_and_flags | (ordinal << 4)));
623+
return Ok(NaiveDate::from_yof(year_and_flags | (ordinal << 4)));
626624
}
627625
}
628626
// do the full check
629627
let year = self.year();
630628
let (mut year_div_400, year_mod_400) = div_mod_floor(year, 400);
631629
let cycle = yo_to_cycle(year_mod_400 as u32, self.ordinal());
632-
let cycle = try_opt!((cycle as i32).checked_add(days));
630+
let cycle = try_ok_or!((cycle as i32).checked_add(days), Error::OutOfRange);
633631
let (cycle_div_400y, cycle) = div_mod_floor(cycle, 146_097);
634632
year_div_400 += cycle_div_400y;
635633

636634
let (year_mod_400, ordinal) = cycle_to_yo(cycle as u32);
637635
let flags = YearFlags::from_year_mod_400(year_mod_400 as i32);
638-
ok!(NaiveDate::from_ordinal_and_flags(
639-
year_div_400 * 400 + year_mod_400 as i32,
640-
ordinal,
641-
flags
642-
))
636+
NaiveDate::from_ordinal_and_flags(year_div_400 * 400 + year_mod_400 as i32, ordinal, flags)
643637
}
644638

645639
/// Makes a new `NaiveDateTime` from the current date and given `NaiveTime`.
@@ -903,7 +897,7 @@ impl NaiveDate {
903897
if days < i32::MIN as i64 || days > i32::MAX as i64 {
904898
return None;
905899
}
906-
self.add_days(days as i32)
900+
ok!(self.add_days(days as i32))
907901
}
908902

909903
/// Subtracts the number of whole days in the given `TimeDelta` from the current date.
@@ -936,7 +930,7 @@ impl NaiveDate {
936930
if days < i32::MIN as i64 || days > i32::MAX as i64 {
937931
return None;
938932
}
939-
self.add_days(days as i32)
933+
ok!(self.add_days(days as i32))
940934
}
941935

942936
/// Subtracts another `NaiveDate` from the current date.
@@ -1995,7 +1989,7 @@ impl Iterator for NaiveDateWeeksIterator {
19951989

19961990
fn next(&mut self) -> Option<Self::Item> {
19971991
let current = self.value;
1998-
self.value = current.checked_add_days(Days::new(7))?;
1992+
self.value = current.checked_add_days(Days::new(7)).ok()?;
19991993
Some(current)
20001994
}
20011995

@@ -2010,7 +2004,7 @@ impl ExactSizeIterator for NaiveDateWeeksIterator {}
20102004
impl DoubleEndedIterator for NaiveDateWeeksIterator {
20112005
fn next_back(&mut self) -> Option<Self::Item> {
20122006
let current = self.value;
2013-
self.value = current.checked_sub_days(Days::new(7))?;
2007+
self.value = current.checked_sub_days(Days::new(7)).ok()?;
20142008
Some(current)
20152009
}
20162010
}

src/naive/date/tests.rs

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -499,10 +499,10 @@ fn test_date_signed_duration_since() {
499499

500500
#[test]
501501
fn test_date_add_days() {
502-
fn check(lhs: Option<NaiveDate>, days: Days, rhs: Option<NaiveDate>) {
502+
fn check(lhs: Result<NaiveDate, Error>, days: Days, rhs: Result<NaiveDate, Error>) {
503503
assert_eq!(lhs.unwrap().checked_add_days(days), rhs);
504504
}
505-
let ymd = |y, m, d| NaiveDate::from_ymd(y, m, d).ok();
505+
let ymd = NaiveDate::from_ymd;
506506

507507
check(ymd(2014, 1, 1), Days::new(0), ymd(2014, 1, 1));
508508
// always round towards zero
@@ -514,16 +514,16 @@ fn test_date_add_days() {
514514
check(ymd(-7, 1, 1), Days::new(365 * 12 + 3), ymd(5, 1, 1));
515515

516516
// overflow check
517-
check(ymd(0, 1, 1), Days::new(MAX_DAYS_FROM_YEAR_0.try_into().unwrap()), ymd(MAX_YEAR, 12, 31));
518-
check(ymd(0, 1, 1), Days::new(u64::try_from(MAX_DAYS_FROM_YEAR_0).unwrap() + 1), None);
517+
check(ymd(0, 1, 1), Days::new(MAX_DAYS_FROM_YEAR_0 as u64), ymd(MAX_YEAR, 12, 31));
518+
check(ymd(0, 1, 1), Days::new(MAX_DAYS_FROM_YEAR_0 as u64 + 1), Err(Error::OutOfRange));
519519
}
520520

521521
#[test]
522522
fn test_date_sub_days() {
523-
fn check(lhs: Option<NaiveDate>, days: Days, rhs: Option<NaiveDate>) {
523+
fn check(lhs: Result<NaiveDate, Error>, days: Days, rhs: Result<NaiveDate, Error>) {
524524
assert_eq!(lhs.unwrap().checked_sub_days(days), rhs);
525525
}
526-
let ymd = |y, m, d| NaiveDate::from_ymd(y, m, d).ok();
526+
let ymd = NaiveDate::from_ymd;
527527

528528
check(ymd(2014, 1, 1), Days::new(0), ymd(2014, 1, 1));
529529
check(ymd(2014, 1, 2), Days::new(1), ymd(2014, 1, 1));
@@ -532,12 +532,8 @@ fn test_date_sub_days() {
532532
check(ymd(2018, 1, 1), Days::new(365 * 4 + 1), ymd(2014, 1, 1));
533533
check(ymd(2414, 1, 1), Days::new(365 * 400 + 97), ymd(2014, 1, 1));
534534

535-
check(ymd(MAX_YEAR, 12, 31), Days::new(MAX_DAYS_FROM_YEAR_0.try_into().unwrap()), ymd(0, 1, 1));
536-
check(
537-
ymd(0, 1, 1),
538-
Days::new((-MIN_DAYS_FROM_YEAR_0).try_into().unwrap()),
539-
ymd(MIN_YEAR, 1, 1),
540-
);
535+
check(ymd(MAX_YEAR, 12, 31), Days::new(MAX_DAYS_FROM_YEAR_0 as u64), ymd(0, 1, 1));
536+
check(ymd(0, 1, 1), Days::new((-MIN_DAYS_FROM_YEAR_0) as u64), ymd(MIN_YEAR, 1, 1));
541537
}
542538

543539
#[test]

src/naive/datetime/mod.rs

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -529,18 +529,16 @@ impl NaiveDateTime {
529529

530530
/// Add a duration in [`Days`] to the date part of the `NaiveDateTime`
531531
///
532-
/// Returns `None` if the resulting date would be out of range.
533-
#[must_use]
534-
pub const fn checked_add_days(self, days: Days) -> Option<Self> {
535-
Some(Self { date: try_opt!(self.date.checked_add_days(days)), ..self })
532+
/// Returns [`Error::OutOfRange`] if the resulting date would be out of range.
533+
pub const fn checked_add_days(self, days: Days) -> Result<Self, Error> {
534+
Ok(Self { date: try_err!(self.date.checked_add_days(days)), ..self })
536535
}
537536

538537
/// Subtract a duration in [`Days`] from the date part of the `NaiveDateTime`
539538
///
540-
/// Returns `None` if the resulting date would be out of range.
541-
#[must_use]
542-
pub const fn checked_sub_days(self, days: Days) -> Option<Self> {
543-
Some(Self { date: try_opt!(self.date.checked_sub_days(days)), ..self })
539+
/// Returns [`Error::OutOfRange`] if the resulting date would be out of range.
540+
pub const fn checked_sub_days(self, days: Days) -> Result<Self, Error> {
541+
Ok(Self { date: try_err!(self.date.checked_sub_days(days)), ..self })
544542
}
545543

546544
/// Subtracts another `NaiveDateTime` from the current date and time.

src/naive/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
77
use core::ops::RangeInclusive;
88

9-
use crate::expect;
109
use crate::Weekday;
10+
use crate::{expect, ok};
1111

1212
pub(crate) mod date;
1313
pub(crate) mod datetime;
@@ -63,7 +63,7 @@ impl NaiveWeek {
6363
// Do not construct an intermediate date beyond `self.date`, because that may be out of
6464
// range if `date` is close to `NaiveDate::MAX`.
6565
let days = start - ref_day - if start > ref_day { 7 } else { 0 };
66-
expect!(self.date.add_days(days), "first weekday out of range for `NaiveDate`")
66+
expect!(ok!(self.date.add_days(days)), "first weekday out of range for `NaiveDate`")
6767
}
6868

6969
/// Returns a date representing the last day of the week.
@@ -91,7 +91,7 @@ impl NaiveWeek {
9191
// Do not construct an intermediate date before `self.date` (like with `first_day()`),
9292
// because that may be out of range if `date` is close to `NaiveDate::MIN`.
9393
let days = end - ref_day + if end < ref_day { 7 } else { 0 };
94-
expect!(self.date.add_days(days), "last weekday out of range for `NaiveDate`")
94+
expect!(ok!(self.date.add_days(days)), "last weekday out of range for `NaiveDate`")
9595
}
9696

9797
/// Returns a [`RangeInclusive<T>`] representing the whole week bounded by

0 commit comments

Comments
 (0)