summaryrefslogtreecommitdiff
path: root/vendor/time/src/format_description/parse
diff options
context:
space:
mode:
authormo khan <mo@mokhan.ca>2025-07-02 18:36:06 -0600
committermo khan <mo@mokhan.ca>2025-07-02 18:36:06 -0600
commit8cdfa445d6629ffef4cb84967ff7017654045bc2 (patch)
tree22f0b0907c024c78d26a731e2e1f5219407d8102 /vendor/time/src/format_description/parse
parent4351c74c7c5f97156bc94d3a8549b9940ac80e3f (diff)
chore: add vendor directory
Diffstat (limited to 'vendor/time/src/format_description/parse')
-rw-r--r--vendor/time/src/format_description/parse/ast.rs384
-rw-r--r--vendor/time/src/format_description/parse/format_item.rs549
-rw-r--r--vendor/time/src/format_description/parse/lexer.rs284
-rw-r--r--vendor/time/src/format_description/parse/mod.rs262
-rw-r--r--vendor/time/src/format_description/parse/strftime.rs487
5 files changed, 1966 insertions, 0 deletions
diff --git a/vendor/time/src/format_description/parse/ast.rs b/vendor/time/src/format_description/parse/ast.rs
new file mode 100644
index 00000000..cf13de89
--- /dev/null
+++ b/vendor/time/src/format_description/parse/ast.rs
@@ -0,0 +1,384 @@
+//! AST for parsing format descriptions.
+
+use alloc::boxed::Box;
+use alloc::string::String;
+use alloc::vec::Vec;
+use core::iter;
+
+use super::{lexer, unused, Error, Location, Spanned, SpannedValue, Unused};
+use crate::internal_macros::bug;
+
+/// One part of a complete format description.
+pub(super) enum Item<'a> {
+ /// A literal string, formatted and parsed as-is.
+ ///
+ /// This should never be present inside a nested format description.
+ Literal(Spanned<&'a [u8]>),
+ /// A sequence of brackets. The first acts as the escape character.
+ ///
+ /// This should never be present if the lexer has `BACKSLASH_ESCAPE` set to `true`.
+ EscapedBracket {
+ /// The first bracket.
+ _first: Unused<Location>,
+ /// The second bracket.
+ _second: Unused<Location>,
+ },
+ /// Part of a type, along with its modifiers.
+ Component {
+ /// Where the opening bracket was in the format string.
+ _opening_bracket: Unused<Location>,
+ /// Whitespace between the opening bracket and name.
+ _leading_whitespace: Unused<Option<Spanned<&'a [u8]>>>,
+ /// The name of the component.
+ name: Spanned<&'a [u8]>,
+ /// The modifiers for the component.
+ modifiers: Box<[Modifier<'a>]>,
+ /// Whitespace between the modifiers and closing bracket.
+ _trailing_whitespace: Unused<Option<Spanned<&'a [u8]>>>,
+ /// Where the closing bracket was in the format string.
+ _closing_bracket: Unused<Location>,
+ },
+ /// An optional sequence of items.
+ Optional {
+ /// Where the opening bracket was in the format string.
+ opening_bracket: Location,
+ /// Whitespace between the opening bracket and "optional".
+ _leading_whitespace: Unused<Option<Spanned<&'a [u8]>>>,
+ /// The "optional" keyword.
+ _optional_kw: Unused<Spanned<&'a [u8]>>,
+ /// Whitespace between the "optional" keyword and the opening bracket.
+ _whitespace: Unused<Spanned<&'a [u8]>>,
+ /// The items within the optional sequence.
+ nested_format_description: NestedFormatDescription<'a>,
+ /// Where the closing bracket was in the format string.
+ closing_bracket: Location,
+ },
+ /// The first matching parse of a sequence of items.
+ First {
+ /// Where the opening bracket was in the format string.
+ opening_bracket: Location,
+ /// Whitespace between the opening bracket and "first".
+ _leading_whitespace: Unused<Option<Spanned<&'a [u8]>>>,
+ /// The "first" keyword.
+ _first_kw: Unused<Spanned<&'a [u8]>>,
+ /// Whitespace between the "first" keyword and the opening bracket.
+ _whitespace: Unused<Spanned<&'a [u8]>>,
+ /// The sequences of items to try.
+ nested_format_descriptions: Box<[NestedFormatDescription<'a>]>,
+ /// Where the closing bracket was in the format string.
+ closing_bracket: Location,
+ },
+}
+
+/// A format description that is nested within another format description.
+pub(super) struct NestedFormatDescription<'a> {
+ /// Where the opening bracket was in the format string.
+ pub(super) _opening_bracket: Unused<Location>,
+ /// The items within the nested format description.
+ pub(super) items: Box<[Item<'a>]>,
+ /// Where the closing bracket was in the format string.
+ pub(super) _closing_bracket: Unused<Location>,
+ /// Whitespace between the closing bracket and the next item.
+ pub(super) _trailing_whitespace: Unused<Option<Spanned<&'a [u8]>>>,
+}
+
+/// A modifier for a component.
+pub(super) struct Modifier<'a> {
+ /// Whitespace preceding the modifier.
+ pub(super) _leading_whitespace: Unused<Spanned<&'a [u8]>>,
+ /// The key of the modifier.
+ pub(super) key: Spanned<&'a [u8]>,
+ /// Where the colon of the modifier was in the format string.
+ pub(super) _colon: Unused<Location>,
+ /// The value of the modifier.
+ pub(super) value: Spanned<&'a [u8]>,
+}
+
+/// Parse the provided tokens into an AST.
+pub(super) fn parse<
+ 'item: 'iter,
+ 'iter,
+ I: Iterator<Item = Result<lexer::Token<'item>, Error>>,
+ const VERSION: usize,
+>(
+ tokens: &'iter mut lexer::Lexed<I>,
+) -> impl Iterator<Item = Result<Item<'item>, Error>> + 'iter {
+ validate_version!(VERSION);
+ parse_inner::<_, false, VERSION>(tokens)
+}
+
+/// Parse the provided tokens into an AST. The const generic indicates whether the resulting
+/// [`Item`] will be used directly or as part of a [`NestedFormatDescription`].
+fn parse_inner<
+ 'item,
+ I: Iterator<Item = Result<lexer::Token<'item>, Error>>,
+ const NESTED: bool,
+ const VERSION: usize,
+>(
+ tokens: &mut lexer::Lexed<I>,
+) -> impl Iterator<Item = Result<Item<'item>, Error>> + '_ {
+ validate_version!(VERSION);
+ iter::from_fn(move || {
+ if NESTED && tokens.peek_closing_bracket().is_some() {
+ return None;
+ }
+
+ let next = match tokens.next()? {
+ Ok(token) => token,
+ Err(err) => return Some(Err(err)),
+ };
+
+ Some(match next {
+ lexer::Token::Literal(Spanned { value: _, span: _ }) if NESTED => {
+ bug!("literal should not be present in nested description")
+ }
+ lexer::Token::Literal(value) => Ok(Item::Literal(value)),
+ lexer::Token::Bracket {
+ kind: lexer::BracketKind::Opening,
+ location,
+ } => {
+ if version!(..=1) {
+ if let Some(second_location) = tokens.next_if_opening_bracket() {
+ Ok(Item::EscapedBracket {
+ _first: unused(location),
+ _second: unused(second_location),
+ })
+ } else {
+ parse_component::<_, VERSION>(location, tokens)
+ }
+ } else {
+ parse_component::<_, VERSION>(location, tokens)
+ }
+ }
+ lexer::Token::Bracket {
+ kind: lexer::BracketKind::Closing,
+ location: _,
+ } if NESTED => {
+ bug!("closing bracket should be caught by the `if` statement")
+ }
+ lexer::Token::Bracket {
+ kind: lexer::BracketKind::Closing,
+ location: _,
+ } => {
+ bug!("closing bracket should have been consumed by `parse_component`")
+ }
+ lexer::Token::ComponentPart {
+ kind: _, // whitespace is significant in nested components
+ value,
+ } if NESTED => Ok(Item::Literal(value)),
+ lexer::Token::ComponentPart { kind: _, value: _ } => {
+ bug!("component part should have been consumed by `parse_component`")
+ }
+ })
+ })
+}
+
+/// Parse a component. This assumes that the opening bracket has already been consumed.
+fn parse_component<
+ 'a,
+ I: Iterator<Item = Result<lexer::Token<'a>, Error>>,
+ const VERSION: usize,
+>(
+ opening_bracket: Location,
+ tokens: &mut lexer::Lexed<I>,
+) -> Result<Item<'a>, Error> {
+ validate_version!(VERSION);
+ let leading_whitespace = tokens.next_if_whitespace();
+
+ let Some(name) = tokens.next_if_not_whitespace() else {
+ let span = match leading_whitespace {
+ Some(Spanned { value: _, span }) => span,
+ None => opening_bracket.to_self(),
+ };
+ return Err(Error {
+ _inner: unused(span.error("expected component name")),
+ public: crate::error::InvalidFormatDescription::MissingComponentName {
+ index: span.start.byte as usize,
+ },
+ });
+ };
+
+ if *name == b"optional" {
+ let Some(whitespace) = tokens.next_if_whitespace() else {
+ return Err(Error {
+ _inner: unused(name.span.error("expected whitespace after `optional`")),
+ public: crate::error::InvalidFormatDescription::Expected {
+ what: "whitespace after `optional`",
+ index: name.span.end.byte as usize,
+ },
+ });
+ };
+
+ let nested = parse_nested::<_, VERSION>(whitespace.span.end, tokens)?;
+
+ let Some(closing_bracket) = tokens.next_if_closing_bracket() else {
+ return Err(Error {
+ _inner: unused(opening_bracket.error("unclosed bracket")),
+ public: crate::error::InvalidFormatDescription::UnclosedOpeningBracket {
+ index: opening_bracket.byte as usize,
+ },
+ });
+ };
+
+ return Ok(Item::Optional {
+ opening_bracket,
+ _leading_whitespace: unused(leading_whitespace),
+ _optional_kw: unused(name),
+ _whitespace: unused(whitespace),
+ nested_format_description: nested,
+ closing_bracket,
+ });
+ }
+
+ if *name == b"first" {
+ let Some(whitespace) = tokens.next_if_whitespace() else {
+ return Err(Error {
+ _inner: unused(name.span.error("expected whitespace after `first`")),
+ public: crate::error::InvalidFormatDescription::Expected {
+ what: "whitespace after `first`",
+ index: name.span.end.byte as usize,
+ },
+ });
+ };
+
+ let mut nested_format_descriptions = Vec::new();
+ while let Ok(description) = parse_nested::<_, VERSION>(whitespace.span.end, tokens) {
+ nested_format_descriptions.push(description);
+ }
+
+ let Some(closing_bracket) = tokens.next_if_closing_bracket() else {
+ return Err(Error {
+ _inner: unused(opening_bracket.error("unclosed bracket")),
+ public: crate::error::InvalidFormatDescription::UnclosedOpeningBracket {
+ index: opening_bracket.byte as usize,
+ },
+ });
+ };
+
+ return Ok(Item::First {
+ opening_bracket,
+ _leading_whitespace: unused(leading_whitespace),
+ _first_kw: unused(name),
+ _whitespace: unused(whitespace),
+ nested_format_descriptions: nested_format_descriptions.into_boxed_slice(),
+ closing_bracket,
+ });
+ }
+
+ let mut modifiers = Vec::new();
+ let trailing_whitespace = loop {
+ let Some(whitespace) = tokens.next_if_whitespace() else {
+ break None;
+ };
+
+ // This is not necessary for proper parsing, but provides a much better error when a nested
+ // description is used where it's not allowed.
+ if let Some(location) = tokens.next_if_opening_bracket() {
+ return Err(Error {
+ _inner: unused(
+ location
+ .to_self()
+ .error("modifier must be of the form `key:value`"),
+ ),
+ public: crate::error::InvalidFormatDescription::InvalidModifier {
+ value: String::from("["),
+ index: location.byte as usize,
+ },
+ });
+ }
+
+ let Some(Spanned { value, span }) = tokens.next_if_not_whitespace() else {
+ break Some(whitespace);
+ };
+
+ let Some(colon_index) = value.iter().position(|&b| b == b':') else {
+ return Err(Error {
+ _inner: unused(span.error("modifier must be of the form `key:value`")),
+ public: crate::error::InvalidFormatDescription::InvalidModifier {
+ value: String::from_utf8_lossy(value).into_owned(),
+ index: span.start.byte as usize,
+ },
+ });
+ };
+ let key = &value[..colon_index];
+ let value = &value[colon_index + 1..];
+
+ if key.is_empty() {
+ return Err(Error {
+ _inner: unused(span.shrink_to_start().error("expected modifier key")),
+ public: crate::error::InvalidFormatDescription::InvalidModifier {
+ value: String::new(),
+ index: span.start.byte as usize,
+ },
+ });
+ }
+ if value.is_empty() {
+ return Err(Error {
+ _inner: unused(span.shrink_to_end().error("expected modifier value")),
+ public: crate::error::InvalidFormatDescription::InvalidModifier {
+ value: String::new(),
+ index: span.shrink_to_end().start.byte as usize,
+ },
+ });
+ }
+
+ modifiers.push(Modifier {
+ _leading_whitespace: unused(whitespace),
+ key: key.spanned(span.shrink_to_before(colon_index as u32)),
+ _colon: unused(span.start.offset(colon_index as u32)),
+ value: value.spanned(span.shrink_to_after(colon_index as u32)),
+ });
+ };
+
+ let Some(closing_bracket) = tokens.next_if_closing_bracket() else {
+ return Err(Error {
+ _inner: unused(opening_bracket.error("unclosed bracket")),
+ public: crate::error::InvalidFormatDescription::UnclosedOpeningBracket {
+ index: opening_bracket.byte as usize,
+ },
+ });
+ };
+
+ Ok(Item::Component {
+ _opening_bracket: unused(opening_bracket),
+ _leading_whitespace: unused(leading_whitespace),
+ name,
+ modifiers: modifiers.into_boxed_slice(),
+ _trailing_whitespace: unused(trailing_whitespace),
+ _closing_bracket: unused(closing_bracket),
+ })
+}
+
+/// Parse a nested format description. The location provided is the the most recent one consumed.
+fn parse_nested<'a, I: Iterator<Item = Result<lexer::Token<'a>, Error>>, const VERSION: usize>(
+ last_location: Location,
+ tokens: &mut lexer::Lexed<I>,
+) -> Result<NestedFormatDescription<'a>, Error> {
+ validate_version!(VERSION);
+ let Some(opening_bracket) = tokens.next_if_opening_bracket() else {
+ return Err(Error {
+ _inner: unused(last_location.error("expected opening bracket")),
+ public: crate::error::InvalidFormatDescription::Expected {
+ what: "opening bracket",
+ index: last_location.byte as usize,
+ },
+ });
+ };
+ let items = parse_inner::<_, true, VERSION>(tokens).collect::<Result<_, _>>()?;
+ let Some(closing_bracket) = tokens.next_if_closing_bracket() else {
+ return Err(Error {
+ _inner: unused(opening_bracket.error("unclosed bracket")),
+ public: crate::error::InvalidFormatDescription::UnclosedOpeningBracket {
+ index: opening_bracket.byte as usize,
+ },
+ });
+ };
+ let trailing_whitespace = tokens.next_if_whitespace();
+
+ Ok(NestedFormatDescription {
+ _opening_bracket: unused(opening_bracket),
+ items,
+ _closing_bracket: unused(closing_bracket),
+ _trailing_whitespace: unused(trailing_whitespace),
+ })
+}
diff --git a/vendor/time/src/format_description/parse/format_item.rs b/vendor/time/src/format_description/parse/format_item.rs
new file mode 100644
index 00000000..d401daa5
--- /dev/null
+++ b/vendor/time/src/format_description/parse/format_item.rs
@@ -0,0 +1,549 @@
+//! Typed, validated representation of a parsed format description.
+
+use alloc::boxed::Box;
+use alloc::string::String;
+use core::num::NonZeroU16;
+use core::str::{self, FromStr};
+
+use super::{ast, unused, Error, Span, Spanned};
+use crate::internal_macros::bug;
+
+/// Parse an AST iterator into a sequence of format items.
+pub(super) fn parse<'a>(
+ ast_items: impl Iterator<Item = Result<ast::Item<'a>, Error>>,
+) -> impl Iterator<Item = Result<Item<'a>, Error>> {
+ ast_items.map(|ast_item| ast_item.and_then(Item::from_ast))
+}
+
+/// A description of how to format and parse one part of a type.
+pub(super) enum Item<'a> {
+ /// A literal string.
+ Literal(&'a [u8]),
+ /// Part of a type, along with its modifiers.
+ Component(Component),
+ /// A sequence of optional items.
+ Optional {
+ /// The items themselves.
+ value: Box<[Self]>,
+ /// The span of the full sequence.
+ span: Span,
+ },
+ /// The first matching parse of a sequence of format descriptions.
+ First {
+ /// The sequence of format descriptions.
+ value: Box<[Box<[Self]>]>,
+ /// The span of the full sequence.
+ span: Span,
+ },
+}
+
+impl Item<'_> {
+ /// Parse an AST item into a format item.
+ pub(super) fn from_ast(ast_item: ast::Item<'_>) -> Result<Item<'_>, Error> {
+ Ok(match ast_item {
+ ast::Item::Component {
+ _opening_bracket: _,
+ _leading_whitespace: _,
+ name,
+ modifiers,
+ _trailing_whitespace: _,
+ _closing_bracket: _,
+ } => Item::Component(component_from_ast(&name, &modifiers)?),
+ ast::Item::Literal(Spanned { value, span: _ }) => Item::Literal(value),
+ ast::Item::EscapedBracket {
+ _first: _,
+ _second: _,
+ } => Item::Literal(b"["),
+ ast::Item::Optional {
+ opening_bracket,
+ _leading_whitespace: _,
+ _optional_kw: _,
+ _whitespace: _,
+ nested_format_description,
+ closing_bracket,
+ } => {
+ let items = nested_format_description
+ .items
+ .into_vec()
+ .into_iter()
+ .map(Item::from_ast)
+ .collect::<Result<_, _>>()?;
+ Item::Optional {
+ value: items,
+ span: opening_bracket.to(closing_bracket),
+ }
+ }
+ ast::Item::First {
+ opening_bracket,
+ _leading_whitespace: _,
+ _first_kw: _,
+ _whitespace: _,
+ nested_format_descriptions,
+ closing_bracket,
+ } => {
+ let items = nested_format_descriptions
+ .into_vec()
+ .into_iter()
+ .map(|nested_format_description| {
+ nested_format_description
+ .items
+ .into_vec()
+ .into_iter()
+ .map(Item::from_ast)
+ .collect()
+ })
+ .collect::<Result<_, _>>()?;
+ Item::First {
+ value: items,
+ span: opening_bracket.to(closing_bracket),
+ }
+ }
+ })
+ }
+}
+
+impl<'a> TryFrom<Item<'a>> for crate::format_description::BorrowedFormatItem<'a> {
+ type Error = Error;
+
+ fn try_from(item: Item<'a>) -> Result<Self, Self::Error> {
+ match item {
+ Item::Literal(literal) => Ok(Self::Literal(literal)),
+ Item::Component(component) => Ok(Self::Component(component.into())),
+ Item::Optional { value: _, span } => Err(Error {
+ _inner: unused(span.error(
+ "optional items are not supported in runtime-parsed format descriptions",
+ )),
+ public: crate::error::InvalidFormatDescription::NotSupported {
+ what: "optional item",
+ context: "runtime-parsed format descriptions",
+ index: span.start.byte as usize,
+ },
+ }),
+ Item::First { value: _, span } => Err(Error {
+ _inner: unused(span.error(
+ "'first' items are not supported in runtime-parsed format descriptions",
+ )),
+ public: crate::error::InvalidFormatDescription::NotSupported {
+ what: "'first' item",
+ context: "runtime-parsed format descriptions",
+ index: span.start.byte as usize,
+ },
+ }),
+ }
+ }
+}
+
+impl From<Item<'_>> for crate::format_description::OwnedFormatItem {
+ fn from(item: Item<'_>) -> Self {
+ match item {
+ Item::Literal(literal) => Self::Literal(literal.to_vec().into_boxed_slice()),
+ Item::Component(component) => Self::Component(component.into()),
+ Item::Optional { value, span: _ } => Self::Optional(Box::new(value.into())),
+ Item::First { value, span: _ } => {
+ Self::First(value.into_vec().into_iter().map(Into::into).collect())
+ }
+ }
+ }
+}
+
+impl<'a> From<Box<[Item<'a>]>> for crate::format_description::OwnedFormatItem {
+ fn from(items: Box<[Item<'a>]>) -> Self {
+ let items = items.into_vec();
+ match <[_; 1]>::try_from(items) {
+ Ok([item]) => item.into(),
+ Err(vec) => Self::Compound(vec.into_iter().map(Into::into).collect()),
+ }
+ }
+}
+
+/// Declare the `Component` struct.
+macro_rules! component_definition {
+ (@if_required required then { $($then:tt)* } $(else { $($else:tt)* })?) => { $($then)* };
+ (@if_required then { $($then:tt)* } $(else { $($else:tt)* })?) => { $($($else)*)? };
+ (@if_from_str from_str then { $($then:tt)* } $(else { $($else:tt)* })?) => { $($then)* };
+ (@if_from_str then { $($then:tt)* } $(else { $($else:tt)* })?) => { $($($else)*)? };
+
+ ($vis:vis enum $name:ident {
+ $($variant:ident = $parse_variant:literal {$(
+ $(#[$required:tt])?
+ $field:ident = $parse_field:literal:
+ Option<$(#[$from_str:tt])? $field_type:ty>
+ => $target_field:ident
+ ),* $(,)?}),* $(,)?
+ }) => {
+ $vis enum $name {
+ $($variant($variant),)*
+ }
+
+ $($vis struct $variant {
+ $($field: Option<$field_type>),*
+ })*
+
+ $(impl $variant {
+ /// Parse the component from the AST, given its modifiers.
+ fn with_modifiers(
+ modifiers: &[ast::Modifier<'_>],
+ _component_span: Span,
+ ) -> Result<Self, Error>
+ {
+ // rustc will complain if the modifier is empty.
+ #[allow(unused_mut)]
+ let mut this = Self {
+ $($field: None),*
+ };
+
+ for modifier in modifiers {
+ $(#[allow(clippy::string_lit_as_bytes)]
+ if modifier.key.eq_ignore_ascii_case($parse_field.as_bytes()) {
+ this.$field = component_definition!(@if_from_str $($from_str)?
+ then {
+ parse_from_modifier_value::<$field_type>(&modifier.value)?
+ } else {
+ <$field_type>::from_modifier_value(&modifier.value)?
+ });
+ continue;
+ })*
+ return Err(Error {
+ _inner: unused(modifier.key.span.error("invalid modifier key")),
+ public: crate::error::InvalidFormatDescription::InvalidModifier {
+ value: String::from_utf8_lossy(*modifier.key).into_owned(),
+ index: modifier.key.span.start.byte as usize,
+ }
+ });
+ }
+
+ $(component_definition! { @if_required $($required)? then {
+ if this.$field.is_none() {
+ return Err(Error {
+ _inner: unused(_component_span.error("missing required modifier")),
+ public:
+ crate::error::InvalidFormatDescription::MissingRequiredModifier {
+ name: $parse_field,
+ index: _component_span.start.byte as usize,
+ }
+ });
+ }
+ }})*
+
+ Ok(this)
+ }
+ })*
+
+ impl From<$name> for crate::format_description::Component {
+ fn from(component: $name) -> Self {
+ match component {$(
+ $name::$variant($variant { $($field),* }) => {
+ $crate::format_description::component::Component::$variant(
+ $crate::format_description::modifier::$variant {$(
+ $target_field: component_definition! { @if_required $($required)?
+ then {
+ match $field {
+ Some(value) => value.into(),
+ None => bug!("required modifier was not set"),
+ }
+ } else {
+ $field.unwrap_or_default().into()
+ }
+ }
+ ),*}
+ )
+ }
+ )*}
+ }
+ }
+
+ /// Parse a component from the AST, given its name and modifiers.
+ fn component_from_ast(
+ name: &Spanned<&[u8]>,
+ modifiers: &[ast::Modifier<'_>],
+ ) -> Result<Component, Error> {
+ $(#[allow(clippy::string_lit_as_bytes)]
+ if name.eq_ignore_ascii_case($parse_variant.as_bytes()) {
+ return Ok(Component::$variant($variant::with_modifiers(&modifiers, name.span)?));
+ })*
+ Err(Error {
+ _inner: unused(name.span.error("invalid component")),
+ public: crate::error::InvalidFormatDescription::InvalidComponentName {
+ name: String::from_utf8_lossy(name).into_owned(),
+ index: name.span.start.byte as usize,
+ },
+ })
+ }
+ }
+}
+
+// Keep in alphabetical order.
+component_definition! {
+ pub(super) enum Component {
+ Day = "day" {
+ padding = "padding": Option<Padding> => padding,
+ },
+ End = "end" {},
+ Hour = "hour" {
+ padding = "padding": Option<Padding> => padding,
+ base = "repr": Option<HourBase> => is_12_hour_clock,
+ },
+ Ignore = "ignore" {
+ #[required]
+ count = "count": Option<#[from_str] NonZeroU16> => count,
+ },
+ Minute = "minute" {
+ padding = "padding": Option<Padding> => padding,
+ },
+ Month = "month" {
+ padding = "padding": Option<Padding> => padding,
+ repr = "repr": Option<MonthRepr> => repr,
+ case_sensitive = "case_sensitive": Option<MonthCaseSensitive> => case_sensitive,
+ },
+ OffsetHour = "offset_hour" {
+ sign_behavior = "sign": Option<SignBehavior> => sign_is_mandatory,
+ padding = "padding": Option<Padding> => padding,
+ },
+ OffsetMinute = "offset_minute" {
+ padding = "padding": Option<Padding> => padding,
+ },
+ OffsetSecond = "offset_second" {
+ padding = "padding": Option<Padding> => padding,
+ },
+ Ordinal = "ordinal" {
+ padding = "padding": Option<Padding> => padding,
+ },
+ Period = "period" {
+ case = "case": Option<PeriodCase> => is_uppercase,
+ case_sensitive = "case_sensitive": Option<PeriodCaseSensitive> => case_sensitive,
+ },
+ Second = "second" {
+ padding = "padding": Option<Padding> => padding,
+ },
+ Subsecond = "subsecond" {
+ digits = "digits": Option<SubsecondDigits> => digits,
+ },
+ UnixTimestamp = "unix_timestamp" {
+ precision = "precision": Option<UnixTimestampPrecision> => precision,
+ sign_behavior = "sign": Option<SignBehavior> => sign_is_mandatory,
+ },
+ Weekday = "weekday" {
+ repr = "repr": Option<WeekdayRepr> => repr,
+ one_indexed = "one_indexed": Option<WeekdayOneIndexed> => one_indexed,
+ case_sensitive = "case_sensitive": Option<WeekdayCaseSensitive> => case_sensitive,
+ },
+ WeekNumber = "week_number" {
+ padding = "padding": Option<Padding> => padding,
+ repr = "repr": Option<WeekNumberRepr> => repr,
+ },
+ Year = "year" {
+ padding = "padding": Option<Padding> => padding,
+ repr = "repr": Option<YearRepr> => repr,
+ range = "range": Option<YearRange> => range,
+ base = "base": Option<YearBase> => iso_week_based,
+ sign_behavior = "sign": Option<SignBehavior> => sign_is_mandatory,
+ },
+ }
+}
+
+/// Get the target type for a given enum.
+macro_rules! target_ty {
+ ($name:ident $type:ty) => {
+ $type
+ };
+ ($name:ident) => {
+ $crate::format_description::modifier::$name
+ };
+}
+
+/// Get the target value for a given enum.
+macro_rules! target_value {
+ ($name:ident $variant:ident $value:expr) => {
+ $value
+ };
+ ($name:ident $variant:ident) => {
+ $crate::format_description::modifier::$name::$variant
+ };
+}
+
+/// Declare the various modifiers.
+///
+/// For the general case, ordinary syntax can be used. Note that you _must_ declare a default
+/// variant. The only significant change is that the string representation of the variant must be
+/// provided after the variant name. For example, `Numerical = b"numerical"` declares a variant
+/// named `Numerical` with the string representation `b"numerical"`. This is the value that will be
+/// used when parsing the modifier. The value is not case sensitive.
+///
+/// If the type in the public API does not have the same name as the type in the internal
+/// representation, then the former must be specified in parenthesis after the internal name. For
+/// example, `HourBase(bool)` has an internal name "HourBase", but is represented as a boolean in
+/// the public API.
+///
+/// By default, the internal variant name is assumed to be the same as the public variant name. If
+/// this is not the case, the qualified path to the variant must be specified in parenthesis after
+/// the internal variant name. For example, `Twelve(true)` has an internal variant name "Twelve",
+/// but is represented as `true` in the public API.
+macro_rules! modifier {
+ ($(
+ enum $name:ident $(($target_ty:ty))? {
+ $(
+ $(#[$attr:meta])?
+ $variant:ident $(($target_value:expr))? = $parse_variant:literal
+ ),* $(,)?
+ }
+ )+) => {$(
+ #[derive(Default)]
+ enum $name {
+ $($(#[$attr])? $variant),*
+ }
+
+ impl $name {
+ /// Parse the modifier from its string representation.
+ fn from_modifier_value(value: &Spanned<&[u8]>) -> Result<Option<Self>, Error> {
+ $(if value.eq_ignore_ascii_case($parse_variant) {
+ return Ok(Some(Self::$variant));
+ })*
+ Err(Error {
+ _inner: unused(value.span.error("invalid modifier value")),
+ public: crate::error::InvalidFormatDescription::InvalidModifier {
+ value: String::from_utf8_lossy(value).into_owned(),
+ index: value.span.start.byte as usize,
+ },
+ })
+ }
+ }
+
+ impl From<$name> for target_ty!($name $($target_ty)?) {
+ fn from(modifier: $name) -> Self {
+ match modifier {
+ $($name::$variant => target_value!($name $variant $($target_value)?)),*
+ }
+ }
+ }
+ )+};
+}
+
+// Keep in alphabetical order.
+modifier! {
+ enum HourBase(bool) {
+ Twelve(true) = b"12",
+ #[default]
+ TwentyFour(false) = b"24",
+ }
+
+ enum MonthCaseSensitive(bool) {
+ False(false) = b"false",
+ #[default]
+ True(true) = b"true",
+ }
+
+ enum MonthRepr {
+ #[default]
+ Numerical = b"numerical",
+ Long = b"long",
+ Short = b"short",
+ }
+
+ enum Padding {
+ Space = b"space",
+ #[default]
+ Zero = b"zero",
+ None = b"none",
+ }
+
+ enum PeriodCase(bool) {
+ Lower(false) = b"lower",
+ #[default]
+ Upper(true) = b"upper",
+ }
+
+ enum PeriodCaseSensitive(bool) {
+ False(false) = b"false",
+ #[default]
+ True(true) = b"true",
+ }
+
+ enum SignBehavior(bool) {
+ #[default]
+ Automatic(false) = b"automatic",
+ Mandatory(true) = b"mandatory",
+ }
+
+ enum SubsecondDigits {
+ One = b"1",
+ Two = b"2",
+ Three = b"3",
+ Four = b"4",
+ Five = b"5",
+ Six = b"6",
+ Seven = b"7",
+ Eight = b"8",
+ Nine = b"9",
+ #[default]
+ OneOrMore = b"1+",
+ }
+
+ enum UnixTimestampPrecision {
+ #[default]
+ Second = b"second",
+ Millisecond = b"millisecond",
+ Microsecond = b"microsecond",
+ Nanosecond = b"nanosecond",
+ }
+
+ enum WeekNumberRepr {
+ #[default]
+ Iso = b"iso",
+ Sunday = b"sunday",
+ Monday = b"monday",
+ }
+
+ enum WeekdayCaseSensitive(bool) {
+ False(false) = b"false",
+ #[default]
+ True(true) = b"true",
+ }
+
+ enum WeekdayOneIndexed(bool) {
+ False(false) = b"false",
+ #[default]
+ True(true) = b"true",
+ }
+
+ enum WeekdayRepr {
+ Short = b"short",
+ #[default]
+ Long = b"long",
+ Sunday = b"sunday",
+ Monday = b"monday",
+ }
+
+ enum YearBase(bool) {
+ #[default]
+ Calendar(false) = b"calendar",
+ IsoWeek(true) = b"iso_week",
+ }
+
+ enum YearRepr {
+ #[default]
+ Full = b"full",
+ Century = b"century",
+ LastTwo = b"last_two",
+ }
+
+ enum YearRange {
+ Standard = b"standard",
+ #[default]
+ Extended = b"extended",
+ }
+}
+
+/// Parse a modifier value using `FromStr`. Requires the modifier value to be valid UTF-8.
+fn parse_from_modifier_value<T: FromStr>(value: &Spanned<&[u8]>) -> Result<Option<T>, Error> {
+ str::from_utf8(value)
+ .ok()
+ .and_then(|val| val.parse::<T>().ok())
+ .map(|val| Some(val))
+ .ok_or_else(|| Error {
+ _inner: unused(value.span.error("invalid modifier value")),
+ public: crate::error::InvalidFormatDescription::InvalidModifier {
+ value: String::from_utf8_lossy(value).into_owned(),
+ index: value.span.start.byte as usize,
+ },
+ })
+}
diff --git a/vendor/time/src/format_description/parse/lexer.rs b/vendor/time/src/format_description/parse/lexer.rs
new file mode 100644
index 00000000..a63722e1
--- /dev/null
+++ b/vendor/time/src/format_description/parse/lexer.rs
@@ -0,0 +1,284 @@
+//! Lexer for parsing format descriptions.
+
+use core::iter;
+
+use super::{attach_location, unused, Error, Location, Spanned, SpannedValue};
+
+/// An iterator over the lexed tokens.
+pub(super) struct Lexed<I: Iterator> {
+ /// The internal iterator.
+ iter: iter::Peekable<I>,
+}
+
+impl<I: Iterator> Iterator for Lexed<I> {
+ type Item = I::Item;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ self.iter.next()
+ }
+}
+
+impl<'iter, 'token: 'iter, I: Iterator<Item = Result<Token<'token>, Error>> + 'iter> Lexed<I> {
+ /// Peek at the next item in the iterator.
+ pub(super) fn peek(&mut self) -> Option<&I::Item> {
+ self.iter.peek()
+ }
+
+ /// Consume the next token if it is whitespace.
+ pub(super) fn next_if_whitespace(&mut self) -> Option<Spanned<&'token [u8]>> {
+ if let Some(&Ok(Token::ComponentPart {
+ kind: ComponentKind::Whitespace,
+ value,
+ })) = self.peek()
+ {
+ self.next(); // consume
+ Some(value)
+ } else {
+ None
+ }
+ }
+
+ /// Consume the next token if it is a component item that is not whitespace.
+ pub(super) fn next_if_not_whitespace(&mut self) -> Option<Spanned<&'token [u8]>> {
+ if let Some(&Ok(Token::ComponentPart {
+ kind: ComponentKind::NotWhitespace,
+ value,
+ })) = self.peek()
+ {
+ self.next(); // consume
+ Some(value)
+ } else {
+ None
+ }
+ }
+
+ /// Consume the next token if it is an opening bracket.
+ pub(super) fn next_if_opening_bracket(&mut self) -> Option<Location> {
+ if let Some(&Ok(Token::Bracket {
+ kind: BracketKind::Opening,
+ location,
+ })) = self.peek()
+ {
+ self.next(); // consume
+ Some(location)
+ } else {
+ None
+ }
+ }
+
+ /// Peek at the next token if it is a closing bracket.
+ pub(super) fn peek_closing_bracket(&'iter mut self) -> Option<&'iter Location> {
+ if let Some(Ok(Token::Bracket {
+ kind: BracketKind::Closing,
+ location,
+ })) = self.peek()
+ {
+ Some(location)
+ } else {
+ None
+ }
+ }
+
+ /// Consume the next token if it is a closing bracket.
+ pub(super) fn next_if_closing_bracket(&mut self) -> Option<Location> {
+ if let Some(&Ok(Token::Bracket {
+ kind: BracketKind::Closing,
+ location,
+ })) = self.peek()
+ {
+ self.next(); // consume
+ Some(location)
+ } else {
+ None
+ }
+ }
+}
+
+/// A token emitted by the lexer. There is no semantic meaning at this stage.
+pub(super) enum Token<'a> {
+ /// A literal string, formatted and parsed as-is.
+ Literal(Spanned<&'a [u8]>),
+ /// An opening or closing bracket. May or may not be the start or end of a component.
+ Bracket {
+ /// Whether the bracket is opening or closing.
+ kind: BracketKind,
+ /// Where the bracket was in the format string.
+ location: Location,
+ },
+ /// One part of a component. This could be its name, a modifier, or whitespace.
+ ComponentPart {
+ /// Whether the part is whitespace or not.
+ kind: ComponentKind,
+ /// The part itself.
+ value: Spanned<&'a [u8]>,
+ },
+}
+
+/// What type of bracket is present.
+pub(super) enum BracketKind {
+ /// An opening bracket: `[`
+ Opening,
+ /// A closing bracket: `]`
+ Closing,
+}
+
+/// Indicates whether the component is whitespace or not.
+pub(super) enum ComponentKind {
+ Whitespace,
+ NotWhitespace,
+}
+
+/// Parse the string into a series of [`Token`]s.
+///
+/// `VERSION` controls the version of the format description that is being parsed. Currently, this
+/// must be 1 or 2.
+///
+/// - When `VERSION` is 1, `[[` is the only escape sequence, resulting in a literal `[`.
+/// - When `VERSION` is 2, all escape sequences begin with `\`. The only characters that may
+/// currently follow are `\`, `[`, and `]`, all of which result in the literal character. All
+/// other characters result in a lex error.
+pub(super) fn lex<const VERSION: usize>(
+ mut input: &[u8],
+) -> Lexed<impl Iterator<Item = Result<Token<'_>, Error>>> {
+ validate_version!(VERSION);
+
+ let mut depth: u8 = 0;
+ let mut iter = attach_location(input.iter()).peekable();
+ let mut second_bracket_location = None;
+
+ let iter = iter::from_fn(move || {
+ // The flag is only set when version is zero.
+ if version!(..=1) {
+ // There is a flag set to emit the second half of an escaped bracket pair.
+ if let Some(location) = second_bracket_location.take() {
+ return Some(Ok(Token::Bracket {
+ kind: BracketKind::Opening,
+ location,
+ }));
+ }
+ }
+
+ Some(Ok(match iter.next()? {
+ // possible escape sequence
+ (b'\\', backslash_loc) if version!(2..) => {
+ match iter.next() {
+ Some((b'\\' | b'[' | b']', char_loc)) => {
+ // The escaped character is emitted as-is.
+ let char = &input[1..2];
+ input = &input[2..];
+ if depth == 0 {
+ Token::Literal(char.spanned(backslash_loc.to(char_loc)))
+ } else {
+ Token::ComponentPart {
+ kind: ComponentKind::NotWhitespace,
+ value: char.spanned(backslash_loc.to(char_loc)),
+ }
+ }
+ }
+ Some((_, loc)) => {
+ return Some(Err(Error {
+ _inner: unused(loc.error("invalid escape sequence")),
+ public: crate::error::InvalidFormatDescription::Expected {
+ what: "valid escape sequence",
+ index: loc.byte as usize,
+ },
+ }));
+ }
+ None => {
+ return Some(Err(Error {
+ _inner: unused(backslash_loc.error("unexpected end of input")),
+ public: crate::error::InvalidFormatDescription::Expected {
+ what: "valid escape sequence",
+ index: backslash_loc.byte as usize,
+ },
+ }));
+ }
+ }
+ }
+ // potentially escaped opening bracket
+ (b'[', location) if version!(..=1) => {
+ if let Some((_, second_location)) = iter.next_if(|&(&byte, _)| byte == b'[') {
+ // Escaped bracket. Store the location of the second so we can emit it later.
+ second_bracket_location = Some(second_location);
+ input = &input[2..];
+ } else {
+ // opening bracket
+ depth += 1;
+ input = &input[1..];
+ }
+
+ Token::Bracket {
+ kind: BracketKind::Opening,
+ location,
+ }
+ }
+ // opening bracket
+ (b'[', location) => {
+ depth += 1;
+ input = &input[1..];
+
+ Token::Bracket {
+ kind: BracketKind::Opening,
+ location,
+ }
+ }
+ // closing bracket
+ (b']', location) if depth > 0 => {
+ depth -= 1;
+ input = &input[1..];
+
+ Token::Bracket {
+ kind: BracketKind::Closing,
+ location,
+ }
+ }
+ // literal
+ (_, start_location) if depth == 0 => {
+ let mut bytes = 1;
+ let mut end_location = start_location;
+
+ while let Some((_, location)) =
+ iter.next_if(|&(&byte, _)| !((version!(2..) && byte == b'\\') || byte == b'['))
+ {
+ end_location = location;
+ bytes += 1;
+ }
+
+ let value = &input[..bytes];
+ input = &input[bytes..];
+
+ Token::Literal(value.spanned(start_location.to(end_location)))
+ }
+ // component part
+ (byte, start_location) => {
+ let mut bytes = 1;
+ let mut end_location = start_location;
+ let is_whitespace = byte.is_ascii_whitespace();
+
+ while let Some((_, location)) = iter.next_if(|&(byte, _)| {
+ !matches!(byte, b'\\' | b'[' | b']')
+ && is_whitespace == byte.is_ascii_whitespace()
+ }) {
+ end_location = location;
+ bytes += 1;
+ }
+
+ let value = &input[..bytes];
+ input = &input[bytes..];
+
+ Token::ComponentPart {
+ kind: if is_whitespace {
+ ComponentKind::Whitespace
+ } else {
+ ComponentKind::NotWhitespace
+ },
+ value: value.spanned(start_location.to(end_location)),
+ }
+ }
+ }))
+ });
+
+ Lexed {
+ iter: iter.peekable(),
+ }
+}
diff --git a/vendor/time/src/format_description/parse/mod.rs b/vendor/time/src/format_description/parse/mod.rs
new file mode 100644
index 00000000..d058594d
--- /dev/null
+++ b/vendor/time/src/format_description/parse/mod.rs
@@ -0,0 +1,262 @@
+//! Parser for format descriptions.
+
+use alloc::boxed::Box;
+use alloc::vec::Vec;
+
+pub use self::strftime::{parse_strftime_borrowed, parse_strftime_owned};
+use crate::{error, format_description};
+
+/// A helper macro to make version restrictions simpler to read and write.
+macro_rules! version {
+ ($range:expr) => {
+ $range.contains(&VERSION)
+ };
+}
+
+/// A helper macro to statically validate the version (when used as a const parameter).
+macro_rules! validate_version {
+ ($version:ident) => {
+ let _ = $crate::format_description::parse::Version::<$version>::IS_VALID;
+ };
+}
+
+mod ast;
+mod format_item;
+mod lexer;
+mod strftime;
+
+/// A struct that is used to ensure that the version is valid.
+struct Version<const N: usize>;
+impl<const N: usize> Version<N> {
+ /// A constant that panics if the version is not valid. This results in a post-monomorphization
+ /// error.
+ const IS_VALID: () = assert!(N >= 1 && N <= 2);
+}
+
+/// Parse a sequence of items from the format description.
+///
+/// The syntax for the format description can be found in [the
+/// book](https://time-rs.github.io/book/api/format-description.html).
+///
+/// This function exists for backward compatibility reasons. It is equivalent to calling
+/// `parse_borrowed::<1>(s)`. In the future, this function will be deprecated in favor of
+/// `parse_borrowed`.
+pub fn parse(
+ s: &str,
+) -> Result<Vec<format_description::BorrowedFormatItem<'_>>, error::InvalidFormatDescription> {
+ parse_borrowed::<1>(s)
+}
+
+/// Parse a sequence of items from the format description.
+///
+/// The syntax for the format description can be found in [the
+/// book](https://time-rs.github.io/book/api/format-description.html). The version of the format
+/// description is provided as the const parameter. **It is recommended to use version 2.**
+pub fn parse_borrowed<const VERSION: usize>(
+ s: &str,
+) -> Result<Vec<format_description::BorrowedFormatItem<'_>>, error::InvalidFormatDescription> {
+ validate_version!(VERSION);
+ let mut lexed = lexer::lex::<VERSION>(s.as_bytes());
+ let ast = ast::parse::<_, VERSION>(&mut lexed);
+ let format_items = format_item::parse(ast);
+ Ok(format_items
+ .map(|res| res.and_then(TryInto::try_into))
+ .collect::<Result<_, _>>()?)
+}
+
+/// Parse a sequence of items from the format description.
+///
+/// The syntax for the format description can be found in [the
+/// book](https://time-rs.github.io/book/api/format-description.html). The version of the format
+/// description is provided as the const parameter.
+///
+/// Unlike [`parse`], this function returns [`OwnedFormatItem`], which owns its contents. This means
+/// that there is no lifetime that needs to be handled. **It is recommended to use version 2.**
+///
+/// [`OwnedFormatItem`]: crate::format_description::OwnedFormatItem
+pub fn parse_owned<const VERSION: usize>(
+ s: &str,
+) -> Result<format_description::OwnedFormatItem, error::InvalidFormatDescription> {
+ validate_version!(VERSION);
+ let mut lexed = lexer::lex::<VERSION>(s.as_bytes());
+ let ast = ast::parse::<_, VERSION>(&mut lexed);
+ let format_items = format_item::parse(ast);
+ let items = format_items.collect::<Result<Box<_>, _>>()?;
+ Ok(items.into())
+}
+
+/// Attach [`Location`] information to each byte in the iterator.
+fn attach_location<'item>(
+ iter: impl Iterator<Item = &'item u8>,
+) -> impl Iterator<Item = (&'item u8, Location)> {
+ let mut byte_pos = 0;
+
+ iter.map(move |byte| {
+ let location = Location { byte: byte_pos };
+ byte_pos += 1;
+ (byte, location)
+ })
+}
+
+/// A location within a string.
+#[derive(Clone, Copy)]
+struct Location {
+ /// The zero-indexed byte of the string.
+ byte: u32,
+}
+
+impl Location {
+ /// Create a new [`Span`] from `self` to `other`.
+ const fn to(self, end: Self) -> Span {
+ Span { start: self, end }
+ }
+
+ /// Create a new [`Span`] consisting entirely of `self`.
+ const fn to_self(self) -> Span {
+ Span {
+ start: self,
+ end: self,
+ }
+ }
+
+ /// Offset the location by the provided amount.
+ ///
+ /// Note that this assumes the resulting location is on the same line as the original location.
+ #[must_use = "this does not modify the original value"]
+ const fn offset(&self, offset: u32) -> Self {
+ Self {
+ byte: self.byte + offset,
+ }
+ }
+
+ /// Create an error with the provided message at this location.
+ const fn error(self, message: &'static str) -> ErrorInner {
+ ErrorInner {
+ _message: message,
+ _span: Span {
+ start: self,
+ end: self,
+ },
+ }
+ }
+}
+
+/// A start and end point within a string.
+#[derive(Clone, Copy)]
+struct Span {
+ start: Location,
+ end: Location,
+}
+
+impl Span {
+ /// Obtain a `Span` pointing at the start of the pre-existing span.
+ #[must_use = "this does not modify the original value"]
+ const fn shrink_to_start(&self) -> Self {
+ Self {
+ start: self.start,
+ end: self.start,
+ }
+ }
+
+ /// Obtain a `Span` pointing at the end of the pre-existing span.
+ #[must_use = "this does not modify the original value"]
+ const fn shrink_to_end(&self) -> Self {
+ Self {
+ start: self.end,
+ end: self.end,
+ }
+ }
+
+ /// Obtain a `Span` that ends before the provided position of the pre-existing span.
+ #[must_use = "this does not modify the original value"]
+ const fn shrink_to_before(&self, pos: u32) -> Self {
+ Self {
+ start: self.start,
+ end: Location {
+ byte: self.start.byte + pos - 1,
+ },
+ }
+ }
+
+ /// Obtain a `Span` that starts after provided position to the end of the pre-existing span.
+ #[must_use = "this does not modify the original value"]
+ const fn shrink_to_after(&self, pos: u32) -> Self {
+ Self {
+ start: Location {
+ byte: self.start.byte + pos + 1,
+ },
+ end: self.end,
+ }
+ }
+
+ /// Create an error with the provided message at this span.
+ const fn error(self, message: &'static str) -> ErrorInner {
+ ErrorInner {
+ _message: message,
+ _span: self,
+ }
+ }
+}
+
+/// A value with an associated [`Span`].
+#[derive(Clone, Copy)]
+struct Spanned<T> {
+ /// The value.
+ value: T,
+ /// Where the value was in the format string.
+ span: Span,
+}
+
+impl<T> core::ops::Deref for Spanned<T> {
+ type Target = T;
+
+ fn deref(&self) -> &Self::Target {
+ &self.value
+ }
+}
+
+/// Helper trait to attach a [`Span`] to a value.
+trait SpannedValue: Sized {
+ /// Attach a [`Span`] to a value.
+ fn spanned(self, span: Span) -> Spanned<Self>;
+}
+
+impl<T> SpannedValue for T {
+ fn spanned(self, span: Span) -> Spanned<Self> {
+ Spanned { value: self, span }
+ }
+}
+
+/// The internal error type.
+struct ErrorInner {
+ /// The message displayed to the user.
+ _message: &'static str,
+ /// Where the error originated.
+ _span: Span,
+}
+
+/// A complete error description.
+struct Error {
+ /// The internal error.
+ _inner: Unused<ErrorInner>,
+ /// The error needed for interoperability with the rest of `time`.
+ public: error::InvalidFormatDescription,
+}
+
+impl From<Error> for error::InvalidFormatDescription {
+ fn from(error: Error) -> Self {
+ error.public
+ }
+}
+
+/// A value that may be used in the future, but currently is not.
+///
+/// This struct exists so that data can semantically be passed around without _actually_ passing it
+/// around. This way the data still exists if it is needed in the future.
+// `PhantomData` is not used directly because we don't want to introduce any trait implementations.
+struct Unused<T>(core::marker::PhantomData<T>);
+
+/// Indicate that a value is currently unused.
+fn unused<T>(_: T) -> Unused<T> {
+ Unused(core::marker::PhantomData)
+}
diff --git a/vendor/time/src/format_description/parse/strftime.rs b/vendor/time/src/format_description/parse/strftime.rs
new file mode 100644
index 00000000..5fcaf184
--- /dev/null
+++ b/vendor/time/src/format_description/parse/strftime.rs
@@ -0,0 +1,487 @@
+use alloc::string::String;
+use alloc::vec::Vec;
+use core::iter;
+
+use crate::error::InvalidFormatDescription;
+use crate::format_description::parse::{
+ attach_location, unused, Error, ErrorInner, Location, Spanned, SpannedValue, Unused,
+};
+use crate::format_description::{self, modifier, BorrowedFormatItem, Component};
+
+/// Parse a sequence of items from the [`strftime` format description][strftime docs].
+///
+/// The only heap allocation required is for the `Vec` itself. All components are bound to the
+/// lifetime of the input.
+///
+/// [strftime docs]: https://man7.org/linux/man-pages/man3/strftime.3.html
+#[doc(alias = "parse_strptime_borrowed")]
+pub fn parse_strftime_borrowed(
+ s: &str,
+) -> Result<Vec<BorrowedFormatItem<'_>>, InvalidFormatDescription> {
+ let tokens = lex(s.as_bytes());
+ let items = into_items(tokens).collect::<Result<_, _>>()?;
+ Ok(items)
+}
+
+/// Parse a sequence of items from the [`strftime` format description][strftime docs].
+///
+/// This requires heap allocation for some owned items.
+///
+/// [strftime docs]: https://man7.org/linux/man-pages/man3/strftime.3.html
+#[doc(alias = "parse_strptime_owned")]
+pub fn parse_strftime_owned(
+ s: &str,
+) -> Result<format_description::OwnedFormatItem, InvalidFormatDescription> {
+ parse_strftime_borrowed(s).map(Into::into)
+}
+
+#[derive(Debug, Clone, Copy, PartialEq)]
+enum Padding {
+ /// The default padding for a numeric component. Indicated by no character.
+ Default,
+ /// Pad a numeric component with spaces. Indicated by an underscore.
+ Spaces,
+ /// Do not pad a numeric component. Indicated by a hyphen.
+ None,
+ /// Pad a numeric component with zeroes. Indicated by a zero.
+ Zeroes,
+}
+
+enum Token<'a> {
+ Literal(Spanned<&'a [u8]>),
+ Component {
+ _percent: Unused<Location>,
+ padding: Spanned<Padding>,
+ component: Spanned<u8>,
+ },
+}
+
+fn lex(mut input: &[u8]) -> iter::Peekable<impl Iterator<Item = Result<Token<'_>, Error>>> {
+ let mut iter = attach_location(input.iter()).peekable();
+
+ iter::from_fn(move || {
+ Some(Ok(match iter.next()? {
+ (b'%', percent_loc) => match iter.next() {
+ Some((padding @ (b'_' | b'-' | b'0'), padding_loc)) => {
+ let padding = match padding {
+ b'_' => Padding::Spaces,
+ b'-' => Padding::None,
+ b'0' => Padding::Zeroes,
+ _ => unreachable!(),
+ };
+ let (&component, component_loc) = iter.next()?;
+ input = &input[3..];
+ Token::Component {
+ _percent: unused(percent_loc),
+ padding: padding.spanned(padding_loc.to_self()),
+ component: component.spanned(component_loc.to_self()),
+ }
+ }
+ Some((&component, component_loc)) => {
+ input = &input[2..];
+ let span = component_loc.to_self();
+ Token::Component {
+ _percent: unused(percent_loc),
+ padding: Padding::Default.spanned(span),
+ component: component.spanned(span),
+ }
+ }
+ None => {
+ return Some(Err(Error {
+ _inner: unused(percent_loc.error("unexpected end of input")),
+ public: InvalidFormatDescription::Expected {
+ what: "valid escape sequence",
+ index: percent_loc.byte as usize,
+ },
+ }));
+ }
+ },
+ (_, start_location) => {
+ let mut bytes = 1;
+ let mut end_location = start_location;
+
+ while let Some((_, location)) = iter.next_if(|&(&byte, _)| byte != b'%') {
+ end_location = location;
+ bytes += 1;
+ }
+
+ let value = &input[..bytes];
+ input = &input[bytes..];
+
+ Token::Literal(value.spanned(start_location.to(end_location)))
+ }
+ }))
+ })
+ .peekable()
+}
+
+fn into_items<'iter, 'token: 'iter>(
+ mut tokens: iter::Peekable<impl Iterator<Item = Result<Token<'token>, Error>> + 'iter>,
+) -> impl Iterator<Item = Result<BorrowedFormatItem<'token>, Error>> + 'iter {
+ iter::from_fn(move || {
+ let next = match tokens.next()? {
+ Ok(token) => token,
+ Err(err) => return Some(Err(err)),
+ };
+
+ Some(match next {
+ Token::Literal(spanned) => Ok(BorrowedFormatItem::Literal(*spanned)),
+ Token::Component {
+ _percent,
+ padding,
+ component,
+ } => parse_component(padding, component),
+ })
+ })
+}
+
+fn parse_component(
+ padding: Spanned<Padding>,
+ component: Spanned<u8>,
+) -> Result<BorrowedFormatItem<'static>, Error> {
+ let padding_or_default = |padding: Padding, default| match padding {
+ Padding::Default => default,
+ Padding::Spaces => modifier::Padding::Space,
+ Padding::None => modifier::Padding::None,
+ Padding::Zeroes => modifier::Padding::Zero,
+ };
+
+ /// Helper macro to create a component.
+ macro_rules! component {
+ ($name:ident { $($inner:tt)* }) => {
+ BorrowedFormatItem::Component(Component::$name(modifier::$name {
+ $($inner)*
+ }))
+ }
+ }
+
+ Ok(match *component {
+ b'%' => BorrowedFormatItem::Literal(b"%"),
+ b'a' => component!(Weekday {
+ repr: modifier::WeekdayRepr::Short,
+ one_indexed: true,
+ case_sensitive: true,
+ }),
+ b'A' => component!(Weekday {
+ repr: modifier::WeekdayRepr::Long,
+ one_indexed: true,
+ case_sensitive: true,
+ }),
+ b'b' | b'h' => component!(Month {
+ repr: modifier::MonthRepr::Short,
+ padding: modifier::Padding::Zero,
+ case_sensitive: true,
+ }),
+ b'B' => component!(Month {
+ repr: modifier::MonthRepr::Long,
+ padding: modifier::Padding::Zero,
+ case_sensitive: true,
+ }),
+ b'c' => BorrowedFormatItem::Compound(&[
+ component!(Weekday {
+ repr: modifier::WeekdayRepr::Short,
+ one_indexed: true,
+ case_sensitive: true,
+ }),
+ BorrowedFormatItem::Literal(b" "),
+ component!(Month {
+ repr: modifier::MonthRepr::Short,
+ padding: modifier::Padding::Zero,
+ case_sensitive: true,
+ }),
+ BorrowedFormatItem::Literal(b" "),
+ component!(Day {
+ padding: modifier::Padding::Space
+ }),
+ BorrowedFormatItem::Literal(b" "),
+ component!(Hour {
+ padding: modifier::Padding::Zero,
+ is_12_hour_clock: false,
+ }),
+ BorrowedFormatItem::Literal(b":"),
+ component!(Minute {
+ padding: modifier::Padding::Zero,
+ }),
+ BorrowedFormatItem::Literal(b":"),
+ component!(Second {
+ padding: modifier::Padding::Zero,
+ }),
+ BorrowedFormatItem::Literal(b" "),
+ component!(Year {
+ padding: modifier::Padding::Zero,
+ repr: modifier::YearRepr::Full,
+ range: modifier::YearRange::Extended,
+ iso_week_based: false,
+ sign_is_mandatory: false,
+ }),
+ ]),
+ b'C' => component!(Year {
+ padding: padding_or_default(*padding, modifier::Padding::Zero),
+ repr: modifier::YearRepr::Century,
+ range: modifier::YearRange::Extended,
+ iso_week_based: false,
+ sign_is_mandatory: false,
+ }),
+ b'd' => component!(Day {
+ padding: padding_or_default(*padding, modifier::Padding::Zero),
+ }),
+ b'D' => BorrowedFormatItem::Compound(&[
+ component!(Month {
+ repr: modifier::MonthRepr::Numerical,
+ padding: modifier::Padding::Zero,
+ case_sensitive: true,
+ }),
+ BorrowedFormatItem::Literal(b"/"),
+ component!(Day {
+ padding: modifier::Padding::Zero,
+ }),
+ BorrowedFormatItem::Literal(b"/"),
+ component!(Year {
+ padding: modifier::Padding::Zero,
+ repr: modifier::YearRepr::LastTwo,
+ range: modifier::YearRange::Extended,
+ iso_week_based: false,
+ sign_is_mandatory: false,
+ }),
+ ]),
+ b'e' => component!(Day {
+ padding: padding_or_default(*padding, modifier::Padding::Space),
+ }),
+ b'F' => BorrowedFormatItem::Compound(&[
+ component!(Year {
+ padding: modifier::Padding::Zero,
+ repr: modifier::YearRepr::Full,
+ range: modifier::YearRange::Extended,
+ iso_week_based: false,
+ sign_is_mandatory: false,
+ }),
+ BorrowedFormatItem::Literal(b"-"),
+ component!(Month {
+ padding: modifier::Padding::Zero,
+ repr: modifier::MonthRepr::Numerical,
+ case_sensitive: true,
+ }),
+ BorrowedFormatItem::Literal(b"-"),
+ component!(Day {
+ padding: modifier::Padding::Zero,
+ }),
+ ]),
+ b'g' => component!(Year {
+ padding: padding_or_default(*padding, modifier::Padding::Zero),
+ repr: modifier::YearRepr::LastTwo,
+ range: modifier::YearRange::Extended,
+ iso_week_based: true,
+ sign_is_mandatory: false,
+ }),
+ b'G' => component!(Year {
+ padding: modifier::Padding::Zero,
+ repr: modifier::YearRepr::Full,
+ range: modifier::YearRange::Extended,
+ iso_week_based: true,
+ sign_is_mandatory: false,
+ }),
+ b'H' => component!(Hour {
+ padding: padding_or_default(*padding, modifier::Padding::Zero),
+ is_12_hour_clock: false,
+ }),
+ b'I' => component!(Hour {
+ padding: padding_or_default(*padding, modifier::Padding::Zero),
+ is_12_hour_clock: true,
+ }),
+ b'j' => component!(Ordinal {
+ padding: padding_or_default(*padding, modifier::Padding::Zero),
+ }),
+ b'k' => component!(Hour {
+ padding: padding_or_default(*padding, modifier::Padding::Space),
+ is_12_hour_clock: false,
+ }),
+ b'l' => component!(Hour {
+ padding: padding_or_default(*padding, modifier::Padding::Space),
+ is_12_hour_clock: true,
+ }),
+ b'm' => component!(Month {
+ padding: padding_or_default(*padding, modifier::Padding::Zero),
+ repr: modifier::MonthRepr::Numerical,
+ case_sensitive: true,
+ }),
+ b'M' => component!(Minute {
+ padding: padding_or_default(*padding, modifier::Padding::Zero),
+ }),
+ b'n' => BorrowedFormatItem::Literal(b"\n"),
+ b'O' => {
+ return Err(Error {
+ _inner: unused(ErrorInner {
+ _message: "unsupported modifier",
+ _span: component.span,
+ }),
+ public: InvalidFormatDescription::NotSupported {
+ what: "modifier",
+ context: "",
+ index: component.span.start.byte as usize,
+ },
+ })
+ }
+ b'p' => component!(Period {
+ is_uppercase: true,
+ case_sensitive: true
+ }),
+ b'P' => component!(Period {
+ is_uppercase: false,
+ case_sensitive: true
+ }),
+ b'r' => BorrowedFormatItem::Compound(&[
+ component!(Hour {
+ padding: modifier::Padding::Zero,
+ is_12_hour_clock: true,
+ }),
+ BorrowedFormatItem::Literal(b":"),
+ component!(Minute {
+ padding: modifier::Padding::Zero,
+ }),
+ BorrowedFormatItem::Literal(b":"),
+ component!(Second {
+ padding: modifier::Padding::Zero,
+ }),
+ BorrowedFormatItem::Literal(b" "),
+ component!(Period {
+ is_uppercase: true,
+ case_sensitive: true,
+ }),
+ ]),
+ b'R' => BorrowedFormatItem::Compound(&[
+ component!(Hour {
+ padding: modifier::Padding::Zero,
+ is_12_hour_clock: false,
+ }),
+ BorrowedFormatItem::Literal(b":"),
+ component!(Minute {
+ padding: modifier::Padding::Zero,
+ }),
+ ]),
+ b's' => component!(UnixTimestamp {
+ precision: modifier::UnixTimestampPrecision::Second,
+ sign_is_mandatory: false,
+ }),
+ b'S' => component!(Second {
+ padding: padding_or_default(*padding, modifier::Padding::Zero),
+ }),
+ b't' => BorrowedFormatItem::Literal(b"\t"),
+ b'T' => BorrowedFormatItem::Compound(&[
+ component!(Hour {
+ padding: modifier::Padding::Zero,
+ is_12_hour_clock: false,
+ }),
+ BorrowedFormatItem::Literal(b":"),
+ component!(Minute {
+ padding: modifier::Padding::Zero,
+ }),
+ BorrowedFormatItem::Literal(b":"),
+ component!(Second {
+ padding: modifier::Padding::Zero,
+ }),
+ ]),
+ b'u' => component!(Weekday {
+ repr: modifier::WeekdayRepr::Monday,
+ one_indexed: true,
+ case_sensitive: true,
+ }),
+ b'U' => component!(WeekNumber {
+ padding: padding_or_default(*padding, modifier::Padding::Zero),
+ repr: modifier::WeekNumberRepr::Sunday,
+ }),
+ b'V' => component!(WeekNumber {
+ padding: padding_or_default(*padding, modifier::Padding::Zero),
+ repr: modifier::WeekNumberRepr::Iso,
+ }),
+ b'w' => component!(Weekday {
+ repr: modifier::WeekdayRepr::Sunday,
+ one_indexed: true,
+ case_sensitive: true,
+ }),
+ b'W' => component!(WeekNumber {
+ padding: padding_or_default(*padding, modifier::Padding::Zero),
+ repr: modifier::WeekNumberRepr::Monday,
+ }),
+ b'x' => BorrowedFormatItem::Compound(&[
+ component!(Month {
+ repr: modifier::MonthRepr::Numerical,
+ padding: modifier::Padding::Zero,
+ case_sensitive: true,
+ }),
+ BorrowedFormatItem::Literal(b"/"),
+ component!(Day {
+ padding: modifier::Padding::Zero
+ }),
+ BorrowedFormatItem::Literal(b"/"),
+ component!(Year {
+ padding: modifier::Padding::Zero,
+ repr: modifier::YearRepr::LastTwo,
+ range: modifier::YearRange::Extended,
+ iso_week_based: false,
+ sign_is_mandatory: false,
+ }),
+ ]),
+ b'X' => BorrowedFormatItem::Compound(&[
+ component!(Hour {
+ padding: modifier::Padding::Zero,
+ is_12_hour_clock: false,
+ }),
+ BorrowedFormatItem::Literal(b":"),
+ component!(Minute {
+ padding: modifier::Padding::Zero,
+ }),
+ BorrowedFormatItem::Literal(b":"),
+ component!(Second {
+ padding: modifier::Padding::Zero,
+ }),
+ ]),
+ b'y' => component!(Year {
+ padding: padding_or_default(*padding, modifier::Padding::Zero),
+ repr: modifier::YearRepr::LastTwo,
+ range: modifier::YearRange::Extended,
+ iso_week_based: false,
+ sign_is_mandatory: false,
+ }),
+ b'Y' => component!(Year {
+ padding: modifier::Padding::Zero,
+ repr: modifier::YearRepr::Full,
+ range: modifier::YearRange::Extended,
+ iso_week_based: false,
+ sign_is_mandatory: false,
+ }),
+ b'z' => BorrowedFormatItem::Compound(&[
+ component!(OffsetHour {
+ sign_is_mandatory: true,
+ padding: modifier::Padding::Zero,
+ }),
+ component!(OffsetMinute {
+ padding: modifier::Padding::Zero,
+ }),
+ ]),
+ b'Z' => {
+ return Err(Error {
+ _inner: unused(ErrorInner {
+ _message: "unsupported component",
+ _span: component.span,
+ }),
+ public: InvalidFormatDescription::NotSupported {
+ what: "component",
+ context: "",
+ index: component.span.start.byte as usize,
+ },
+ })
+ }
+ _ => {
+ return Err(Error {
+ _inner: unused(ErrorInner {
+ _message: "invalid component",
+ _span: component.span,
+ }),
+ public: InvalidFormatDescription::InvalidComponentName {
+ name: String::from_utf8_lossy(&[*component]).into_owned(),
+ index: component.span.start.byte as usize,
+ },
+ })
+ }
+ })
+}