Skip to content

Commit

Permalink
Convert NaiveDate::from_ymd to return Result
Browse files Browse the repository at this point in the history
  • Loading branch information
pitdicker committed Feb 14, 2024
1 parent ee62e45 commit d0d929f
Show file tree
Hide file tree
Showing 9 changed files with 71 additions and 64 deletions.
8 changes: 8 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,21 @@ pub enum Error {
///
/// An example is creating a `NaiveTime` with 25 as the hour value.
InvalidArgument,

/// The result, or an intermediate value necessary for calculating a result, would be out of
/// range.
///
/// An example is a date for the year 500.000, which is out of the range supported by chrono's
/// types.
OutOfRange,
}

impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Error::DoesNotExist => write!(f, "date or datetime does not exist"),
Error::InvalidArgument => write!(f, "invalid parameter"),
Error::OutOfRange => write!(f, "date outside of the supported range"),

Check warning on line 35 in src/error.rs

View check run for this annotation

Codecov / codecov/patch

src/error.rs#L35

Added line #L35 was not covered by tests
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/format/parsed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,7 @@ impl Parsed {
let (verified, parsed_date) = match (given_year, given_isoyear, self) {
(Some(year), _, &Parsed { month: Some(month), day: Some(day), .. }) => {
// year, month, day
let date = NaiveDate::from_ymd_opt(year, month, day).ok_or(OUT_OF_RANGE)?;
let date = NaiveDate::from_ymd_opt(year, month, day).map_err(|_| OUT_OF_RANGE)?;
(verify_isoweekdate(date) && verify_ordinal(date), date)
}

Expand Down
10 changes: 5 additions & 5 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,20 +129,20 @@
//! # fn doctest() -> Option<()> {
//!
//! let dt = Utc.with_ymd_and_hms(2014, 7, 8, 9, 10, 11).unwrap(); // `2014-07-08T09:10:11Z`
//! assert_eq!(dt, NaiveDate::from_ymd_opt(2014, 7, 8)?.and_hms_opt(9, 10, 11).unwrap().and_local_timezone(Utc).unwrap());
//! assert_eq!(dt, NaiveDate::from_ymd_opt(2014, 7, 8).unwrap().and_hms_opt(9, 10, 11).unwrap().and_local_timezone(Utc).unwrap());
//!
//! // July 8 is 188th day of the year 2014 (`o` for "ordinal")
//! assert_eq!(dt, NaiveDate::from_yo_opt(2014, 189)?.and_hms_opt(9, 10, 11).unwrap().and_utc());
//! // July 8 is Tuesday in ISO week 28 of the year 2014.
//! assert_eq!(dt, NaiveDate::from_isoywd_opt(2014, 28, Weekday::Tue)?.and_hms_opt(9, 10, 11).unwrap().and_utc());
//!
//! let dt = NaiveDate::from_ymd_opt(2014, 7, 8)?.and_hms_milli_opt(9, 10, 11, 12).unwrap().and_local_timezone(Utc).unwrap(); // `2014-07-08T09:10:11.012Z`
//! assert_eq!(dt, NaiveDate::from_ymd_opt(2014, 7, 8)?.and_hms_micro_opt(9, 10, 11, 12_000).unwrap().and_local_timezone(Utc).unwrap());
//! assert_eq!(dt, NaiveDate::from_ymd_opt(2014, 7, 8)?.and_hms_nano_opt(9, 10, 11, 12_000_000).unwrap().and_local_timezone(Utc).unwrap());
//! let dt = NaiveDate::from_ymd_opt(2014, 7, 8).unwrap().and_hms_milli_opt(9, 10, 11, 12).unwrap().and_local_timezone(Utc).unwrap(); // `2014-07-08T09:10:11.012Z`
//! assert_eq!(dt, NaiveDate::from_ymd_opt(2014, 7, 8).unwrap().and_hms_micro_opt(9, 10, 11, 12_000).unwrap().and_local_timezone(Utc).unwrap());
//! assert_eq!(dt, NaiveDate::from_ymd_opt(2014, 7, 8).unwrap().and_hms_nano_opt(9, 10, 11, 12_000_000).unwrap().and_local_timezone(Utc).unwrap());
//!
//! // dynamic verification
//! assert_eq!(Utc.with_ymd_and_hms(2014, 7, 8, 21, 15, 33),
//! LocalResult::Single(NaiveDate::from_ymd_opt(2014, 7, 8)?.and_hms_opt(21, 15, 33).unwrap().and_utc()));
//! LocalResult::Single(NaiveDate::from_ymd_opt(2014, 7, 8).unwrap().and_hms_opt(21, 15, 33).unwrap().and_utc()));
//! assert_eq!(Utc.with_ymd_and_hms(2014, 7, 8, 80, 15, 33), LocalResult::None);
//! assert_eq!(Utc.with_ymd_and_hms(2014, 7, 38, 21, 15, 33), LocalResult::None);
//!
Expand Down
57 changes: 28 additions & 29 deletions src/naive/date/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,46 +141,42 @@ impl NaiveDate {

/// Makes a new `NaiveDate` from year and packed month-day-flags.
/// Does not check whether the flags are correct for the provided year.
const fn from_mdf(year: i32, mdf: Mdf) -> Option<NaiveDate> {
#[inline]
const fn from_mdf(year: i32, mdf: Mdf) -> Result<NaiveDate, Error> {
if year < MIN_YEAR || year > MAX_YEAR {
return None; // Out-of-range
return Err(Error::OutOfRange);
}
Some(NaiveDate::from_yof((year << 13) | try_opt!(ok!(mdf.ordinal_and_flags()))))
Ok(NaiveDate::from_yof((year << 13) | try_err!(mdf.ordinal_and_flags())))
}

/// Makes a new `NaiveDate` from the [calendar date](#calendar-date)
/// (year, month and day).
///
/// # Errors
///
/// Returns `None` if:
/// - The specified calendar day does not exist (for example 2023-04-31).
/// - The value for `month` or `day` is invalid.
/// - `year` is out of range for `NaiveDate`.
/// This method returns:
/// - [`Error::DoesNotExist`] if the specified calendar day does not exist (for example
/// 2023-04-31).
/// - [`Error::InvalidArgument`] if the value for `month` or `day` is invalid.
/// - [`Error::OutOfRange`] if `year` is out of range for a `NaiveDate`.
///
/// # Example
///
/// ```
/// use chrono::NaiveDate;
/// use chrono::{Error, NaiveDate};
///
/// let from_ymd_opt = NaiveDate::from_ymd_opt;
///
/// assert!(from_ymd_opt(2015, 3, 14).is_some());
/// assert!(from_ymd_opt(2015, 0, 14).is_none());
/// assert!(from_ymd_opt(2015, 2, 29).is_none());
/// assert!(from_ymd_opt(-4, 2, 29).is_some()); // 5 BCE is a leap year
/// assert!(from_ymd_opt(400000, 1, 1).is_none());
/// assert!(from_ymd_opt(-400000, 1, 1).is_none());
/// assert!(from_ymd_opt(2015, 3, 14).is_ok());
/// assert_eq!(from_ymd_opt(2015, 0, 14), Err(Error::InvalidArgument));
/// assert_eq!(from_ymd_opt(2015, 2, 29), Err(Error::DoesNotExist));
/// assert!(from_ymd_opt(-4, 2, 29).is_ok()); // 5 BCE is a leap year
/// assert_eq!(from_ymd_opt(400000, 1, 1), Err(Error::OutOfRange));
/// assert_eq!(from_ymd_opt(-400000, 1, 1), Err(Error::OutOfRange));
/// ```
#[must_use]
pub const fn from_ymd_opt(year: i32, month: u32, day: u32) -> Option<NaiveDate> {
pub const fn from_ymd_opt(year: i32, month: u32, day: u32) -> Result<NaiveDate, Error> {
let flags = YearFlags::from_year(year);

if let Ok(mdf) = Mdf::new(month, day, flags) {
NaiveDate::from_mdf(year, mdf)
} else {
None
}
NaiveDate::from_mdf(year, try_err!(Mdf::new(month, day, flags)))
}

/// Makes a new `NaiveDate` from the [ordinal date](#ordinal-date)
Expand Down Expand Up @@ -346,7 +342,7 @@ impl NaiveDate {
/// ```
/// use chrono::{NaiveDate, Weekday};
/// assert_eq!(NaiveDate::from_weekday_of_month_opt(2017, 3, Weekday::Fri, 2),
/// NaiveDate::from_ymd_opt(2017, 3, 10))
/// NaiveDate::from_ymd_opt(2017, 3, 10).ok())
/// ```
#[must_use]
pub const fn from_weekday_of_month_opt(
Expand All @@ -358,10 +354,10 @@ impl NaiveDate {
if n == 0 {
return None;
}
let first = try_opt!(NaiveDate::from_ymd_opt(year, month, 1)).weekday();
let first = try_opt!(ok!(NaiveDate::from_ymd_opt(year, month, 1))).weekday();
let first_to_dow = (7 + weekday.number_from_monday() - first.number_from_monday()) % 7;
let day = (n - 1) as u32 * 7 + first_to_dow + 1;
NaiveDate::from_ymd_opt(year, month, day)
ok!(NaiveDate::from_ymd_opt(year, month, day))
}

/// Parses a string with the specified format string and returns a new `NaiveDate`.
Expand Down Expand Up @@ -546,7 +542,7 @@ impl NaiveDate {
day = day_max;
};

NaiveDate::from_mdf(year, try_opt!(ok!(Mdf::new(month as u32, day, flags))))
ok!(NaiveDate::from_mdf(year, try_opt!(ok!(Mdf::new(month as u32, day, flags)))))
}

/// Add a duration in [`Days`] to the date
Expand Down Expand Up @@ -853,7 +849,7 @@ impl NaiveDate {
let new_shifted_ordinal = (self.yof() & ORDINAL_MASK) - (1 << 4);
match new_shifted_ordinal > 0 {
true => Some(NaiveDate::from_yof(self.yof() & !ORDINAL_MASK | new_shifted_ordinal)),
false => NaiveDate::from_ymd_opt(self.year() - 1, 12, 31),
false => ok!(NaiveDate::from_ymd_opt(self.year() - 1, 12, 31)),
}
}

Expand Down Expand Up @@ -1467,7 +1463,7 @@ impl Datelike for NaiveDate {
let flags = YearFlags::from_year(year);
let mdf = mdf.with_flags(flags);

NaiveDate::from_mdf(year, mdf)
ok!(NaiveDate::from_mdf(year, mdf))
}

/// Makes a new `NaiveDate` with the month number (starting from 1) changed.
Expand Down Expand Up @@ -2151,7 +2147,10 @@ where
Some(NaiveDate::from_ymd_opt(2016, 7, 8).unwrap())
);
assert_eq!(from_str(r#""2016-7-8""#).ok(), Some(NaiveDate::from_ymd_opt(2016, 7, 8).unwrap()));
assert_eq!(from_str(r#""+002016-07-08""#).ok(), NaiveDate::from_ymd_opt(2016, 7, 8));
assert_eq!(
from_str(r#""+002016-07-08""#).ok(),
Some(NaiveDate::from_ymd_opt(2016, 7, 8).unwrap())
);
assert_eq!(from_str(r#""0000-01-01""#).ok(), Some(NaiveDate::from_ymd_opt(0, 1, 1).unwrap()));
assert_eq!(from_str(r#""0-1-1""#).ok(), Some(NaiveDate::from_ymd_opt(0, 1, 1).unwrap()));
assert_eq!(
Expand Down
42 changes: 21 additions & 21 deletions src/naive/date/tests.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use super::{Days, Months, NaiveDate, MAX_YEAR, MIN_YEAR};
use crate::naive::internals::{YearFlags, A, AG, B, BA, C, CB, D, DC, E, ED, F, FE, G, GF};
use crate::{Datelike, TimeDelta, Weekday};
use crate::{Datelike, Error, TimeDelta, Weekday};

// as it is hard to verify year flags in `NaiveDate::MIN` and `NaiveDate::MAX`,
// we use a separate run-time test.
Expand Down Expand Up @@ -153,16 +153,16 @@ fn test_readme_doomsday() {
fn test_date_from_ymd() {
let ymd_opt = NaiveDate::from_ymd_opt;

assert!(ymd_opt(2012, 0, 1).is_none());
assert!(ymd_opt(2012, 1, 1).is_some());
assert!(ymd_opt(2012, 2, 29).is_some());
assert!(ymd_opt(2014, 2, 29).is_none());
assert!(ymd_opt(2014, 3, 0).is_none());
assert!(ymd_opt(2014, 3, 1).is_some());
assert!(ymd_opt(2014, 3, 31).is_some());
assert!(ymd_opt(2014, 3, 32).is_none());
assert!(ymd_opt(2014, 12, 31).is_some());
assert!(ymd_opt(2014, 13, 1).is_none());
assert_eq!(ymd_opt(2012, 0, 1), Err(Error::InvalidArgument));
assert!(ymd_opt(2012, 1, 1).is_ok());
assert!(ymd_opt(2012, 2, 29).is_ok());
assert_eq!(ymd_opt(2014, 2, 29), Err(Error::DoesNotExist));
assert_eq!(ymd_opt(2014, 3, 0), Err(Error::InvalidArgument));
assert!(ymd_opt(2014, 3, 1).is_ok());
assert!(ymd_opt(2014, 3, 31).is_ok());
assert_eq!(ymd_opt(2014, 3, 32), Err(Error::InvalidArgument));
assert!(ymd_opt(2014, 12, 31).is_ok());
assert_eq!(ymd_opt(2014, 13, 1), Err(Error::InvalidArgument));
}

#[test]
Expand Down Expand Up @@ -255,7 +255,7 @@ fn test_date_from_isoywd_and_iso_week() {
for month in 1..13 {
for day in 1..32 {
let d = NaiveDate::from_ymd_opt(year, month, day);
if let Some(d) = d {
if let Ok(d) = d {
let w = d.iso_week();
let d_ = NaiveDate::from_isoywd_opt(w.year(), w.week(), d.weekday());
assert_eq!(d, d_.unwrap());
Expand Down Expand Up @@ -667,13 +667,13 @@ fn test_date_parse_from_str() {
assert!(NaiveDate::parse_from_str("2014", "%Y").is_err()); // insufficient

assert_eq!(
NaiveDate::parse_from_str("2020-01-0", "%Y-%W-%w").ok(),
NaiveDate::from_ymd_opt(2020, 1, 12),
NaiveDate::parse_from_str("2020-01-0", "%Y-%W-%w").unwrap(),
NaiveDate::from_ymd_opt(2020, 1, 12).unwrap(),
);

assert_eq!(
NaiveDate::parse_from_str("2019-01-0", "%Y-%W-%w").ok(),
NaiveDate::from_ymd_opt(2019, 1, 13),
NaiveDate::parse_from_str("2019-01-0", "%Y-%W-%w").unwrap(),
NaiveDate::from_ymd_opt(2019, 1, 13).unwrap(),
);
}

Expand All @@ -700,12 +700,12 @@ fn test_weeks_from() {
// tests per: https://github.com/chronotope/chrono/issues/961
// these internally use `weeks_from` via the parsing infrastructure
assert_eq!(
NaiveDate::parse_from_str("2020-01-0", "%Y-%W-%w").ok(),
NaiveDate::from_ymd_opt(2020, 1, 12),
NaiveDate::parse_from_str("2020-01-0", "%Y-%W-%w").unwrap(),
NaiveDate::from_ymd_opt(2020, 1, 12).unwrap(),
);
assert_eq!(
NaiveDate::parse_from_str("2019-01-0", "%Y-%W-%w").ok(),
NaiveDate::from_ymd_opt(2019, 1, 13),
NaiveDate::parse_from_str("2019-01-0", "%Y-%W-%w").unwrap(),
NaiveDate::from_ymd_opt(2019, 1, 13).unwrap(),
);

// direct tests
Expand All @@ -730,7 +730,7 @@ fn test_weeks_from() {
] {
assert_eq!(
NaiveDate::from_ymd_opt(*y, 1, 1).map(|d| d.weeks_from(*day)),
Some(if day == starts_on { 1 } else { 0 })
Ok(if day == starts_on { 1 } else { 0 })
);

// last day must always be in week 52 or 53
Expand Down
4 changes: 2 additions & 2 deletions src/naive/datetime/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use crate::naive::{Days, IsoWeek, NaiveDate, NaiveTime};
use crate::offset::Utc;
use crate::time_delta::NANOS_PER_SEC;
use crate::{
expect, try_opt, DateTime, Datelike, FixedOffset, LocalResult, Months, TimeDelta, TimeZone,
expect, ok, try_opt, DateTime, Datelike, FixedOffset, LocalResult, Months, TimeDelta, TimeZone,
Timelike, Weekday,
};

Expand Down Expand Up @@ -1046,7 +1046,7 @@ impl NaiveDateTime {

/// The Unix Epoch, 1970-01-01 00:00:00.
pub const UNIX_EPOCH: Self =
expect!(NaiveDate::from_ymd_opt(1970, 1, 1), "").and_time(NaiveTime::MIN);
expect!(ok!(NaiveDate::from_ymd_opt(1970, 1, 1)), "").and_time(NaiveTime::MIN);
}

impl From<NaiveDate> for NaiveDateTime {
Expand Down
1 change: 1 addition & 0 deletions src/offset/local/windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ fn system_time_from_naive_date_time(st: SYSTEMTIME, year: i32) -> Option<NaiveDa
let day_of_week = Weekday::try_from(u8::try_from(st.wDayOfWeek).ok()?).ok()?.pred();
if st.wYear != 0 {
return NaiveDate::from_ymd_opt(st.wYear as i32, st.wMonth as u32, st.wDay as u32)
.ok()
.map(|d| d.and_time(time));
}
let date = if let Some(date) =
Expand Down
7 changes: 3 additions & 4 deletions src/offset/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,11 +130,10 @@ pub trait TimeZone: Sized + Clone {
min: u32,
sec: u32,
) -> LocalResult<DateTime<Self>> {
match NaiveDate::from_ymd_opt(year, month, day)
.and_then(|d| d.and_hms_opt(hour, min, sec).ok())
match NaiveDate::from_ymd_opt(year, month, day).and_then(|d| d.and_hms_opt(hour, min, sec))
{
Some(dt) => self.from_local_datetime(&dt),
None => LocalResult::None,
Ok(dt) => self.from_local_datetime(&dt),
Err(_) => LocalResult::None,
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ pub trait Datelike: Sized {
///
/// Don't combine multiple `Datelike::with_*` methods. The intermediate value may not exist.
/// ```
/// use chrono::{NaiveDate, Datelike};
/// use chrono::{Error, NaiveDate, Datelike};
///
/// fn with_year_month(date: NaiveDate, year: i32, month: u32) -> Option<NaiveDate> {
/// date.with_year(year)?.with_month(month)
Expand All @@ -155,7 +155,7 @@ pub trait Datelike: Sized {
/// assert!(with_year_month(d, 2019, 1).is_none()); // fails because of invalid intermediate value
///
/// // Correct version:
/// fn with_year_month_fixed(date: NaiveDate, year: i32, month: u32) -> Option<NaiveDate> {
/// fn with_year_month_fixed(date: NaiveDate, year: i32, month: u32) -> Result<NaiveDate, Error> {
/// NaiveDate::from_ymd_opt(year, month, date.day())
/// }
/// let d = NaiveDate::from_ymd_opt(2020, 2, 29).unwrap();
Expand Down

0 comments on commit d0d929f

Please sign in to comment.