diff --git a/Cargo.toml b/Cargo.toml index b2c5613d8..c80c946f7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ time-core = { path = "time-core", version = "=0.1.1" } time-macros = { path = "time-macros", version = "=0.2.10" } criterion = { version = "0.5.1", default-features = false } +deranged = { version = "0.3.3", default-features = false } itoa = "1.0.1" js-sys = "0.3.58" libc = "0.2.98" diff --git a/tests/parsing.rs b/tests/parsing.rs index e3e46bafb..a171f9dee 100644 --- a/tests/parsing.rs +++ b/tests/parsing.rs @@ -680,9 +680,9 @@ fn parse_time_err() -> time::Result<()> { )); assert!(matches!( Time::parse("13 PM", &fd::parse("[hour repr:12] [period]")?), - Err(error::Parse::TryFromParsed( - error::TryFromParsed::ComponentRange(component) - )) if component.name() == "hour" + Err(error::Parse::ParseFromDescription( + error::ParseFromDescription::InvalidComponent("hour") + )) )); assert!(matches!( Time::parse(" ", &fd::parse("")?), @@ -878,28 +878,61 @@ fn parse_date_err() -> time::Result<()> { )); assert!(matches!( Date::parse("2021-12-32", &fd::parse("[year]-[month]-[day]")?), + Err(error::Parse::ParseFromDescription( + error::ParseFromDescription::InvalidComponent("day") + )) + )); + assert!(matches!( + Date::parse("2021-02-30", &fd::parse("[year]-[month]-[day]")?), Err(error::Parse::TryFromParsed( error::TryFromParsed::ComponentRange(component) )) if component.name() == "day" )); assert!(matches!( - Date::parse("2021-W54-1", &fd::parse("[year base:iso_week]-W[week_number]-[weekday repr:monday]")?), + Date::parse("2019-W53-1", &fd::parse("[year base:iso_week]-W[week_number]-[weekday repr:monday]")?), Err(error::Parse::TryFromParsed( error::TryFromParsed::ComponentRange(component) )) if component.name() == "week" )); assert!(matches!( - Date::parse("2021-W54-1", &fd::parse("[year]-W[week_number repr:sunday]-[weekday repr:monday]")?), + Date::parse( + "2021-W54-1", + &fd::parse("[year base:iso_week]-W[week_number]-[weekday repr:monday]")? + ), + Err(error::Parse::ParseFromDescription( + error::ParseFromDescription::InvalidComponent("week number") + )) + )); + assert!(matches!( + Date::parse("2019-W53-1", &fd::parse("[year]-W[week_number repr:sunday]-[weekday repr:monday]")?), Err(error::Parse::TryFromParsed( error::TryFromParsed::ComponentRange(component) )) if component.name() == "ordinal" )); assert!(matches!( - Date::parse("2021-W54-1", &fd::parse("[year]-W[week_number repr:monday]-[weekday repr:monday]")?), + Date::parse( + "2021-W54-1", + &fd::parse("[year]-W[week_number repr:sunday]-[weekday repr:monday]")? + ), + Err(error::Parse::ParseFromDescription( + error::ParseFromDescription::InvalidComponent("week number") + )) + )); + assert!(matches!( + Date::parse("2019-W53-1", &fd::parse("[year]-W[week_number repr:monday]-[weekday repr:monday]")?), Err(error::Parse::TryFromParsed( error::TryFromParsed::ComponentRange(component) )) if component.name() == "ordinal" )); + assert!(matches!( + Date::parse( + "2021-W54-1", + &fd::parse("[year]-W[week_number repr:monday]-[weekday repr:monday]")? + ), + Err(error::Parse::ParseFromDescription( + error::ParseFromDescription::InvalidComponent("week number") + )) + )); assert!(matches!( Date::parse("Ja", &fd::parse("[month repr:short]")?), Err(error::Parse::ParseFromDescription( @@ -953,15 +986,24 @@ fn parse_offset_err() -> time::Result<()> { ); assert!(matches!( UtcOffset::parse("24", &fd::parse("[offset_hour]")?), - Err(error::Parse::TryFromParsed(error::TryFromParsed::ComponentRange(component))) if component.name() == "offset hour" + Err(error::Parse::ParseFromDescription( + error::ParseFromDescription::InvalidComponent("offset hour") + )) )); assert!(matches!( UtcOffset::parse("00:60", &fd::parse("[offset_hour]:[offset_minute]")?), - Err(error::Parse::TryFromParsed(error::TryFromParsed::ComponentRange(component))) if component.name() == "offset minute" + Err(error::Parse::ParseFromDescription( + error::ParseFromDescription::InvalidComponent("offset minute") + )) )); assert!(matches!( - UtcOffset::parse("00:00:60", &fd::parse("[offset_hour]:[offset_minute]:[offset_second]")?), - Err(error::Parse::TryFromParsed(error::TryFromParsed::ComponentRange(component))) if component.name() == "offset second" + UtcOffset::parse( + "00:00:60", + &fd::parse("[offset_hour]:[offset_minute]:[offset_second]")? + ), + Err(error::Parse::ParseFromDescription( + error::ParseFromDescription::InvalidComponent("offset second") + )) )); Ok(()) @@ -976,10 +1018,13 @@ fn parse_primitive_date_time_err() -> time::Result<()> { )) )); assert!(matches!( - PrimitiveDateTime::parse("2021-001 13 PM", &fd::parse("[year]-[ordinal] [hour repr:12] [period]")?), - Err(error::Parse::TryFromParsed( - error::TryFromParsed::ComponentRange(component) - )) if component.name() == "hour" + PrimitiveDateTime::parse( + "2021-001 13 PM", + &fd::parse("[year]-[ordinal] [hour repr:12] [period]")? + ), + Err(error::Parse::ParseFromDescription( + error::ParseFromDescription::InvalidComponent("hour") + )) )); Ok(()) @@ -1000,10 +1045,13 @@ fn parse_offset_date_time_err() -> time::Result<()> { )) )); assert!(matches!( - OffsetDateTime::parse("2021-001 12 PM +25", &fd::parse("[year]-[ordinal] [hour repr:12] [period] [offset_hour sign:mandatory]")?), - Err(error::Parse::TryFromParsed( - error::TryFromParsed::ComponentRange(component) - )) if component.name() == "offset hour" + OffsetDateTime::parse( + "2021-001 12 PM +25", + &fd::parse("[year]-[ordinal] [hour repr:12] [period] [offset_hour sign:mandatory]")? + ), + Err(error::Parse::ParseFromDescription( + error::ParseFromDescription::InvalidComponent("offset hour") + )) )); Ok(()) diff --git a/tests/serde/mod.rs b/tests/serde/mod.rs index 50de939dc..f27a0e77d 100644 --- a/tests/serde/mod.rs +++ b/tests/serde/mod.rs @@ -61,10 +61,10 @@ fn time_error() { ); assert_de_tokens_error::>( &[Token::BorrowedStr("24:00:00.0")], - "hour must be in the range 0..=23", + "the 'hour' component could not be parsed", ); assert_de_tokens_error::>( - &[Token::BorrowedStr("24-00:00.0")], + &[Token::BorrowedStr("23-00:00.0")], "a character literal was not valid", ); assert_de_tokens_error::>( diff --git a/time/Cargo.toml b/time/Cargo.toml index f7090e9d6..36964f0ec 100644 --- a/time/Cargo.toml +++ b/time/Cargo.toml @@ -34,18 +34,19 @@ large-dates = ["time-macros?/large-dates"] local-offset = ["std", "dep:libc", "dep:num_threads"] macros = ["dep:time-macros"] parsing = ["time-macros?/parsing"] -quickcheck = ["dep:quickcheck", "alloc"] -rand = ["dep:rand"] -serde = ["dep:serde", "time-macros?/serde"] +quickcheck = ["dep:quickcheck", "alloc", "deranged/quickcheck"] +rand = ["dep:rand", "deranged/rand"] +serde = ["dep:serde", "time-macros?/serde", "deranged/serde"] serde-human-readable = ["serde", "formatting", "parsing"] # Deprecated in favor of using the relevant flags directly. serde-well-known = ["serde", "formatting", "parsing"] -std = ["alloc"] +std = ["alloc", "deranged/std"] wasm-bindgen = ["dep:js-sys"] # If adding an optional dependency, be sure to use the `dep:` prefix above to avoid an implicit # feature gate. [dependencies] +deranged = { workspace = true } itoa = { workspace = true, optional = true } quickcheck = { workspace = true, optional = true } rand = { workspace = true, optional = true } diff --git a/time/src/parsing/parsable.rs b/time/src/parsing/parsable.rs index 449920278..aacaaa045 100644 --- a/time/src/parsing/parsable.rs +++ b/time/src/parsing/parsable.rs @@ -237,8 +237,7 @@ impl sealed::Sealed for Rfc2822 { }; // The RFC explicitly allows leap seconds. - // Safety: The flag does not represent a field. - unsafe { parsed.set_flag(Parsed::LEAP_SECOND_ALLOWED_FLAG, true) }; + parsed.leap_second_allowed = true; #[allow(clippy::unnecessary_lazy_evaluations)] // rust-lang/rust-clippy#8522 let zone_literal = first_match( @@ -534,8 +533,7 @@ impl sealed::Sealed for Rfc3339 { }; // The RFC explicitly allows leap seconds. - // Safety: The flag does not represent a field. - unsafe { parsed.set_flag(Parsed::LEAP_SECOND_ALLOWED_FLAG, true) }; + parsed.leap_second_allowed = true; if let Some(ParsedItem(input, ())) = ascii_char_ignore_case::(input) { parsed diff --git a/time/src/parsing/parsed.rs b/time/src/parsing/parsed.rs index 7a103458a..b3cdc1492 100644 --- a/time/src/parsing/parsed.rs +++ b/time/src/parsing/parsed.rs @@ -1,8 +1,14 @@ //! Information parsed from an input and format description. -use core::mem::MaybeUninit; use core::num::{NonZeroU16, NonZeroU8}; +use deranged::{ + OptionRangedI128, OptionRangedI32, OptionRangedI8, OptionRangedU16, OptionRangedU32, + OptionRangedU8, RangedI128, RangedI32, RangedI8, RangedU16, RangedU32, RangedU8, +}; + +use crate::convert::{Day, Hour, Minute, Nanosecond, Second}; +use crate::date::{MAX_YEAR, MIN_YEAR}; use crate::date_time::{maybe_offset_from_offset, offset_kind, DateTime, MaybeOffset}; use crate::error::TryFromParsed::InsufficientInformation; use crate::format_description::modifier::{WeekNumberRepr, YearRepr}; @@ -99,152 +105,105 @@ impl sealed::AnyFormatItem for OwnedFormatItem { } } -/// The type of the `flags` field in [`Parsed`]. Allows for changing a single location and having it -/// effect all uses. -type Flag = u32; - /// All information parsed. /// /// This information is directly used to construct the final values. /// /// Most users will not need think about this struct in any way. It is public to allow for manual /// control over values, in the instance that the default parser is insufficient. -#[derive(Clone, Copy)] +#[derive(Debug, Clone, Copy)] pub struct Parsed { - /// Bitflags indicating whether a particular field is present. - /// - /// **Do not set this field directly.** It is unsafe to do so. Use `Parsed::set_flag` instead. - flags: Flag, /// Calendar year. - year: MaybeUninit, + year: OptionRangedI32<{ MIN_YEAR }, { MAX_YEAR }>, /// The last two digits of the calendar year. - year_last_two: MaybeUninit, + year_last_two: OptionRangedU8<0, 99>, /// Year of the [ISO week date](https://en.wikipedia.org/wiki/ISO_week_date). - iso_year: MaybeUninit, + iso_year: OptionRangedI32<{ MIN_YEAR }, { MAX_YEAR }>, /// The last two digits of the ISO week year. - iso_year_last_two: MaybeUninit, + iso_year_last_two: OptionRangedU8<0, 99>, /// Month of the year. month: Option, /// Week of the year, where week one begins on the first Sunday of the calendar year. - sunday_week_number: MaybeUninit, + sunday_week_number: OptionRangedU8<0, 53>, /// Week of the year, where week one begins on the first Monday of the calendar year. - monday_week_number: MaybeUninit, + monday_week_number: OptionRangedU8<0, 53>, /// Week of the year, where week one is the Monday-to-Sunday period containing January 4. - iso_week_number: Option, + iso_week_number: OptionRangedU8<1, 53>, /// Day of the week. weekday: Option, /// Day of the year. - ordinal: Option, + ordinal: OptionRangedU16<1, 366>, /// Day of the month. - day: Option, + day: OptionRangedU8<1, 31>, /// Hour within the day. - hour_24: MaybeUninit, + hour_24: OptionRangedU8<0, { Hour.per(Day) - 1 }>, /// Hour within the 12-hour period (midnight to noon or vice versa). This is typically used in /// conjunction with AM/PM, which is indicated by the `hour_12_is_pm` field. - hour_12: Option, + hour_12: OptionRangedU8<1, 12>, /// Whether the `hour_12` field indicates a time that "PM". hour_12_is_pm: Option, /// Minute within the hour. - minute: MaybeUninit, + // minute: MaybeUninit, + minute: OptionRangedU8<0, { Minute.per(Hour) - 1 }>, /// Second within the minute. - second: MaybeUninit, + // do not subtract one, as leap seconds may be allowed + second: OptionRangedU8<0, { Second.per(Minute) }>, /// Nanosecond within the second. - subsecond: MaybeUninit, + subsecond: OptionRangedU32<0, { Nanosecond.per(Second) - 1 }>, /// Whole hours of the UTC offset. - offset_hour: MaybeUninit, + offset_hour: OptionRangedI8<-23, 23>, /// Minutes within the hour of the UTC offset. - offset_minute: MaybeUninit, + offset_minute: + OptionRangedI8<{ -((Minute.per(Hour) - 1) as i8) }, { (Minute.per(Hour) - 1) as _ }>, /// Seconds within the minute of the UTC offset. - offset_second: MaybeUninit, + offset_second: + OptionRangedI8<{ -((Second.per(Minute) - 1) as i8) }, { (Second.per(Minute) - 1) as _ }>, /// The Unix timestamp in nanoseconds. - unix_timestamp_nanos: MaybeUninit, -} - -impl core::fmt::Debug for Parsed { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_struct("Parsed") - .field("year", &self.year()) - .field("year_last_two", &self.year_last_two()) - .field("iso_year", &self.iso_year()) - .field("iso_year_last_two", &self.iso_year_last_two()) - .field("month", &self.month()) - .field("sunday_week_number", &self.sunday_week_number()) - .field("monday_week_number", &self.monday_week_number()) - .field("iso_week_number", &self.iso_week_number()) - .field("weekday", &self.weekday()) - .field("ordinal", &self.ordinal()) - .field("day", &self.day()) - .field("hour_24", &self.hour_24()) - .field("hour_12", &self.hour_12()) - .field("hour_12_is_pm", &self.hour_12_is_pm()) - .field("minute", &self.minute()) - .field("second", &self.second()) - .field("subsecond", &self.subsecond()) - .field("offset_hour", &self.offset_hour()) - .field("offset_minute", &self.offset_minute_signed()) - .field("offset_second", &self.offset_second_signed()) - .field("unix_timestamp_nanos", &self.unix_timestamp_nanos()) - .field( - "leap_second_allowed", - &self.get_flag(Self::LEAP_SECOND_ALLOWED_FLAG), - ) - .finish() - } -} - -#[allow(clippy::missing_docs_in_private_items)] -impl Parsed { - const YEAR_FLAG: Flag = 1 << 0; - const YEAR_LAST_TWO_FLAG: Flag = 1 << 1; - const ISO_YEAR_FLAG: Flag = 1 << 2; - const ISO_YEAR_LAST_TWO_FLAG: Flag = 1 << 3; - const SUNDAY_WEEK_NUMBER_FLAG: Flag = 1 << 4; - const MONDAY_WEEK_NUMBER_FLAG: Flag = 1 << 5; - const HOUR_24_FLAG: Flag = 1 << 6; - const MINUTE_FLAG: Flag = 1 << 7; - const SECOND_FLAG: Flag = 1 << 8; - const SUBSECOND_FLAG: Flag = 1 << 9; - const OFFSET_HOUR_FLAG: Flag = 1 << 10; - const OFFSET_MINUTE_FLAG: Flag = 1 << 11; - const OFFSET_SECOND_FLAG: Flag = 1 << 12; + // unix_timestamp_nanos: MaybeUninit, + unix_timestamp_nanos: OptionRangedI128< + { Date::MIN.midnight().assume_utc().unix_timestamp_nanos() }, + { + Date::MAX + .with_time(Time::MAX) + .assume_utc() + .unix_timestamp_nanos() + }, + >, + /// Indicates whether the [`UtcOffset`] is negative. This information is obtained when parsing + /// the offset hour, but may not otherwise be stored due to "-0" being equivalent to "0". + offset_is_negative: Option, /// Indicates whether a leap second is permitted to be parsed. This is required by some /// well-known formats. - pub(super) const LEAP_SECOND_ALLOWED_FLAG: Flag = 1 << 13; - /// Indicates whether the `UtcOffset` is negative. This information is obtained when parsing the - /// offset hour, but may not otherwise be stored due to "-0" being equivalent to "0". - const OFFSET_IS_NEGATIVE_FLAG: Flag = 1 << 14; - /// Does the value at `OFFSET_IS_NEGATIVE_FLAG` have any semantic meaning, or is it just the - /// default value? If the latter, the value should be considered to have no meaning. - const OFFSET_IS_NEGATIVE_FLAG_IS_INITIALIZED: Flag = 1 << 15; - const UNIX_TIMESTAMP_NANOS_FLAG: Flag = 1 << 16; + pub(super) leap_second_allowed: bool, } impl Parsed { /// Create a new instance of `Parsed` with no information known. pub const fn new() -> Self { Self { - flags: 0, - year: MaybeUninit::uninit(), - year_last_two: MaybeUninit::uninit(), - iso_year: MaybeUninit::uninit(), - iso_year_last_two: MaybeUninit::uninit(), + year: OptionRangedI32::None, + year_last_two: OptionRangedU8::None, + iso_year: OptionRangedI32::None, + iso_year_last_two: OptionRangedU8::None, month: None, - sunday_week_number: MaybeUninit::uninit(), - monday_week_number: MaybeUninit::uninit(), - iso_week_number: None, + sunday_week_number: OptionRangedU8::None, + monday_week_number: OptionRangedU8::None, + iso_week_number: OptionRangedU8::None, weekday: None, - ordinal: None, - day: None, - hour_24: MaybeUninit::uninit(), - hour_12: None, + ordinal: OptionRangedU16::None, + day: OptionRangedU8::None, + hour_24: OptionRangedU8::None, + hour_12: OptionRangedU8::None, hour_12_is_pm: None, - minute: MaybeUninit::uninit(), - second: MaybeUninit::uninit(), - subsecond: MaybeUninit::uninit(), - offset_hour: MaybeUninit::uninit(), - offset_minute: MaybeUninit::uninit(), - offset_second: MaybeUninit::uninit(), - unix_timestamp_nanos: MaybeUninit::uninit(), + minute: OptionRangedU8::None, + second: OptionRangedU8::None, + subsecond: OptionRangedU32::None, + offset_hour: OptionRangedI8::None, + offset_minute: OptionRangedI8::None, + offset_second: OptionRangedI8::None, + unix_timestamp_nanos: OptionRangedI128::None, + offset_is_negative: None, + leap_second_allowed: false, } } @@ -367,11 +326,7 @@ impl Parsed { .and_then(|parsed| { parsed.consume_value(|(value, is_negative)| { self.set_offset_hour(value)?; - // Safety: the `offset_hour` field was just initialized. - unsafe { - self.set_flag(Self::OFFSET_IS_NEGATIVE_FLAG, is_negative); - self.set_flag(Self::OFFSET_IS_NEGATIVE_FLAG_IS_INITIALIZED, true); - } + self.offset_is_negative = Some(is_negative); Some(()) }) }) @@ -396,146 +351,147 @@ impl Parsed { .ok_or(InvalidComponent("unix_timestamp")), } } +} - /// Get the value of the provided flag. - const fn get_flag(&self, flag: Flag) -> bool { - self.flags & flag == flag +/// Getter methods +impl Parsed { + /// Obtain the `year` component. + pub const fn year(&self) -> Option { + self.year.get_primitive() } - /// Set the value of the provided flag. - /// - /// Safety: If `flag` represents a field and `value` is true, the field must be initialized. - pub(super) unsafe fn set_flag(&mut self, flag: Flag, value: bool) { - if value { - self.flags |= flag; - } else { - self.flags &= !flag; - } + /// Obtain the `year_last_two` component. + pub const fn year_last_two(&self) -> Option { + self.year_last_two.get_primitive() } -} -/// Generate getters for each of the fields. -macro_rules! getters { - ($($(@$flag:ident)? $name:ident: $ty:ty),+ $(,)?) => {$( - getters!(! $(@$flag)? $name: $ty); - )*}; - (! $name:ident : $ty:ty) => { - /// Obtain the named component. - pub const fn $name(&self) -> Option<$ty> { - self.$name - } - }; - (! @$flag:ident $name:ident : $ty:ty) => { - /// Obtain the named component. - pub const fn $name(&self) -> Option<$ty> { - if !self.get_flag(Self::$flag) { - None - } else { - // SAFETY: We just checked if the field is present. - Some(unsafe { self.$name.assume_init() }) - } - } - }; -} + /// Obtain the `iso_year` component. + pub const fn iso_year(&self) -> Option { + self.iso_year.get_primitive() + } -/// Getter methods -impl Parsed { - getters! { - @YEAR_FLAG year: i32, - @YEAR_LAST_TWO_FLAG year_last_two: u8, - @ISO_YEAR_FLAG iso_year: i32, - @ISO_YEAR_LAST_TWO_FLAG iso_year_last_two: u8, - month: Month, - @SUNDAY_WEEK_NUMBER_FLAG sunday_week_number: u8, - @MONDAY_WEEK_NUMBER_FLAG monday_week_number: u8, - iso_week_number: NonZeroU8, - weekday: Weekday, - ordinal: NonZeroU16, - day: NonZeroU8, - @HOUR_24_FLAG hour_24: u8, - hour_12: NonZeroU8, - hour_12_is_pm: bool, - @MINUTE_FLAG minute: u8, - @SECOND_FLAG second: u8, - @SUBSECOND_FLAG subsecond: u32, - @OFFSET_HOUR_FLAG offset_hour: i8, - @UNIX_TIMESTAMP_NANOS_FLAG unix_timestamp_nanos: i128, - } - - /// Obtain the absolute value of the offset minute. + /// Obtain the `iso_year_last_two` component. + pub const fn iso_year_last_two(&self) -> Option { + self.iso_year_last_two.get_primitive() + } + + /// Obtain the `month` component. + pub const fn month(&self) -> Option { + self.month + } + + /// Obtain the `sunday_week_number` component. + pub const fn sunday_week_number(&self) -> Option { + self.sunday_week_number.get_primitive() + } + + /// Obtain the `monday_week_number` component. + pub const fn monday_week_number(&self) -> Option { + self.monday_week_number.get_primitive() + } + + /// Obtain the `iso_week_number` component. + pub const fn iso_week_number(&self) -> Option { + NonZeroU8::new(const_try_opt!(self.iso_week_number.get_primitive())) + } + + /// Obtain the `weekday` component. + pub const fn weekday(&self) -> Option { + self.weekday + } + + /// Obtain the `ordinal` component. + pub const fn ordinal(&self) -> Option { + NonZeroU16::new(const_try_opt!(self.ordinal.get_primitive())) + } + + /// Obtain the `day` component. + pub const fn day(&self) -> Option { + NonZeroU8::new(const_try_opt!(self.day.get_primitive())) + } + + /// Obtain the `hour_24` component. + pub const fn hour_24(&self) -> Option { + self.hour_24.get_primitive() + } + + /// Obtain the `hour_12` component. + pub const fn hour_12(&self) -> Option { + NonZeroU8::new(const_try_opt!(self.hour_12.get_primitive())) + } + + /// Obtain the `hour_12_is_pm` component. + pub const fn hour_12_is_pm(&self) -> Option { + self.hour_12_is_pm + } + + /// Obtain the `minute` component. + pub const fn minute(&self) -> Option { + self.minute.get_primitive() + } + + /// Obtain the `second` component. + pub const fn second(&self) -> Option { + self.second.get_primitive() + } + + /// Obtain the `subsecond` component. + pub const fn subsecond(&self) -> Option { + self.subsecond.get_primitive() + } + + /// Obtain the `offset_hour` component. + pub const fn offset_hour(&self) -> Option { + self.offset_hour.get_primitive() + } + + /// Obtain the absolute value of the `offset_minute` component. #[doc(hidden)] #[deprecated(since = "0.3.8", note = "use `parsed.offset_minute_signed()` instead")] pub const fn offset_minute(&self) -> Option { Some(const_try_opt!(self.offset_minute_signed()).unsigned_abs()) } - /// Obtain the offset minute as an `i8`. + /// Obtain the `offset_minute` component. pub const fn offset_minute_signed(&self) -> Option { - if !self.get_flag(Self::OFFSET_MINUTE_FLAG) { - None - } else { - // SAFETY: We just checked if the field is present. - let value = unsafe { self.offset_minute.assume_init() }; - - if self.get_flag(Self::OFFSET_IS_NEGATIVE_FLAG_IS_INITIALIZED) - && (value.is_negative() != self.get_flag(Self::OFFSET_IS_NEGATIVE_FLAG)) - { - Some(-value) - } else { - Some(value) - } + match (self.offset_minute.get_primitive(), self.offset_is_negative) { + (Some(offset_minute), Some(true)) => Some(-offset_minute), + (Some(offset_minute), _) => Some(offset_minute), + (None, _) => None, } } - /// Obtain the absolute value of the offset second. + /// Obtain the absolute value of the `offset_second` component. #[doc(hidden)] #[deprecated(since = "0.3.8", note = "use `parsed.offset_second_signed()` instead")] pub const fn offset_second(&self) -> Option { Some(const_try_opt!(self.offset_second_signed()).unsigned_abs()) } - /// Obtain the offset second as an `i8`. + /// Obtain the `offset_second` component. pub const fn offset_second_signed(&self) -> Option { - if !self.get_flag(Self::OFFSET_SECOND_FLAG) { - None - } else { - // SAFETY: We just checked if the field is present. - let value = unsafe { self.offset_second.assume_init() }; - - if self.get_flag(Self::OFFSET_IS_NEGATIVE_FLAG_IS_INITIALIZED) - && (value.is_negative() != self.get_flag(Self::OFFSET_IS_NEGATIVE_FLAG)) - { - Some(-value) - } else { - Some(value) - } + match (self.offset_second.get_primitive(), self.offset_is_negative) { + (Some(offset_second), Some(true)) => Some(-offset_second), + (Some(offset_second), _) => Some(offset_second), + (None, _) => None, } } + + /// Obtain the `unix_timestamp_nanos` component. + pub const fn unix_timestamp_nanos(&self) -> Option { + self.unix_timestamp_nanos.get_primitive() + } } -/// Generate setters for each of the fields. -/// -/// This macro should only be used for fields where the value is not validated beyond its type. +/// Generate setters based on the builders. macro_rules! setters { - ($($(@$flag:ident)? $setter_name:ident $name:ident: $ty:ty),+ $(,)?) => {$( - setters!(! $(@$flag)? $setter_name $name: $ty); - )*}; - (! $setter_name:ident $name:ident : $ty:ty) => { - /// Set the named component. - pub fn $setter_name(&mut self, value: $ty) -> Option<()> { - self.$name = Some(value); - Some(()) - } - }; - (! @$flag:ident $setter_name:ident $name:ident : $ty:ty) => { - /// Set the named component. - pub fn $setter_name(&mut self, value: $ty) -> Option<()> { - self.$name = MaybeUninit::new(value); - // Safety: The field was just initialized. - unsafe { self.set_flag(Self::$flag, true) }; + ($($name:ident $setter:ident $builder:ident $type:ty;)*) => {$( + #[doc = concat!("Set the `", stringify!($setter), "` component.")] + pub fn $setter(&mut self, value: $type) -> Option<()> { + *self = self.$builder(value)?; Some(()) } - }; + )*}; } /// Setter methods @@ -544,28 +500,30 @@ macro_rules! setters { /// setters _may_ fail if the value is invalid, though behavior is not guaranteed. impl Parsed { setters! { - @YEAR_FLAG set_year year: i32, - @YEAR_LAST_TWO_FLAG set_year_last_two year_last_two: u8, - @ISO_YEAR_FLAG set_iso_year iso_year: i32, - @ISO_YEAR_LAST_TWO_FLAG set_iso_year_last_two iso_year_last_two: u8, - set_month month: Month, - @SUNDAY_WEEK_NUMBER_FLAG set_sunday_week_number sunday_week_number: u8, - @MONDAY_WEEK_NUMBER_FLAG set_monday_week_number monday_week_number: u8, - set_iso_week_number iso_week_number: NonZeroU8, - set_weekday weekday: Weekday, - set_ordinal ordinal: NonZeroU16, - set_day day: NonZeroU8, - @HOUR_24_FLAG set_hour_24 hour_24: u8, - set_hour_12 hour_12: NonZeroU8, - set_hour_12_is_pm hour_12_is_pm: bool, - @MINUTE_FLAG set_minute minute: u8, - @SECOND_FLAG set_second second: u8, - @SUBSECOND_FLAG set_subsecond subsecond: u32, - @OFFSET_HOUR_FLAG set_offset_hour offset_hour: i8, - @UNIX_TIMESTAMP_NANOS_FLAG set_unix_timestamp_nanos unix_timestamp_nanos: i128, - } - - /// Set the named component. + year set_year with_year i32; + year_last_two set_year_last_two with_year_last_two u8; + iso_year set_iso_year with_iso_year i32; + iso_year_last_two set_iso_year_last_two with_iso_year_last_two u8; + month set_month with_month Month; + sunday_week_number set_sunday_week_number with_sunday_week_number u8; + monday_week_number set_monday_week_number with_monday_week_number u8; + iso_week_number set_iso_week_number with_iso_week_number NonZeroU8; + weekday set_weekday with_weekday Weekday; + ordinal set_ordinal with_ordinal NonZeroU16; + day set_day with_day NonZeroU8; + hour_24 set_hour_24 with_hour_24 u8; + hour_12 set_hour_12 with_hour_12 NonZeroU8; + hour_12_is_pm set_hour_12_is_pm with_hour_12_is_pm bool; + minute set_minute with_minute u8; + second set_second with_second u8; + subsecond set_subsecond with_subsecond u32; + offset_hour set_offset_hour with_offset_hour i8; + offset_minute set_offset_minute_signed with_offset_minute_signed i8; + offset_second set_offset_second_signed with_offset_second_signed i8; + unix_timestamp_nanos set_unix_timestamp_nanos with_unix_timestamp_nanos i128; + } + + /// Set the `offset_minute` component. #[doc(hidden)] #[deprecated( since = "0.3.8", @@ -580,14 +538,6 @@ impl Parsed { } /// Set the `offset_minute` component. - pub fn set_offset_minute_signed(&mut self, value: i8) -> Option<()> { - self.offset_minute = MaybeUninit::new(value); - // Safety: The field was just initialized. - unsafe { self.set_flag(Self::OFFSET_MINUTE_FLAG, true) }; - Some(()) - } - - /// Set the named component. #[doc(hidden)] #[deprecated( since = "0.3.8", @@ -600,39 +550,6 @@ impl Parsed { self.set_offset_second_signed(value as _) } } - - /// Set the `offset_second` component. - pub fn set_offset_second_signed(&mut self, value: i8) -> Option<()> { - self.offset_second = MaybeUninit::new(value); - // Safety: The field was just initialized. - unsafe { self.set_flag(Self::OFFSET_SECOND_FLAG, true) }; - Some(()) - } -} - -/// Generate build methods for each of the fields. -/// -/// This macro should only be used for fields where the value is not validated beyond its type. -macro_rules! builders { - ($($(@$flag:ident)? $builder_name:ident $name:ident: $ty:ty),+ $(,)?) => {$( - builders!(! $(@$flag)? $builder_name $name: $ty); - )*}; - (! $builder_name:ident $name:ident : $ty:ty) => { - /// Set the named component and return `self`. - pub const fn $builder_name(mut self, value: $ty) -> Option { - self.$name = Some(value); - Some(self) - } - }; - (! @$flag:ident $builder_name:ident $name:ident : $ty:ty) => { - /// Set the named component and return `self`. - pub const fn $builder_name(mut self, value: $ty) -> Option { - self.$name = MaybeUninit::new(value); - // Safety: The field was just initialized. - self.flags |= Self::$flag; - Some(self) - } - }; } /// Builder methods @@ -640,29 +557,115 @@ macro_rules! builders { /// All builder methods return `Option`, which is `Some` if the value was set, and `None` if /// not. The builder methods _may_ fail if the value is invalid, though behavior is not guaranteed. impl Parsed { - builders! { - @YEAR_FLAG with_year year: i32, - @YEAR_LAST_TWO_FLAG with_year_last_two year_last_two: u8, - @ISO_YEAR_FLAG with_iso_year iso_year: i32, - @ISO_YEAR_LAST_TWO_FLAG with_iso_year_last_two iso_year_last_two: u8, - with_month month: Month, - @SUNDAY_WEEK_NUMBER_FLAG with_sunday_week_number sunday_week_number: u8, - @MONDAY_WEEK_NUMBER_FLAG with_monday_week_number monday_week_number: u8, - with_iso_week_number iso_week_number: NonZeroU8, - with_weekday weekday: Weekday, - with_ordinal ordinal: NonZeroU16, - with_day day: NonZeroU8, - @HOUR_24_FLAG with_hour_24 hour_24: u8, - with_hour_12 hour_12: NonZeroU8, - with_hour_12_is_pm hour_12_is_pm: bool, - @MINUTE_FLAG with_minute minute: u8, - @SECOND_FLAG with_second second: u8, - @SUBSECOND_FLAG with_subsecond subsecond: u32, - @OFFSET_HOUR_FLAG with_offset_hour offset_hour: i8, - @UNIX_TIMESTAMP_NANOS_FLAG with_unix_timestamp_nanos unix_timestamp_nanos: i128, - } - - /// Set the named component and return `self`. + /// Set the `year` component and return `self`. + pub const fn with_year(mut self, value: i32) -> Option { + self.year = OptionRangedI32::Some(const_try_opt!(RangedI32::new(value))); + Some(self) + } + + /// Set the `year_last_two` component and return `self`. + pub const fn with_year_last_two(mut self, value: u8) -> Option { + self.year_last_two = OptionRangedU8::Some(const_try_opt!(RangedU8::new(value))); + Some(self) + } + + /// Set the `iso_year` component and return `self`. + pub const fn with_iso_year(mut self, value: i32) -> Option { + self.iso_year = OptionRangedI32::Some(const_try_opt!(RangedI32::new(value))); + Some(self) + } + + /// Set the `iso_year_last_two` component and return `self`. + pub const fn with_iso_year_last_two(mut self, value: u8) -> Option { + self.iso_year_last_two = OptionRangedU8::Some(const_try_opt!(RangedU8::new(value))); + Some(self) + } + + /// Set the `month` component and return `self`. + pub const fn with_month(mut self, value: Month) -> Option { + self.month = Some(value); + Some(self) + } + + /// Set the `sunday_week_number` component and return `self`. + pub const fn with_sunday_week_number(mut self, value: u8) -> Option { + self.sunday_week_number = OptionRangedU8::Some(const_try_opt!(RangedU8::new(value))); + Some(self) + } + + /// Set the `monday_week_number` component and return `self`. + pub const fn with_monday_week_number(mut self, value: u8) -> Option { + self.monday_week_number = OptionRangedU8::Some(const_try_opt!(RangedU8::new(value))); + Some(self) + } + + /// Set the `iso_week_number` component and return `self`. + pub const fn with_iso_week_number(mut self, value: NonZeroU8) -> Option { + self.iso_week_number = OptionRangedU8::Some(const_try_opt!(RangedU8::new(value.get()))); + Some(self) + } + + /// Set the `weekday` component and return `self`. + pub const fn with_weekday(mut self, value: Weekday) -> Option { + self.weekday = Some(value); + Some(self) + } + + /// Set the `ordinal` component and return `self`. + pub const fn with_ordinal(mut self, value: NonZeroU16) -> Option { + self.ordinal = OptionRangedU16::Some(const_try_opt!(RangedU16::new(value.get()))); + Some(self) + } + + /// Set the `day` component and return `self`. + pub const fn with_day(mut self, value: NonZeroU8) -> Option { + self.day = OptionRangedU8::Some(const_try_opt!(RangedU8::new(value.get()))); + Some(self) + } + + /// Set the `hour_24` component and return `self`. + pub const fn with_hour_24(mut self, value: u8) -> Option { + self.hour_24 = OptionRangedU8::Some(const_try_opt!(RangedU8::new(value))); + Some(self) + } + + /// Set the `hour_12` component and return `self`. + pub const fn with_hour_12(mut self, value: NonZeroU8) -> Option { + self.hour_12 = OptionRangedU8::Some(const_try_opt!(RangedU8::new(value.get()))); + Some(self) + } + + /// Set the `hour_12_is_pm` component and return `self`. + pub const fn with_hour_12_is_pm(mut self, value: bool) -> Option { + self.hour_12_is_pm = Some(value); + Some(self) + } + + /// Set the `minute` component and return `self`. + pub const fn with_minute(mut self, value: u8) -> Option { + self.minute = OptionRangedU8::Some(const_try_opt!(RangedU8::new(value))); + Some(self) + } + + /// Set the `second` component and return `self`. + pub const fn with_second(mut self, value: u8) -> Option { + self.second = OptionRangedU8::Some(const_try_opt!(RangedU8::new(value))); + Some(self) + } + + /// Set the `subsecond` component and return `self`. + pub const fn with_subsecond(mut self, value: u32) -> Option { + self.subsecond = OptionRangedU32::Some(const_try_opt!(RangedU32::new(value))); + Some(self) + } + + /// Set the `offset_hour` component and return `self`. + pub const fn with_offset_hour(mut self, value: i8) -> Option { + self.offset_hour = OptionRangedI8::Some(const_try_opt!(RangedI8::new(value))); + Some(self) + } + + /// Set the `offset_minute` component and return `self`. #[doc(hidden)] #[deprecated( since = "0.3.8", @@ -678,13 +681,11 @@ impl Parsed { /// Set the `offset_minute` component and return `self`. pub const fn with_offset_minute_signed(mut self, value: i8) -> Option { - self.offset_minute = MaybeUninit::new(value); - // Safety: The field was just initialized. - self.flags |= Self::OFFSET_MINUTE_FLAG; + self.offset_minute = OptionRangedI8::Some(const_try_opt!(RangedI8::new(value))); Some(self) } - /// Set the named component and return `self`. + /// Set the `offset_minute` component and return `self`. #[doc(hidden)] #[deprecated( since = "0.3.8", @@ -700,8 +701,13 @@ impl Parsed { /// Set the `offset_second` component and return `self`. pub const fn with_offset_second_signed(mut self, value: i8) -> Option { - self.offset_second = MaybeUninit::new(value); - self.flags |= Self::OFFSET_SECOND_FLAG; + self.offset_second = OptionRangedI8::Some(const_try_opt!(RangedI8::new(value))); + Some(self) + } + + /// Set the `unix_timestamp_nanos` component and return `self`. + pub const fn with_unix_timestamp_nanos(mut self, value: i128) -> Option { + self.unix_timestamp_nanos = OptionRangedI128::Some(const_try_opt!(RangedI128::new(value))); Some(self) } } @@ -857,16 +863,15 @@ impl TryFrom for DateTime { // Some well-known formats explicitly allow leap seconds. We don't currently support them, // so treat it as the nearest preceding moment that can be represented. Because leap seconds // always fall at the end of a month UTC, reject any that are at other times. - let leap_second_input = - if parsed.get_flag(Parsed::LEAP_SECOND_ALLOWED_FLAG) && parsed.second() == Some(60) { - parsed.set_second(59).expect("59 is a valid second"); - parsed - .set_subsecond(999_999_999) - .expect("999_999_999 is a valid subsecond"); - true - } else { - false - }; + let leap_second_input = if parsed.leap_second_allowed && parsed.second() == Some(60) { + parsed.set_second(59).expect("59 is a valid second"); + parsed + .set_subsecond(999_999_999) + .expect("999_999_999 is a valid subsecond"); + true + } else { + false + }; let dt = Self { date: Date::try_from(parsed)?,