Skip to main content

time_macros/format_description/
format_item.rs

1use std::num::NonZero;
2use std::str::{self, FromStr};
3
4use super::{Error, OptionExt as _, Span, Spanned, Unused, ast, unused};
5use crate::FormatDescriptionVersion;
6use crate::format_description::{SpannedValue as _, public};
7
8macro_rules! parse_modifiers {
9    ($modifiers:expr, struct {}) => {{
10        struct Parsed {}
11        drop($modifiers);
12        Ok(Parsed {})
13    }};
14    ($modifiers:expr, struct { $($field:ident : $modifier:ident),* $(,)? }) => {
15        'block: {
16            struct Parsed {
17                $($field: Option<Spanned<<$modifier as ModifierValue>::Type>>),*
18            }
19
20            let mut parsed = Parsed {
21                $($field: None),*
22            };
23
24            for modifier in $modifiers {
25                $(if modifier.key.eq_ignore_ascii_case(stringify!($field).as_bytes()) {
26                    if parsed.$field.is_some() {
27                        break 'block Err(modifier.key.span.error("duplicate modifier key"));
28                    }
29                    match <$modifier>::from_modifier_value(&modifier.value) {
30                        Ok(value) => {
31                            parsed.$field = Some(
32                                <<$modifier as ModifierValue>::Type>::from(value)
33                                    .spanned(modifier.value.span)
34                            )
35                        },
36                        Err(err) => break 'block Err(err),
37                    }
38                    continue;
39                })*
40                break 'block Err(modifier.key.span.error("invalid modifier key"));
41            }
42
43            Ok(parsed)
44        }
45    };
46}
47
48pub(super) fn parse<'a>(
49    ast_items: impl Iterator<Item = Result<ast::Item<'a>, Error>>,
50) -> impl Iterator<Item = Result<Item<'a>, Error>> {
51    ast_items.map(|ast_item| ast_item.and_then(Item::from_ast))
52}
53
54pub(super) enum Item<'a> {
55    Literal(&'a [u8]),
56    StringLiteral(&'a str),
57    Component(Component),
58    Optional {
59        value: Box<[Self]>,
60        format: Spanned<bool>,
61        _span: Unused<Span>,
62    },
63    First {
64        value: Box<[Box<[Self]>]>,
65        _span: Unused<Span>,
66    },
67}
68
69impl Item<'_> {
70    pub(super) fn from_ast(ast_item: ast::Item<'_>) -> Result<Item<'_>, Error> {
71        Ok(match ast_item {
72            ast::Item::Component {
73                _opening_bracket: _,
74                _leading_whitespace: _,
75                name,
76                modifiers,
77                _trailing_whitespace: _,
78                _closing_bracket: _,
79            } => Item::Component(component_from_ast(&name, &modifiers)?),
80            ast::Item::Literal {
81                version,
82                value: Spanned { value, span },
83            } => {
84                if let Ok(value) = str::from_utf8(value) {
85                    Item::StringLiteral(value)
86                } else if version.is_at_least_v3() {
87                    return Err(span.error("v3 format descriptions must be valid UTF-8"));
88                } else {
89                    Item::Literal(value)
90                }
91            }
92            ast::Item::EscapedBracket {
93                _first: _,
94                _second: _,
95            } => Item::StringLiteral("["),
96            ast::Item::Optional {
97                version,
98                opening_bracket,
99                _leading_whitespace: _,
100                _optional_kw: _,
101                modifiers,
102                _whitespace_after_modifiers: _,
103                nested_format_description,
104                closing_bracket,
105            } => {
106                let items = nested_format_description
107                    .items
108                    .into_vec()
109                    .into_iter()
110                    .map(Item::from_ast)
111                    .collect::<Result<_, _>>()?;
112
113                let modifiers = parse_modifiers!(modifiers, struct { format: OptionalFormat })?;
114                let format = modifiers.format.transpose().map(|val| val.unwrap_or(true));
115                if version.is_at_most_v2() && !*format {
116                    return Err(format
117                        .span
118                        .error("v1 and v2 format descriptions must format optional items"));
119                }
120
121                Item::Optional {
122                    value: items,
123                    format,
124                    _span: unused(opening_bracket.to(closing_bracket)),
125                }
126            }
127            ast::Item::First {
128                opening_bracket,
129                _leading_whitespace: _,
130                _first_kw: _,
131                modifiers,
132                _whitespace_after_modifiers: _,
133                nested_format_descriptions,
134                closing_bracket,
135            } => {
136                let items = nested_format_descriptions
137                    .into_vec()
138                    .into_iter()
139                    .map(|nested_format_description| {
140                        nested_format_description
141                            .items
142                            .into_vec()
143                            .into_iter()
144                            .map(Item::from_ast)
145                            .collect()
146                    })
147                    .collect::<Result<_, _>>()?;
148
149                let _modifiers = parse_modifiers!(modifiers, struct {})?;
150
151                Item::First {
152                    value: items,
153                    _span: unused(opening_bracket.to(closing_bracket)),
154                }
155            }
156        })
157    }
158}
159
160impl TryFrom<(FormatDescriptionVersion, Item<'_>)> for public::OwnedFormatItemInner {
161    type Error = Error;
162
163    fn try_from(
164        (version, item): (FormatDescriptionVersion, Item<'_>),
165    ) -> Result<Self, Self::Error> {
166        Ok(match item {
167            Item::Literal(literal) => Self::Literal(literal.to_vec()),
168            Item::StringLiteral(string) => Self::StringLiteral(string.to_owned()),
169            Item::Component(component) => Self::Component((version, component).try_into()?),
170            Item::Optional {
171                format,
172                value,
173                _span: _,
174            } => Self::Optional {
175                format: *format,
176                item: Box::new((version, value).try_into()?),
177            },
178            Item::First { value, _span: _ } => Self::First(
179                value
180                    .into_iter()
181                    .map(|item| (version, item).try_into())
182                    .collect::<Result<_, _>>()?,
183            ),
184        })
185    }
186}
187
188impl<'a> TryFrom<(FormatDescriptionVersion, Box<[Item<'a>]>)> for public::OwnedFormatItemInner {
189    type Error = Error;
190
191    fn try_from(
192        (version, items): (FormatDescriptionVersion, Box<[Item<'a>]>),
193    ) -> Result<Self, Self::Error> {
194        Ok(Self::Compound(
195            items
196                .into_iter()
197                .map(|item| (version, item).try_into())
198                .collect::<Result<_, _>>()?,
199        ))
200    }
201}
202
203macro_rules! component_definition {
204    (@if_required required then { $($then:tt)* } $(else { $($else:tt)* })?) => { $($then)* };
205    (@if_required then { $($then:tt)* } $(else { $($else:tt)* })?) => { $($($else)*)? };
206    (@if_from_str from_str then { $($then:tt)* } $(else { $($else:tt)* })?) => { $($then)* };
207    (@if_from_str then { $($then:tt)* } $(else { $($else:tt)* })?) => { $($($else)*)? };
208
209    ($vis:vis enum $name:ident {
210        $($variant:ident = $parse_variant:literal {$(
211            $(#[$required:tt])?
212            $field:ident = $parse_field:literal:
213            Option<$(#[$from_str:tt])? $field_type:ty>
214        ),* $(,)?}),* $(,)?
215    }) => {
216        $vis enum $name {
217            $($variant($variant),)*
218        }
219
220        $($vis struct $variant {
221            $($field: Spanned<Option<$field_type>>),*
222        })*
223
224        $(impl $variant {
225            fn with_modifiers(
226                modifiers: &[ast::Modifier<'_>],
227                _component_span: Span,
228            ) -> Result<Self, Error>
229            {
230                #[allow(unused_mut)]
231                let mut this = Self {
232                    $($field: None.spanned(Span::dummy())),*
233                };
234
235                for modifier in modifiers {
236                    $(#[allow(clippy::string_lit_as_bytes)]
237                    if modifier.key.eq_ignore_ascii_case($parse_field.as_bytes()) {
238                        this.$field = Some(
239                            component_definition!(@if_from_str $($from_str)?
240                                then {
241                                    parse_from_modifier_value::<$field_type>(&modifier.value)?
242                                } else {
243                                    <$field_type>::from_modifier_value(&modifier.value)?
244                                }
245                            )
246                        ).spanned(modifier.key_value_span());
247                        continue;
248                    })*
249                    return Err(modifier.key.span.error("invalid modifier key"));
250                }
251
252                $(component_definition! { @if_required $($required)? then {
253                    if this.$field.is_none() {
254                        return Err(_component_span.error("missing required modifier"));
255                    }
256                }})*
257
258                Ok(this)
259            }
260        })*
261
262        fn component_from_ast(
263            name: &Spanned<&[u8]>,
264            modifiers: &[ast::Modifier<'_>],
265        ) -> Result<Component, Error> {
266            $(#[allow(clippy::string_lit_as_bytes)]
267            if name.eq_ignore_ascii_case($parse_variant.as_bytes()) {
268                return Ok(Component::$variant($variant::with_modifiers(&modifiers, name.span)?));
269            })*
270            Err(name.span.error("invalid component"))
271        }
272    }
273}
274
275component_definition! {
276    pub(super) enum Component {
277        Day = "day" {
278            padding = "padding": Option<Padding>,
279        },
280        End = "end" {
281            trailing_input = "trailing_input": Option<TrailingInput>,
282        },
283        Hour = "hour" {
284            padding = "padding": Option<Padding>,
285            base = "repr": Option<HourBase>,
286        },
287        Ignore = "ignore" {
288            #[required]
289            count = "count": Option<#[from_str] NonZero<u16>>,
290        },
291        Minute = "minute" {
292            padding = "padding": Option<Padding>,
293        },
294        Month = "month" {
295            padding = "padding": Option<Padding>,
296            repr = "repr": Option<MonthRepr>,
297            case_sensitive = "case_sensitive": Option<MonthCaseSensitive>,
298        },
299        OffsetHour = "offset_hour" {
300            sign_behavior = "sign": Option<SignBehavior>,
301            padding = "padding": Option<Padding>,
302        },
303        OffsetMinute = "offset_minute" {
304            padding = "padding": Option<Padding>,
305        },
306        OffsetSecond = "offset_second" {
307            padding = "padding": Option<Padding>,
308        },
309        Ordinal = "ordinal" {
310            padding = "padding": Option<Padding>,
311        },
312        Period = "period" {
313            case = "case": Option<PeriodCase>,
314            case_sensitive = "case_sensitive": Option<PeriodCaseSensitive>,
315        },
316        Second = "second" {
317            padding = "padding": Option<Padding>,
318        },
319        Subsecond = "subsecond" {
320            digits = "digits": Option<SubsecondDigits>,
321        },
322        UnixTimestamp = "unix_timestamp" {
323            precision = "precision": Option<UnixTimestampPrecision>,
324            sign_behavior = "sign": Option<SignBehavior>,
325        },
326        Weekday = "weekday" {
327            repr = "repr": Option<WeekdayRepr>,
328            one_indexed = "one_indexed": Option<WeekdayOneIndexed>,
329            case_sensitive = "case_sensitive": Option<WeekdayCaseSensitive>,
330        },
331        WeekNumber = "week_number" {
332            padding = "padding": Option<Padding>,
333            repr = "repr": Option<WeekNumberRepr>,
334        },
335        Year = "year" {
336            padding = "padding": Option<Padding>,
337            repr = "repr": Option<YearRepr>,
338            range = "range": Option<YearRange>,
339            base = "base": Option<YearBase>,
340            sign_behavior = "sign": Option<SignBehavior>,
341        },
342    }
343}
344
345impl TryFrom<(FormatDescriptionVersion, Component)> for public::Component {
346    type Error = Error;
347
348    #[inline]
349    fn try_from(
350        (version, component): (FormatDescriptionVersion, Component),
351    ) -> Result<Self, Self::Error> {
352        macro_rules! reject_modifier {
353            ($modifier:ident, $modifier_str:literal, $context:literal) => {
354                if version.is_at_least_v3() && $modifier.value.is_some() {
355                    return Err($modifier.span.error(concat!(
356                        "the '",
357                        $modifier_str,
358                        "' modifier is not valid ",
359                        $context
360                    )));
361                }
362            };
363        }
364
365        use crate::format_description::public::modifier;
366        Ok(match component {
367            Component::Day(Day { padding }) => Self::Day(modifier::Day {
368                padding: padding.unwrap_or_default().into(),
369            }),
370            Component::End(End { trailing_input }) => Self::End(modifier::End {
371                trailing_input: trailing_input.unwrap_or_default().into(),
372            }),
373            Component::Hour(Hour { padding, base }) => match base.unwrap_or_default() {
374                HourBase::Twelve => Self::Hour12(modifier::Hour12 {
375                    padding: padding.unwrap_or_default().into(),
376                }),
377                HourBase::TwentyFour => Self::Hour24(modifier::Hour24 {
378                    padding: padding.unwrap_or_default().into(),
379                }),
380            },
381            Component::Ignore(Ignore { count }) => Self::Ignore(modifier::Ignore {
382                count: match *count {
383                    Some(value) => value,
384                    None => bug!("required modifier was not set"),
385                },
386            }),
387            Component::Minute(Minute { padding }) => Self::Minute(modifier::Minute {
388                padding: padding.unwrap_or_default().into(),
389            }),
390            Component::Month(Month {
391                padding,
392                repr,
393                case_sensitive,
394            }) => match repr.unwrap_or_default() {
395                MonthRepr::Numerical => {
396                    reject_modifier!(case_sensitive, "case_sensitive", "for numerical month");
397                    Self::MonthNumerical(modifier::MonthNumerical {
398                        padding: padding.unwrap_or_default().into(),
399                    })
400                }
401                MonthRepr::Long => {
402                    reject_modifier!(padding, "padding", "for long month");
403                    Self::MonthLong(modifier::MonthLong {
404                        case_sensitive: case_sensitive.unwrap_or_default().into(),
405                    })
406                }
407                MonthRepr::Short => {
408                    reject_modifier!(padding, "padding", "for short month");
409                    Self::MonthShort(modifier::MonthShort {
410                        case_sensitive: case_sensitive.unwrap_or_default().into(),
411                    })
412                }
413            },
414            Component::OffsetHour(OffsetHour {
415                sign_behavior,
416                padding,
417            }) => Self::OffsetHour(modifier::OffsetHour {
418                sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
419                padding: padding.unwrap_or_default().into(),
420            }),
421            Component::OffsetMinute(OffsetMinute { padding }) => {
422                Self::OffsetMinute(modifier::OffsetMinute {
423                    padding: padding.unwrap_or_default().into(),
424                })
425            }
426            Component::OffsetSecond(OffsetSecond { padding }) => {
427                Self::OffsetSecond(modifier::OffsetSecond {
428                    padding: padding.unwrap_or_default().into(),
429                })
430            }
431            Component::Ordinal(Ordinal { padding }) => Self::Ordinal(modifier::Ordinal {
432                padding: padding.unwrap_or_default().into(),
433            }),
434            Component::Period(Period {
435                case,
436                case_sensitive,
437            }) => Self::Period(modifier::Period {
438                is_uppercase: case.unwrap_or_default().into(),
439                case_sensitive: case_sensitive.unwrap_or_default().into(),
440            }),
441            Component::Second(Second { padding }) => Self::Second(modifier::Second {
442                padding: padding.unwrap_or_default().into(),
443            }),
444            Component::Subsecond(Subsecond { digits }) => Self::Subsecond(modifier::Subsecond {
445                digits: digits.unwrap_or_default().into(),
446            }),
447            Component::UnixTimestamp(UnixTimestamp {
448                precision,
449                sign_behavior,
450            }) => match precision.unwrap_or_default() {
451                UnixTimestampPrecision::Second => {
452                    Self::UnixTimestampSecond(modifier::UnixTimestampSecond {
453                        sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
454                    })
455                }
456                UnixTimestampPrecision::Millisecond => {
457                    Self::UnixTimestampMillisecond(modifier::UnixTimestampMillisecond {
458                        sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
459                    })
460                }
461                UnixTimestampPrecision::Microsecond => {
462                    Self::UnixTimestampMicrosecond(modifier::UnixTimestampMicrosecond {
463                        sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
464                    })
465                }
466                UnixTimestampPrecision::Nanosecond => {
467                    Self::UnixTimestampNanosecond(modifier::UnixTimestampNanosecond {
468                        sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
469                    })
470                }
471            },
472            Component::Weekday(Weekday {
473                repr,
474                one_indexed,
475                case_sensitive,
476            }) => match repr.unwrap_or_default() {
477                WeekdayRepr::Short => {
478                    reject_modifier!(one_indexed, "one_indexed", "for short weekday");
479                    Self::WeekdayShort(modifier::WeekdayShort {
480                        case_sensitive: case_sensitive.unwrap_or_default().into(),
481                    })
482                }
483                WeekdayRepr::Long => {
484                    reject_modifier!(one_indexed, "one_indexed", "for long weekday");
485                    Self::WeekdayLong(modifier::WeekdayLong {
486                        case_sensitive: case_sensitive.unwrap_or_default().into(),
487                    })
488                }
489                WeekdayRepr::Sunday => {
490                    reject_modifier!(case_sensitive, "case_sensitive", "for numerical weekday");
491                    Self::WeekdaySunday(modifier::WeekdaySunday {
492                        one_indexed: one_indexed.unwrap_or_default().into(),
493                    })
494                }
495                WeekdayRepr::Monday => {
496                    reject_modifier!(case_sensitive, "case_sensitive", "for numerical weekday");
497                    Self::WeekdayMonday(modifier::WeekdayMonday {
498                        one_indexed: one_indexed.unwrap_or_default().into(),
499                    })
500                }
501            },
502            Component::WeekNumber(WeekNumber { padding, repr }) => match repr.unwrap_or_default() {
503                WeekNumberRepr::Iso => Self::WeekNumberIso(modifier::WeekNumberIso {
504                    padding: padding.unwrap_or_default().into(),
505                }),
506                WeekNumberRepr::Sunday => Self::WeekNumberSunday(modifier::WeekNumberSunday {
507                    padding: padding.unwrap_or_default().into(),
508                }),
509                WeekNumberRepr::Monday => Self::WeekNumberMonday(modifier::WeekNumberMonday {
510                    padding: padding.unwrap_or_default().into(),
511                }),
512            },
513            Component::Year(Year {
514                padding,
515                repr,
516                range,
517                base,
518                sign_behavior,
519            }) => {
520                #[cfg(not(feature = "large-dates"))]
521                reject_modifier!(
522                    range,
523                    "range",
524                    "when the `large-dates` feature is not enabled"
525                );
526
527                match (
528                    base.unwrap_or_default(),
529                    repr.unwrap_or_default(),
530                    range.unwrap_or_default(),
531                ) {
532                    #[cfg(feature = "large-dates")]
533                    (YearBase::Calendar, YearRepr::Full, YearRange::Extended) => {
534                        Self::CalendarYearFullExtendedRange(
535                            modifier::CalendarYearFullExtendedRange {
536                                padding: padding.unwrap_or_default().into(),
537                                sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
538                            },
539                        )
540                    }
541                    (YearBase::Calendar, YearRepr::Full, _) => Self::CalendarYearFullStandardRange(
542                        modifier::CalendarYearFullStandardRange {
543                            padding: padding.unwrap_or_default().into(),
544                            sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
545                        },
546                    ),
547                    #[cfg(feature = "large-dates")]
548                    (YearBase::Calendar, YearRepr::Century, YearRange::Extended) => {
549                        Self::CalendarYearCenturyExtendedRange(
550                            modifier::CalendarYearCenturyExtendedRange {
551                                padding: padding.unwrap_or_default().into(),
552                                sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
553                            },
554                        )
555                    }
556                    (YearBase::Calendar, YearRepr::Century, _) => {
557                        Self::CalendarYearCenturyStandardRange(
558                            modifier::CalendarYearCenturyStandardRange {
559                                padding: padding.unwrap_or_default().into(),
560                                sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
561                            },
562                        )
563                    }
564                    #[cfg(feature = "large-dates")]
565                    (YearBase::IsoWeek, YearRepr::Full, YearRange::Extended) => {
566                        Self::IsoYearFullExtendedRange(modifier::IsoYearFullExtendedRange {
567                            padding: padding.unwrap_or_default().into(),
568                            sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
569                        })
570                    }
571                    (YearBase::IsoWeek, YearRepr::Full, _) => {
572                        Self::IsoYearFullStandardRange(modifier::IsoYearFullStandardRange {
573                            padding: padding.unwrap_or_default().into(),
574                            sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
575                        })
576                    }
577                    #[cfg(feature = "large-dates")]
578                    (YearBase::IsoWeek, YearRepr::Century, YearRange::Extended) => {
579                        Self::IsoYearCenturyExtendedRange(modifier::IsoYearCenturyExtendedRange {
580                            padding: padding.unwrap_or_default().into(),
581                            sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
582                        })
583                    }
584                    (YearBase::IsoWeek, YearRepr::Century, _) => {
585                        Self::IsoYearCenturyStandardRange(modifier::IsoYearCenturyStandardRange {
586                            padding: padding.unwrap_or_default().into(),
587                            sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
588                        })
589                    }
590                    (YearBase::Calendar, YearRepr::LastTwo, _) => {
591                        #[cfg(feature = "large-dates")]
592                        reject_modifier!(range, "range", "when `repr:last_two` is used");
593                        Self::CalendarYearLastTwo(modifier::CalendarYearLastTwo {
594                            padding: padding.unwrap_or_default().into(),
595                        })
596                    }
597                    (YearBase::IsoWeek, YearRepr::LastTwo, _) => {
598                        #[cfg(feature = "large-dates")]
599                        reject_modifier!(range, "range", "when `repr:last_two` is used");
600                        Self::IsoYearLastTwo(modifier::IsoYearLastTwo {
601                            padding: padding.unwrap_or_default().into(),
602                        })
603                    }
604                }
605            }
606        })
607    }
608}
609
610macro_rules! target_ty {
611    ($name:ident $type:ty) => {
612        $type
613    };
614    ($name:ident) => {
615        super::public::modifier::$name
616    };
617}
618
619/// Get the target value for a given enum.
620macro_rules! target_value {
621    ($name:ident $variant:ident $value:expr) => {
622        $value
623    };
624    ($name:ident $variant:ident) => {
625        super::public::modifier::$name::$variant
626    };
627}
628
629macro_rules! if_not_parse_only {
630    (@parse_only $($x:tt)*) => {};
631    ($($x:tt)*) => { $($x)* };
632}
633
634trait ModifierValue {
635    type Type;
636}
637
638macro_rules! modifier {
639    ($(
640        $(@$instruction:ident)? enum $name:ident $(($target_ty:ty))? {
641            $(
642                $(#[$attr:meta])?
643                $variant:ident $(($target_value:expr))? = $parse_variant:literal
644            ),* $(,)?
645        }
646    )+) => {$(
647        #[derive(Default, Clone, Copy)]
648        enum $name {
649            $($(#[$attr])? $variant),*
650        }
651
652        impl $name {
653            /// Parse the modifier from its string representation.
654            fn from_modifier_value(value: &Spanned<&[u8]>) -> Result<Self, Error> {
655                $(if value.eq_ignore_ascii_case($parse_variant) {
656                    return Ok(Self::$variant);
657                })*
658                Err(value.span.error("invalid modifier value"))
659            }
660        }
661
662        if_not_parse_only! { $(@$instruction)?
663            impl ModifierValue for $name {
664                type Type = target_ty!($name $($target_ty)?);
665            }
666
667            impl From<$name> for <$name as ModifierValue>::Type {
668                #[inline]
669                fn from(modifier: $name) -> Self {
670                    match modifier {
671                        $($name::$variant => target_value!($name $variant $($target_value)?)),*
672                    }
673                }
674            }
675        }
676    )+};
677}
678
679modifier! {
680    enum HourBase(bool) {
681        Twelve(true) = b"12",
682        #[default]
683        TwentyFour(false) = b"24",
684    }
685
686    enum MonthCaseSensitive(bool) {
687        False(false) = b"false",
688        #[default]
689        True(true) = b"true",
690    }
691
692    @parse_only enum MonthRepr {
693        #[default]
694        Numerical = b"numerical",
695        Long = b"long",
696        Short = b"short",
697    }
698
699    enum OptionalFormat(bool) {
700        False(false) = b"false",
701        #[default]
702        True(true) = b"true",
703    }
704
705    enum Padding {
706        Space = b"space",
707        #[default]
708        Zero = b"zero",
709        None = b"none",
710    }
711
712    enum PeriodCase(bool) {
713        Lower(false) = b"lower",
714        #[default]
715        Upper(true) = b"upper",
716    }
717
718    enum PeriodCaseSensitive(bool) {
719        False(false) = b"false",
720        #[default]
721        True(true) = b"true",
722    }
723
724    enum SignBehavior(bool) {
725        #[default]
726        Automatic(false) = b"automatic",
727        Mandatory(true) = b"mandatory",
728    }
729
730    enum SubsecondDigits {
731        One = b"1",
732        Two = b"2",
733        Three = b"3",
734        Four = b"4",
735        Five = b"5",
736        Six = b"6",
737        Seven = b"7",
738        Eight = b"8",
739        Nine = b"9",
740        #[default]
741        OneOrMore = b"1+",
742    }
743
744    enum TrailingInput {
745        #[default]
746        Prohibit = b"prohibit",
747        Discard = b"discard",
748    }
749
750    @parse_only enum UnixTimestampPrecision {
751        #[default]
752        Second = b"second",
753        Millisecond = b"millisecond",
754        Microsecond = b"microsecond",
755        Nanosecond = b"nanosecond",
756    }
757
758    @parse_only enum WeekNumberRepr {
759        #[default]
760        Iso = b"iso",
761        Sunday = b"sunday",
762        Monday = b"monday",
763    }
764
765    enum WeekdayCaseSensitive(bool) {
766        False(false) = b"false",
767        #[default]
768        True(true) = b"true",
769    }
770
771    enum WeekdayOneIndexed(bool) {
772        False(false) = b"false",
773        #[default]
774        True(true) = b"true",
775    }
776
777    @parse_only enum WeekdayRepr {
778        Short = b"short",
779        #[default]
780        Long = b"long",
781        Sunday = b"sunday",
782        Monday = b"monday",
783    }
784
785    enum YearBase(bool) {
786        #[default]
787        Calendar(false) = b"calendar",
788        IsoWeek(true) = b"iso_week",
789    }
790
791    @parse_only enum YearRepr {
792        #[default]
793        Full = b"full",
794        Century = b"century",
795        LastTwo = b"last_two",
796    }
797
798    @parse_only enum YearRange {
799        Standard = b"standard",
800        #[default]
801        Extended = b"extended",
802    }
803}
804
805fn parse_from_modifier_value<T: FromStr>(value: &Spanned<&[u8]>) -> Result<T, Error> {
806    str::from_utf8(value)
807        .ok()
808        .and_then(|val| val.parse::<T>().ok())
809        .ok_or_else(|| value.span.error("invalid modifier value"))
810}