| //! Information parsed from an input and format description. |
| |
| use core::num::{NonZeroU16, NonZeroU8}; |
| |
| use deranged::{ |
| OptionRangedI128, OptionRangedI32, OptionRangedI8, OptionRangedU16, OptionRangedU32, |
| OptionRangedU8, RangedI128, RangedI32, RangedI8, RangedU16, RangedU32, RangedU8, |
| }; |
| use num_conv::prelude::*; |
| |
| use crate::convert::{Day, Hour, Minute, Nanosecond, Second}; |
| use crate::date::{MAX_YEAR, MIN_YEAR}; |
| use crate::error::TryFromParsed::InsufficientInformation; |
| #[cfg(feature = "alloc")] |
| use crate::format_description::OwnedFormatItem; |
| use crate::format_description::{modifier, Component, FormatItem}; |
| use crate::internal_macros::{bug, const_try_opt}; |
| use crate::parsing::component::{ |
| parse_day, parse_end, parse_hour, parse_ignore, parse_minute, parse_month, parse_offset_hour, |
| parse_offset_minute, parse_offset_second, parse_ordinal, parse_period, parse_second, |
| parse_subsecond, parse_unix_timestamp, parse_week_number, parse_weekday, parse_year, Period, |
| }; |
| use crate::parsing::ParsedItem; |
| use crate::{error, Date, Month, OffsetDateTime, PrimitiveDateTime, Time, UtcOffset, Weekday}; |
| |
| /// Sealed to prevent downstream implementations. |
| mod sealed { |
| use super::*; |
| |
| /// A trait to allow `parse_item` to be generic. |
| pub trait AnyFormatItem { |
| /// Parse a single item, returning the remaining input on success. |
| fn parse_item<'a>( |
| &self, |
| parsed: &mut Parsed, |
| input: &'a [u8], |
| ) -> Result<&'a [u8], error::ParseFromDescription>; |
| } |
| } |
| |
| impl sealed::AnyFormatItem for FormatItem<'_> { |
| fn parse_item<'a>( |
| &self, |
| parsed: &mut Parsed, |
| input: &'a [u8], |
| ) -> Result<&'a [u8], error::ParseFromDescription> { |
| match self { |
| Self::Literal(literal) => Parsed::parse_literal(input, literal), |
| Self::Component(component) => parsed.parse_component(input, *component), |
| Self::Compound(compound) => parsed.parse_items(input, compound), |
| Self::Optional(item) => parsed.parse_item(input, *item).or(Ok(input)), |
| Self::First(items) => { |
| let mut first_err = None; |
| |
| for item in items.iter() { |
| match parsed.parse_item(input, item) { |
| Ok(remaining_input) => return Ok(remaining_input), |
| Err(err) if first_err.is_none() => first_err = Some(err), |
| Err(_) => {} |
| } |
| } |
| |
| match first_err { |
| Some(err) => Err(err), |
| // This location will be reached if the slice is empty, skipping the `for` loop. |
| // As this case is expected to be uncommon, there's no need to check up front. |
| None => Ok(input), |
| } |
| } |
| } |
| } |
| } |
| |
| #[cfg(feature = "alloc")] |
| impl sealed::AnyFormatItem for OwnedFormatItem { |
| fn parse_item<'a>( |
| &self, |
| parsed: &mut Parsed, |
| input: &'a [u8], |
| ) -> Result<&'a [u8], error::ParseFromDescription> { |
| match self { |
| Self::Literal(literal) => Parsed::parse_literal(input, literal), |
| Self::Component(component) => parsed.parse_component(input, *component), |
| Self::Compound(compound) => parsed.parse_items(input, compound), |
| Self::Optional(item) => parsed.parse_item(input, item.as_ref()).or(Ok(input)), |
| Self::First(items) => { |
| let mut first_err = None; |
| |
| for item in items.iter() { |
| match parsed.parse_item(input, item) { |
| Ok(remaining_input) => return Ok(remaining_input), |
| Err(err) if first_err.is_none() => first_err = Some(err), |
| Err(_) => {} |
| } |
| } |
| |
| match first_err { |
| Some(err) => Err(err), |
| // This location will be reached if the slice is empty, skipping the `for` loop. |
| // As this case is expected to be uncommon, there's no need to check up front. |
| None => Ok(input), |
| } |
| } |
| } |
| } |
| } |
| |
| /// 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(Debug, Clone, Copy)] |
| pub struct Parsed { |
| /// Calendar year. |
| year: OptionRangedI32<{ MIN_YEAR }, { MAX_YEAR }>, |
| /// The last two digits of the calendar year. |
| year_last_two: OptionRangedU8<0, 99>, |
| /// Year of the [ISO week date](https://en.wikipedia.org/wiki/ISO_week_date). |
| iso_year: OptionRangedI32<{ MIN_YEAR }, { MAX_YEAR }>, |
| /// The last two digits of the ISO week year. |
| iso_year_last_two: OptionRangedU8<0, 99>, |
| /// Month of the year. |
| month: Option<Month>, |
| /// Week of the year, where week one begins on the first Sunday of the calendar year. |
| 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: OptionRangedU8<0, 53>, |
| /// Week of the year, where week one is the Monday-to-Sunday period containing January 4. |
| iso_week_number: OptionRangedU8<1, 53>, |
| /// Day of the week. |
| weekday: Option<Weekday>, |
| /// Day of the year. |
| ordinal: OptionRangedU16<1, 366>, |
| /// Day of the month. |
| day: OptionRangedU8<1, 31>, |
| /// Hour within the day. |
| 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: OptionRangedU8<1, 12>, |
| /// Whether the `hour_12` field indicates a time that "PM". |
| hour_12_is_pm: Option<bool>, |
| /// Minute within the hour. |
| // minute: MaybeUninit<u8>, |
| minute: OptionRangedU8<0, { Minute::per(Hour) - 1 }>, |
| /// Second within the minute. |
| // do not subtract one, as leap seconds may be allowed |
| second: OptionRangedU8<0, { Second::per(Minute) }>, |
| /// Nanosecond within the second. |
| subsecond: OptionRangedU32<0, { Nanosecond::per(Second) - 1 }>, |
| /// Whole hours of the UTC offset. |
| offset_hour: OptionRangedI8<-23, 23>, |
| /// Minutes within the hour of the UTC offset. |
| offset_minute: |
| OptionRangedI8<{ -((Minute::per(Hour) - 1) as i8) }, { (Minute::per(Hour) - 1) as _ }>, |
| /// Seconds within the minute of the UTC offset. |
| offset_second: |
| OptionRangedI8<{ -((Second::per(Minute) - 1) as i8) }, { (Second::per(Minute) - 1) as _ }>, |
| /// The Unix timestamp in nanoseconds. |
| unix_timestamp_nanos: OptionRangedI128< |
| { |
| OffsetDateTime::new_in_offset(Date::MIN, Time::MIDNIGHT, UtcOffset::UTC) |
| .unix_timestamp_nanos() |
| }, |
| { |
| OffsetDateTime::new_in_offset(Date::MAX, Time::MAX, UtcOffset::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<bool>, |
| /// Indicates whether a leap second is permitted to be parsed. This is required by some |
| /// well-known formats. |
| pub(super) leap_second_allowed: bool, |
| } |
| |
| impl Parsed { |
| /// Create a new instance of `Parsed` with no information known. |
| pub const fn new() -> Self { |
| Self { |
| year: OptionRangedI32::None, |
| year_last_two: OptionRangedU8::None, |
| iso_year: OptionRangedI32::None, |
| iso_year_last_two: OptionRangedU8::None, |
| month: None, |
| sunday_week_number: OptionRangedU8::None, |
| monday_week_number: OptionRangedU8::None, |
| iso_week_number: OptionRangedU8::None, |
| weekday: None, |
| ordinal: OptionRangedU16::None, |
| day: OptionRangedU8::None, |
| hour_24: OptionRangedU8::None, |
| hour_12: OptionRangedU8::None, |
| hour_12_is_pm: None, |
| 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, |
| } |
| } |
| |
| /// Parse a single [`FormatItem`] or [`OwnedFormatItem`], mutating the struct. The remaining |
| /// input is returned as the `Ok` value. |
| /// |
| /// If a [`FormatItem::Optional`] or [`OwnedFormatItem::Optional`] is passed, parsing will not |
| /// fail; the input will be returned as-is if the expected format is not present. |
| pub fn parse_item<'a>( |
| &mut self, |
| input: &'a [u8], |
| item: &impl sealed::AnyFormatItem, |
| ) -> Result<&'a [u8], error::ParseFromDescription> { |
| item.parse_item(self, input) |
| } |
| |
| /// Parse a sequence of [`FormatItem`]s or [`OwnedFormatItem`]s, mutating the struct. The |
| /// remaining input is returned as the `Ok` value. |
| /// |
| /// This method will fail if any of the contained [`FormatItem`]s or [`OwnedFormatItem`]s fail |
| /// to parse. `self` will not be mutated in this instance. |
| pub fn parse_items<'a>( |
| &mut self, |
| mut input: &'a [u8], |
| items: &[impl sealed::AnyFormatItem], |
| ) -> Result<&'a [u8], error::ParseFromDescription> { |
| // Make a copy that we can mutate. It will only be set to the user's copy if everything |
| // succeeds. |
| let mut this = *self; |
| for item in items { |
| input = this.parse_item(input, item)?; |
| } |
| *self = this; |
| Ok(input) |
| } |
| |
| /// Parse a literal byte sequence. The remaining input is returned as the `Ok` value. |
| pub fn parse_literal<'a>( |
| input: &'a [u8], |
| literal: &[u8], |
| ) -> Result<&'a [u8], error::ParseFromDescription> { |
| input |
| .strip_prefix(literal) |
| .ok_or(error::ParseFromDescription::InvalidLiteral) |
| } |
| |
| /// Parse a single component, mutating the struct. The remaining input is returned as the `Ok` |
| /// value. |
| pub fn parse_component<'a>( |
| &mut self, |
| input: &'a [u8], |
| component: Component, |
| ) -> Result<&'a [u8], error::ParseFromDescription> { |
| use error::ParseFromDescription::InvalidComponent; |
| |
| match component { |
| Component::Day(modifiers) => parse_day(input, modifiers) |
| .and_then(|parsed| parsed.consume_value(|value| self.set_day(value))) |
| .ok_or(InvalidComponent("day")), |
| Component::Month(modifiers) => parse_month(input, modifiers) |
| .and_then(|parsed| parsed.consume_value(|value| self.set_month(value))) |
| .ok_or(InvalidComponent("month")), |
| Component::Ordinal(modifiers) => parse_ordinal(input, modifiers) |
| .and_then(|parsed| parsed.consume_value(|value| self.set_ordinal(value))) |
| .ok_or(InvalidComponent("ordinal")), |
| Component::Weekday(modifiers) => parse_weekday(input, modifiers) |
| .and_then(|parsed| parsed.consume_value(|value| self.set_weekday(value))) |
| .ok_or(InvalidComponent("weekday")), |
| Component::WeekNumber(modifiers) => { |
| let ParsedItem(remaining, value) = |
| parse_week_number(input, modifiers).ok_or(InvalidComponent("week number"))?; |
| match modifiers.repr { |
| modifier::WeekNumberRepr::Iso => { |
| NonZeroU8::new(value).and_then(|value| self.set_iso_week_number(value)) |
| } |
| modifier::WeekNumberRepr::Sunday => self.set_sunday_week_number(value), |
| modifier::WeekNumberRepr::Monday => self.set_monday_week_number(value), |
| } |
| .ok_or(InvalidComponent("week number"))?; |
| Ok(remaining) |
| } |
| Component::Year(modifiers) => { |
| let ParsedItem(remaining, value) = |
| parse_year(input, modifiers).ok_or(InvalidComponent("year"))?; |
| match (modifiers.iso_week_based, modifiers.repr) { |
| (false, modifier::YearRepr::Full) => self.set_year(value), |
| (false, modifier::YearRepr::LastTwo) => { |
| self.set_year_last_two(value.cast_unsigned().truncate()) |
| } |
| (true, modifier::YearRepr::Full) => self.set_iso_year(value), |
| (true, modifier::YearRepr::LastTwo) => { |
| self.set_iso_year_last_two(value.cast_unsigned().truncate()) |
| } |
| } |
| .ok_or(InvalidComponent("year"))?; |
| Ok(remaining) |
| } |
| Component::Hour(modifiers) => { |
| let ParsedItem(remaining, value) = |
| parse_hour(input, modifiers).ok_or(InvalidComponent("hour"))?; |
| if modifiers.is_12_hour_clock { |
| NonZeroU8::new(value).and_then(|value| self.set_hour_12(value)) |
| } else { |
| self.set_hour_24(value) |
| } |
| .ok_or(InvalidComponent("hour"))?; |
| Ok(remaining) |
| } |
| Component::Minute(modifiers) => parse_minute(input, modifiers) |
| .and_then(|parsed| parsed.consume_value(|value| self.set_minute(value))) |
| .ok_or(InvalidComponent("minute")), |
| Component::Period(modifiers) => parse_period(input, modifiers) |
| .and_then(|parsed| { |
| parsed.consume_value(|value| self.set_hour_12_is_pm(value == Period::Pm)) |
| }) |
| .ok_or(InvalidComponent("period")), |
| Component::Second(modifiers) => parse_second(input, modifiers) |
| .and_then(|parsed| parsed.consume_value(|value| self.set_second(value))) |
| .ok_or(InvalidComponent("second")), |
| Component::Subsecond(modifiers) => parse_subsecond(input, modifiers) |
| .and_then(|parsed| parsed.consume_value(|value| self.set_subsecond(value))) |
| .ok_or(InvalidComponent("subsecond")), |
| Component::OffsetHour(modifiers) => parse_offset_hour(input, modifiers) |
| .and_then(|parsed| { |
| parsed.consume_value(|(value, is_negative)| { |
| self.set_offset_hour(value)?; |
| self.offset_is_negative = Some(is_negative); |
| Some(()) |
| }) |
| }) |
| .ok_or(InvalidComponent("offset hour")), |
| Component::OffsetMinute(modifiers) => parse_offset_minute(input, modifiers) |
| .and_then(|parsed| { |
| parsed.consume_value(|value| self.set_offset_minute_signed(value)) |
| }) |
| .ok_or(InvalidComponent("offset minute")), |
| Component::OffsetSecond(modifiers) => parse_offset_second(input, modifiers) |
| .and_then(|parsed| { |
| parsed.consume_value(|value| self.set_offset_second_signed(value)) |
| }) |
| .ok_or(InvalidComponent("offset second")), |
| Component::Ignore(modifiers) => parse_ignore(input, modifiers) |
| .map(ParsedItem::<()>::into_inner) |
| .ok_or(InvalidComponent("ignore")), |
| Component::UnixTimestamp(modifiers) => parse_unix_timestamp(input, modifiers) |
| .and_then(|parsed| { |
| parsed.consume_value(|value| self.set_unix_timestamp_nanos(value)) |
| }) |
| .ok_or(InvalidComponent("unix_timestamp")), |
| Component::End(modifiers) => parse_end(input, modifiers) |
| .map(ParsedItem::<()>::into_inner) |
| .ok_or(error::ParseFromDescription::UnexpectedTrailingCharacters), |
| } |
| } |
| } |
| |
| /// Getter methods |
| impl Parsed { |
| /// Obtain the `year` component. |
| pub const fn year(&self) -> Option<i32> { |
| self.year.get_primitive() |
| } |
| |
| /// Obtain the `year_last_two` component. |
| pub const fn year_last_two(&self) -> Option<u8> { |
| self.year_last_two.get_primitive() |
| } |
| |
| /// Obtain the `iso_year` component. |
| pub const fn iso_year(&self) -> Option<i32> { |
| self.iso_year.get_primitive() |
| } |
| |
| /// Obtain the `iso_year_last_two` component. |
| pub const fn iso_year_last_two(&self) -> Option<u8> { |
| self.iso_year_last_two.get_primitive() |
| } |
| |
| /// Obtain the `month` component. |
| pub const fn month(&self) -> Option<Month> { |
| self.month |
| } |
| |
| /// Obtain the `sunday_week_number` component. |
| pub const fn sunday_week_number(&self) -> Option<u8> { |
| self.sunday_week_number.get_primitive() |
| } |
| |
| /// Obtain the `monday_week_number` component. |
| pub const fn monday_week_number(&self) -> Option<u8> { |
| self.monday_week_number.get_primitive() |
| } |
| |
| /// Obtain the `iso_week_number` component. |
| pub const fn iso_week_number(&self) -> Option<NonZeroU8> { |
| NonZeroU8::new(const_try_opt!(self.iso_week_number.get_primitive())) |
| } |
| |
| /// Obtain the `weekday` component. |
| pub const fn weekday(&self) -> Option<Weekday> { |
| self.weekday |
| } |
| |
| /// Obtain the `ordinal` component. |
| pub const fn ordinal(&self) -> Option<NonZeroU16> { |
| NonZeroU16::new(const_try_opt!(self.ordinal.get_primitive())) |
| } |
| |
| /// Obtain the `day` component. |
| pub const fn day(&self) -> Option<NonZeroU8> { |
| NonZeroU8::new(const_try_opt!(self.day.get_primitive())) |
| } |
| |
| /// Obtain the `hour_24` component. |
| pub const fn hour_24(&self) -> Option<u8> { |
| self.hour_24.get_primitive() |
| } |
| |
| /// Obtain the `hour_12` component. |
| pub const fn hour_12(&self) -> Option<NonZeroU8> { |
| 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<bool> { |
| self.hour_12_is_pm |
| } |
| |
| /// Obtain the `minute` component. |
| pub const fn minute(&self) -> Option<u8> { |
| self.minute.get_primitive() |
| } |
| |
| /// Obtain the `second` component. |
| pub const fn second(&self) -> Option<u8> { |
| self.second.get_primitive() |
| } |
| |
| /// Obtain the `subsecond` component. |
| pub const fn subsecond(&self) -> Option<u32> { |
| self.subsecond.get_primitive() |
| } |
| |
| /// Obtain the `offset_hour` component. |
| pub const fn offset_hour(&self) -> Option<i8> { |
| 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<u8> { |
| Some(const_try_opt!(self.offset_minute_signed()).unsigned_abs()) |
| } |
| |
| /// Obtain the `offset_minute` component. |
| pub const fn offset_minute_signed(&self) -> Option<i8> { |
| 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` component. |
| #[doc(hidden)] |
| #[deprecated(since = "0.3.8", note = "use `parsed.offset_second_signed()` instead")] |
| pub const fn offset_second(&self) -> Option<u8> { |
| Some(const_try_opt!(self.offset_second_signed()).unsigned_abs()) |
| } |
| |
| /// Obtain the `offset_second` component. |
| pub const fn offset_second_signed(&self) -> Option<i8> { |
| 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<i128> { |
| self.unix_timestamp_nanos.get_primitive() |
| } |
| } |
| |
| /// Generate setters based on the builders. |
| macro_rules! setters { |
| ($($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 |
| /// |
| /// All setters return `Option<()>`, which is `Some` if the value was set, and `None` if not. The |
| /// setters _may_ fail if the value is invalid, though behavior is not guaranteed. |
| impl Parsed { |
| setters! { |
| 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", |
| note = "use `parsed.set_offset_minute_signed()` instead" |
| )] |
| pub fn set_offset_minute(&mut self, value: u8) -> Option<()> { |
| if value > i8::MAX.cast_unsigned() { |
| None |
| } else { |
| self.set_offset_minute_signed(value.cast_signed()) |
| } |
| } |
| |
| /// Set the `offset_minute` component. |
| #[doc(hidden)] |
| #[deprecated( |
| since = "0.3.8", |
| note = "use `parsed.set_offset_second_signed()` instead" |
| )] |
| pub fn set_offset_second(&mut self, value: u8) -> Option<()> { |
| if value > i8::MAX.cast_unsigned() { |
| None |
| } else { |
| self.set_offset_second_signed(value.cast_signed()) |
| } |
| } |
| } |
| |
| /// Builder methods |
| /// |
| /// All builder methods return `Option<Self>`, 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 { |
| /// Set the `year` component and return `self`. |
| pub const fn with_year(mut self, value: i32) -> Option<Self> { |
| 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> { |
| 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> { |
| 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> { |
| 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> { |
| 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> { |
| 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> { |
| 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> { |
| 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> { |
| self.weekday = Some(value); |
| Some(self) |
| } |
| |
| /// Set the `ordinal` component and return `self`. |
| pub const fn with_ordinal(mut self, value: NonZeroU16) -> Option<Self> { |
| 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> { |
| 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> { |
| 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> { |
| 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> { |
| 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> { |
| 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> { |
| 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> { |
| 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> { |
| 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", |
| note = "use `parsed.with_offset_minute_signed()` instead" |
| )] |
| pub const fn with_offset_minute(self, value: u8) -> Option<Self> { |
| if value > i8::MAX as u8 { |
| None |
| } else { |
| self.with_offset_minute_signed(value as _) |
| } |
| } |
| |
| /// Set the `offset_minute` component and return `self`. |
| pub const fn with_offset_minute_signed(mut self, value: i8) -> Option<Self> { |
| self.offset_minute = 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", |
| note = "use `parsed.with_offset_second_signed()` instead" |
| )] |
| pub const fn with_offset_second(self, value: u8) -> Option<Self> { |
| if value > i8::MAX as u8 { |
| None |
| } else { |
| self.with_offset_second_signed(value as _) |
| } |
| } |
| |
| /// Set the `offset_second` component and return `self`. |
| pub const fn with_offset_second_signed(mut self, value: i8) -> Option<Self> { |
| 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> { |
| self.unix_timestamp_nanos = OptionRangedI128::Some(const_try_opt!(RangedI128::new(value))); |
| Some(self) |
| } |
| } |
| |
| impl TryFrom<Parsed> for Date { |
| type Error = error::TryFromParsed; |
| |
| fn try_from(parsed: Parsed) -> Result<Self, Self::Error> { |
| /// Match on the components that need to be present. |
| macro_rules! match_ { |
| (_ => $catch_all:expr $(,)?) => { |
| $catch_all |
| }; |
| (($($name:ident),* $(,)?) => $arm:expr, $($rest:tt)*) => { |
| if let ($(Some($name)),*) = ($(parsed.$name()),*) { |
| $arm |
| } else { |
| match_!($($rest)*) |
| } |
| }; |
| } |
| |
| /// Get the value needed to adjust the ordinal day for Sunday and Monday-based week |
| /// numbering. |
| const fn adjustment(year: i32) -> i16 { |
| // Safety: `ordinal` is not zero. |
| match unsafe { Date::__from_ordinal_date_unchecked(year, 1) }.weekday() { |
| Weekday::Monday => 7, |
| Weekday::Tuesday => 1, |
| Weekday::Wednesday => 2, |
| Weekday::Thursday => 3, |
| Weekday::Friday => 4, |
| Weekday::Saturday => 5, |
| Weekday::Sunday => 6, |
| } |
| } |
| |
| // TODO Only the basics have been covered. There are many other valid values that are not |
| // currently constructed from the information known. |
| |
| match_! { |
| (year, ordinal) => Ok(Self::from_ordinal_date(year, ordinal.get())?), |
| (year, month, day) => Ok(Self::from_calendar_date(year, month, day.get())?), |
| (iso_year, iso_week_number, weekday) => Ok(Self::from_iso_week_date( |
| iso_year, |
| iso_week_number.get(), |
| weekday, |
| )?), |
| (year, sunday_week_number, weekday) => Ok(Self::from_ordinal_date( |
| year, |
| (sunday_week_number.cast_signed().extend::<i16>() * 7 |
| + weekday.number_days_from_sunday().cast_signed().extend::<i16>() |
| - adjustment(year) |
| + 1).cast_unsigned(), |
| )?), |
| (year, monday_week_number, weekday) => Ok(Self::from_ordinal_date( |
| year, |
| (monday_week_number.cast_signed().extend::<i16>() * 7 |
| + weekday.number_days_from_monday().cast_signed().extend::<i16>() |
| - adjustment(year) |
| + 1).cast_unsigned(), |
| )?), |
| _ => Err(InsufficientInformation), |
| } |
| } |
| } |
| |
| impl TryFrom<Parsed> for Time { |
| type Error = error::TryFromParsed; |
| |
| fn try_from(parsed: Parsed) -> Result<Self, Self::Error> { |
| let hour = match (parsed.hour_24(), parsed.hour_12(), parsed.hour_12_is_pm()) { |
| (Some(hour), _, _) => hour, |
| (_, Some(hour), Some(false)) if hour.get() == 12 => 0, |
| (_, Some(hour), Some(true)) if hour.get() == 12 => 12, |
| (_, Some(hour), Some(false)) => hour.get(), |
| (_, Some(hour), Some(true)) => hour.get() + 12, |
| _ => return Err(InsufficientInformation), |
| }; |
| |
| if parsed.hour_24().is_none() |
| && parsed.hour_12().is_some() |
| && parsed.hour_12_is_pm().is_some() |
| && parsed.minute().is_none() |
| && parsed.second().is_none() |
| && parsed.subsecond().is_none() |
| { |
| return Ok(Self::from_hms_nano(hour, 0, 0, 0)?); |
| } |
| |
| // Reject combinations such as hour-second with minute omitted. |
| match (parsed.minute(), parsed.second(), parsed.subsecond()) { |
| (None, None, None) => Ok(Self::from_hms_nano(hour, 0, 0, 0)?), |
| (Some(minute), None, None) => Ok(Self::from_hms_nano(hour, minute, 0, 0)?), |
| (Some(minute), Some(second), None) => Ok(Self::from_hms_nano(hour, minute, second, 0)?), |
| (Some(minute), Some(second), Some(subsecond)) => { |
| Ok(Self::from_hms_nano(hour, minute, second, subsecond)?) |
| } |
| _ => Err(InsufficientInformation), |
| } |
| } |
| } |
| |
| impl TryFrom<Parsed> for UtcOffset { |
| type Error = error::TryFromParsed; |
| |
| fn try_from(parsed: Parsed) -> Result<Self, Self::Error> { |
| let hour = parsed.offset_hour().ok_or(InsufficientInformation)?; |
| let minute = parsed.offset_minute_signed().unwrap_or(0); |
| let second = parsed.offset_second_signed().unwrap_or(0); |
| |
| Self::from_hms(hour, minute, second).map_err(|mut err| { |
| // Provide the user a more accurate error. |
| if err.name == "hours" { |
| err.name = "offset hour"; |
| } else if err.name == "minutes" { |
| err.name = "offset minute"; |
| } else if err.name == "seconds" { |
| err.name = "offset second"; |
| } |
| err.into() |
| }) |
| } |
| } |
| |
| impl TryFrom<Parsed> for PrimitiveDateTime { |
| type Error = error::TryFromParsed; |
| |
| fn try_from(parsed: Parsed) -> Result<Self, Self::Error> { |
| Ok(Self::new(parsed.try_into()?, parsed.try_into()?)) |
| } |
| } |
| |
| impl TryFrom<Parsed> for OffsetDateTime { |
| type Error = error::TryFromParsed; |
| |
| fn try_from(mut parsed: Parsed) -> Result<Self, Self::Error> { |
| if let Some(timestamp) = parsed.unix_timestamp_nanos() { |
| let mut value = Self::from_unix_timestamp_nanos(timestamp)?; |
| if let Some(subsecond) = parsed.subsecond() { |
| value = value.replace_nanosecond(subsecond)?; |
| } |
| return Ok(value); |
| } |
| |
| // 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.leap_second_allowed && parsed.second() == Some(60) { |
| if parsed.set_second(59).is_none() { |
| bug!("59 is a valid second"); |
| } |
| if parsed.set_subsecond(999_999_999).is_none() { |
| bug!("999_999_999 is a valid subsecond"); |
| } |
| true |
| } else { |
| false |
| }; |
| |
| let dt = Self::new_in_offset( |
| Date::try_from(parsed)?, |
| Time::try_from(parsed)?, |
| UtcOffset::try_from(parsed)?, |
| ); |
| |
| if leap_second_input && !dt.is_valid_leap_second_stand_in() { |
| return Err(error::TryFromParsed::ComponentRange( |
| error::ComponentRange { |
| name: "second", |
| minimum: 0, |
| maximum: 59, |
| value: 60, |
| conditional_range: true, |
| }, |
| )); |
| } |
| Ok(dt) |
| } |
| } |