diff options
Diffstat (limited to 'vendor/time/src/format_description/parse/strftime.rs')
| -rw-r--r-- | vendor/time/src/format_description/parse/strftime.rs | 487 |
1 files changed, 487 insertions, 0 deletions
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, + }, + }) + } + }) +} |
