Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

0.5: Converting the API to return Results (part 1) #1410

Merged
merged 3 commits into from
Feb 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//! Error type
use core::fmt;

/// Error type for date and time operations.
#[non_exhaustive]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum Error {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of adding this at the beginning, I would prefer to add variants one-by-one as they are needed by the changes you make.

Also, please order the variants alphabetically.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, that can work for me.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One more nit, I think it would now make sense to just inline this commit into the next one, so we don't introduce unused code here.

/// A date or datetime does not exist.
///
/// Examples are:
/// - April 31,
/// - February 29 in a non-leap year,
/// - a time that falls in the gap created by moving the clock forward during a DST transition,
/// - a leap second on a non-minute boundary.
DoesNotExist,

/// One or more of the arguments to a function are invalid.
///
/// An example is creating a `NaiveTime` with 25 as the hour value.
InvalidArgument,
}

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"),

Check warning on line 27 in src/error.rs

View check run for this annotation

Codecov / codecov/patch

src/error.rs#L24-L27

Added lines #L24 - L27 were not covered by tests
}
}

Check warning on line 29 in src/error.rs

View check run for this annotation

Codecov / codecov/patch

src/error.rs#L29

Added line #L29 was not covered by tests
}

#[cfg(feature = "std")]
impl std::error::Error for Error {}
2 changes: 1 addition & 1 deletion src/format/parsed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -526,7 +526,7 @@ impl Parsed {
None => 0,
};

NaiveTime::from_hms_nano(hour, minute, second, nano).ok_or(OUT_OF_RANGE)
NaiveTime::from_hms_nano(hour, minute, second, nano).map_err(|_| OUT_OF_RANGE)
}

/// Returns a parsed naive date and time out of given fields,
Expand Down
45 changes: 38 additions & 7 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)?.and_local_timezone(Utc).unwrap());
//! assert_eq!(dt, NaiveDate::from_ymd_opt(2014, 7, 8)?.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)?.and_utc());
//! 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)?.and_utc());
//! 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)?.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)?.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)?.and_local_timezone(Utc).unwrap());
//! 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());
//!
//! // 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)?.and_utc()));
//! LocalResult::Single(NaiveDate::from_ymd_opt(2014, 7, 8)?.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 Expand Up @@ -480,6 +480,9 @@ pub mod prelude {
mod datetime;
pub use datetime::DateTime;

pub(crate) mod error;
pub use error::Error;

pub mod format;
/// L10n locales.
#[cfg(feature = "unstable-locales")]
Expand Down Expand Up @@ -591,6 +594,18 @@ macro_rules! try_opt {
};
}

/// Workaround because `?` is not (yet) available in const context.
#[macro_export]
#[doc(hidden)]
macro_rules! try_err {
($e:expr) => {
match $e {
Ok(v) => v,
Err(e) => return Err(e),
}
};
}

/// Workaround because `.expect()` is not (yet) available in const context.
#[macro_export]
#[doc(hidden)]
Expand All @@ -602,3 +617,19 @@ macro_rules! expect {
}
};
}

/// Workaround because `.ok()` is not (yet) available in const context.
///
/// FIXME: This is a temporary macro, intended to be used while we convert our API from returning
/// `Option` to `Result` piece-by-piece. Remove when that work is done.
#[macro_export]
#[allow(unused)]
#[doc(hidden)]
macro_rules! ok {
($e:expr) => {
match $e {
Ok(v) => Some(v),
Err(_) => None,
}
};
}
101 changes: 53 additions & 48 deletions src/naive/date.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ use crate::format::{
};
use crate::month::Months;
use crate::naive::{IsoWeek, NaiveDateTime, NaiveTime};
use crate::{expect, try_opt};
use crate::{Datelike, TimeDelta, Weekday};
use crate::{expect, try_err, try_opt};
use crate::{Datelike, Error, TimeDelta, Weekday};

use super::internals::{self, DateImpl, Mdf, Of, YearFlags};
use super::isoweek;
Expand Down Expand Up @@ -752,24 +752,23 @@ impl NaiveDate {
///
/// # Errors
///
/// Returns `None` on invalid hour, minute and/or second.
/// Returns [`Error::InvalidArgument`] on invalid hour, minute and/or second.
///
/// # Example
///
/// ```
/// use chrono::NaiveDate;
/// use chrono::{Error, NaiveDate};
///
/// let d = NaiveDate::from_ymd_opt(2015, 6, 3).unwrap();
/// assert!(d.and_hms_opt(12, 34, 56).is_some());
/// assert!(d.and_hms_opt(12, 34, 60).is_none()); // use `and_hms_milli_opt` instead
/// assert!(d.and_hms_opt(12, 60, 56).is_none());
/// assert!(d.and_hms_opt(24, 34, 56).is_none());
/// assert!(d.and_hms_opt(12, 34, 56).is_ok());
/// assert_eq!(d.and_hms_opt(12, 34, 60), Err(Error::InvalidArgument));
/// assert_eq!(d.and_hms_opt(12, 60, 56), Err(Error::InvalidArgument));
/// assert_eq!(d.and_hms_opt(24, 34, 56), Err(Error::InvalidArgument));
/// ```
#[inline]
#[must_use]
pub const fn and_hms_opt(&self, hour: u32, min: u32, sec: u32) -> Option<NaiveDateTime> {
let time = try_opt!(NaiveTime::from_hms(hour, min, sec));
Some(self.and_time(time))
pub const fn and_hms_opt(&self, hour: u32, min: u32, sec: u32) -> Result<NaiveDateTime, Error> {
let time = try_err!(NaiveTime::from_hms(hour, min, sec));
Ok(self.and_time(time))
}

/// Makes a new `NaiveDateTime` from the current date, hour, minute, second and millisecond.
Expand All @@ -779,32 +778,34 @@ impl NaiveDate {
///
/// # Errors
///
/// Returns `None` on invalid hour, minute, second and/or millisecond.
/// Returns [`Error::InvalidArgument`] on invalid hour, minute, second and/or millisecond.
///
/// Returns [`Error::DoesNotExist`] if the millisecond part to represent a leap second is not on
/// a minute boundary.
///
/// # Example
///
/// ```
/// use chrono::NaiveDate;
/// use chrono::{Error, NaiveDate};
///
/// let d = NaiveDate::from_ymd_opt(2015, 6, 3).unwrap();
/// assert!(d.and_hms_milli_opt(12, 34, 56, 789).is_some());
/// assert!(d.and_hms_milli_opt(12, 34, 59, 1_789).is_some()); // leap second
/// assert!(d.and_hms_milli_opt(12, 34, 59, 2_789).is_none());
/// assert!(d.and_hms_milli_opt(12, 34, 60, 789).is_none());
/// assert!(d.and_hms_milli_opt(12, 60, 56, 789).is_none());
/// assert!(d.and_hms_milli_opt(24, 34, 56, 789).is_none());
/// assert!(d.and_hms_milli_opt(12, 34, 56, 789).is_ok());
/// assert!(d.and_hms_milli_opt(12, 34, 59, 1_789).is_ok()); // leap second
/// assert_eq!(d.and_hms_milli_opt(12, 34, 59, 2_789), Err(Error::InvalidArgument));
/// assert_eq!(d.and_hms_milli_opt(12, 34, 60, 789), Err(Error::InvalidArgument));
/// assert_eq!(d.and_hms_milli_opt(12, 60, 56, 789), Err(Error::InvalidArgument));
/// assert_eq!(d.and_hms_milli_opt(24, 34, 56, 789), Err(Error::InvalidArgument));
/// ```
#[inline]
#[must_use]
pub const fn and_hms_milli_opt(
&self,
hour: u32,
min: u32,
sec: u32,
milli: u32,
) -> Option<NaiveDateTime> {
let time = try_opt!(NaiveTime::from_hms_milli(hour, min, sec, milli));
Some(self.and_time(time))
) -> Result<NaiveDateTime, Error> {
let time = try_err!(NaiveTime::from_hms_milli(hour, min, sec, milli));
Ok(self.and_time(time))
}

/// Makes a new `NaiveDateTime` from the current date, hour, minute, second and microsecond.
Expand All @@ -814,32 +815,34 @@ impl NaiveDate {
///
/// # Errors
///
/// Returns `None` on invalid hour, minute, second and/or microsecond.
/// Returns [`Error::InvalidArgument`] on invalid hour, minute, second and/or microsecond.
///
/// Returns [`Error::DoesNotExist`] if the microsecond part to represent a leap second is not on
/// a minute boundary.
///
/// # Example
///
/// ```
/// use chrono::NaiveDate;
/// use chrono::{Error, NaiveDate};
///
/// let d = NaiveDate::from_ymd_opt(2015, 6, 3).unwrap();
/// assert!(d.and_hms_micro_opt(12, 34, 56, 789_012).is_some());
/// assert!(d.and_hms_micro_opt(12, 34, 59, 1_789_012).is_some()); // leap second
/// assert!(d.and_hms_micro_opt(12, 34, 59, 2_789_012).is_none());
/// assert!(d.and_hms_micro_opt(12, 34, 60, 789_012).is_none());
/// assert!(d.and_hms_micro_opt(12, 60, 56, 789_012).is_none());
/// assert!(d.and_hms_micro_opt(24, 34, 56, 789_012).is_none());
/// assert!(d.and_hms_micro_opt(12, 34, 56, 789_012).is_ok());
/// assert!(d.and_hms_micro_opt(12, 34, 59, 1_789_012).is_ok()); // leap second
/// assert_eq!(d.and_hms_micro_opt(12, 34, 59, 2_789_012), Err(Error::InvalidArgument));
/// assert_eq!(d.and_hms_micro_opt(12, 34, 60, 789_012), Err(Error::InvalidArgument));
/// assert_eq!(d.and_hms_micro_opt(12, 60, 56, 789_012), Err(Error::InvalidArgument));
/// assert_eq!(d.and_hms_micro_opt(24, 34, 56, 789_012), Err(Error::InvalidArgument));
/// ```
#[inline]
#[must_use]
pub const fn and_hms_micro_opt(
&self,
hour: u32,
min: u32,
sec: u32,
micro: u32,
) -> Option<NaiveDateTime> {
let time = try_opt!(NaiveTime::from_hms_micro(hour, min, sec, micro));
Some(self.and_time(time))
) -> Result<NaiveDateTime, Error> {
let time = try_err!(NaiveTime::from_hms_micro(hour, min, sec, micro));
Ok(self.and_time(time))
}

/// Makes a new `NaiveDateTime` from the current date, hour, minute, second and nanosecond.
Expand All @@ -849,32 +852,34 @@ impl NaiveDate {
///
/// # Errors
///
/// Returns `None` on invalid hour, minute, second and/or nanosecond.
/// Returns [`Error::InvalidArgument`] on invalid hour, minute, second and/or nanosecond.
///
/// Returns [`Error::DoesNotExist`] if the nanosecond part to represent a leap second is not on
/// a minute boundary.
///
/// # Example
///
/// ```
/// use chrono::NaiveDate;
/// use chrono::{Error, NaiveDate};
///
/// let d = NaiveDate::from_ymd_opt(2015, 6, 3).unwrap();
/// assert!(d.and_hms_nano_opt(12, 34, 56, 789_012_345).is_some());
/// assert!(d.and_hms_nano_opt(12, 34, 59, 1_789_012_345).is_some()); // leap second
/// assert!(d.and_hms_nano_opt(12, 34, 59, 2_789_012_345).is_none());
/// assert!(d.and_hms_nano_opt(12, 34, 60, 789_012_345).is_none());
/// assert!(d.and_hms_nano_opt(12, 60, 56, 789_012_345).is_none());
/// assert!(d.and_hms_nano_opt(24, 34, 56, 789_012_345).is_none());
/// assert!(d.and_hms_nano_opt(12, 34, 56, 789_012_345).is_ok());
/// assert!(d.and_hms_nano_opt(12, 34, 59, 1_789_012_345).is_ok()); // leap second
/// assert_eq!(d.and_hms_nano_opt(12, 34, 59, 2_789_012_345), Err(Error::InvalidArgument));
/// assert_eq!(d.and_hms_nano_opt(12, 34, 60, 789_012_345), Err(Error::InvalidArgument));
/// assert_eq!(d.and_hms_nano_opt(12, 60, 56, 789_012_345), Err(Error::InvalidArgument));
/// assert_eq!(d.and_hms_nano_opt(24, 34, 56, 789_012_345), Err(Error::InvalidArgument));
/// ```
#[inline]
#[must_use]
pub const fn and_hms_nano_opt(
&self,
hour: u32,
min: u32,
sec: u32,
nano: u32,
) -> Option<NaiveDateTime> {
let time = try_opt!(NaiveTime::from_hms_nano(hour, min, sec, nano));
Some(self.and_time(time))
) -> Result<NaiveDateTime, Error> {
let time = try_err!(NaiveTime::from_hms_nano(hour, min, sec, nano));
Ok(self.and_time(time))
}

/// Returns the packed month-day-flags.
Expand Down
2 changes: 1 addition & 1 deletion src/naive/datetime/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ impl NaiveDateTime {
NaiveDate::from_num_days_from_ce_opt(try_opt!((days as i32).checked_add(719_163)));
let time = NaiveTime::from_num_seconds_from_midnight(secs as u32, nsecs);
match (date, time) {
(Some(date), Some(time)) => Some(NaiveDateTime { date, time }),
(Some(date), Ok(time)) => Some(NaiveDateTime { date, time }),
(_, _) => None,
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/naive/datetime/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -507,7 +507,7 @@ fn test_and_utc() {
#[test]
fn test_checked_add_offset() {
let ymdhmsm = |y, m, d, h, mn, s, mi| {
NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_milli_opt(h, mn, s, mi)
Some(NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_milli_opt(h, mn, s, mi).unwrap())
};

let positive_offset = FixedOffset::east(2 * 60 * 60).unwrap();
Expand All @@ -534,7 +534,7 @@ fn test_checked_add_offset() {
#[test]
fn test_checked_sub_offset() {
let ymdhmsm = |y, m, d, h, mn, s, mi| {
NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_milli_opt(h, mn, s, mi)
Some(NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_milli_opt(h, mn, s, mi).unwrap())
};

let positive_offset = FixedOffset::east(2 * 60 * 60).unwrap();
Expand Down
Loading
Loading