Skip to content

Commit

Permalink
Forward-compatible component range error type
Browse files Browse the repository at this point in the history
  • Loading branch information
jhpratt committed Dec 12, 2019
1 parent 20b27cb commit 8359a07
Show file tree
Hide file tree
Showing 4 changed files with 128 additions and 102 deletions.
95 changes: 52 additions & 43 deletions src/date.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
use crate::no_std_prelude::*;
use crate::{
format::parse::{parse, ParseError, ParseResult, ParsedItems},
DeferredFormat, Duration, PrimitiveDateTime, Time,
ComponentRangeError, DeferredFormat, Duration, PrimitiveDateTime, Time,
Weekday::{self, Friday, Monday, Saturday, Sunday, Thursday, Tuesday, Wednesday},
};
use core::{
Expand Down Expand Up @@ -139,18 +139,18 @@ impl Date {
///
/// ```rust
/// # use time::Date;
/// assert!(Date::try_from_ymd(2019, 1, 1).is_some());
/// assert!(Date::try_from_ymd(2019, 12, 31).is_some());
/// assert!(Date::try_from_ymd(2019, 1, 1).is_ok());
/// assert!(Date::try_from_ymd(2019, 12, 31).is_ok());
/// ```
///
/// Returns `None` if the date is not valid.
///
/// ```rust
/// # use time::Date;
/// assert!(Date::try_from_ymd(2019, 2, 29).is_none()); // 2019 isn't a leap year.
/// assert!(Date::try_from_ymd(2019, 2, 29).is_err()); // 2019 isn't a leap year.
/// ```
#[inline]
pub fn try_from_ymd(year: i32, month: u8, day: u8) -> Option<Self> {
pub fn try_from_ymd(year: i32, month: u8, day: u8) -> Result<Self, ComponentRangeError> {
/// Cumulative days through the beginning of a month in both common and
/// leap years.
const DAYS_CUMULATIVE_COMMON_LEAP: [[u16; 12]; 2] = [
Expand All @@ -163,7 +163,7 @@ impl Date {

let ordinal = DAYS_CUMULATIVE_COMMON_LEAP[is_leap_year(year) as usize][month as usize - 1];

Some(Self {
Ok(Self {
year,
ordinal: ordinal + day as u16,
})
Expand Down Expand Up @@ -193,20 +193,20 @@ impl Date {
///
/// ```rust
/// # use time::Date;
/// assert!(Date::try_from_yo(2019, 1).is_some());
/// assert!(Date::try_from_yo(2019, 365).is_some());
/// assert!(Date::try_from_yo(2019, 1).is_ok());
/// assert!(Date::try_from_yo(2019, 365).is_ok());
/// ```
///
/// Returns `None` if the date is not valid.
///
/// ```rust
/// # use time::Date;
/// assert!(Date::try_from_yo(2019, 366).is_none()); // 2019 isn't a leap year.
/// assert!(Date::try_from_yo(2019, 366).is_err()); // 2019 isn't a leap year.
/// ```
#[inline(always)]
pub fn try_from_yo(year: i32, ordinal: u16) -> Option<Self> {
pub fn try_from_yo(year: i32, ordinal: u16) -> Result<Self, ComponentRangeError> {
ensure_value_in_range!(ordinal in 1 => days_in_year(year), given year);
Some(Self { year, ordinal })
Ok(Self { year, ordinal })
}

/// Create a `Date` from the ISO year, week, and weekday.
Expand Down Expand Up @@ -256,33 +256,37 @@ impl Date {
///
/// ```rust
/// # use time::{Date, Weekday::*};
/// assert!(Date::try_from_iso_ywd(2019, 1, Monday).is_some());
/// assert!(Date::try_from_iso_ywd(2019, 1, Tuesday).is_some());
/// assert!(Date::try_from_iso_ywd(2020, 53, Friday).is_some());
/// assert!(Date::try_from_iso_ywd(2019, 1, Monday).is_ok());
/// assert!(Date::try_from_iso_ywd(2019, 1, Tuesday).is_ok());
/// assert!(Date::try_from_iso_ywd(2020, 53, Friday).is_ok());
/// ```
///
/// Returns `None` if the week is not valid.
///
/// ```rust
/// # use time::{Date, Weekday::*};
/// assert!(Date::try_from_iso_ywd(2019, 53, Monday).is_none()); // 2019 doesn't have 53 weeks.
/// assert!(Date::try_from_iso_ywd(2019, 53, Monday).is_err()); // 2019 doesn't have 53 weeks.
/// ```
#[inline]
pub fn try_from_iso_ywd(year: i32, week: u8, weekday: Weekday) -> Option<Self> {
pub fn try_from_iso_ywd(
year: i32,
week: u8,
weekday: Weekday,
) -> Result<Self, ComponentRangeError> {
ensure_value_in_range!(week in 1 => weeks_in_year(year), given year);

let ordinal = week as u16 * 7 + weekday.iso_weekday_number() as u16
- (Self::from_yo(year, 4).weekday().iso_weekday_number() as u16 + 3);

if ordinal < 1 {
return Some(Self::from_yo(year - 1, ordinal + days_in_year(year - 1)));
return Ok(Self::from_yo(year - 1, ordinal + days_in_year(year - 1)));
}

let days_in_cur_year = days_in_year(year);
if ordinal > days_in_cur_year {
Some(Self::from_yo(year + 1, ordinal - days_in_cur_year))
Ok(Self::from_yo(year + 1, ordinal - days_in_cur_year))
} else {
Some(Self::from_yo(year, ordinal))
Ok(Self::from_yo(year, ordinal))
}
}

Expand Down Expand Up @@ -707,12 +711,17 @@ impl Date {
///
/// ```rust
/// # use time::Date;
/// assert!(Date::from_ymd(1970, 1, 1).try_with_hms(0, 0, 0).is_some());
/// assert!(Date::from_ymd(1970, 1, 1).try_with_hms(24, 0, 0).is_none());
/// assert!(Date::from_ymd(1970, 1, 1).try_with_hms(0, 0, 0).is_ok());
/// assert!(Date::from_ymd(1970, 1, 1).try_with_hms(24, 0, 0).is_err());
/// ```
#[inline(always)]
pub fn try_with_hms(self, hour: u8, minute: u8, second: u8) -> Option<PrimitiveDateTime> {
Some(PrimitiveDateTime::new(
pub fn try_with_hms(
self,
hour: u8,
minute: u8,
second: u8,
) -> Result<PrimitiveDateTime, ComponentRangeError> {
Ok(PrimitiveDateTime::new(
self,
Time::try_from_hms(hour, minute, second)?,
))
Expand Down Expand Up @@ -747,10 +756,10 @@ impl Date {
/// # use time::Date;
/// assert!(Date::from_ymd(1970, 1, 1)
/// .try_with_hms_milli(0, 0, 0, 0)
/// .is_some());
/// .is_ok());
/// assert!(Date::from_ymd(1970, 1, 1)
/// .try_with_hms_milli(24, 0, 0, 0)
/// .is_none());
/// .is_err());
/// ```
#[inline(always)]
pub fn try_with_hms_milli(
Expand All @@ -759,8 +768,8 @@ impl Date {
minute: u8,
second: u8,
millisecond: u16,
) -> Option<PrimitiveDateTime> {
Some(PrimitiveDateTime::new(
) -> Result<PrimitiveDateTime, ComponentRangeError> {
Ok(PrimitiveDateTime::new(
self,
Time::try_from_hms_milli(hour, minute, second, millisecond)?,
))
Expand Down Expand Up @@ -795,10 +804,10 @@ impl Date {
/// # use time::Date;
/// assert!(Date::from_ymd(1970, 1, 1)
/// .try_with_hms_micro(0, 0, 0, 0)
/// .is_some());
/// .is_ok());
/// assert!(Date::from_ymd(1970, 1, 1)
/// .try_with_hms_micro(24, 0, 0, 0)
/// .is_none());
/// .is_err());
/// ```
#[inline(always)]
pub fn try_with_hms_micro(
Expand All @@ -807,8 +816,8 @@ impl Date {
minute: u8,
second: u8,
microsecond: u32,
) -> Option<PrimitiveDateTime> {
Some(PrimitiveDateTime::new(
) -> Result<PrimitiveDateTime, ComponentRangeError> {
Ok(PrimitiveDateTime::new(
self,
Time::try_from_hms_micro(hour, minute, second, microsecond)?,
))
Expand Down Expand Up @@ -840,10 +849,10 @@ impl Date {
/// # use time::Date;
/// assert!(Date::from_ymd(1970, 1, 1)
/// .try_with_hms_nano(0, 0, 0, 0)
/// .is_some());
/// .is_ok());
/// assert!(Date::from_ymd(1970, 1, 1)
/// .try_with_hms_nano(24, 0, 0, 0)
/// .is_none());
/// .is_err());
/// ```
#[inline(always)]
pub fn try_with_hms_nano(
Expand All @@ -852,8 +861,8 @@ impl Date {
minute: u8,
second: u8,
nanosecond: u32,
) -> Option<PrimitiveDateTime> {
Some(PrimitiveDateTime::new(
) -> Result<PrimitiveDateTime, ComponentRangeError> {
Ok(PrimitiveDateTime::new(
self,
Time::try_from_hms_nano(hour, minute, second, nanosecond)?,
))
Expand Down Expand Up @@ -2084,9 +2093,9 @@ mod test {
fn try_with_hms() {
assert_eq!(
ymd!(1970, 1, 1).try_with_hms(0, 0, 0),
Some(ymd!(1970, 1, 1).with_time(Time::from_hms(0, 0, 0))),
Ok(ymd!(1970, 1, 1).with_time(Time::from_hms(0, 0, 0))),
);
assert_eq!(ymd!(1970, 1, 1).try_with_hms(24, 0, 0), None);
assert!(ymd!(1970, 1, 1).try_with_hms(24, 0, 0).is_err());
}

#[test]
Expand All @@ -2101,9 +2110,9 @@ mod test {
fn try_with_hms_milli() {
assert_eq!(
ymd!(1970, 1, 1).try_with_hms_milli(0, 0, 0, 0),
Some(ymd!(1970, 1, 1).with_time(Time::from_hms_milli(0, 0, 0, 0))),
Ok(ymd!(1970, 1, 1).with_time(Time::from_hms_milli(0, 0, 0, 0))),
);
assert_eq!(ymd!(1970, 1, 1).try_with_hms_milli(24, 0, 0, 0), None);
assert!(ymd!(1970, 1, 1).try_with_hms_milli(24, 0, 0, 0).is_err());
}

#[test]
Expand All @@ -2118,9 +2127,9 @@ mod test {
fn try_with_hms_micro() {
assert_eq!(
ymd!(1970, 1, 1).try_with_hms_micro(0, 0, 0, 0),
Some(ymd!(1970, 1, 1).with_time(Time::from_hms_micro(0, 0, 0, 0))),
Ok(ymd!(1970, 1, 1).with_time(Time::from_hms_micro(0, 0, 0, 0))),
);
assert_eq!(ymd!(1970, 1, 1).try_with_hms_micro(24, 0, 0, 0), None);
assert!(ymd!(1970, 1, 1).try_with_hms_micro(24, 0, 0, 0).is_err());
}

#[test]
Expand All @@ -2135,9 +2144,9 @@ mod test {
fn try_with_hms_nano() {
assert_eq!(
ymd!(1970, 1, 1).try_with_hms_nano(0, 0, 0, 0),
Some(ymd!(1970, 1, 1).with_time(Time::from_hms_nano(0, 0, 0, 0))),
Ok(ymd!(1970, 1, 1).with_time(Time::from_hms_nano(0, 0, 0, 0))),
);
assert_eq!(ymd!(1970, 1, 1).try_with_hms_nano(24, 0, 0, 0), None);
assert!(ymd!(1970, 1, 1).try_with_hms_nano(24, 0, 0, 0).is_err());
}

#[test]
Expand Down
2 changes: 1 addition & 1 deletion src/duration.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#[cfg(feature = "std")]
use crate::Instant;
use crate::{
NumberExt, ConversionRangeError,
ConversionRangeError, NumberExt,
Sign::{self, Negative, Positive, Zero},
};
use core::{
Expand Down
30 changes: 16 additions & 14 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,19 +210,19 @@ macro_rules! assert_value_in_range {
macro_rules! ensure_value_in_range {
($value:ident in $start:expr => $end:expr) => {
if !($start..=$end).contains(&$value) {
return None;
return Err(ComponentRangeError);
}
};

($value:ident in $start:expr => exclusive $end:expr) => {
if !($start..$end).contains(&$value) {
return None;
return Err(ComponentRangeError);
}
};

($value:ident in $start:expr => $end:expr,given $($conditional:ident),+ $(,)?) => {
if !($start..=$end).contains(&$value) {
return None;
return Err(ComponentRangeError);
};
};
}
Expand Down Expand Up @@ -329,21 +329,23 @@ impl fmt::Display for ConversionRangeError {
#[cfg(feature = "std")]
impl std::error::Error for ConversionRangeError {}

#[cfg(test)]
mod test {
use super::*;
#[cfg(not(feature = "std"))]
use crate::no_std_prelude::*;
/// An error type indicating that a component provided to a method was out of
/// range, causing a failure.
#[allow(missing_copy_implementations)] // Non-copy fields may be added.
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ComponentRangeError;

#[test]
fn out_of_range_error_format() {
assert_eq!(
ComponentRangeError.to_string(),
"Source value is out of range for the target type",
);
impl fmt::Display for ComponentRangeError {
#[inline(always)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str("A component's value is out of range")
}
}

#[cfg(feature = "std")]
impl std::error::Error for ComponentRangeError {}

// For some back-compatibility, we're also implementing some deprecated types
// and methods. They will be removed completely in 0.3.

Expand Down

0 comments on commit 8359a07

Please sign in to comment.