summaryrefslogtreecommitdiffstats
path: root/third_party/rust/time/src/parsing
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
commit43a97878ce14b72f0981164f87f2e35e14151312 (patch)
tree620249daf56c0258faa40cbdcf9cfba06de2a846 /third_party/rust/time/src/parsing
parentInitial commit. (diff)
downloadfirefox-43a97878ce14b72f0981164f87f2e35e14151312.tar.xz
firefox-43a97878ce14b72f0981164f87f2e35e14151312.zip
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/time/src/parsing')
-rw-r--r--third_party/rust/time/src/parsing/combinator/mod.rs192
-rw-r--r--third_party/rust/time/src/parsing/combinator/rfc/iso8601.rs173
-rw-r--r--third_party/rust/time/src/parsing/combinator/rfc/mod.rs10
-rw-r--r--third_party/rust/time/src/parsing/combinator/rfc/rfc2234.rs13
-rw-r--r--third_party/rust/time/src/parsing/combinator/rfc/rfc2822.rs115
-rw-r--r--third_party/rust/time/src/parsing/component.rs296
-rw-r--r--third_party/rust/time/src/parsing/iso8601.rs308
-rw-r--r--third_party/rust/time/src/parsing/mod.rs50
-rw-r--r--third_party/rust/time/src/parsing/parsable.rs754
-rw-r--r--third_party/rust/time/src/parsing/parsed.rs759
-rw-r--r--third_party/rust/time/src/parsing/shim.rs50
11 files changed, 2720 insertions, 0 deletions
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<ParsedItem<'_, u8>> {
+ 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<Item = (&'a [u8], T)>,
+ case_sensitive: bool,
+) -> impl FnMut(&'a [u8]) -> Option<ParsedItem<'a, T>> {
+ 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<ParsedItem<'a, ()>>>(
+ 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<ParsedItem<'a, ()>>>(
+ parser: P,
+) -> impl Fn(&'a [u8]) -> Option<ParsedItem<'a, ()>> {
+ 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<ParsedItem<'a, T>>,
+>(
+ parser: P,
+) -> impl Fn(&'a [u8]) -> Option<ParsedItem<'a, &'a [u8]>> {
+ 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<const N: u8, const M: u8, T: Integer>(
+ input: &[u8],
+) -> Option<ParsedItem<'_, T>> {
+ debug_assert!(M >= N);
+ n_to_m::<N, M, _, _>(any_digit)(input)?.flat_map(|value| value.parse_bytes())
+}
+
+/// Consume exactly `n` digits, returning the numerical value.
+pub(crate) fn exactly_n_digits<const N: u8, T: Integer>(input: &[u8]) -> Option<ParsedItem<'_, T>> {
+ n_to_m_digits::<N, N, _>(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<ParsedItem<'a, T>> {
+ n_to_m_digits_padded::<N, N, _>(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<ParsedItem<'a, T>> {
+ 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::<b' '>(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::<N, M, _>(input),
+ }
+}
+
+/// Consume exactly one digit.
+pub(crate) const fn any_digit(input: &[u8]) -> Option<ParsedItem<'_, u8>> {
+ 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<const CHAR: u8>(input: &[u8]) -> Option<ParsedItem<'_, ()>> {
+ 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<const CHAR: u8>(input: &[u8]) -> Option<ParsedItem<'_, ()>> {
+ 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<ParsedItem<'a, T>>,
+) -> impl Fn(&'a [u8]) -> ParsedItem<'a, Option<T>> {
+ 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<ParsedItem<'_, i32>> {
+ 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<ParsedItem<'_, Month>> {
+ 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<ParsedItem<'_, NonZeroU8>> {
+ exactly_n_digits::<2, _>(input)
+}
+
+/// Parse a day of the month.
+pub(crate) fn day(input: &[u8]) -> Option<ParsedItem<'_, NonZeroU8>> {
+ exactly_n_digits::<2, _>(input)
+}
+
+/// Parse a day of the week.
+pub(crate) fn dayk(input: &[u8]) -> Option<ParsedItem<'_, Weekday>> {
+ 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<ParsedItem<'_, NonZeroU16>> {
+ exactly_n_digits::<3, _>(input)
+}
+
+/// Parse the hour.
+pub(crate) fn hour(input: &[u8]) -> Option<ParsedItem<'_, u8>> {
+ exactly_n_digits::<2, _>(input)
+}
+
+/// Parse the minute.
+pub(crate) fn min(input: &[u8]) -> Option<ParsedItem<'_, u8>> {
+ 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<ParsedItem<'_, (u8, Option<f64>)>> {
+ // 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<ParsedItem<'_, ()>> {
+ ascii_char::<b'.'>(input).or_else(|| ascii_char::<b','>(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<ParsedItem<'_, ()>> {
+ 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<ParsedItem<'_, ()>> {
+ 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<ParsedItem<'_, ()>> {
+ one_or_more(|input| fws(input).or_else(|| comment(input)))(input)
+}
+
+/// Consume the `comment` rule.
+fn comment(mut input: &[u8]) -> Option<ParsedItem<'_, ()>> {
+ input = ascii_char::<b'('>(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::<b')'>(input)?.into_inner();
+
+ Some(ParsedItem(input, ()))
+}
+
+/// Consume the `ccontent` rule.
+fn ccontent(input: &[u8]) -> Option<ParsedItem<'_, ()>> {
+ 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<ParsedItem<'_, ()>> {
+ 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<ParsedItem<'_, ()>> {
+ input = ascii_char::<b'\\'>(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<ParsedItem<'_, ()>> {
+ 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::<b'\n'>)(input).into_inner();
+ input = zero_or_more(ascii_char::<b'\r'>)(input).into_inner();
+ while let Some(rest) = obs_char(input) {
+ input = rest;
+ input = zero_or_more(ascii_char::<b'\n'>)(input).into_inner();
+ input = zero_or_more(ascii_char::<b'\r'>)(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<ParsedItem<'_, i32>> {
+ 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<ParsedItem<'_, Month>> {
+ 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<ParsedItem<'_, u8>> {
+ 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<ParsedItem<'_, Weekday>> {
+ 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<ParsedItem<'_, NonZeroU16>> {
+ 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<ParsedItem<'_, NonZeroU8>> {
+ 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<ParsedItem<'_, u8>> {
+ 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<ParsedItem<'_, u8>> {
+ 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<ParsedItem<'_, u8>> {
+ 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<ParsedItem<'_, Period>> {
+ 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<ParsedItem<'_, u32>> {
+ 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<ParsedItem<'_, i8>> {
+ 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<ParsedItem<'_, i8>> {
+ 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<ParsedItem<'_, i8>> {
+ 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<const CONFIG: EncodedConfig> Iso8601<CONFIG> {
+ // 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::<b'-'>(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::<b'-'>(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::<b'W'>(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::<b'-'>(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::<b'T'>(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::<b':'>(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::<b':'>(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::<b'Z'>(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::<b':'>(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<U>(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<U>(self, f: impl FnOnce(T) -> Option<U>) -> Option<ParsedItem<'a, U>> {
+ 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<const CONFIG: EncodedConfig> Parsable for Iso8601<CONFIG> {}
+impl<T: Deref> 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<Parsed, error::Parse> {
+ 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<Date, error::Parse> {
+ Ok(self.parse(input)?.try_into()?)
+ }
+
+ /// Parse a [`Time`] from the format description.
+ fn parse_time(&self, input: &[u8]) -> Result<Time, error::Parse> {
+ Ok(self.parse(input)?.try_into()?)
+ }
+
+ /// Parse a [`UtcOffset`] from the format description.
+ fn parse_offset(&self, input: &[u8]) -> Result<UtcOffset, error::Parse> {
+ Ok(self.parse(input)?.try_into()?)
+ }
+
+ /// Parse a [`PrimitiveDateTime`] from the format description.
+ fn parse_date_time(&self, input: &[u8]) -> Result<PrimitiveDateTime, error::Parse> {
+ Ok(self.parse(input)?.try_into()?)
+ }
+
+ /// Parse a [`OffsetDateTime`] from the format description.
+ fn parse_offset_date_time(&self, input: &[u8]) -> Result<OffsetDateTime, error::Parse> {
+ 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<T: Deref> 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::<b':'>;
+ let comma = ascii_char::<b','>;
+
+ 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<OffsetDateTime, 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::<b':'>;
+ let comma = ascii_char::<b','>;
+
+ 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::<b'-'>;
+ let colon = ascii_char::<b':'>;
+
+ 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::<b'T'>(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::<b'.'>(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::<b'Z'>(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<OffsetDateTime, 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::<b'-'>;
+ let colon = ascii_char::<b':'>;
+
+ 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::<b'T'>(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::<b'.'>(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::<b'Z'>(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<const CONFIG: EncodedConfig> sealed::Sealed for Iso8601<CONFIG> {
+ 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<i32>,
+ /// The last two digits of the calendar year.
+ year_last_two: MaybeUninit<u8>,
+ /// Year of the [ISO week date](https://en.wikipedia.org/wiki/ISO_week_date).
+ iso_year: MaybeUninit<i32>,
+ /// The last two digits of the ISO week year.
+ iso_year_last_two: MaybeUninit<u8>,
+ /// 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: MaybeUninit<u8>,
+ /// Week of the year, where week one begins on the first Monday of the calendar year.
+ monday_week_number: MaybeUninit<u8>,
+ /// Week of the year, where week one is the Monday-to-Sunday period containing January 4.
+ iso_week_number: Option<NonZeroU8>,
+ /// Day of the week.
+ weekday: Option<Weekday>,
+ /// Day of the year.
+ ordinal: Option<NonZeroU16>,
+ /// Day of the month.
+ day: Option<NonZeroU8>,
+ /// Hour within the day.
+ hour_24: MaybeUninit<u8>,
+ /// 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<NonZeroU8>,
+ /// Whether the `hour_12` field indicates a time that "PM".
+ hour_12_is_pm: Option<bool>,
+ /// Minute within the hour.
+ minute: MaybeUninit<u8>,
+ /// Second within the minute.
+ second: MaybeUninit<u8>,
+ /// Nanosecond within the second.
+ subsecond: MaybeUninit<u32>,
+ /// Whole hours of the UTC offset.
+ offset_hour: MaybeUninit<i8>,
+ /// Minutes within the hour of the UTC offset.
+ offset_minute: MaybeUninit<i8>,
+ /// Seconds within the minute of the UTC offset.
+ offset_second: MaybeUninit<i8>,
+}
+
+#[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<u8> {
+ 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<i8> {
+ 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<u8> {
+ 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<i8> {
+ 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> {
+ 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> {
+ self.$name = MaybeUninit::new(value);
+ self.flags |= Self::$flag;
+ Some(self)
+ }
+ };
+}
+
+/// 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 {
+ 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<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 = 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<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 = MaybeUninit::new(value);
+ self.flags |= Self::OFFSET_SECOND_FLAG;
+ 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 {
+ 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<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)?);
+ }
+ 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<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;
+
+ #[allow(clippy::unwrap_in_result)] // We know the values are valid.
+ fn try_from(mut parsed: Parsed) -> Result<Self, Self::Error> {
+ // 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<T> {
+ #[allow(clippy::missing_docs_in_private_items)]
+ fn parse_bytes(&self) -> Option<T>;
+}
+
+impl<T: Integer> IntegerParseBytes<T> for [u8] {
+ fn parse_bytes(&self) -> Option<T> {
+ 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<Self>;
+}
+
+/// 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<Self> {
+ src.iter().try_fold::<Self, _, _>(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> {
+ Self::new(src.parse_bytes()?)
+ }
+ }
+ )*}
+}
+
+impl_parse_bytes_nonzero! {
+ core::num::NonZeroU8
+ core::num::NonZeroU16
+}