From c7e0adede81d0d54338f7d78d27d5502c1698863 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 4 Feb 2024 11:13:42 +0100 Subject: [PATCH 1/3] Make `NaiveTime::from_hms*` return `Result` --- src/error.rs | 33 +++++++++ src/format/parsed.rs | 2 +- src/lib.rs | 31 ++++++++ src/naive/date.rs | 10 +-- src/naive/time/mod.rs | 156 +++++++++++++++++++++++----------------- src/naive/time/tests.rs | 28 ++++---- 6 files changed, 175 insertions(+), 85 deletions(-) create mode 100644 src/error.rs diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000000..2f40a93afe --- /dev/null +++ b/src/error.rs @@ -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 { + /// 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"), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for Error {} diff --git a/src/format/parsed.rs b/src/format/parsed.rs index d7992d788e..2bfbf3f285 100644 --- a/src/format/parsed.rs +++ b/src/format/parsed.rs @@ -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, diff --git a/src/lib.rs b/src/lib.rs index 84f4ec19cb..2c0622717c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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")] @@ -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)] @@ -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, + } + }; +} diff --git a/src/naive/date.rs b/src/naive/date.rs index 6ffb379065..8fd4227bc1 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -24,7 +24,7 @@ use crate::format::{ }; use crate::month::Months; use crate::naive::{IsoWeek, NaiveDateTime, NaiveTime}; -use crate::{expect, try_opt}; +use crate::{expect, ok, try_opt}; use crate::{Datelike, TimeDelta, Weekday}; use super::internals::{self, DateImpl, Mdf, Of, YearFlags}; @@ -768,7 +768,7 @@ impl NaiveDate { #[inline] #[must_use] pub const fn and_hms_opt(&self, hour: u32, min: u32, sec: u32) -> Option { - let time = try_opt!(NaiveTime::from_hms(hour, min, sec)); + let time = try_opt!(ok!(NaiveTime::from_hms(hour, min, sec))); Some(self.and_time(time)) } @@ -803,7 +803,7 @@ impl NaiveDate { sec: u32, milli: u32, ) -> Option { - let time = try_opt!(NaiveTime::from_hms_milli(hour, min, sec, milli)); + let time = try_opt!(ok!(NaiveTime::from_hms_milli(hour, min, sec, milli))); Some(self.and_time(time)) } @@ -838,7 +838,7 @@ impl NaiveDate { sec: u32, micro: u32, ) -> Option { - let time = try_opt!(NaiveTime::from_hms_micro(hour, min, sec, micro)); + let time = try_opt!(ok!(NaiveTime::from_hms_micro(hour, min, sec, micro))); Some(self.and_time(time)) } @@ -873,7 +873,7 @@ impl NaiveDate { sec: u32, nano: u32, ) -> Option { - let time = try_opt!(NaiveTime::from_hms_nano(hour, min, sec, nano)); + let time = try_opt!(ok!(NaiveTime::from_hms_nano(hour, min, sec, nano))); Some(self.and_time(time)) } diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index 1836d39cb0..928ed9adc3 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -12,14 +12,14 @@ use core::{fmt, str}; #[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] use rkyv::{Archive, Deserialize, Serialize}; +use crate::expect; #[cfg(feature = "alloc")] use crate::format::DelayedFormat; use crate::format::{ parse, parse_and_remainder, write_hundreds, Fixed, Item, Numeric, Pad, ParseError, ParseResult, Parsed, StrftimeItems, }; -use crate::{expect, try_opt}; -use crate::{FixedOffset, TimeDelta, Timelike}; +use crate::{Error, FixedOffset, TimeDelta, Timelike}; #[cfg(feature = "serde")] mod serde; @@ -230,29 +230,28 @@ impl arbitrary::Arbitrary<'_> for NaiveTime { impl NaiveTime { /// Makes a new `NaiveTime` from hour, minute and second. /// - /// The millisecond part is allowed to exceed 1,000,000,000 in order to represent a - /// [leap second](#leap-second-handling), but only when `sec == 59`. + /// No [leap second](./struct.NaiveTime.html#leap-second-handling) is allowed here; + /// use `NaiveTime::from_hms_*` methods with a subsecond parameter instead. /// /// # Errors /// - /// Returns `None` on invalid hour, minute and/or second. + /// Returns [`Error::InvalidArgument`] on invalid hour, minute and/or second. /// /// # Example /// /// ``` - /// use chrono::NaiveTime; + /// use chrono::{Error, NaiveTime}; /// /// let from_hms = NaiveTime::from_hms; /// - /// assert!(from_hms(0, 0, 0).is_some()); - /// assert!(from_hms(23, 59, 59).is_some()); - /// assert!(from_hms(24, 0, 0).is_none()); - /// assert!(from_hms(23, 60, 0).is_none()); - /// assert!(from_hms(23, 59, 60).is_none()); + /// assert!(from_hms(0, 0, 0).is_ok()); + /// assert!(from_hms(23, 59, 59).is_ok()); + /// assert_eq!(from_hms(24, 0, 0), Err(Error::InvalidArgument)); + /// assert_eq!(from_hms(23, 60, 0), Err(Error::InvalidArgument)); + /// assert_eq!(from_hms(23, 59, 60), Err(Error::InvalidArgument)); /// ``` #[inline] - #[must_use] - pub const fn from_hms(hour: u32, min: u32, sec: u32) -> Option { + pub const fn from_hms(hour: u32, min: u32, sec: u32) -> Result { NaiveTime::from_hms_nano(hour, min, sec, 0) } @@ -263,28 +262,38 @@ impl NaiveTime { /// /// # 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::NaiveTime; - /// - /// let from_hmsm_opt = NaiveTime::from_hms_milli; - /// - /// assert!(from_hmsm_opt(0, 0, 0, 0).is_some()); - /// assert!(from_hmsm_opt(23, 59, 59, 999).is_some()); - /// assert!(from_hmsm_opt(23, 59, 59, 1_999).is_some()); // a leap second after 23:59:59 - /// assert!(from_hmsm_opt(24, 0, 0, 0).is_none()); - /// assert!(from_hmsm_opt(23, 60, 0, 0).is_none()); - /// assert!(from_hmsm_opt(23, 59, 60, 0).is_none()); - /// assert!(from_hmsm_opt(23, 59, 59, 2_000).is_none()); + /// use chrono::{Error, NaiveTime}; + /// + /// let from_hms_milli = NaiveTime::from_hms_milli; + /// + /// assert!(from_hms_milli(0, 0, 0, 0).is_ok()); + /// assert!(from_hms_milli(23, 59, 59, 999).is_ok()); + /// assert!(from_hms_milli(23, 59, 59, 1_999).is_ok()); // a leap second after 23:59:59 + /// assert_eq!(from_hms_milli(24, 0, 0, 0), Err(Error::InvalidArgument)); + /// assert_eq!(from_hms_milli(23, 60, 0, 0), Err(Error::InvalidArgument)); + /// assert_eq!(from_hms_milli(23, 59, 60, 0), Err(Error::InvalidArgument)); + /// assert_eq!(from_hms_milli(23, 59, 59, 2_000), Err(Error::InvalidArgument)); + /// assert_eq!(from_hms_milli(23, 59, 30, 1_999), Err(Error::DoesNotExist)); /// ``` #[inline] - #[must_use] - pub const fn from_hms_milli(hour: u32, min: u32, sec: u32, milli: u32) -> Option { - let nano = try_opt!(milli.checked_mul(1_000_000)); - NaiveTime::from_hms_nano(hour, min, sec, nano) + pub const fn from_hms_milli( + hour: u32, + min: u32, + sec: u32, + milli: u32, + ) -> Result { + match milli.checked_mul(1_000_000) { + Some(nano) => NaiveTime::from_hms_nano(hour, min, sec, nano), + None => Err(Error::InvalidArgument), + } } /// Makes a new `NaiveTime` from hour, minute, second and microsecond. @@ -294,28 +303,38 @@ impl NaiveTime { /// /// # 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::NaiveTime; - /// - /// let from_hmsu_opt = NaiveTime::from_hms_micro; - /// - /// assert!(from_hmsu_opt(0, 0, 0, 0).is_some()); - /// assert!(from_hmsu_opt(23, 59, 59, 999_999).is_some()); - /// assert!(from_hmsu_opt(23, 59, 59, 1_999_999).is_some()); // a leap second after 23:59:59 - /// assert!(from_hmsu_opt(24, 0, 0, 0).is_none()); - /// assert!(from_hmsu_opt(23, 60, 0, 0).is_none()); - /// assert!(from_hmsu_opt(23, 59, 60, 0).is_none()); - /// assert!(from_hmsu_opt(23, 59, 59, 2_000_000).is_none()); + /// use chrono::{Error, NaiveTime}; + /// + /// let from_hms_micro = NaiveTime::from_hms_micro; + /// + /// assert!(from_hms_micro(0, 0, 0, 0).is_ok()); + /// assert!(from_hms_micro(23, 59, 59, 999_999).is_ok()); + /// assert!(from_hms_micro(23, 59, 59, 1_999_999).is_ok()); // a leap second after 23:59:59 + /// assert_eq!(from_hms_micro(24, 0, 0, 0), Err(Error::InvalidArgument)); + /// assert_eq!(from_hms_micro(23, 60, 0, 0), Err(Error::InvalidArgument)); + /// assert_eq!(from_hms_micro(23, 59, 60, 0), Err(Error::InvalidArgument)); + /// assert_eq!(from_hms_micro(23, 59, 59, 2_000_000), Err(Error::InvalidArgument)); + /// assert_eq!(from_hms_micro(23, 59, 30, 1_999_999), Err(Error::DoesNotExist)); /// ``` #[inline] - #[must_use] - pub const fn from_hms_micro(hour: u32, min: u32, sec: u32, micro: u32) -> Option { - let nano = try_opt!(micro.checked_mul(1_000)); - NaiveTime::from_hms_nano(hour, min, sec, nano) + pub const fn from_hms_micro( + hour: u32, + min: u32, + sec: u32, + micro: u32, + ) -> Result { + match micro.checked_mul(1_000) { + Some(nano) => NaiveTime::from_hms_nano(hour, min, sec, nano), + None => Err(Error::InvalidArgument), + } } /// Makes a new `NaiveTime` from hour, minute, second and nanosecond. @@ -325,34 +344,41 @@ impl NaiveTime { /// /// # 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::NaiveTime; - /// - /// let from_hmsn_opt = NaiveTime::from_hms_nano; - /// - /// assert!(from_hmsn_opt(0, 0, 0, 0).is_some()); - /// assert!(from_hmsn_opt(23, 59, 59, 999_999_999).is_some()); - /// assert!(from_hmsn_opt(23, 59, 59, 1_999_999_999).is_some()); // a leap second after 23:59:59 - /// assert!(from_hmsn_opt(24, 0, 0, 0).is_none()); - /// assert!(from_hmsn_opt(23, 60, 0, 0).is_none()); - /// assert!(from_hmsn_opt(23, 59, 60, 0).is_none()); - /// assert!(from_hmsn_opt(23, 59, 59, 2_000_000_000).is_none()); + /// use chrono::{Error, NaiveTime}; + /// + /// let from_hms_nano = NaiveTime::from_hms_nano; + /// + /// assert!(from_hms_nano(0, 0, 0, 0).is_ok()); + /// assert!(from_hms_nano(23, 59, 59, 999_999_999).is_ok()); + /// assert!(from_hms_nano(23, 59, 59, 1_999_999_999).is_ok()); // a leap second after 23:59:59 + /// assert_eq!(from_hms_nano(24, 0, 0, 0), Err(Error::InvalidArgument)); + /// assert_eq!(from_hms_nano(23, 60, 0, 0), Err(Error::InvalidArgument)); + /// assert_eq!(from_hms_nano(23, 59, 60, 0), Err(Error::InvalidArgument)); + /// assert_eq!(from_hms_nano(23, 59, 59, 2_000_000_000), Err(Error::InvalidArgument)); + /// assert_eq!(from_hms_nano(23, 59, 30, 1_999_999_999), Err(Error::DoesNotExist)); /// ``` #[inline] - #[must_use] - pub const fn from_hms_nano(hour: u32, min: u32, sec: u32, nano: u32) -> Option { - if (hour >= 24 || min >= 60 || sec >= 60) - || (nano >= 1_000_000_000 && sec != 59) - || nano >= 2_000_000_000 - { - return None; + pub const fn from_hms_nano( + hour: u32, + min: u32, + sec: u32, + nano: u32, + ) -> Result { + if hour >= 24 || min >= 60 || sec >= 60 || nano >= 2_000_000_000 { + return Err(Error::InvalidArgument); + } else if nano >= 1_000_000_000 && sec != 59 { + return Err(Error::DoesNotExist); } let secs = hour * 3600 + min * 60 + sec; - Some(NaiveTime { secs, frac: nano }) + Ok(NaiveTime { secs, frac: nano }) } /// Makes a new `NaiveTime` from the number of seconds since midnight and nanosecond. diff --git a/src/naive/time/tests.rs b/src/naive/time/tests.rs index 29e6df518b..1e24d70db8 100644 --- a/src/naive/time/tests.rs +++ b/src/naive/time/tests.rs @@ -1,46 +1,46 @@ use super::NaiveTime; -use crate::{FixedOffset, TimeDelta, Timelike}; +use crate::{Error, FixedOffset, TimeDelta, Timelike}; #[test] fn test_time_from_hms_milli() { assert_eq!( NaiveTime::from_hms_milli(3, 5, 7, 0), - Some(NaiveTime::from_hms_nano(3, 5, 7, 0).unwrap()) + Ok(NaiveTime::from_hms_nano(3, 5, 7, 0).unwrap()) ); assert_eq!( NaiveTime::from_hms_milli(3, 5, 7, 777), - Some(NaiveTime::from_hms_nano(3, 5, 7, 777_000_000).unwrap()) + Ok(NaiveTime::from_hms_nano(3, 5, 7, 777_000_000).unwrap()) ); assert_eq!( NaiveTime::from_hms_milli(3, 5, 59, 1_999), - Some(NaiveTime::from_hms_nano(3, 5, 59, 1_999_000_000).unwrap()) + Ok(NaiveTime::from_hms_nano(3, 5, 59, 1_999_000_000).unwrap()) ); - assert_eq!(NaiveTime::from_hms_milli(3, 5, 59, 2_000), None); - assert_eq!(NaiveTime::from_hms_milli(3, 5, 59, 5_000), None); // overflow check - assert_eq!(NaiveTime::from_hms_milli(3, 5, 59, u32::MAX), None); + assert_eq!(NaiveTime::from_hms_milli(3, 5, 59, 2_000), Err(Error::InvalidArgument)); + assert_eq!(NaiveTime::from_hms_milli(3, 5, 59, 5_000), Err(Error::InvalidArgument)); // overflow check + assert_eq!(NaiveTime::from_hms_milli(3, 5, 59, u32::MAX), Err(Error::InvalidArgument)); } #[test] fn test_time_from_hms_micro() { assert_eq!( NaiveTime::from_hms_micro(3, 5, 7, 0), - Some(NaiveTime::from_hms_nano(3, 5, 7, 0).unwrap()) + Ok(NaiveTime::from_hms_nano(3, 5, 7, 0).unwrap()) ); assert_eq!( NaiveTime::from_hms_micro(3, 5, 7, 333), - Some(NaiveTime::from_hms_nano(3, 5, 7, 333_000).unwrap()) + Ok(NaiveTime::from_hms_nano(3, 5, 7, 333_000).unwrap()) ); assert_eq!( NaiveTime::from_hms_micro(3, 5, 7, 777_777), - Some(NaiveTime::from_hms_nano(3, 5, 7, 777_777_000).unwrap()) + Ok(NaiveTime::from_hms_nano(3, 5, 7, 777_777_000).unwrap()) ); assert_eq!( NaiveTime::from_hms_micro(3, 5, 59, 1_999_999), - Some(NaiveTime::from_hms_nano(3, 5, 59, 1_999_999_000).unwrap()) + Ok(NaiveTime::from_hms_nano(3, 5, 59, 1_999_999_000).unwrap()) ); - assert_eq!(NaiveTime::from_hms_micro(3, 5, 59, 2_000_000), None); - assert_eq!(NaiveTime::from_hms_micro(3, 5, 59, 5_000_000), None); // overflow check - assert_eq!(NaiveTime::from_hms_micro(3, 5, 59, u32::MAX), None); + assert_eq!(NaiveTime::from_hms_micro(3, 5, 59, 2_000_000), Err(Error::InvalidArgument)); + assert_eq!(NaiveTime::from_hms_micro(3, 5, 59, 5_000_000), Err(Error::InvalidArgument)); // overflow check + assert_eq!(NaiveTime::from_hms_micro(3, 5, 59, u32::MAX), Err(Error::InvalidArgument)); } #[test] From fe8a655d57bc028ebb57c2f9a3bdfeed23a0c393 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 4 Feb 2024 13:14:12 +0100 Subject: [PATCH 2/3] Make `NaiveDate::and_hms*` return `Result` --- src/lib.rs | 14 ++--- src/naive/date.rs | 101 +++++++++++++++++++----------------- src/naive/datetime/tests.rs | 4 +- src/offset/local/mod.rs | 4 +- src/offset/mod.rs | 3 +- 5 files changed, 66 insertions(+), 60 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 2c0622717c..f831a409e8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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); //! diff --git a/src/naive/date.rs b/src/naive/date.rs index 8fd4227bc1..8e3014d7a0 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -24,8 +24,8 @@ use crate::format::{ }; use crate::month::Months; use crate::naive::{IsoWeek, NaiveDateTime, NaiveTime}; -use crate::{expect, ok, 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; @@ -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 { - let time = try_opt!(ok!(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 { + 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. @@ -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 { - let time = try_opt!(ok!(NaiveTime::from_hms_milli(hour, min, sec, milli))); - Some(self.and_time(time)) + ) -> Result { + 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. @@ -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 { - let time = try_opt!(ok!(NaiveTime::from_hms_micro(hour, min, sec, micro))); - Some(self.and_time(time)) + ) -> Result { + 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. @@ -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 { - let time = try_opt!(ok!(NaiveTime::from_hms_nano(hour, min, sec, nano))); - Some(self.and_time(time)) + ) -> Result { + let time = try_err!(NaiveTime::from_hms_nano(hour, min, sec, nano)); + Ok(self.and_time(time)) } /// Returns the packed month-day-flags. diff --git a/src/naive/datetime/tests.rs b/src/naive/datetime/tests.rs index 91f105236a..c6e487003a 100644 --- a/src/naive/datetime/tests.rs +++ b/src/naive/datetime/tests.rs @@ -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(); @@ -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(); diff --git a/src/offset/local/mod.rs b/src/offset/local/mod.rs index bdd9203e89..dad4243de1 100644 --- a/src/offset/local/mod.rs +++ b/src/offset/local/mod.rs @@ -220,7 +220,7 @@ mod tests { // issue #123 let today = Utc::now().date_naive(); - if let Some(dt) = today.and_hms_milli_opt(15, 2, 59, 1000) { + if let Ok(dt) = today.and_hms_milli_opt(15, 2, 59, 1000) { let timestr = dt.time().to_string(); // the OS API may or may not support the leap second, // but there are only two sensible options. @@ -231,7 +231,7 @@ mod tests { ); } - if let Some(dt) = today.and_hms_milli_opt(15, 2, 3, 1234) { + if let Ok(dt) = today.and_hms_milli_opt(15, 2, 3, 1234) { let timestr = dt.time().to_string(); assert!( timestr == "15:02:03.234" || timestr == "15:02:04.234", diff --git a/src/offset/mod.rs b/src/offset/mod.rs index 943bbb2d3f..5a83c85394 100644 --- a/src/offset/mod.rs +++ b/src/offset/mod.rs @@ -130,7 +130,8 @@ pub trait TimeZone: Sized + Clone { min: u32, sec: u32, ) -> LocalResult> { - match NaiveDate::from_ymd_opt(year, month, day).and_then(|d| d.and_hms_opt(hour, min, sec)) + match NaiveDate::from_ymd_opt(year, month, day) + .and_then(|d| d.and_hms_opt(hour, min, sec).ok()) { Some(dt) => self.from_local_datetime(&dt), None => LocalResult::None, From ebf70c9d2bf909ffa593b4c0d479c7ee5bdb7636 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 4 Feb 2024 13:00:53 +0100 Subject: [PATCH 3/3] Make `NaiveTime::from_num_seconds_from_midnight` return `Result` --- src/naive/datetime/mod.rs | 2 +- src/naive/time/mod.rs | 31 ++++++++++++++++++------------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 3a0766ea6f..1062f5e673 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -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, } } diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index 928ed9adc3..907178d2f2 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -388,28 +388,33 @@ impl NaiveTime { /// /// # Errors /// - /// Returns `None` on invalid number of seconds and/or nanosecond. + /// Returns `[`Error::InvalidArgument`]` on invalid number of seconds and/or nanosecond. + /// + /// Returns [`Error::DoesNotExist`] if the nanosecond part to represent a leap second is not on + /// a minute boundary. /// /// # Example /// /// ``` - /// use chrono::NaiveTime; + /// use chrono::{Error, NaiveTime}; /// - /// let from_nsecs_opt = NaiveTime::from_num_seconds_from_midnight; + /// let from_nsecs = NaiveTime::from_num_seconds_from_midnight; /// - /// assert!(from_nsecs_opt(0, 0).is_some()); - /// assert!(from_nsecs_opt(86399, 999_999_999).is_some()); - /// assert!(from_nsecs_opt(86399, 1_999_999_999).is_some()); // a leap second after 23:59:59 - /// assert!(from_nsecs_opt(86_400, 0).is_none()); - /// assert!(from_nsecs_opt(86399, 2_000_000_000).is_none()); + /// assert!(from_nsecs(0, 0).is_ok()); + /// assert!(from_nsecs(86399, 999_999_999).is_ok()); + /// assert!(from_nsecs(86399, 1_999_999_999).is_ok()); // a leap second after 23:59:59 + /// assert_eq!(from_nsecs(86_400, 0), Err(Error::InvalidArgument)); + /// assert_eq!(from_nsecs(86399, 2_000_000_000), Err(Error::InvalidArgument)); + /// assert_eq!(from_nsecs(1, 1_999_999_999), Err(Error::DoesNotExist)); /// ``` #[inline] - #[must_use] - pub const fn from_num_seconds_from_midnight(secs: u32, nano: u32) -> Option { - if secs >= 86_400 || nano >= 2_000_000_000 || (nano >= 1_000_000_000 && secs % 60 != 59) { - return None; + pub const fn from_num_seconds_from_midnight(secs: u32, nano: u32) -> Result { + if secs >= 86_400 || nano >= 2_000_000_000 { + return Err(Error::InvalidArgument); + } else if nano >= 1_000_000_000 && secs % 60 != 59 { + return Err(Error::DoesNotExist); } - Some(NaiveTime { secs, frac: nano }) + Ok(NaiveTime { secs, frac: nano }) } /// Parses a string with the specified format string and returns a new `NaiveTime`.