From 43a97878ce14b72f0981164f87f2e35e14151312 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 11:22:09 +0200 Subject: Adding upstream version 110.0.1. Signed-off-by: Daniel Baumann --- .../rust/time/src/parsing/combinator/mod.rs | 192 ++++++ .../time/src/parsing/combinator/rfc/iso8601.rs | 173 +++++ .../rust/time/src/parsing/combinator/rfc/mod.rs | 10 + .../time/src/parsing/combinator/rfc/rfc2234.rs | 13 + .../time/src/parsing/combinator/rfc/rfc2822.rs | 115 ++++ third_party/rust/time/src/parsing/component.rs | 296 ++++++++ third_party/rust/time/src/parsing/iso8601.rs | 308 +++++++++ third_party/rust/time/src/parsing/mod.rs | 50 ++ third_party/rust/time/src/parsing/parsable.rs | 754 ++++++++++++++++++++ third_party/rust/time/src/parsing/parsed.rs | 759 +++++++++++++++++++++ third_party/rust/time/src/parsing/shim.rs | 50 ++ 11 files changed, 2720 insertions(+) create mode 100644 third_party/rust/time/src/parsing/combinator/mod.rs create mode 100644 third_party/rust/time/src/parsing/combinator/rfc/iso8601.rs create mode 100644 third_party/rust/time/src/parsing/combinator/rfc/mod.rs create mode 100644 third_party/rust/time/src/parsing/combinator/rfc/rfc2234.rs create mode 100644 third_party/rust/time/src/parsing/combinator/rfc/rfc2822.rs create mode 100644 third_party/rust/time/src/parsing/component.rs create mode 100644 third_party/rust/time/src/parsing/iso8601.rs create mode 100644 third_party/rust/time/src/parsing/mod.rs create mode 100644 third_party/rust/time/src/parsing/parsable.rs create mode 100644 third_party/rust/time/src/parsing/parsed.rs create mode 100644 third_party/rust/time/src/parsing/shim.rs (limited to 'third_party/rust/time/src/parsing') diff --git a/third_party/rust/time/src/parsing/combinator/mod.rs b/third_party/rust/time/src/parsing/combinator/mod.rs new file mode 100644 index 0000000000..3b4bc7a81b --- /dev/null +++ b/third_party/rust/time/src/parsing/combinator/mod.rs @@ -0,0 +1,192 @@ +//! Implementations of the low-level parser combinators. + +pub(crate) mod rfc; + +use crate::format_description::modifier::Padding; +use crate::parsing::shim::{Integer, IntegerParseBytes}; +use crate::parsing::ParsedItem; + +/// Parse a "+" or "-" sign. Returns the ASCII byte representing the sign, if present. +pub(crate) const fn sign(input: &[u8]) -> Option> { + match input { + [sign @ (b'-' | b'+'), remaining @ ..] => Some(ParsedItem(remaining, *sign)), + _ => None, + } +} + +/// Consume the first matching item, returning its associated value. +pub(crate) fn first_match<'a, T>( + options: impl IntoIterator, + case_sensitive: bool, +) -> impl FnMut(&'a [u8]) -> Option> { + let mut options = options.into_iter(); + move |input| { + options.find_map(|(expected, t)| { + if case_sensitive { + Some(ParsedItem(input.strip_prefix(expected)?, t)) + } else { + let n = expected.len(); + if n <= input.len() { + let (head, tail) = input.split_at(n); + if head.eq_ignore_ascii_case(expected) { + return Some(ParsedItem(tail, t)); + } + } + None + } + }) + } +} + +/// Consume zero or more instances of the provided parser. The parser must return the unit value. +pub(crate) fn zero_or_more<'a, P: Fn(&'a [u8]) -> Option>>( + parser: P, +) -> impl FnMut(&'a [u8]) -> ParsedItem<'a, ()> { + move |mut input| { + while let Some(remaining) = parser(input) { + input = remaining.into_inner(); + } + ParsedItem(input, ()) + } +} + +/// Consume one of or more instances of the provided parser. The parser must produce the unit value. +pub(crate) fn one_or_more<'a, P: Fn(&'a [u8]) -> Option>>( + parser: P, +) -> impl Fn(&'a [u8]) -> Option> { + move |mut input| { + input = parser(input)?.into_inner(); + while let Some(remaining) = parser(input) { + input = remaining.into_inner(); + } + Some(ParsedItem(input, ())) + } +} + +/// Consume between `n` and `m` instances of the provided parser. +pub(crate) fn n_to_m< + 'a, + const N: u8, + const M: u8, + T, + P: Fn(&'a [u8]) -> Option>, +>( + parser: P, +) -> impl Fn(&'a [u8]) -> Option> { + debug_assert!(M >= N); + move |mut input| { + // We need to keep this to determine the total length eventually consumed. + let orig_input = input; + + // Mandatory + for _ in 0..N { + input = parser(input)?.0; + } + + // Optional + for _ in N..M { + match parser(input) { + Some(parsed) => input = parsed.0, + None => break, + } + } + + Some(ParsedItem( + input, + &orig_input[..(orig_input.len() - input.len())], + )) + } +} + +/// Consume between `n` and `m` digits, returning the numerical value. +pub(crate) fn n_to_m_digits( + input: &[u8], +) -> Option> { + debug_assert!(M >= N); + n_to_m::(any_digit)(input)?.flat_map(|value| value.parse_bytes()) +} + +/// Consume exactly `n` digits, returning the numerical value. +pub(crate) fn exactly_n_digits(input: &[u8]) -> Option> { + n_to_m_digits::(input) +} + +/// Consume exactly `n` digits, returning the numerical value. +pub(crate) fn exactly_n_digits_padded<'a, const N: u8, T: Integer>( + padding: Padding, +) -> impl Fn(&'a [u8]) -> Option> { + n_to_m_digits_padded::(padding) +} + +/// Consume between `n` and `m` digits, returning the numerical value. +pub(crate) fn n_to_m_digits_padded<'a, const N: u8, const M: u8, T: Integer>( + padding: Padding, +) -> impl Fn(&'a [u8]) -> Option> { + debug_assert!(M >= N); + move |mut input| match padding { + Padding::None => n_to_m_digits::<1, M, _>(input), + Padding::Space => { + debug_assert!(N > 0); + + let mut orig_input = input; + for _ in 0..(N - 1) { + match ascii_char::(input) { + Some(parsed) => input = parsed.0, + None => break, + } + } + let pad_width = (orig_input.len() - input.len()) as u8; + + orig_input = input; + for _ in 0..(N - pad_width) { + input = any_digit(input)?.0; + } + for _ in N..M { + match any_digit(input) { + Some(parsed) => input = parsed.0, + None => break, + } + } + + ParsedItem(input, &orig_input[..(orig_input.len() - input.len())]) + .flat_map(|value| value.parse_bytes()) + } + Padding::Zero => n_to_m_digits::(input), + } +} + +/// Consume exactly one digit. +pub(crate) const fn any_digit(input: &[u8]) -> Option> { + match input { + [c, remaining @ ..] if c.is_ascii_digit() => Some(ParsedItem(remaining, *c)), + _ => None, + } +} + +/// Consume exactly one of the provided ASCII characters. +pub(crate) fn ascii_char(input: &[u8]) -> Option> { + debug_assert!(CHAR.is_ascii_graphic() || CHAR.is_ascii_whitespace()); + match input { + [c, remaining @ ..] if *c == CHAR => Some(ParsedItem(remaining, ())), + _ => None, + } +} + +/// Consume exactly one of the provided ASCII characters, case-insensitive. +pub(crate) fn ascii_char_ignore_case(input: &[u8]) -> Option> { + debug_assert!(CHAR.is_ascii_graphic() || CHAR.is_ascii_whitespace()); + match input { + [c, remaining @ ..] if c.eq_ignore_ascii_case(&CHAR) => Some(ParsedItem(remaining, ())), + _ => None, + } +} + +/// Optionally consume an input with a given parser. +pub(crate) fn opt<'a, T>( + parser: impl Fn(&'a [u8]) -> Option>, +) -> impl Fn(&'a [u8]) -> ParsedItem<'a, Option> { + move |input| match parser(input) { + Some(value) => value.map(Some), + None => ParsedItem(input, None), + } +} diff --git a/third_party/rust/time/src/parsing/combinator/rfc/iso8601.rs b/third_party/rust/time/src/parsing/combinator/rfc/iso8601.rs new file mode 100644 index 0000000000..613a9057ff --- /dev/null +++ b/third_party/rust/time/src/parsing/combinator/rfc/iso8601.rs @@ -0,0 +1,173 @@ +//! Rules defined in [ISO 8601]. +//! +//! [ISO 8601]: https://www.iso.org/iso-8601-date-and-time-format.html + +use core::num::{NonZeroU16, NonZeroU8}; + +use crate::parsing::combinator::{any_digit, ascii_char, exactly_n_digits, first_match, sign}; +use crate::parsing::ParsedItem; +use crate::{Month, Weekday}; + +/// What kind of format is being parsed. This is used to ensure each part of the format (date, time, +/// offset) is the same kind. +#[derive(Debug, Clone, Copy)] +pub(crate) enum ExtendedKind { + /// The basic format. + Basic, + /// The extended format. + Extended, + /// ¯\_(ツ)_/¯ + Unknown, +} + +impl ExtendedKind { + /// Is it possible that the format is extended? + pub(crate) const fn maybe_extended(self) -> bool { + matches!(self, Self::Extended | Self::Unknown) + } + + /// Is the format known for certain to be extended? + pub(crate) const fn is_extended(self) -> bool { + matches!(self, Self::Extended) + } + + /// If the kind is `Unknown`, make it `Basic`. Otherwise, do nothing. Returns `Some` if and only + /// if the kind is now `Basic`. + pub(crate) fn coerce_basic(&mut self) -> Option<()> { + match self { + Self::Basic => Some(()), + Self::Extended => None, + Self::Unknown => { + *self = Self::Basic; + Some(()) + } + } + } + + /// If the kind is `Unknown`, make it `Extended`. Otherwise, do nothing. Returns `Some` if and + /// only if the kind is now `Extended`. + pub(crate) fn coerce_extended(&mut self) -> Option<()> { + match self { + Self::Basic => None, + Self::Extended => Some(()), + Self::Unknown => { + *self = Self::Extended; + Some(()) + } + } + } +} + +/// Parse a possibly expanded year. +pub(crate) fn year(input: &[u8]) -> Option> { + Some(match sign(input) { + Some(ParsedItem(input, sign)) => exactly_n_digits::<6, u32>(input)?.map(|val| { + let val = val as i32; + if sign == b'-' { -val } else { val } + }), + None => exactly_n_digits::<4, u32>(input)?.map(|val| val as _), + }) +} + +/// Parse a month. +pub(crate) fn month(input: &[u8]) -> Option> { + first_match( + [ + (b"01".as_slice(), Month::January), + (b"02".as_slice(), Month::February), + (b"03".as_slice(), Month::March), + (b"04".as_slice(), Month::April), + (b"05".as_slice(), Month::May), + (b"06".as_slice(), Month::June), + (b"07".as_slice(), Month::July), + (b"08".as_slice(), Month::August), + (b"09".as_slice(), Month::September), + (b"10".as_slice(), Month::October), + (b"11".as_slice(), Month::November), + (b"12".as_slice(), Month::December), + ], + true, + )(input) +} + +/// Parse a week number. +pub(crate) fn week(input: &[u8]) -> Option> { + exactly_n_digits::<2, _>(input) +} + +/// Parse a day of the month. +pub(crate) fn day(input: &[u8]) -> Option> { + exactly_n_digits::<2, _>(input) +} + +/// Parse a day of the week. +pub(crate) fn dayk(input: &[u8]) -> Option> { + first_match( + [ + (b"1".as_slice(), Weekday::Monday), + (b"2".as_slice(), Weekday::Tuesday), + (b"3".as_slice(), Weekday::Wednesday), + (b"4".as_slice(), Weekday::Thursday), + (b"5".as_slice(), Weekday::Friday), + (b"6".as_slice(), Weekday::Saturday), + (b"7".as_slice(), Weekday::Sunday), + ], + true, + )(input) +} + +/// Parse a day of the year. +pub(crate) fn dayo(input: &[u8]) -> Option> { + exactly_n_digits::<3, _>(input) +} + +/// Parse the hour. +pub(crate) fn hour(input: &[u8]) -> Option> { + exactly_n_digits::<2, _>(input) +} + +/// Parse the minute. +pub(crate) fn min(input: &[u8]) -> Option> { + exactly_n_digits::<2, _>(input) +} + +/// Parse a floating point number as its integer and optional fractional parts. +/// +/// The number must have two digits before the decimal point. If a decimal point is present, at +/// least one digit must follow. +/// +/// The return type is a tuple of the integer part and optional fraction part. +pub(crate) fn float(input: &[u8]) -> Option)>> { + // Two digits before the decimal. + let ParsedItem(input, integer_part) = match input { + [ + first_digit @ b'0'..=b'9', + second_digit @ b'0'..=b'9', + input @ .., + ] => ParsedItem(input, (first_digit - b'0') * 10 + (second_digit - b'0')), + _ => return None, + }; + + if let Some(ParsedItem(input, ())) = decimal_sign(input) { + // Mandatory post-decimal digit. + let ParsedItem(mut input, mut fractional_part) = + any_digit(input)?.map(|digit| ((digit - b'0') as f64) / 10.); + + let mut divisor = 10.; + // Any number of subsequent digits. + while let Some(ParsedItem(new_input, digit)) = any_digit(input) { + input = new_input; + divisor *= 10.; + fractional_part += (digit - b'0') as f64 / divisor; + } + + Some(ParsedItem(input, (integer_part, Some(fractional_part)))) + } else { + Some(ParsedItem(input, (integer_part, None))) + } +} + +/// Parse a "decimal sign", which is either a comma or a period. +fn decimal_sign(input: &[u8]) -> Option> { + ascii_char::(input).or_else(|| ascii_char::(input)) +} diff --git a/third_party/rust/time/src/parsing/combinator/rfc/mod.rs b/third_party/rust/time/src/parsing/combinator/rfc/mod.rs new file mode 100644 index 0000000000..2974a4d5c4 --- /dev/null +++ b/third_party/rust/time/src/parsing/combinator/rfc/mod.rs @@ -0,0 +1,10 @@ +//! Combinators for rules as defined in a standard. +//! +//! When applicable, these rules have been converted strictly following the ABNF syntax as specified +//! in [RFC 2234]. +//! +//! [RFC 2234]: https://datatracker.ietf.org/doc/html/rfc2234 + +pub(crate) mod iso8601; +pub(crate) mod rfc2234; +pub(crate) mod rfc2822; diff --git a/third_party/rust/time/src/parsing/combinator/rfc/rfc2234.rs b/third_party/rust/time/src/parsing/combinator/rfc/rfc2234.rs new file mode 100644 index 0000000000..6753444358 --- /dev/null +++ b/third_party/rust/time/src/parsing/combinator/rfc/rfc2234.rs @@ -0,0 +1,13 @@ +//! Rules defined in [RFC 2234]. +//! +//! [RFC 2234]: https://datatracker.ietf.org/doc/html/rfc2234 + +use crate::parsing::ParsedItem; + +/// Consume exactly one space or tab. +pub(crate) const fn wsp(input: &[u8]) -> Option> { + match input { + [b' ' | b'\t', rest @ ..] => Some(ParsedItem(rest, ())), + _ => None, + } +} diff --git a/third_party/rust/time/src/parsing/combinator/rfc/rfc2822.rs b/third_party/rust/time/src/parsing/combinator/rfc/rfc2822.rs new file mode 100644 index 0000000000..8410de06e3 --- /dev/null +++ b/third_party/rust/time/src/parsing/combinator/rfc/rfc2822.rs @@ -0,0 +1,115 @@ +//! Rules defined in [RFC 2822]. +//! +//! [RFC 2822]: https://datatracker.ietf.org/doc/html/rfc2822 + +use crate::parsing::combinator::rfc::rfc2234::wsp; +use crate::parsing::combinator::{ascii_char, one_or_more, zero_or_more}; +use crate::parsing::ParsedItem; + +/// Consume the `fws` rule. +// The full rule is equivalent to /\r\n[ \t]+|[ \t]+(?:\r\n[ \t]+)*/ +pub(crate) fn fws(mut input: &[u8]) -> Option> { + if let [b'\r', b'\n', rest @ ..] = input { + one_or_more(wsp)(rest) + } else { + input = one_or_more(wsp)(input)?.into_inner(); + while let [b'\r', b'\n', rest @ ..] = input { + input = one_or_more(wsp)(rest)?.into_inner(); + } + Some(ParsedItem(input, ())) + } +} + +/// Consume the `cfws` rule. +// The full rule is equivalent to any combination of `fws` and `comment` so long as it is not empty. +pub(crate) fn cfws(input: &[u8]) -> Option> { + one_or_more(|input| fws(input).or_else(|| comment(input)))(input) +} + +/// Consume the `comment` rule. +fn comment(mut input: &[u8]) -> Option> { + input = ascii_char::(input)?.into_inner(); + input = zero_or_more(fws)(input).into_inner(); + while let Some(rest) = ccontent(input) { + input = rest.into_inner(); + input = zero_or_more(fws)(input).into_inner(); + } + input = ascii_char::(input)?.into_inner(); + + Some(ParsedItem(input, ())) +} + +/// Consume the `ccontent` rule. +fn ccontent(input: &[u8]) -> Option> { + ctext(input) + .or_else(|| quoted_pair(input)) + .or_else(|| comment(input)) +} + +/// Consume the `ctext` rule. +#[allow(clippy::unnecessary_lazy_evaluations)] // rust-lang/rust-clippy#8522 +fn ctext(input: &[u8]) -> Option> { + no_ws_ctl(input).or_else(|| match input { + [33..=39 | 42..=91 | 93..=126, rest @ ..] => Some(ParsedItem(rest, ())), + _ => None, + }) +} + +/// Consume the `quoted_pair` rule. +fn quoted_pair(mut input: &[u8]) -> Option> { + input = ascii_char::(input)?.into_inner(); + + let old_input_len = input.len(); + + input = text(input).into_inner(); + + // If nothing is parsed, this means we hit the `obs-text` rule and nothing matched. This is + // technically a success, but we should still check the `obs-qp` rule to ensure we consume + // everything possible. + if input.len() == old_input_len { + match input { + [0..=127, rest @ ..] => Some(ParsedItem(rest, ())), + _ => Some(ParsedItem(input, ())), + } + } else { + Some(ParsedItem(input, ())) + } +} + +/// Consume the `no_ws_ctl` rule. +const fn no_ws_ctl(input: &[u8]) -> Option> { + match input { + [1..=8 | 11..=12 | 14..=31 | 127, rest @ ..] => Some(ParsedItem(rest, ())), + _ => None, + } +} + +/// Consume the `text` rule. +fn text<'a>(input: &'a [u8]) -> ParsedItem<'a, ()> { + let new_text = |input: &'a [u8]| match input { + [1..=9 | 11..=12 | 14..=127, rest @ ..] => Some(ParsedItem(rest, ())), + _ => None, + }; + + let obs_char = |input: &'a [u8]| match input { + // This is technically allowed, but consuming this would mean the rest of the string is + // eagerly consumed without consideration for where the comment actually ends. + [b')', ..] => None, + [0..=9 | 11..=12 | 14..=127, rest @ ..] => Some(rest), + _ => None, + }; + + let obs_text = |mut input| { + input = zero_or_more(ascii_char::)(input).into_inner(); + input = zero_or_more(ascii_char::)(input).into_inner(); + while let Some(rest) = obs_char(input) { + input = rest; + input = zero_or_more(ascii_char::)(input).into_inner(); + input = zero_or_more(ascii_char::)(input).into_inner(); + } + + ParsedItem(input, ()) + }; + + new_text(input).unwrap_or_else(|| obs_text(input)) +} diff --git a/third_party/rust/time/src/parsing/component.rs b/third_party/rust/time/src/parsing/component.rs new file mode 100644 index 0000000000..38d632a0da --- /dev/null +++ b/third_party/rust/time/src/parsing/component.rs @@ -0,0 +1,296 @@ +//! Parsing implementations for all [`Component`](crate::format_description::Component)s. + +use core::num::{NonZeroU16, NonZeroU8}; + +use crate::format_description::modifier; +#[cfg(feature = "large-dates")] +use crate::parsing::combinator::n_to_m_digits_padded; +use crate::parsing::combinator::{ + any_digit, exactly_n_digits, exactly_n_digits_padded, first_match, opt, sign, +}; +use crate::parsing::ParsedItem; +use crate::{Month, Weekday}; + +// region: date components +/// Parse the "year" component of a `Date`. +pub(crate) fn parse_year(input: &[u8], modifiers: modifier::Year) -> Option> { + match modifiers.repr { + modifier::YearRepr::Full => { + let ParsedItem(input, sign) = opt(sign)(input); + #[cfg(not(feature = "large-dates"))] + let ParsedItem(input, year) = + exactly_n_digits_padded::<4, u32>(modifiers.padding)(input)?; + #[cfg(feature = "large-dates")] + let ParsedItem(input, year) = + n_to_m_digits_padded::<4, 6, u32>(modifiers.padding)(input)?; + match sign { + Some(b'-') => Some(ParsedItem(input, -(year as i32))), + None if modifiers.sign_is_mandatory || year >= 10_000 => None, + _ => Some(ParsedItem(input, year as i32)), + } + } + modifier::YearRepr::LastTwo => { + Some(exactly_n_digits_padded::<2, u32>(modifiers.padding)(input)?.map(|v| v as i32)) + } + } +} + +/// Parse the "month" component of a `Date`. +pub(crate) fn parse_month( + input: &[u8], + modifiers: modifier::Month, +) -> Option> { + use Month::*; + let ParsedItem(remaining, value) = first_match( + match modifiers.repr { + modifier::MonthRepr::Numerical => { + return exactly_n_digits_padded::<2, _>(modifiers.padding)(input)? + .flat_map(|n| Month::from_number(n).ok()); + } + modifier::MonthRepr::Long => [ + (b"January".as_slice(), January), + (b"February".as_slice(), February), + (b"March".as_slice(), March), + (b"April".as_slice(), April), + (b"May".as_slice(), May), + (b"June".as_slice(), June), + (b"July".as_slice(), July), + (b"August".as_slice(), August), + (b"September".as_slice(), September), + (b"October".as_slice(), October), + (b"November".as_slice(), November), + (b"December".as_slice(), December), + ], + modifier::MonthRepr::Short => [ + (b"Jan".as_slice(), January), + (b"Feb".as_slice(), February), + (b"Mar".as_slice(), March), + (b"Apr".as_slice(), April), + (b"May".as_slice(), May), + (b"Jun".as_slice(), June), + (b"Jul".as_slice(), July), + (b"Aug".as_slice(), August), + (b"Sep".as_slice(), September), + (b"Oct".as_slice(), October), + (b"Nov".as_slice(), November), + (b"Dec".as_slice(), December), + ], + }, + modifiers.case_sensitive, + )(input)?; + Some(ParsedItem(remaining, value)) +} + +/// Parse the "week number" component of a `Date`. +pub(crate) fn parse_week_number( + input: &[u8], + modifiers: modifier::WeekNumber, +) -> Option> { + exactly_n_digits_padded::<2, _>(modifiers.padding)(input) +} + +/// Parse the "weekday" component of a `Date`. +pub(crate) fn parse_weekday( + input: &[u8], + modifiers: modifier::Weekday, +) -> Option> { + first_match( + match (modifiers.repr, modifiers.one_indexed) { + (modifier::WeekdayRepr::Short, _) => [ + (b"Mon".as_slice(), Weekday::Monday), + (b"Tue".as_slice(), Weekday::Tuesday), + (b"Wed".as_slice(), Weekday::Wednesday), + (b"Thu".as_slice(), Weekday::Thursday), + (b"Fri".as_slice(), Weekday::Friday), + (b"Sat".as_slice(), Weekday::Saturday), + (b"Sun".as_slice(), Weekday::Sunday), + ], + (modifier::WeekdayRepr::Long, _) => [ + (b"Monday".as_slice(), Weekday::Monday), + (b"Tuesday".as_slice(), Weekday::Tuesday), + (b"Wednesday".as_slice(), Weekday::Wednesday), + (b"Thursday".as_slice(), Weekday::Thursday), + (b"Friday".as_slice(), Weekday::Friday), + (b"Saturday".as_slice(), Weekday::Saturday), + (b"Sunday".as_slice(), Weekday::Sunday), + ], + (modifier::WeekdayRepr::Sunday, false) => [ + (b"1".as_slice(), Weekday::Monday), + (b"2".as_slice(), Weekday::Tuesday), + (b"3".as_slice(), Weekday::Wednesday), + (b"4".as_slice(), Weekday::Thursday), + (b"5".as_slice(), Weekday::Friday), + (b"6".as_slice(), Weekday::Saturday), + (b"0".as_slice(), Weekday::Sunday), + ], + (modifier::WeekdayRepr::Sunday, true) => [ + (b"2".as_slice(), Weekday::Monday), + (b"3".as_slice(), Weekday::Tuesday), + (b"4".as_slice(), Weekday::Wednesday), + (b"5".as_slice(), Weekday::Thursday), + (b"6".as_slice(), Weekday::Friday), + (b"7".as_slice(), Weekday::Saturday), + (b"1".as_slice(), Weekday::Sunday), + ], + (modifier::WeekdayRepr::Monday, false) => [ + (b"0".as_slice(), Weekday::Monday), + (b"1".as_slice(), Weekday::Tuesday), + (b"2".as_slice(), Weekday::Wednesday), + (b"3".as_slice(), Weekday::Thursday), + (b"4".as_slice(), Weekday::Friday), + (b"5".as_slice(), Weekday::Saturday), + (b"6".as_slice(), Weekday::Sunday), + ], + (modifier::WeekdayRepr::Monday, true) => [ + (b"1".as_slice(), Weekday::Monday), + (b"2".as_slice(), Weekday::Tuesday), + (b"3".as_slice(), Weekday::Wednesday), + (b"4".as_slice(), Weekday::Thursday), + (b"5".as_slice(), Weekday::Friday), + (b"6".as_slice(), Weekday::Saturday), + (b"7".as_slice(), Weekday::Sunday), + ], + }, + modifiers.case_sensitive, + )(input) +} + +/// Parse the "ordinal" component of a `Date`. +pub(crate) fn parse_ordinal( + input: &[u8], + modifiers: modifier::Ordinal, +) -> Option> { + exactly_n_digits_padded::<3, _>(modifiers.padding)(input) +} + +/// Parse the "day" component of a `Date`. +pub(crate) fn parse_day( + input: &[u8], + modifiers: modifier::Day, +) -> Option> { + exactly_n_digits_padded::<2, _>(modifiers.padding)(input) +} +// endregion date components + +// region: time components +/// Indicate whether the hour is "am" or "pm". +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum Period { + #[allow(clippy::missing_docs_in_private_items)] + Am, + #[allow(clippy::missing_docs_in_private_items)] + Pm, +} + +/// Parse the "hour" component of a `Time`. +pub(crate) fn parse_hour(input: &[u8], modifiers: modifier::Hour) -> Option> { + exactly_n_digits_padded::<2, _>(modifiers.padding)(input) +} + +/// Parse the "minute" component of a `Time`. +pub(crate) fn parse_minute( + input: &[u8], + modifiers: modifier::Minute, +) -> Option> { + exactly_n_digits_padded::<2, _>(modifiers.padding)(input) +} + +/// Parse the "second" component of a `Time`. +pub(crate) fn parse_second( + input: &[u8], + modifiers: modifier::Second, +) -> Option> { + exactly_n_digits_padded::<2, _>(modifiers.padding)(input) +} + +/// Parse the "period" component of a `Time`. Required if the hour is on a 12-hour clock. +pub(crate) fn parse_period( + input: &[u8], + modifiers: modifier::Period, +) -> Option> { + first_match( + if modifiers.is_uppercase { + [ + (b"AM".as_slice(), Period::Am), + (b"PM".as_slice(), Period::Pm), + ] + } else { + [ + (b"am".as_slice(), Period::Am), + (b"pm".as_slice(), Period::Pm), + ] + }, + modifiers.case_sensitive, + )(input) +} + +/// Parse the "subsecond" component of a `Time`. +pub(crate) fn parse_subsecond( + input: &[u8], + modifiers: modifier::Subsecond, +) -> Option> { + use modifier::SubsecondDigits::*; + Some(match modifiers.digits { + One => exactly_n_digits::<1, u32>(input)?.map(|v| v * 100_000_000), + Two => exactly_n_digits::<2, u32>(input)?.map(|v| v * 10_000_000), + Three => exactly_n_digits::<3, u32>(input)?.map(|v| v * 1_000_000), + Four => exactly_n_digits::<4, u32>(input)?.map(|v| v * 100_000), + Five => exactly_n_digits::<5, u32>(input)?.map(|v| v * 10_000), + Six => exactly_n_digits::<6, u32>(input)?.map(|v| v * 1_000), + Seven => exactly_n_digits::<7, u32>(input)?.map(|v| v * 100), + Eight => exactly_n_digits::<8, u32>(input)?.map(|v| v * 10), + Nine => exactly_n_digits::<9, _>(input)?, + OneOrMore => { + let ParsedItem(mut input, mut value) = + any_digit(input)?.map(|v| (v - b'0') as u32 * 100_000_000); + + let mut multiplier = 10_000_000; + while let Some(ParsedItem(new_input, digit)) = any_digit(input) { + value += (digit - b'0') as u32 * multiplier; + input = new_input; + multiplier /= 10; + } + + ParsedItem(input, value) + } + }) +} +// endregion time components + +// region: offset components +/// Parse the "hour" component of a `UtcOffset`. +pub(crate) fn parse_offset_hour( + input: &[u8], + modifiers: modifier::OffsetHour, +) -> Option> { + let ParsedItem(input, sign) = opt(sign)(input); + let ParsedItem(input, hour) = exactly_n_digits_padded::<2, u8>(modifiers.padding)(input)?; + match sign { + Some(b'-') => Some(ParsedItem(input, -(hour as i8))), + None if modifiers.sign_is_mandatory => None, + _ => Some(ParsedItem(input, hour as i8)), + } +} + +/// Parse the "minute" component of a `UtcOffset`. +pub(crate) fn parse_offset_minute( + input: &[u8], + modifiers: modifier::OffsetMinute, +) -> Option> { + Some( + exactly_n_digits_padded::<2, u8>(modifiers.padding)(input)? + .map(|offset_minute| offset_minute as _), + ) +} + +/// Parse the "second" component of a `UtcOffset`. +pub(crate) fn parse_offset_second( + input: &[u8], + modifiers: modifier::OffsetSecond, +) -> Option> { + Some( + exactly_n_digits_padded::<2, u8>(modifiers.padding)(input)? + .map(|offset_second| offset_second as _), + ) +} +// endregion offset components diff --git a/third_party/rust/time/src/parsing/iso8601.rs b/third_party/rust/time/src/parsing/iso8601.rs new file mode 100644 index 0000000000..21c89bab3f --- /dev/null +++ b/third_party/rust/time/src/parsing/iso8601.rs @@ -0,0 +1,308 @@ +//! Parse parts of an ISO 8601-formatted value. + +use crate::error; +use crate::error::ParseFromDescription::{InvalidComponent, InvalidLiteral}; +use crate::format_description::well_known::iso8601::EncodedConfig; +use crate::format_description::well_known::Iso8601; +use crate::parsing::combinator::rfc::iso8601::{ + day, dayk, dayo, float, hour, min, month, week, year, ExtendedKind, +}; +use crate::parsing::combinator::{ascii_char, sign}; +use crate::parsing::{Parsed, ParsedItem}; + +impl Iso8601 { + // Basic: [year][month][day] + // Extended: [year]["-"][month]["-"][day] + // Basic: [year][dayo] + // Extended: [year]["-"][dayo] + // Basic: [year]["W"][week][dayk] + // Extended: [year]["-"]["W"][week]["-"][dayk] + /// Parse a date in the basic or extended format. Reduced precision is permitted. + pub(crate) fn parse_date<'a>( + parsed: &'a mut Parsed, + extended_kind: &'a mut ExtendedKind, + ) -> impl FnMut(&[u8]) -> Result<&[u8], error::Parse> + 'a { + move |input| { + // Same for any acceptable format. + let ParsedItem(mut input, year) = year(input).ok_or(InvalidComponent("year"))?; + *extended_kind = match ascii_char::(input) { + Some(ParsedItem(new_input, ())) => { + input = new_input; + ExtendedKind::Extended + } + None => ExtendedKind::Basic, // no separator before mandatory month/ordinal/week + }; + + let mut ret_error = match (|| { + let ParsedItem(mut input, month) = month(input).ok_or(InvalidComponent("month"))?; + if extended_kind.is_extended() { + input = ascii_char::(input) + .ok_or(InvalidLiteral)? + .into_inner(); + } + let ParsedItem(input, day) = day(input).ok_or(InvalidComponent("day"))?; + Ok(ParsedItem(input, (month, day))) + })() { + Ok(ParsedItem(input, (month, day))) => { + *parsed = parsed + .with_year(year) + .ok_or(InvalidComponent("year"))? + .with_month(month) + .ok_or(InvalidComponent("month"))? + .with_day(day) + .ok_or(InvalidComponent("day"))?; + return Ok(input); + } + Err(err) => err, + }; + + // Don't check for `None`, as the error from year-month-day will always take priority. + if let Some(ParsedItem(input, ordinal)) = dayo(input) { + *parsed = parsed + .with_year(year) + .ok_or(InvalidComponent("year"))? + .with_ordinal(ordinal) + .ok_or(InvalidComponent("ordinal"))?; + return Ok(input); + } + + match (|| { + let input = ascii_char::(input) + .ok_or((false, InvalidLiteral))? + .into_inner(); + let ParsedItem(mut input, week) = + week(input).ok_or((true, InvalidComponent("week")))?; + if extended_kind.is_extended() { + input = ascii_char::(input) + .ok_or((true, InvalidLiteral))? + .into_inner(); + } + let ParsedItem(input, weekday) = + dayk(input).ok_or((true, InvalidComponent("weekday")))?; + Ok(ParsedItem(input, (week, weekday))) + })() { + Ok(ParsedItem(input, (week, weekday))) => { + *parsed = parsed + .with_iso_year(year) + .ok_or(InvalidComponent("year"))? + .with_iso_week_number(week) + .ok_or(InvalidComponent("week"))? + .with_weekday(weekday) + .ok_or(InvalidComponent("weekday"))?; + return Ok(input); + } + Err((false, _err)) => {} + // This error is more accurate than the one from year-month-day. + Err((true, err)) => ret_error = err, + } + + Err(ret_error.into()) + } + } + + // Basic: ["T"][hour][min][sec] + // Extended: ["T"][hour][":"][min][":"][sec] + // Reduced precision: components after [hour] (including their preceding separator) can be + // omitted. ["T"] can be omitted if there is no date present. + /// Parse a time in the basic or extended format. Reduced precision is permitted. + pub(crate) fn parse_time<'a>( + parsed: &'a mut Parsed, + extended_kind: &'a mut ExtendedKind, + date_is_present: bool, + ) -> impl FnMut(&[u8]) -> Result<&[u8], error::Parse> + 'a { + move |mut input| { + if date_is_present { + input = ascii_char::(input) + .ok_or(InvalidLiteral)? + .into_inner(); + } + + let ParsedItem(mut input, hour) = float(input).ok_or(InvalidComponent("hour"))?; + match hour { + (hour, None) => parsed.set_hour_24(hour).ok_or(InvalidComponent("hour"))?, + (hour, Some(fractional_part)) => { + *parsed = parsed + .with_hour_24(hour) + .ok_or(InvalidComponent("hour"))? + .with_minute((fractional_part * 60.0) as _) + .ok_or(InvalidComponent("minute"))? + .with_second((fractional_part * 3600.0 % 60.) as _) + .ok_or(InvalidComponent("second"))? + .with_subsecond( + (fractional_part * 3_600. * 1_000_000_000. % 1_000_000_000.) as _, + ) + .ok_or(InvalidComponent("subsecond"))?; + return Ok(input); + } + }; + + if let Some(ParsedItem(new_input, ())) = ascii_char::(input) { + extended_kind + .coerce_extended() + .ok_or(InvalidComponent("minute"))?; + input = new_input; + }; + + let mut input = match float(input) { + Some(ParsedItem(input, (minute, None))) => { + extended_kind.coerce_basic(); + parsed + .set_minute(minute) + .ok_or(InvalidComponent("minute"))?; + input + } + Some(ParsedItem(input, (minute, Some(fractional_part)))) => { + // `None` is valid behavior, so don't error if this fails. + extended_kind.coerce_basic(); + *parsed = parsed + .with_minute(minute) + .ok_or(InvalidComponent("minute"))? + .with_second((fractional_part * 60.) as _) + .ok_or(InvalidComponent("second"))? + .with_subsecond( + (fractional_part * 60. * 1_000_000_000. % 1_000_000_000.) as _, + ) + .ok_or(InvalidComponent("subsecond"))?; + return Ok(input); + } + // colon was present, so minutes are required + None if extended_kind.is_extended() => { + return Err(error::Parse::ParseFromDescription(InvalidComponent( + "minute", + ))); + } + None => { + // Missing components are assumed to be zero. + *parsed = parsed + .with_minute(0) + .ok_or(InvalidComponent("minute"))? + .with_second(0) + .ok_or(InvalidComponent("second"))? + .with_subsecond(0) + .ok_or(InvalidComponent("subsecond"))?; + return Ok(input); + } + }; + + if extended_kind.is_extended() { + match ascii_char::(input) { + Some(ParsedItem(new_input, ())) => input = new_input, + None => { + *parsed = parsed + .with_second(0) + .ok_or(InvalidComponent("second"))? + .with_subsecond(0) + .ok_or(InvalidComponent("subsecond"))?; + return Ok(input); + } + } + } + + let (input, second, subsecond) = match float(input) { + Some(ParsedItem(input, (second, None))) => (input, second, 0), + Some(ParsedItem(input, (second, Some(fractional_part)))) => { + (input, second, round(fractional_part * 1_000_000_000.) as _) + } + None if extended_kind.is_extended() => { + return Err(error::Parse::ParseFromDescription(InvalidComponent( + "second", + ))); + } + // Missing components are assumed to be zero. + None => (input, 0, 0), + }; + *parsed = parsed + .with_second(second) + .ok_or(InvalidComponent("second"))? + .with_subsecond(subsecond) + .ok_or(InvalidComponent("subsecond"))?; + + Ok(input) + } + } + + // Basic: [±][hour][min] or ["Z"] + // Extended: [±][hour][":"][min] or ["Z"] + // Reduced precision: [±][hour] or ["Z"] + /// Parse a UTC offset in the basic or extended format. Reduced precision is supported. + pub(crate) fn parse_offset<'a>( + parsed: &'a mut Parsed, + extended_kind: &'a mut ExtendedKind, + ) -> impl FnMut(&[u8]) -> Result<&[u8], error::Parse> + 'a { + move |input| { + if let Some(ParsedItem(input, ())) = ascii_char::(input) { + *parsed = parsed + .with_offset_hour(0) + .ok_or(InvalidComponent("offset hour"))? + .with_offset_minute_signed(0) + .ok_or(InvalidComponent("offset minute"))? + .with_offset_second_signed(0) + .ok_or(InvalidComponent("offset second"))?; + return Ok(input); + } + + let ParsedItem(input, sign) = sign(input).ok_or(InvalidComponent("offset hour"))?; + let mut input = hour(input) + .and_then(|parsed_item| { + parsed_item.consume_value(|hour| { + parsed.set_offset_hour(if sign == b'-' { + -(hour as i8) + } else { + hour as _ + }) + }) + }) + .ok_or(InvalidComponent("offset hour"))?; + + if extended_kind.maybe_extended() { + if let Some(ParsedItem(new_input, ())) = ascii_char::(input) { + extended_kind + .coerce_extended() + .ok_or(InvalidComponent("offset minute"))?; + input = new_input; + }; + } + + let input = min(input) + .and_then(|parsed_item| { + parsed_item.consume_value(|min| { + parsed.set_offset_minute_signed(if sign == b'-' { + -(min as i8) + } else { + min as _ + }) + }) + }) + .ok_or(InvalidComponent("offset minute"))?; + // If `:` was present, the format has already been set to extended. As such, this call + // will do nothing in that case. If there wasn't `:` but minutes were + // present, we know it's the basic format. Do not use `?` on the call, as + // returning `None` is valid behavior. + extended_kind.coerce_basic(); + + Ok(input) + } + } +} + +/// Round wrapper that uses hardware implementation if `std` is available, falling back to manual +/// implementation for `no_std` +fn round(value: f64) -> f64 { + #[cfg(feature = "std")] + { + value.round() + } + #[cfg(not(feature = "std"))] + { + round_impl(value) + } +} + +#[cfg(not(feature = "std"))] +#[allow(clippy::missing_docs_in_private_items)] +fn round_impl(value: f64) -> f64 { + debug_assert!(value.is_sign_positive() && !value.is_nan()); + + let f = value % 1.; + if f < 0.5 { value - f } else { value - f + 1. } +} diff --git a/third_party/rust/time/src/parsing/mod.rs b/third_party/rust/time/src/parsing/mod.rs new file mode 100644 index 0000000000..4a2aa829f1 --- /dev/null +++ b/third_party/rust/time/src/parsing/mod.rs @@ -0,0 +1,50 @@ +//! Parsing for various types. + +pub(crate) mod combinator; +pub(crate) mod component; +mod iso8601; +pub(crate) mod parsable; +mod parsed; +pub(crate) mod shim; + +pub use self::parsable::Parsable; +pub use self::parsed::Parsed; + +/// An item that has been parsed. Represented as a `(remaining, value)` pair. +#[derive(Debug)] +pub(crate) struct ParsedItem<'a, T>(pub(crate) &'a [u8], pub(crate) T); + +impl<'a, T> ParsedItem<'a, T> { + /// Map the value to a new value, preserving the remaining input. + pub(crate) fn map(self, f: impl FnOnce(T) -> U) -> ParsedItem<'a, U> { + ParsedItem(self.0, f(self.1)) + } + + /// Map the value to a new, optional value, preserving the remaining input. + pub(crate) fn flat_map(self, f: impl FnOnce(T) -> Option) -> Option> { + Some(ParsedItem(self.0, f(self.1)?)) + } + + /// Consume the stored value with the provided function. The remaining input is returned. + #[must_use = "this returns the remaining input"] + pub(crate) fn consume_value(self, f: impl FnOnce(T) -> Option<()>) -> Option<&'a [u8]> { + f(self.1)?; + Some(self.0) + } +} + +impl<'a> ParsedItem<'a, ()> { + /// Discard the unit value, returning the remaining input. + #[must_use = "this returns the remaining input"] + pub(crate) const fn into_inner(self) -> &'a [u8] { + self.0 + } +} + +impl<'a> ParsedItem<'a, Option<()>> { + /// Discard the potential unit value, returning the remaining input. + #[must_use = "this returns the remaining input"] + pub(crate) const fn into_inner(self) -> &'a [u8] { + self.0 + } +} diff --git a/third_party/rust/time/src/parsing/parsable.rs b/third_party/rust/time/src/parsing/parsable.rs new file mode 100644 index 0000000000..badb638088 --- /dev/null +++ b/third_party/rust/time/src/parsing/parsable.rs @@ -0,0 +1,754 @@ +//! A trait that can be used to parse an item from an input. + +use core::ops::Deref; + +use crate::error::TryFromParsed; +use crate::format_description::well_known::iso8601::EncodedConfig; +use crate::format_description::well_known::{Iso8601, Rfc2822, Rfc3339}; +use crate::format_description::FormatItem; +#[cfg(feature = "alloc")] +use crate::format_description::OwnedFormatItem; +use crate::parsing::{Parsed, ParsedItem}; +use crate::{error, Date, Month, OffsetDateTime, PrimitiveDateTime, Time, UtcOffset, Weekday}; + +/// A type that can be parsed. +#[cfg_attr(__time_03_docs, doc(notable_trait))] +pub trait Parsable: sealed::Sealed {} +impl Parsable for FormatItem<'_> {} +impl Parsable for [FormatItem<'_>] {} +#[cfg(feature = "alloc")] +impl Parsable for OwnedFormatItem {} +#[cfg(feature = "alloc")] +impl Parsable for [OwnedFormatItem] {} +impl Parsable for Rfc2822 {} +impl Parsable for Rfc3339 {} +impl Parsable for Iso8601 {} +impl Parsable for T where T::Target: Parsable {} + +/// Seal the trait to prevent downstream users from implementing it, while still allowing it to +/// exist in generic bounds. +mod sealed { + + #[allow(clippy::wildcard_imports)] + use super::*; + + /// Parse the item using a format description and an input. + pub trait Sealed { + /// Parse the item into the provided [`Parsed`] struct. + /// + /// This method can be used to parse a single component without parsing the full value. + fn parse_into<'a>( + &self, + input: &'a [u8], + parsed: &mut Parsed, + ) -> Result<&'a [u8], error::Parse>; + + /// Parse the item into a new [`Parsed`] struct. + /// + /// This method can only be used to parse a complete value of a type. If any characters + /// remain after parsing, an error will be returned. + fn parse(&self, input: &[u8]) -> Result { + let mut parsed = Parsed::new(); + if self.parse_into(input, &mut parsed)?.is_empty() { + Ok(parsed) + } else { + Err(error::Parse::UnexpectedTrailingCharacters) + } + } + + /// Parse a [`Date`] from the format description. + fn parse_date(&self, input: &[u8]) -> Result { + Ok(self.parse(input)?.try_into()?) + } + + /// Parse a [`Time`] from the format description. + fn parse_time(&self, input: &[u8]) -> Result { + Ok(self.parse(input)?.try_into()?) + } + + /// Parse a [`UtcOffset`] from the format description. + fn parse_offset(&self, input: &[u8]) -> Result { + Ok(self.parse(input)?.try_into()?) + } + + /// Parse a [`PrimitiveDateTime`] from the format description. + fn parse_date_time(&self, input: &[u8]) -> Result { + Ok(self.parse(input)?.try_into()?) + } + + /// Parse a [`OffsetDateTime`] from the format description. + fn parse_offset_date_time(&self, input: &[u8]) -> Result { + Ok(self.parse(input)?.try_into()?) + } + } +} + +// region: custom formats +impl sealed::Sealed for FormatItem<'_> { + fn parse_into<'a>( + &self, + input: &'a [u8], + parsed: &mut Parsed, + ) -> Result<&'a [u8], error::Parse> { + Ok(parsed.parse_item(input, self)?) + } +} + +impl sealed::Sealed for [FormatItem<'_>] { + fn parse_into<'a>( + &self, + input: &'a [u8], + parsed: &mut Parsed, + ) -> Result<&'a [u8], error::Parse> { + Ok(parsed.parse_items(input, self)?) + } +} + +#[cfg(feature = "alloc")] +impl sealed::Sealed for OwnedFormatItem { + fn parse_into<'a>( + &self, + input: &'a [u8], + parsed: &mut Parsed, + ) -> Result<&'a [u8], error::Parse> { + Ok(parsed.parse_item(input, self)?) + } +} + +#[cfg(feature = "alloc")] +impl sealed::Sealed for [OwnedFormatItem] { + fn parse_into<'a>( + &self, + input: &'a [u8], + parsed: &mut Parsed, + ) -> Result<&'a [u8], error::Parse> { + Ok(parsed.parse_items(input, self)?) + } +} + +impl sealed::Sealed for T +where + T::Target: sealed::Sealed, +{ + fn parse_into<'a>( + &self, + input: &'a [u8], + parsed: &mut Parsed, + ) -> Result<&'a [u8], error::Parse> { + self.deref().parse_into(input, parsed) + } +} +// endregion custom formats + +// region: well-known formats +impl sealed::Sealed for Rfc2822 { + fn parse_into<'a>( + &self, + input: &'a [u8], + parsed: &mut Parsed, + ) -> Result<&'a [u8], error::Parse> { + use crate::error::ParseFromDescription::{InvalidComponent, InvalidLiteral}; + use crate::parsing::combinator::rfc::rfc2822::{cfws, fws}; + use crate::parsing::combinator::{ + ascii_char, exactly_n_digits, first_match, n_to_m_digits, opt, sign, + }; + + let colon = ascii_char::; + let comma = ascii_char::; + + let input = opt(fws)(input).into_inner(); + let input = first_match( + [ + (b"Mon".as_slice(), Weekday::Monday), + (b"Tue".as_slice(), Weekday::Tuesday), + (b"Wed".as_slice(), Weekday::Wednesday), + (b"Thu".as_slice(), Weekday::Thursday), + (b"Fri".as_slice(), Weekday::Friday), + (b"Sat".as_slice(), Weekday::Saturday), + (b"Sun".as_slice(), Weekday::Sunday), + ], + false, + )(input) + .and_then(|item| item.consume_value(|value| parsed.set_weekday(value))) + .ok_or(InvalidComponent("weekday"))?; + let input = comma(input).ok_or(InvalidLiteral)?.into_inner(); + let input = cfws(input).ok_or(InvalidLiteral)?.into_inner(); + let input = n_to_m_digits::<1, 2, _>(input) + .and_then(|item| item.consume_value(|value| parsed.set_day(value))) + .ok_or(InvalidComponent("day"))?; + let input = cfws(input).ok_or(InvalidLiteral)?.into_inner(); + let input = first_match( + [ + (b"Jan".as_slice(), Month::January), + (b"Feb".as_slice(), Month::February), + (b"Mar".as_slice(), Month::March), + (b"Apr".as_slice(), Month::April), + (b"May".as_slice(), Month::May), + (b"Jun".as_slice(), Month::June), + (b"Jul".as_slice(), Month::July), + (b"Aug".as_slice(), Month::August), + (b"Sep".as_slice(), Month::September), + (b"Oct".as_slice(), Month::October), + (b"Nov".as_slice(), Month::November), + (b"Dec".as_slice(), Month::December), + ], + false, + )(input) + .and_then(|item| item.consume_value(|value| parsed.set_month(value))) + .ok_or(InvalidComponent("month"))?; + let input = cfws(input).ok_or(InvalidLiteral)?.into_inner(); + let input = match exactly_n_digits::<4, u32>(input) { + Some(item) => { + let input = item + .flat_map(|year| if year >= 1900 { Some(year) } else { None }) + .and_then(|item| item.consume_value(|value| parsed.set_year(value as _))) + .ok_or(InvalidComponent("year"))?; + fws(input).ok_or(InvalidLiteral)?.into_inner() + } + None => { + let input = exactly_n_digits::<2, u32>(input) + .and_then(|item| { + item.map(|year| if year < 50 { year + 2000 } else { year + 1900 }) + .map(|year| year as _) + .consume_value(|value| parsed.set_year(value)) + }) + .ok_or(InvalidComponent("year"))?; + cfws(input).ok_or(InvalidLiteral)?.into_inner() + } + }; + + let input = exactly_n_digits::<2, _>(input) + .and_then(|item| item.consume_value(|value| parsed.set_hour_24(value))) + .ok_or(InvalidComponent("hour"))?; + let input = opt(cfws)(input).into_inner(); + let input = colon(input).ok_or(InvalidLiteral)?.into_inner(); + let input = opt(cfws)(input).into_inner(); + let input = exactly_n_digits::<2, _>(input) + .and_then(|item| item.consume_value(|value| parsed.set_minute(value))) + .ok_or(InvalidComponent("minute"))?; + + let input = if let Some(input) = colon(opt(cfws)(input).into_inner()) { + let input = input.into_inner(); // discard the colon + let input = opt(cfws)(input).into_inner(); + let input = exactly_n_digits::<2, _>(input) + .and_then(|item| item.consume_value(|value| parsed.set_second(value))) + .ok_or(InvalidComponent("second"))?; + cfws(input).ok_or(InvalidLiteral)?.into_inner() + } else { + cfws(input).ok_or(InvalidLiteral)?.into_inner() + }; + + // The RFC explicitly allows leap seconds. + parsed.set_leap_second_allowed(true); + + #[allow(clippy::unnecessary_lazy_evaluations)] // rust-lang/rust-clippy#8522 + let zone_literal = first_match( + [ + (b"UT".as_slice(), 0), + (b"GMT".as_slice(), 0), + (b"EST".as_slice(), -5), + (b"EDT".as_slice(), -4), + (b"CST".as_slice(), -6), + (b"CDT".as_slice(), -5), + (b"MST".as_slice(), -7), + (b"MDT".as_slice(), -6), + (b"PST".as_slice(), -8), + (b"PDT".as_slice(), -7), + ], + false, + )(input) + .or_else(|| match input { + [ + b'a'..=b'i' | b'k'..=b'z' | b'A'..=b'I' | b'K'..=b'Z', + rest @ .., + ] => Some(ParsedItem(rest, 0)), + _ => None, + }); + if let Some(zone_literal) = zone_literal { + let input = zone_literal + .consume_value(|value| parsed.set_offset_hour(value)) + .ok_or(InvalidComponent("offset hour"))?; + parsed + .set_offset_minute_signed(0) + .ok_or(InvalidComponent("offset minute"))?; + parsed + .set_offset_second_signed(0) + .ok_or(InvalidComponent("offset second"))?; + return Ok(input); + } + + let ParsedItem(input, offset_sign) = sign(input).ok_or(InvalidComponent("offset hour"))?; + let input = exactly_n_digits::<2, u8>(input) + .and_then(|item| { + item.map(|offset_hour| { + if offset_sign == b'-' { + -(offset_hour as i8) + } else { + offset_hour as _ + } + }) + .consume_value(|value| parsed.set_offset_hour(value)) + }) + .ok_or(InvalidComponent("offset hour"))?; + let input = exactly_n_digits::<2, u8>(input) + .and_then(|item| { + item.consume_value(|value| parsed.set_offset_minute_signed(value as _)) + }) + .ok_or(InvalidComponent("offset minute"))?; + + Ok(input) + } + + fn parse_offset_date_time(&self, input: &[u8]) -> Result { + use crate::error::ParseFromDescription::{InvalidComponent, InvalidLiteral}; + use crate::parsing::combinator::rfc::rfc2822::{cfws, fws}; + use crate::parsing::combinator::{ + ascii_char, exactly_n_digits, first_match, n_to_m_digits, opt, sign, + }; + + let colon = ascii_char::; + let comma = ascii_char::; + + let input = opt(fws)(input).into_inner(); + // This parses the weekday, but we don't actually use the value anywhere. Because of this, + // just return `()` to avoid unnecessary generated code. + let ParsedItem(input, ()) = first_match( + [ + (b"Mon".as_slice(), ()), + (b"Tue".as_slice(), ()), + (b"Wed".as_slice(), ()), + (b"Thu".as_slice(), ()), + (b"Fri".as_slice(), ()), + (b"Sat".as_slice(), ()), + (b"Sun".as_slice(), ()), + ], + false, + )(input) + .ok_or(InvalidComponent("weekday"))?; + let input = comma(input).ok_or(InvalidLiteral)?.into_inner(); + let input = cfws(input).ok_or(InvalidLiteral)?.into_inner(); + let ParsedItem(input, day) = + n_to_m_digits::<1, 2, _>(input).ok_or(InvalidComponent("day"))?; + let input = cfws(input).ok_or(InvalidLiteral)?.into_inner(); + let ParsedItem(input, month) = first_match( + [ + (b"Jan".as_slice(), Month::January), + (b"Feb".as_slice(), Month::February), + (b"Mar".as_slice(), Month::March), + (b"Apr".as_slice(), Month::April), + (b"May".as_slice(), Month::May), + (b"Jun".as_slice(), Month::June), + (b"Jul".as_slice(), Month::July), + (b"Aug".as_slice(), Month::August), + (b"Sep".as_slice(), Month::September), + (b"Oct".as_slice(), Month::October), + (b"Nov".as_slice(), Month::November), + (b"Dec".as_slice(), Month::December), + ], + false, + )(input) + .ok_or(InvalidComponent("month"))?; + let input = cfws(input).ok_or(InvalidLiteral)?.into_inner(); + let (input, year) = match exactly_n_digits::<4, u32>(input) { + Some(item) => { + let ParsedItem(input, year) = item + .flat_map(|year| if year >= 1900 { Some(year) } else { None }) + .ok_or(InvalidComponent("year"))?; + let input = fws(input).ok_or(InvalidLiteral)?.into_inner(); + (input, year) + } + None => { + let ParsedItem(input, year) = exactly_n_digits::<2, u32>(input) + .map(|item| item.map(|year| if year < 50 { year + 2000 } else { year + 1900 })) + .ok_or(InvalidComponent("year"))?; + let input = cfws(input).ok_or(InvalidLiteral)?.into_inner(); + (input, year) + } + }; + + let ParsedItem(input, hour) = + exactly_n_digits::<2, _>(input).ok_or(InvalidComponent("hour"))?; + let input = opt(cfws)(input).into_inner(); + let input = colon(input).ok_or(InvalidLiteral)?.into_inner(); + let input = opt(cfws)(input).into_inner(); + let ParsedItem(input, minute) = + exactly_n_digits::<2, _>(input).ok_or(InvalidComponent("minute"))?; + + let (input, mut second) = if let Some(input) = colon(opt(cfws)(input).into_inner()) { + let input = input.into_inner(); // discard the colon + let input = opt(cfws)(input).into_inner(); + let ParsedItem(input, second) = + exactly_n_digits::<2, _>(input).ok_or(InvalidComponent("second"))?; + let input = cfws(input).ok_or(InvalidLiteral)?.into_inner(); + (input, second) + } else { + (cfws(input).ok_or(InvalidLiteral)?.into_inner(), 0) + }; + + #[allow(clippy::unnecessary_lazy_evaluations)] // rust-lang/rust-clippy#8522 + let zone_literal = first_match( + [ + (b"UT".as_slice(), 0), + (b"GMT".as_slice(), 0), + (b"EST".as_slice(), -5), + (b"EDT".as_slice(), -4), + (b"CST".as_slice(), -6), + (b"CDT".as_slice(), -5), + (b"MST".as_slice(), -7), + (b"MDT".as_slice(), -6), + (b"PST".as_slice(), -8), + (b"PDT".as_slice(), -7), + ], + false, + )(input) + .or_else(|| match input { + [ + b'a'..=b'i' | b'k'..=b'z' | b'A'..=b'I' | b'K'..=b'Z', + rest @ .., + ] => Some(ParsedItem(rest, 0)), + _ => None, + }); + + let (input, offset_hour, offset_minute) = if let Some(zone_literal) = zone_literal { + let ParsedItem(input, offset_hour) = zone_literal; + (input, offset_hour, 0) + } else { + let ParsedItem(input, offset_sign) = + sign(input).ok_or(InvalidComponent("offset hour"))?; + let ParsedItem(input, offset_hour) = exactly_n_digits::<2, u8>(input) + .map(|item| { + item.map(|offset_hour| { + if offset_sign == b'-' { + -(offset_hour as i8) + } else { + offset_hour as _ + } + }) + }) + .ok_or(InvalidComponent("offset hour"))?; + let ParsedItem(input, offset_minute) = + exactly_n_digits::<2, u8>(input).ok_or(InvalidComponent("offset minute"))?; + (input, offset_hour, offset_minute as i8) + }; + + if !input.is_empty() { + return Err(error::Parse::UnexpectedTrailingCharacters); + } + + let mut nanosecond = 0; + let leap_second_input = if second == 60 { + second = 59; + nanosecond = 999_999_999; + true + } else { + false + }; + + let dt = (|| { + let date = Date::from_calendar_date(year as _, month, day)?; + let time = Time::from_hms_nano(hour, minute, second, nanosecond)?; + let offset = UtcOffset::from_hms(offset_hour, offset_minute, 0)?; + Ok(date.with_time(time).assume_offset(offset)) + })() + .map_err(TryFromParsed::ComponentRange)?; + + if leap_second_input && !dt.is_valid_leap_second_stand_in() { + return Err(error::Parse::TryFromParsed(TryFromParsed::ComponentRange( + error::ComponentRange { + name: "second", + minimum: 0, + maximum: 59, + value: 60, + conditional_range: true, + }, + ))); + } + + Ok(dt) + } +} + +impl sealed::Sealed for Rfc3339 { + fn parse_into<'a>( + &self, + input: &'a [u8], + parsed: &mut Parsed, + ) -> Result<&'a [u8], error::Parse> { + use crate::error::ParseFromDescription::{InvalidComponent, InvalidLiteral}; + use crate::parsing::combinator::{ + any_digit, ascii_char, ascii_char_ignore_case, exactly_n_digits, sign, + }; + + let dash = ascii_char::; + let colon = ascii_char::; + + let input = exactly_n_digits::<4, u32>(input) + .and_then(|item| item.consume_value(|value| parsed.set_year(value as _))) + .ok_or(InvalidComponent("year"))?; + let input = dash(input).ok_or(InvalidLiteral)?.into_inner(); + let input = exactly_n_digits::<2, _>(input) + .and_then(|item| item.flat_map(|value| Month::from_number(value).ok())) + .and_then(|item| item.consume_value(|value| parsed.set_month(value))) + .ok_or(InvalidComponent("month"))?; + let input = dash(input).ok_or(InvalidLiteral)?.into_inner(); + let input = exactly_n_digits::<2, _>(input) + .and_then(|item| item.consume_value(|value| parsed.set_day(value))) + .ok_or(InvalidComponent("day"))?; + let input = ascii_char_ignore_case::(input) + .ok_or(InvalidLiteral)? + .into_inner(); + let input = exactly_n_digits::<2, _>(input) + .and_then(|item| item.consume_value(|value| parsed.set_hour_24(value))) + .ok_or(InvalidComponent("hour"))?; + let input = colon(input).ok_or(InvalidLiteral)?.into_inner(); + let input = exactly_n_digits::<2, _>(input) + .and_then(|item| item.consume_value(|value| parsed.set_minute(value))) + .ok_or(InvalidComponent("minute"))?; + let input = colon(input).ok_or(InvalidLiteral)?.into_inner(); + let input = exactly_n_digits::<2, _>(input) + .and_then(|item| item.consume_value(|value| parsed.set_second(value))) + .ok_or(InvalidComponent("second"))?; + let input = if let Some(ParsedItem(input, ())) = ascii_char::(input) { + let ParsedItem(mut input, mut value) = any_digit(input) + .ok_or(InvalidComponent("subsecond"))? + .map(|v| (v - b'0') as u32 * 100_000_000); + + let mut multiplier = 10_000_000; + while let Some(ParsedItem(new_input, digit)) = any_digit(input) { + value += (digit - b'0') as u32 * multiplier; + input = new_input; + multiplier /= 10; + } + + parsed + .set_subsecond(value) + .ok_or(InvalidComponent("subsecond"))?; + input + } else { + input + }; + + // The RFC explicitly allows leap seconds. + parsed.set_leap_second_allowed(true); + + if let Some(ParsedItem(input, ())) = ascii_char_ignore_case::(input) { + parsed + .set_offset_hour(0) + .ok_or(InvalidComponent("offset hour"))?; + parsed + .set_offset_minute_signed(0) + .ok_or(InvalidComponent("offset minute"))?; + parsed + .set_offset_second_signed(0) + .ok_or(InvalidComponent("offset second"))?; + return Ok(input); + } + + let ParsedItem(input, offset_sign) = sign(input).ok_or(InvalidComponent("offset hour"))?; + let input = exactly_n_digits::<2, u8>(input) + .and_then(|item| { + item.map(|offset_hour| { + if offset_sign == b'-' { + -(offset_hour as i8) + } else { + offset_hour as _ + } + }) + .consume_value(|value| parsed.set_offset_hour(value)) + }) + .ok_or(InvalidComponent("offset hour"))?; + let input = colon(input).ok_or(InvalidLiteral)?.into_inner(); + let input = exactly_n_digits::<2, u8>(input) + .and_then(|item| { + item.map(|offset_minute| { + if offset_sign == b'-' { + -(offset_minute as i8) + } else { + offset_minute as _ + } + }) + .consume_value(|value| parsed.set_offset_minute_signed(value)) + }) + .ok_or(InvalidComponent("offset minute"))?; + + Ok(input) + } + + fn parse_offset_date_time(&self, input: &[u8]) -> Result { + use crate::error::ParseFromDescription::{InvalidComponent, InvalidLiteral}; + use crate::parsing::combinator::{ + any_digit, ascii_char, ascii_char_ignore_case, exactly_n_digits, sign, + }; + + let dash = ascii_char::; + let colon = ascii_char::; + + let ParsedItem(input, year) = + exactly_n_digits::<4, u32>(input).ok_or(InvalidComponent("year"))?; + let input = dash(input).ok_or(InvalidLiteral)?.into_inner(); + let ParsedItem(input, month) = + exactly_n_digits::<2, _>(input).ok_or(InvalidComponent("month"))?; + let input = dash(input).ok_or(InvalidLiteral)?.into_inner(); + let ParsedItem(input, day) = + exactly_n_digits::<2, _>(input).ok_or(InvalidComponent("day"))?; + let input = ascii_char_ignore_case::(input) + .ok_or(InvalidLiteral)? + .into_inner(); + let ParsedItem(input, hour) = + exactly_n_digits::<2, _>(input).ok_or(InvalidComponent("hour"))?; + let input = colon(input).ok_or(InvalidLiteral)?.into_inner(); + let ParsedItem(input, minute) = + exactly_n_digits::<2, _>(input).ok_or(InvalidComponent("minute"))?; + let input = colon(input).ok_or(InvalidLiteral)?.into_inner(); + let ParsedItem(input, mut second) = + exactly_n_digits::<2, _>(input).ok_or(InvalidComponent("second"))?; + let ParsedItem(input, mut nanosecond) = + if let Some(ParsedItem(input, ())) = ascii_char::(input) { + let ParsedItem(mut input, mut value) = any_digit(input) + .ok_or(InvalidComponent("subsecond"))? + .map(|v| (v - b'0') as u32 * 100_000_000); + + let mut multiplier = 10_000_000; + while let Some(ParsedItem(new_input, digit)) = any_digit(input) { + value += (digit - b'0') as u32 * multiplier; + input = new_input; + multiplier /= 10; + } + + ParsedItem(input, value) + } else { + ParsedItem(input, 0) + }; + let ParsedItem(input, offset) = { + if let Some(ParsedItem(input, ())) = ascii_char_ignore_case::(input) { + ParsedItem(input, UtcOffset::UTC) + } else { + let ParsedItem(input, offset_sign) = + sign(input).ok_or(InvalidComponent("offset hour"))?; + let ParsedItem(input, offset_hour) = + exactly_n_digits::<2, u8>(input).ok_or(InvalidComponent("offset hour"))?; + let input = colon(input).ok_or(InvalidLiteral)?.into_inner(); + let ParsedItem(input, offset_minute) = + exactly_n_digits::<2, u8>(input).ok_or(InvalidComponent("offset minute"))?; + UtcOffset::from_hms( + if offset_sign == b'-' { + -(offset_hour as i8) + } else { + offset_hour as _ + }, + if offset_sign == b'-' { + -(offset_minute as i8) + } else { + offset_minute as _ + }, + 0, + ) + .map(|offset| ParsedItem(input, offset)) + .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"; + } + err + }) + .map_err(TryFromParsed::ComponentRange)? + } + }; + + if !input.is_empty() { + return Err(error::Parse::UnexpectedTrailingCharacters); + } + + // The RFC explicitly permits leap seconds. We don't currently support them, so treat it as + // the preceding nanosecond. However, leap seconds can only occur as the last second of the + // month UTC. + let leap_second_input = if second == 60 { + second = 59; + nanosecond = 999_999_999; + true + } else { + false + }; + + let dt = Month::from_number(month) + .and_then(|month| Date::from_calendar_date(year as _, month, day)) + .and_then(|date| date.with_hms_nano(hour, minute, second, nanosecond)) + .map(|date| date.assume_offset(offset)) + .map_err(TryFromParsed::ComponentRange)?; + + if leap_second_input && !dt.is_valid_leap_second_stand_in() { + return Err(error::Parse::TryFromParsed(TryFromParsed::ComponentRange( + error::ComponentRange { + name: "second", + minimum: 0, + maximum: 59, + value: 60, + conditional_range: true, + }, + ))); + } + + Ok(dt) + } +} + +impl sealed::Sealed for Iso8601 { + fn parse_into<'a>( + &self, + mut input: &'a [u8], + parsed: &mut Parsed, + ) -> Result<&'a [u8], error::Parse> { + use crate::parsing::combinator::rfc::iso8601::ExtendedKind; + + let mut extended_kind = ExtendedKind::Unknown; + let mut date_is_present = false; + let mut time_is_present = false; + let mut offset_is_present = false; + let mut first_error = None; + + match Self::parse_date(parsed, &mut extended_kind)(input) { + Ok(new_input) => { + input = new_input; + date_is_present = true; + } + Err(err) => { + first_error.get_or_insert(err); + } + } + + match Self::parse_time(parsed, &mut extended_kind, date_is_present)(input) { + Ok(new_input) => { + input = new_input; + time_is_present = true; + } + Err(err) => { + first_error.get_or_insert(err); + } + } + + // If a date and offset are present, a time must be as well. + if !date_is_present || time_is_present { + match Self::parse_offset(parsed, &mut extended_kind)(input) { + Ok(new_input) => { + input = new_input; + offset_is_present = true; + } + Err(err) => { + first_error.get_or_insert(err); + } + } + } + + if !date_is_present && !time_is_present && !offset_is_present { + match first_error { + Some(err) => return Err(err), + None => unreachable!("an error should be present if no components were parsed"), + } + } + + Ok(input) + } +} +// endregion well-known formats diff --git a/third_party/rust/time/src/parsing/parsed.rs b/third_party/rust/time/src/parsing/parsed.rs new file mode 100644 index 0000000000..7b2279cbb1 --- /dev/null +++ b/third_party/rust/time/src/parsing/parsed.rs @@ -0,0 +1,759 @@ +//! Information parsed from an input and format description. + +use core::mem::MaybeUninit; +use core::num::{NonZeroU16, NonZeroU8}; + +use crate::error::TryFromParsed::InsufficientInformation; +use crate::format_description::modifier::{WeekNumberRepr, YearRepr}; +#[cfg(feature = "alloc")] +use crate::format_description::OwnedFormatItem; +use crate::format_description::{Component, FormatItem}; +use crate::parsing::component::{ + parse_day, parse_hour, parse_minute, parse_month, parse_offset_hour, parse_offset_minute, + parse_offset_second, parse_ordinal, parse_period, parse_second, parse_subsecond, + 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 { + /// Bitflags indicating whether a particular field is present. + flags: u16, + /// Calendar year. + year: MaybeUninit, + /// The last two digits of the calendar year. + year_last_two: MaybeUninit, + /// Year of the [ISO week date](https://en.wikipedia.org/wiki/ISO_week_date). + iso_year: MaybeUninit, + /// The last two digits of the ISO week year. + iso_year_last_two: MaybeUninit, + /// 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, + /// Week of the year, where week one begins on the first Monday of the calendar year. + monday_week_number: MaybeUninit, + /// Week of the year, where week one is the Monday-to-Sunday period containing January 4. + iso_week_number: Option, + /// Day of the week. + weekday: Option, + /// Day of the year. + ordinal: Option, + /// Day of the month. + day: Option, + /// Hour within the day. + hour_24: MaybeUninit, + /// 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, + /// Whether the `hour_12` field indicates a time that "PM". + hour_12_is_pm: Option, + /// Minute within the hour. + minute: MaybeUninit, + /// Second within the minute. + second: MaybeUninit, + /// Nanosecond within the second. + subsecond: MaybeUninit, + /// Whole hours of the UTC offset. + offset_hour: MaybeUninit, + /// Minutes within the hour of the UTC offset. + offset_minute: MaybeUninit, + /// Seconds within the minute of the UTC offset. + offset_second: MaybeUninit, +} + +#[allow(clippy::missing_docs_in_private_items)] +impl Parsed { + const YEAR_FLAG: u16 = 1 << 0; + const YEAR_LAST_TWO_FLAG: u16 = 1 << 1; + const ISO_YEAR_FLAG: u16 = 1 << 2; + const ISO_YEAR_LAST_TWO_FLAG: u16 = 1 << 3; + const SUNDAY_WEEK_NUMBER_FLAG: u16 = 1 << 4; + const MONDAY_WEEK_NUMBER_FLAG: u16 = 1 << 5; + const HOUR_24_FLAG: u16 = 1 << 6; + const MINUTE_FLAG: u16 = 1 << 7; + const SECOND_FLAG: u16 = 1 << 8; + const SUBSECOND_FLAG: u16 = 1 << 9; + const OFFSET_HOUR_FLAG: u16 = 1 << 10; + const OFFSET_MINUTE_FLAG: u16 = 1 << 11; + const OFFSET_SECOND_FLAG: u16 = 1 << 12; + /// Indicates whether a leap second is permitted to be parsed. This is required by some + /// well-known formats. + const LEAP_SECOND_ALLOWED_FLAG: u16 = 1 << 13; +} + +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(), + month: None, + sunday_week_number: MaybeUninit::uninit(), + monday_week_number: MaybeUninit::uninit(), + iso_week_number: None, + weekday: None, + ordinal: None, + day: None, + hour_24: MaybeUninit::uninit(), + hour_12: 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(), + } + } + + /// 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 { + WeekNumberRepr::Iso => { + NonZeroU8::new(value).and_then(|value| self.set_iso_week_number(value)) + } + WeekNumberRepr::Sunday => self.set_sunday_week_number(value), + 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, YearRepr::Full) => self.set_year(value), + (false, YearRepr::LastTwo) => self.set_year_last_two(value as _), + (true, YearRepr::Full) => self.set_iso_year(value), + (true, YearRepr::LastTwo) => self.set_iso_year_last_two(value as _), + } + .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| self.set_offset_hour(value))) + .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")), + } + } +} + +/// 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.flags & Self::$flag != Self::$flag { + None + } else { + // SAFETY: We just checked if the field is present. + Some(unsafe { self.$name.assume_init() }) + } + } + }; +} + +/// 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, + } + + /// Obtain the absolute value of the offset minute. + #[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`. + pub const fn offset_minute_signed(&self) -> Option { + if self.flags & Self::OFFSET_MINUTE_FLAG != Self::OFFSET_MINUTE_FLAG { + None + } else { + // SAFETY: We just checked if the field is present. + Some(unsafe { self.offset_minute.assume_init() }) + } + } + + /// Obtain the absolute value of the offset second. + #[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`. + pub const fn offset_second_signed(&self) -> Option { + if self.flags & Self::OFFSET_SECOND_FLAG != Self::OFFSET_SECOND_FLAG { + None + } else { + // SAFETY: We just checked if the field is present. + Some(unsafe { self.offset_second.assume_init() }) + } + } + + /// Obtain whether leap seconds are permitted in the current format. + pub(crate) const fn leap_second_allowed(&self) -> bool { + self.flags & Self::LEAP_SECOND_ALLOWED_FLAG == Self::LEAP_SECOND_ALLOWED_FLAG + } +} + +/// Generate setters for each of the fields. +/// +/// This macro should only be used for fields where the value is not validated beyond its type. +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); + self.flags |= Self::$flag; + 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_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, + } + + /// Set the named component. + #[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 as u8 { + None + } else { + self.set_offset_minute_signed(value as _) + } + } + + /// Set the `offset_minute` component. + pub fn set_offset_minute_signed(&mut self, value: i8) -> Option<()> { + self.offset_minute = MaybeUninit::new(value); + self.flags |= Self::OFFSET_MINUTE_FLAG; + Some(()) + } + + /// Set the named component. + #[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 as u8 { + None + } else { + 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); + self.flags |= Self::OFFSET_SECOND_FLAG; + Some(()) + } + + /// Set the leap second allowed flag. + pub(crate) fn set_leap_second_allowed(&mut self, value: bool) { + if value { + self.flags |= Self::LEAP_SECOND_ALLOWED_FLAG; + } else { + self.flags &= !Self::LEAP_SECOND_ALLOWED_FLAG; + } + } +} + +/// 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); + self.flags |= Self::$flag; + Some(self) + } + }; +} + +/// Builder methods +/// +/// 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, + } + + /// Set the named component and return `self`. + #[deprecated( + since = "0.3.8", + note = "use `parsed.with_offset_minute_signed()` instead" + )] + pub const fn with_offset_minute(self, value: u8) -> Option { + 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.offset_minute = MaybeUninit::new(value); + self.flags |= Self::OFFSET_MINUTE_FLAG; + Some(self) + } + + /// Set the named component and return `self`. + #[deprecated( + since = "0.3.8", + note = "use `parsed.with_offset_second_signed()` instead" + )] + pub const fn with_offset_second(self, value: u8) -> Option { + 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.offset_second = MaybeUninit::new(value); + self.flags |= Self::OFFSET_SECOND_FLAG; + Some(self) + } +} + +impl TryFrom for Date { + type Error = error::TryFromParsed; + + fn try_from(parsed: Parsed) -> Result { + /// 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 { + match 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 as i16 * 7 + weekday.number_days_from_sunday() as i16 + - adjustment(year) + + 1) as u16, + )?), + (year, monday_week_number, weekday) => Ok(Self::from_ordinal_date( + year, + (monday_week_number as i16 * 7 + weekday.number_days_from_monday() as i16 + - adjustment(year) + + 1) as u16, + )?), + _ => Err(InsufficientInformation), + } + } +} + +impl TryFrom for Time { + type Error = error::TryFromParsed; + + fn try_from(parsed: Parsed) -> Result { + 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)?); + } + let minute = parsed.minute().ok_or(InsufficientInformation)?; + let second = parsed.second().unwrap_or(0); + let subsecond = parsed.subsecond().unwrap_or(0); + Ok(Self::from_hms_nano(hour, minute, second, subsecond)?) + } +} + +impl TryFrom for UtcOffset { + type Error = error::TryFromParsed; + + fn try_from(parsed: Parsed) -> Result { + 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 for PrimitiveDateTime { + type Error = error::TryFromParsed; + + fn try_from(parsed: Parsed) -> Result { + Ok(Self::new(parsed.try_into()?, parsed.try_into()?)) + } +} + +impl TryFrom for OffsetDateTime { + type Error = error::TryFromParsed; + + #[allow(clippy::unwrap_in_result)] // We know the values are valid. + fn try_from(mut parsed: Parsed) -> Result { + // 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) { + 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 = PrimitiveDateTime::try_from(parsed)?.assume_offset(parsed.try_into()?); + 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) + } +} diff --git a/third_party/rust/time/src/parsing/shim.rs b/third_party/rust/time/src/parsing/shim.rs new file mode 100644 index 0000000000..00aaf4852b --- /dev/null +++ b/third_party/rust/time/src/parsing/shim.rs @@ -0,0 +1,50 @@ +//! Extension traits for things either not implemented or not yet stable in the MSRV. + +/// Equivalent of `foo.parse()` for slices. +pub(crate) trait IntegerParseBytes { + #[allow(clippy::missing_docs_in_private_items)] + fn parse_bytes(&self) -> Option; +} + +impl IntegerParseBytes for [u8] { + fn parse_bytes(&self) -> Option { + T::parse_bytes(self) + } +} + +/// Marker trait for all integer types, including `NonZero*` +pub(crate) trait Integer: Sized { + #[allow(clippy::missing_docs_in_private_items)] + fn parse_bytes(src: &[u8]) -> Option; +} + +/// Parse the given types from bytes. +macro_rules! impl_parse_bytes { + ($($t:ty)*) => ($( + impl Integer for $t { + #[allow(trivial_numeric_casts)] + fn parse_bytes(src: &[u8]) -> Option { + src.iter().try_fold::(0, |result, c| { + result.checked_mul(10)?.checked_add((c - b'0') as Self) + }) + } + } + )*) +} +impl_parse_bytes! { u8 u16 u32 } + +/// Parse the given types from bytes. +macro_rules! impl_parse_bytes_nonzero { + ($($t:ty)*) => {$( + impl Integer for $t { + fn parse_bytes(src: &[u8]) -> Option { + Self::new(src.parse_bytes()?) + } + } + )*} +} + +impl_parse_bytes_nonzero! { + core::num::NonZeroU8 + core::num::NonZeroU16 +} -- cgit v1.2.3