Skip to main content

time/format_description/parse/
format_item.rs

1//! Typed, validated representation of a parsed format description.
2
3use alloc::borrow::ToOwned as _;
4use alloc::boxed::Box;
5use alloc::vec::Vec;
6use core::num::NonZero;
7use core::str::{self, FromStr};
8
9use super::lexer_ast::{Modifier, NestedFormatDescription};
10use super::{Error, Location, OptionExt as _, Span, Spanned, SpannedValue as _, unused};
11use crate::error::InvalidFormatDescription;
12use crate::internal_macros::{bug, try_likely_ok};
13
14macro_rules! parse_modifiers {
15    ($version:expr, $modifiers:expr, struct { $($field:ident : $modifier:ident),* $(,)? }) => {
16        'block: {
17            struct Parsed {
18                $($field: Option<Spanned<<$modifier as ModifierValue>::Type>>),*
19            }
20
21            let mut parsed = Parsed {
22                $($field: None),*
23            };
24
25            for modifier in $modifiers {
26                $(if ident_eq::<VERSION>(*modifier.key, stringify!($field)) {
27                    if parsed.$field.is_some() {
28                        break 'block Err(Error {
29                            _inner: unused(modifier.key.span.error("duplicate modifier key")),
30                            public: InvalidFormatDescription::DuplicateModifier {
31                                name: stringify!($field),
32                                index: modifier.key.span.start.byte as usize,
33                            }
34                        });
35                    }
36                    match <$modifier>::from_modifier_value::<VERSION>(&modifier.value) {
37                        Ok(value) => {
38                            parsed.$field = Some(
39                                <<$modifier as ModifierValue>::Type>::from(value)
40                                    .spanned(modifier.value.span)
41                            )
42                        },
43                        Err(err) => break 'block Err(err),
44                    }
45                    continue;
46                })*
47                break 'block Err(Error {
48                    _inner: unused(modifier.key.span.error("invalid modifier key")),
49                    public: InvalidFormatDescription::InvalidModifier {
50                        value: (**modifier.key).to_owned(),
51                        index: modifier.key.span.start.byte as usize,
52                    }
53                });
54            }
55
56            Ok(parsed)
57        }
58    };
59}
60
61#[inline]
62pub(super) fn ident_eq<const VERSION: u8>(provided: &str, expected: &str) -> bool {
63    assert_version!();
64    if version!(3..) {
65        provided == expected
66    } else {
67        provided.len() == expected.len()
68            && core::iter::zip(provided.bytes(), expected.bytes())
69                .all(|(p, e)| p.to_ascii_lowercase() == e)
70    }
71}
72
73/// A description of how to format and parse one part of a type.
74pub(super) enum Item<'a, const VERSION: u8> {
75    /// A literal string.
76    Literal(&'a str),
77    /// Part of a type, along with its modifiers.
78    Component(AstComponent),
79    /// A sequence of optional items.
80    Optional {
81        /// The items themselves.
82        value: Vec<Self>,
83        /// Whether the value should be formatted.
84        format: Spanned<bool>,
85        /// The span of the full sequence.
86        span: Span,
87    },
88    /// The first matching parse of a sequence of format descriptions.
89    First {
90        /// The sequence of format descriptions.
91        value: Vec<Vec<Self>>,
92        /// The span of the full sequence.
93        span: Span,
94    },
95}
96
97impl<'a, const VERSION: u8> Item<'a, VERSION> {
98    pub(super) fn optional_from_parts(
99        opening_bracket: Location,
100        modifiers: &[Modifier<'_>],
101        nested_format_descriptions: Vec<NestedFormatDescription<'a, VERSION>>,
102        closing_bracket: Location,
103    ) -> Result<Self, Error> {
104        assert_version!();
105
106        let modifiers = parse_modifiers!(VERSION, modifiers, struct {
107            format: OptionalFormat,
108        })?;
109
110        let nested_format_description = if let Some(second_fd) = nested_format_descriptions.get(1) {
111            return Err(Error {
112                _inner: unused(
113                    second_fd
114                        .opening_bracket
115                        .to(second_fd.closing_bracket)
116                        .error(
117                            "the `optional` component only allows a single nested format \
118                             description",
119                        ),
120                ),
121                public: InvalidFormatDescription::NotSupported {
122                    what: "more than one nested format description",
123                    context: "`optional` components",
124                    index: second_fd.opening_bracket.byte as usize,
125                },
126            });
127        } else if let Ok([first_fd]) = <[_; 1]>::try_from(nested_format_descriptions) {
128            first_fd
129        } else {
130            return Err(Error {
131                _inner: unused(
132                    opening_bracket
133                        .to(closing_bracket)
134                        .error("missing nested format description for `optional` component"),
135                ),
136                public: InvalidFormatDescription::Expected {
137                    what: "nested format description",
138                    index: closing_bracket.byte as usize,
139                },
140            });
141        };
142
143        let format = modifiers.format.transpose().map(|val| val.unwrap_or(true));
144        let items = nested_format_description.items;
145
146        Ok(Item::Optional {
147            value: items,
148            format,
149            span: opening_bracket.to(closing_bracket),
150        })
151    }
152}
153
154impl<'a, const VERSION: u8> TryFrom<Item<'a, VERSION>>
155    for crate::format_description::BorrowedFormatItem<'a>
156{
157    type Error = Error;
158
159    #[inline]
160    fn try_from(item: Item<'a, VERSION>) -> Result<Self, Self::Error> {
161        assert_version!();
162        match item {
163            Item::Literal(literal) => Ok(Self::StringLiteral(literal)),
164            Item::Component(component) => Ok(Self::Component(component.try_into()?)),
165            Item::Optional {
166                value: _,
167                format: _,
168                span,
169            } => Err(Error {
170                _inner: unused(span.error(
171                    "optional items are not supported in runtime-parsed format descriptions",
172                )),
173                public: InvalidFormatDescription::NotSupported {
174                    what: "optional item",
175                    context: "runtime-parsed format descriptions",
176                    index: span.start.byte as usize,
177                },
178            }),
179            Item::First { value: _, span } => Err(Error {
180                _inner: unused(span.error(
181                    "'first' items are not supported in runtime-parsed format descriptions",
182                )),
183                public: InvalidFormatDescription::NotSupported {
184                    what: "'first' item",
185                    context: "runtime-parsed format descriptions",
186                    index: span.start.byte as usize,
187                },
188            }),
189        }
190    }
191}
192
193impl<const VERSION: u8> TryFrom<Item<'_, VERSION>> for crate::format_description::OwnedFormatItem {
194    type Error = Error;
195
196    #[inline]
197    fn try_from(item: Item<'_, VERSION>) -> Result<Self, Self::Error> {
198        assert_version!();
199        match item {
200            Item::Literal(literal) => Ok(Self::StringLiteral(literal.to_owned().into_boxed_str())),
201            Item::Component(component) => Ok(Self::Component(component.try_into()?)),
202            Item::Optional {
203                value,
204                format,
205                span: _,
206            } => {
207                if !*format {
208                    return Err(Error {
209                        _inner: unused(format.span.error(
210                            "v1 and v2 format descriptions do not support optional items that are \
211                             not formatted",
212                        )),
213                        public: InvalidFormatDescription::NotSupported {
214                            what: "optional item with `format:false`",
215                            context: "v1 and v2 format descriptions",
216                            index: format.span.start.byte as usize,
217                        },
218                    });
219                }
220                Ok(Self::Optional(Box::new(value.try_into()?)))
221            }
222            Item::First { value, span: _ } => Ok(Self::First(
223                value
224                    .into_iter()
225                    .map(Self::try_from)
226                    .collect::<Result<_, _>>()?,
227            )),
228        }
229    }
230}
231
232impl<'a, const VERSION: u8> TryFrom<Vec<Item<'a, VERSION>>>
233    for crate::format_description::OwnedFormatItem
234{
235    type Error = Error;
236
237    #[inline]
238    fn try_from(items: Vec<Item<'a, VERSION>>) -> Result<Self, Self::Error> {
239        assert_version!();
240        match <[_; 1]>::try_from(items) {
241            Ok([item]) => item.try_into(),
242            Err(vec) => Ok(Self::Compound(
243                vec.into_iter()
244                    .map(Self::try_from)
245                    .collect::<Result<_, _>>()?,
246            )),
247        }
248    }
249}
250
251impl<'a, const VERSION: u8> TryFrom<Item<'a, VERSION>>
252    for crate::format_description::__private::FormatDescriptionV3Inner<'a>
253{
254    type Error = Error;
255
256    #[inline]
257    fn try_from(item: Item<'a, VERSION>) -> Result<Self, Self::Error> {
258        assert_version!();
259        match item {
260            Item::Literal(literal) => Ok(Self::BorrowedLiteral(literal)),
261            Item::Component(component) => Ok(component.try_into()?),
262            Item::Optional {
263                value,
264                format,
265                span: _,
266            } => Ok(Self::OwnedOptional {
267                format: *format,
268                item: Box::new(value.try_into()?),
269            }),
270            Item::First { value, span: _ } => Ok(Self::OwnedFirst(
271                value
272                    .into_iter()
273                    .map(Self::try_from)
274                    .collect::<Result<_, _>>()?,
275            )),
276        }
277    }
278}
279
280impl<'a, const VERSION: u8> TryFrom<Vec<Item<'a, VERSION>>>
281    for crate::format_description::__private::FormatDescriptionV3Inner<'a>
282{
283    type Error = Error;
284
285    #[inline]
286    fn try_from(items: Vec<Item<'a, VERSION>>) -> Result<Self, Self::Error> {
287        assert_version!();
288        match <[_; 1]>::try_from(items) {
289            Ok([item]) => item.try_into(),
290            Err(vec) => Ok(Self::OwnedCompound(
291                vec.into_iter()
292                    .map(Self::try_from)
293                    .collect::<Result<_, _>>()?,
294            )),
295        }
296    }
297}
298
299/// Declare the `Component` struct.
300macro_rules! component_definition {
301    (@if_required required then { $($then:tt)* } $(else { $($else:tt)* })?) => { $($then)* };
302    (@if_required then { $($then:tt)* } $(else { $($else:tt)* })?) => { $($($else)*)? };
303    (@if_from_str from_str then { $($then:tt)* } $(else { $($else:tt)* })?) => { $($then)* };
304    (@if_from_str then { $($then:tt)* } $(else { $($else:tt)* })?) => { $($($else)*)? };
305
306    ($vis:vis enum $name:ident {$(
307        $variant:ident = $parse_variant:literal {$(
308            $(#[$required:tt])?
309            $field:ident = $parse_field:literal:
310            Option<$(#[$from_str:tt])? $field_type:ty>
311        ),* $(,)?}
312    ),* $(,)?}) => {
313        $vis enum $name {
314            $($variant($variant),)*
315        }
316
317        $($vis struct $variant {
318            $($field: Spanned<Option<$field_type>>),*
319        })*
320
321        $(impl $variant {
322            /// Parse the component from the AST, given its modifiers.
323            #[inline]
324            fn with_modifiers<const VERSION: u8>(
325                modifiers: &[Modifier<'_>],
326                _component_span: Span,
327            ) -> Result<Self, Error>
328            {
329                assert_version!();
330
331                // rustc will complain if the modifier is empty.
332                #[allow(unused_mut)]
333                let mut this = Self {
334                    $($field: None.spanned(Span::DUMMY)),*
335                };
336
337                for modifier in modifiers {
338                    $(if ident_eq::<VERSION>(*modifier.key, $parse_field) {
339                        if version!(3..) && this.$field.is_some() {
340                            return Err(Error {
341                                _inner: unused(modifier.key.span.error("duplicate modifier key")),
342                                public: InvalidFormatDescription::DuplicateModifier {
343                                    name: stringify!($field),
344                                    index: modifier.key.span.start.byte as usize,
345                                }
346                            });
347                        }
348                        this.$field = Some(
349                            component_definition!(@if_from_str $($from_str)?
350                                then {
351                                    parse_from_modifier_value::<$field_type>(&modifier.value)?
352                                } else {
353                                    <$field_type>::from_modifier_value::<VERSION>(&modifier.value)?
354                                }
355                            )
356                        ).spanned(modifier.key_value_span());
357                        continue;
358                    })*
359                    return Err(Error {
360                        _inner: unused(modifier.key.span.error("invalid modifier key")),
361                        public: InvalidFormatDescription::InvalidModifier {
362                            value: (**modifier.key).to_owned(),
363                            index: modifier.key.span.start.byte as usize,
364                        }
365                    });
366                }
367
368                $(component_definition! { @if_required $($required)? then {
369                    if this.$field.is_none() {
370                        return Err(Error {
371                            _inner: unused(_component_span.error("missing required modifier")),
372                            public:
373                                InvalidFormatDescription::MissingRequiredModifier {
374                                    name: $parse_field,
375                                    index: _component_span.start.byte as usize,
376                                }
377                        });
378                    }
379                }})*
380
381                Ok(this)
382            }
383        })*
384
385        /// Parse a component from the AST, given its name and modifiers.
386        #[inline]
387        pub(super) fn component_from_ast<const VERSION: u8>(
388            name: &Spanned<&str>,
389            modifiers: &[Modifier<'_>],
390        ) -> Result<AstComponent, Error> {
391            assert_version!();
392
393            $(if ident_eq::<VERSION>(&name, $parse_variant) {
394                let mut component = AstComponent::$variant(
395                    try_likely_ok!($variant::with_modifiers::<VERSION>(&modifiers, name.span))
396                );
397                if version!(3..)
398                    && let AstComponent::Year(y) = &mut component
399                    && y.range.value.is_none()
400                {
401                    y.range = Some(YearRange::Standard).spanned(Span::DUMMY);
402                }
403                return Ok(component);
404            })*
405            Err(Error {
406                _inner: unused(name.span.error("invalid component")),
407                public: InvalidFormatDescription::InvalidComponentName {
408                    name: (**name).to_owned(),
409                    index: name.span.start.byte as usize,
410                },
411            })
412        }
413    }
414}
415
416// Keep in alphabetical order.
417component_definition! {
418    pub(super) enum AstComponent {
419        Day = "day" {
420            padding = "padding": Option<Padding>,
421        },
422        End = "end" {
423            trailing_input = "trailing_input": Option<TrailingInput>,
424        },
425        Hour = "hour" {
426            padding = "padding": Option<Padding>,
427            base = "repr": Option<HourBase>,
428        },
429        Ignore = "ignore" {
430            #[required]
431            count = "count": Option<#[from_str] NonZero<u16>>,
432        },
433        Minute = "minute" {
434            padding = "padding": Option<Padding>,
435        },
436        Month = "month" {
437            padding = "padding": Option<Padding>,
438            repr = "repr": Option<MonthRepr>,
439            case_sensitive = "case_sensitive": Option<MonthCaseSensitive>,
440        },
441        OffsetHour = "offset_hour" {
442            sign_behavior = "sign": Option<SignBehavior>,
443            padding = "padding": Option<Padding>,
444        },
445        OffsetMinute = "offset_minute" {
446            padding = "padding": Option<Padding>,
447        },
448        OffsetSecond = "offset_second" {
449            padding = "padding": Option<Padding>,
450        },
451        Ordinal = "ordinal" {
452            padding = "padding": Option<Padding>,
453        },
454        Period = "period" {
455            case = "case": Option<PeriodCase>,
456            case_sensitive = "case_sensitive": Option<PeriodCaseSensitive>,
457        },
458        Second = "second" {
459            padding = "padding": Option<Padding>,
460        },
461        Subsecond = "subsecond" {
462            digits = "digits": Option<SubsecondDigits>,
463        },
464        UnixTimestamp = "unix_timestamp" {
465            precision = "precision": Option<UnixTimestampPrecision>,
466            sign_behavior = "sign": Option<SignBehavior>,
467        },
468        Weekday = "weekday" {
469            repr = "repr": Option<WeekdayRepr>,
470            one_indexed = "one_indexed": Option<WeekdayOneIndexed>,
471            case_sensitive = "case_sensitive": Option<WeekdayCaseSensitive>,
472        },
473        WeekNumber = "week_number" {
474            padding = "padding": Option<Padding>,
475            repr = "repr": Option<WeekNumberRepr>,
476        },
477        Year = "year" {
478            padding = "padding": Option<Padding>,
479            repr = "repr": Option<YearRepr>,
480            range = "range": Option<YearRange>,
481            base = "base": Option<YearBase>,
482            sign_behavior = "sign": Option<SignBehavior>,
483        },
484    }
485}
486
487macro_rules! impl_from_ast_component_for {
488    ($([$reject_nonsensical:literal] $ty:ty),+ $(,)?) => {$(
489        impl TryFrom<AstComponent> for $ty {
490            type Error = Error;
491
492            #[inline]
493            fn try_from(component: AstComponent) -> Result<Self, Self::Error> {
494                macro_rules! reject_modifier {
495                    ($modifier:ident, $modifier_str:literal, $context:literal) => {
496                        if $reject_nonsensical && $modifier.value.is_some() {
497                            return Err(Error {
498                                _inner: unused($modifier.span.error(concat!(
499                                    "the '",
500                                    $modifier_str,
501                                    "' modifier is not valid ",
502                                    $context
503                                ))),
504                                public: InvalidFormatDescription::InvalidModifierCombination {
505                                    modifier: $modifier_str,
506                                    context: $context,
507                                    index: $modifier.span.start.byte as usize,
508                                },
509                            });
510                        }
511                    };
512                }
513
514                use crate::format_description::modifier;
515                Ok(match component {
516                    AstComponent::Day(Day { padding }) => Self::Day(modifier::Day {
517                        padding: padding.unwrap_or_default().into(),
518                    }),
519                    AstComponent::End(End { trailing_input }) => Self::End(modifier::End {
520                        trailing_input: trailing_input.unwrap_or_default().into(),
521                    }),
522                    AstComponent::Hour(Hour { padding, base }) => match base.unwrap_or_default() {
523                        HourBase::Twelve => Self::Hour12(modifier::Hour12 {
524                            padding: padding.unwrap_or_default().into(),
525                        }),
526                        HourBase::TwentyFour => Self::Hour24(modifier::Hour24 {
527                            padding: padding.unwrap_or_default().into(),
528                        }),
529                    },
530                    AstComponent::Ignore(Ignore { count }) => Self::Ignore(modifier::Ignore {
531                        count: match *count {
532                            Some(value) => value,
533                            None => bug!("required modifier was not set"),
534                        },
535                    }),
536                    AstComponent::Minute(Minute { padding }) => Self::Minute(modifier::Minute {
537                        padding: padding.unwrap_or_default().into(),
538                    }),
539                    AstComponent::Month(Month {
540                        padding,
541                        repr,
542                        case_sensitive,
543                    }) => match repr.unwrap_or_default() {
544                        MonthRepr::Numerical => {
545                            reject_modifier!(
546                                case_sensitive,
547                                "case_sensitive",
548                                "for numerical month"
549                            );
550                            Self::MonthNumerical(modifier::MonthNumerical {
551                                padding: padding.unwrap_or_default().into(),
552                            })
553                        },
554                        MonthRepr::Long => {
555                            reject_modifier!(padding, "padding", "for long month");
556                            Self::MonthLong(modifier::MonthLong {
557                                case_sensitive: case_sensitive.unwrap_or_default().into(),
558                            })
559                        },
560                        MonthRepr::Short => {
561                            reject_modifier!(padding, "padding", "for short month");
562                            Self::MonthShort(modifier::MonthShort {
563                                case_sensitive: case_sensitive.unwrap_or_default().into(),
564                            })
565                        },
566                    },
567                    AstComponent::OffsetHour(OffsetHour {
568                        sign_behavior,
569                        padding,
570                    }) => Self::OffsetHour(modifier::OffsetHour {
571                        sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
572                        padding: padding.unwrap_or_default().into(),
573                    }),
574                    AstComponent::OffsetMinute(OffsetMinute { padding }) => {
575                        Self::OffsetMinute(modifier::OffsetMinute {
576                            padding: padding.unwrap_or_default().into(),
577                        })
578                    }
579                    AstComponent::OffsetSecond(OffsetSecond { padding }) => {
580                        Self::OffsetSecond(modifier::OffsetSecond {
581                            padding: padding.unwrap_or_default().into(),
582                        })
583                    }
584                    AstComponent::Ordinal(Ordinal { padding }) => Self::Ordinal(modifier::Ordinal {
585                        padding: padding.unwrap_or_default().into(),
586                    }),
587                    AstComponent::Period(Period {
588                        case,
589                        case_sensitive,
590                    }) => Self::Period(modifier::Period {
591                        is_uppercase: case.unwrap_or_default().into(),
592                        case_sensitive: case_sensitive.unwrap_or_default().into(),
593                    }),
594                    AstComponent::Second(Second { padding }) => Self::Second(modifier::Second {
595                        padding: padding.unwrap_or_default().into(),
596                    }),
597                    AstComponent::Subsecond(Subsecond { digits }) => {
598                        Self::Subsecond(modifier::Subsecond {
599                            digits: digits.unwrap_or_default().into(),
600                        })
601                    },
602                    AstComponent::UnixTimestamp(UnixTimestamp {
603                        precision,
604                        sign_behavior,
605                    }) => match precision.unwrap_or_default() {
606                        UnixTimestampPrecision::Second => {
607                            Self::UnixTimestampSecond(modifier::UnixTimestampSecond {
608                                sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
609                            })
610                        }
611                        UnixTimestampPrecision::Millisecond => {
612                            Self::UnixTimestampMillisecond(modifier::UnixTimestampMillisecond {
613                                sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
614                            })
615                        }
616                        UnixTimestampPrecision::Microsecond => {
617                            Self::UnixTimestampMicrosecond(modifier::UnixTimestampMicrosecond {
618                                sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
619                            })
620                        }
621                        UnixTimestampPrecision::Nanosecond => {
622                            Self::UnixTimestampNanosecond(modifier::UnixTimestampNanosecond {
623                                sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
624                            })
625                        }
626                    },
627                    AstComponent::Weekday(Weekday {
628                        repr,
629                        one_indexed,
630                        case_sensitive,
631                    }) => match repr.unwrap_or_default() {
632                        WeekdayRepr::Short => {
633                            reject_modifier!(one_indexed, "one_indexed", "for short weekday");
634                            Self::WeekdayShort(modifier::WeekdayShort {
635                                case_sensitive: case_sensitive.unwrap_or_default().into(),
636                            })
637                        },
638                        WeekdayRepr::Long => {
639                            reject_modifier!(one_indexed, "one_indexed", "for long weekday");
640                            Self::WeekdayLong(modifier::WeekdayLong {
641                                case_sensitive: case_sensitive.unwrap_or_default().into(),
642                            })
643                        },
644                        WeekdayRepr::Sunday => {
645                            reject_modifier!(
646                                case_sensitive,
647                                "case_sensitive",
648                                "for numerical weekday"
649                            );
650                            Self::WeekdaySunday(modifier::WeekdaySunday {
651                                one_indexed: one_indexed.unwrap_or_default().into(),
652                            })
653                        },
654                        WeekdayRepr::Monday => {
655                            reject_modifier!(
656                                case_sensitive,
657                                "case_sensitive",
658                                "for numerical weekday"
659                            );
660                            Self::WeekdayMonday(modifier::WeekdayMonday {
661                                one_indexed: one_indexed.unwrap_or_default().into(),
662                            })
663                        },
664                    },
665                    AstComponent::WeekNumber(WeekNumber { padding, repr }) => {
666                        match repr.unwrap_or_default() {
667                            WeekNumberRepr::Iso => {
668                                Self::WeekNumberIso(modifier::WeekNumberIso {
669                                    padding: padding.unwrap_or_default().into(),
670                                })
671                            },
672                            WeekNumberRepr::Sunday => {
673                                Self::WeekNumberSunday(modifier::WeekNumberSunday {
674                                    padding: padding.unwrap_or_default().into(),
675                                })
676                            },
677                            WeekNumberRepr::Monday => {
678                                Self::WeekNumberMonday(modifier::WeekNumberMonday {
679                                    padding: padding.unwrap_or_default().into(),
680                                })
681                            },
682                        }
683                    }
684                    AstComponent::Year(Year {
685                        padding,
686                        repr,
687                        range,
688                        base,
689                        sign_behavior,
690                    }) => {
691                        #[cfg(not(feature = "large-dates"))]
692                        reject_modifier!(
693                            range,
694                            "range",
695                            "when the `large-dates` feature is not enabled"
696                        );
697
698                        match (
699                            base.unwrap_or_default(),
700                            repr.unwrap_or_default(),
701                            range.unwrap_or_default(),
702                        ) {
703                            #[cfg(feature = "large-dates")]
704                            (YearBase::Calendar, YearRepr::Full, YearRange::Extended) => {
705                                Self::CalendarYearFullExtendedRange(
706                                    modifier::CalendarYearFullExtendedRange {
707                                        padding: padding.unwrap_or_default().into(),
708                                        sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
709                                    },
710                                )
711                            }
712                            (YearBase::Calendar, YearRepr::Full, _) => {
713                                Self::CalendarYearFullStandardRange(
714                                    modifier::CalendarYearFullStandardRange {
715                                        padding: padding.unwrap_or_default().into(),
716                                        sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
717                                    },
718                                )
719                            }
720                            #[cfg(feature = "large-dates")]
721                            (YearBase::Calendar, YearRepr::Century, YearRange::Extended) => {
722                                Self::CalendarYearCenturyExtendedRange(
723                                    modifier::CalendarYearCenturyExtendedRange {
724                                        padding: padding.unwrap_or_default().into(),
725                                        sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
726                                    },
727                                )
728                            }
729                            (YearBase::Calendar, YearRepr::Century, _) => {
730                                Self::CalendarYearCenturyStandardRange(
731                                    modifier::CalendarYearCenturyStandardRange {
732                                        padding: padding.unwrap_or_default().into(),
733                                        sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
734                                    },
735                                )
736                            }
737                            #[cfg(feature = "large-dates")]
738                            (YearBase::IsoWeek, YearRepr::Full, YearRange::Extended) => {
739                                Self::IsoYearFullExtendedRange(modifier::IsoYearFullExtendedRange {
740                                    padding: padding.unwrap_or_default().into(),
741                                    sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
742                                })
743                            }
744                            (YearBase::IsoWeek, YearRepr::Full, _) => {
745                                Self::IsoYearFullStandardRange(modifier::IsoYearFullStandardRange {
746                                    padding: padding.unwrap_or_default().into(),
747                                    sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
748                                })
749                            }
750                            #[cfg(feature = "large-dates")]
751                            (YearBase::IsoWeek, YearRepr::Century, YearRange::Extended) => {
752                                Self::IsoYearCenturyExtendedRange(
753                                    modifier::IsoYearCenturyExtendedRange {
754                                        padding: padding.unwrap_or_default().into(),
755                                        sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
756                                    },
757                                )
758                            }
759                            (YearBase::IsoWeek, YearRepr::Century, _) => {
760                                Self::IsoYearCenturyStandardRange(
761                                    modifier::IsoYearCenturyStandardRange {
762                                        padding: padding.unwrap_or_default().into(),
763                                        sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
764                                    },
765                                )
766                            }
767                            (YearBase::Calendar, YearRepr::LastTwo, _) => {
768                                #[cfg(feature = "large-dates")]
769                                reject_modifier!(range, "range", "when `repr:last_two` is used");
770                                reject_modifier!(
771                                    sign_behavior,
772                                    "sign",
773                                    "when `repr:last_two` is used"
774                                );
775                                Self::CalendarYearLastTwo(modifier::CalendarYearLastTwo {
776                                    padding: padding.unwrap_or_default().into(),
777                                })
778                            }
779                            (YearBase::IsoWeek, YearRepr::LastTwo, _) => {
780                                #[cfg(feature = "large-dates")]
781                                reject_modifier!(range, "range", "when `repr:last_two` is used");
782                                reject_modifier!(
783                                    sign_behavior,
784                                    "sign",
785                                    "when `repr:last_two` is used"
786                                );
787                                Self::IsoYearLastTwo(modifier::IsoYearLastTwo {
788                                    padding: padding.unwrap_or_default().into(),
789                                })
790                            }
791                        }
792                    }
793                })
794            }
795        })+
796    }
797}
798
799impl_from_ast_component_for!(
800    [false] crate::format_description::Component,
801    [true] crate::format_description::__private::FormatDescriptionV3Inner<'_>,
802);
803
804/// Get the target type for a given enum.
805macro_rules! target_ty {
806    ($name:ident $type:ty) => {
807        $type
808    };
809    ($name:ident) => {
810        $crate::format_description::modifier::$name
811    };
812}
813
814/// Get the target value for a given enum.
815macro_rules! target_value {
816    ($name:ident $variant:ident $value:expr) => {
817        $value
818    };
819    ($name:ident $variant:ident) => {
820        $crate::format_description::modifier::$name::$variant
821    };
822}
823
824trait ModifierValue {
825    type Type;
826}
827
828/// Declare the various modifiers.
829///
830/// For the general case, ordinary syntax can be used. Note that you _must_ declare a default
831/// variant. The only significant change is that the string representation of the variant must be
832/// provided after the variant name. For example, `Numerical = b"numerical"` declares a variant
833/// named `Numerical` with the string representation `b"numerical"`. This is the value that will be
834/// used when parsing the modifier. The value is not case sensitive.
835///
836/// If the type in the public API does not have the same name as the type in the internal
837/// representation, then the former must be specified in parenthesis after the internal name. For
838/// example, `HourBase(bool)` has an internal name "HourBase", but is represented as a boolean in
839/// the public API.
840///
841/// By default, the internal variant name is assumed to be the same as the public variant name. If
842/// this is not the case, the qualified path to the variant must be specified in parenthesis after
843/// the internal variant name. For example, `Twelve(true)` has an internal variant name "Twelve",
844/// but is represented as `true` in the public API.
845macro_rules! modifier {
846    ($(
847        $(#[expect($expect_inner:meta)])?
848        enum $name:ident $(($target_ty:ty))? {
849            $(
850                $(#[$attr:meta])?
851                $variant:ident $(($target_value:expr))? = $parse_variant:literal
852            ),* $(,)?
853        }
854    )+) => {$(
855        #[derive(Default, Clone, Copy)]
856        enum $name {
857            $($(#[$attr])? $variant),*
858        }
859
860        impl $name {
861            /// Parse the modifier from its string representation.
862            #[inline]
863            fn from_modifier_value<const VERSION: u8>(
864                value: &Spanned<&str>,
865            ) -> Result<Self, Error>
866            {
867                assert_version!();
868
869                $(if ident_eq::<VERSION>(&value, $parse_variant) {
870                    return Ok(Self::$variant);
871                })*
872                Err(Error {
873                    _inner: unused(value.span.error("invalid modifier value")),
874                    public: InvalidFormatDescription::InvalidModifier {
875                        value: (**value).to_owned(),
876                        index: value.span.start.byte as usize,
877                    },
878                })
879            }
880        }
881
882        $(#[expect($expect_inner)])?
883        impl ModifierValue for $name {
884            type Type = target_ty!($name $($target_ty)?);
885        }
886
887        $(#[expect($expect_inner)])?
888        impl From<$name> for <$name as ModifierValue>::Type {
889            #[inline]
890            fn from(modifier: $name) -> Self {
891                match modifier {
892                    $($name::$variant => target_value!($name $variant $($target_value)?)),*
893                }
894            }
895        }
896    )+};
897}
898
899// Keep in alphabetical order.
900modifier! {
901    enum HourBase(bool) {
902        Twelve(true) = "12",
903        #[default]
904        TwentyFour(false) = "24",
905    }
906
907    enum MonthCaseSensitive(bool) {
908        False(false) = "false",
909        #[default]
910        True(true) = "true",
911    }
912
913    #[expect(deprecated)]
914    enum MonthRepr {
915        #[default]
916        Numerical = "numerical",
917        Long = "long",
918        Short = "short",
919    }
920
921    enum OptionalFormat(bool) {
922        False(false) = "false",
923        #[default]
924        True(true) = "true",
925    }
926
927    enum Padding {
928        Space = "space",
929        #[default]
930        Zero = "zero",
931        None = "none",
932    }
933
934    enum PeriodCase(bool) {
935        Lower(false) = "lower",
936        #[default]
937        Upper(true) = "upper",
938    }
939
940    enum PeriodCaseSensitive(bool) {
941        False(false) = "false",
942        #[default]
943        True(true) = "true",
944    }
945
946    enum SignBehavior(bool) {
947        #[default]
948        Automatic(false) = "automatic",
949        Mandatory(true) = "mandatory",
950    }
951
952    enum SubsecondDigits {
953        One = "1",
954        Two = "2",
955        Three = "3",
956        Four = "4",
957        Five = "5",
958        Six = "6",
959        Seven = "7",
960        Eight = "8",
961        Nine = "9",
962        #[default]
963        OneOrMore = "1+",
964    }
965
966    enum TrailingInput {
967        #[default]
968        Prohibit = "prohibit",
969        Discard = "discard",
970    }
971
972    #[expect(deprecated)]
973    enum UnixTimestampPrecision {
974        #[default]
975        Second = "second",
976        Millisecond = "millisecond",
977        Microsecond = "microsecond",
978        Nanosecond = "nanosecond",
979    }
980
981    #[expect(deprecated)]
982    enum WeekNumberRepr {
983        #[default]
984        Iso = "iso",
985        Sunday = "sunday",
986        Monday = "monday",
987    }
988
989    enum WeekdayCaseSensitive(bool) {
990        False(false) = "false",
991        #[default]
992        True(true) = "true",
993    }
994
995    enum WeekdayOneIndexed(bool) {
996        False(false) = "false",
997        #[default]
998        True(true) = "true",
999    }
1000
1001    #[expect(deprecated)]
1002    enum WeekdayRepr {
1003        Short = "short",
1004        #[default]
1005        Long = "long",
1006        Sunday = "sunday",
1007        Monday = "monday",
1008    }
1009
1010    enum YearBase(bool) {
1011        #[default]
1012        Calendar(false) = "calendar",
1013        IsoWeek(true) = "iso_week",
1014    }
1015
1016    #[expect(deprecated)]
1017    enum YearRepr {
1018        #[default]
1019        Full = "full",
1020        Century = "century",
1021        LastTwo = "last_two",
1022    }
1023
1024    // For v1 and v2 format descriptions, the default is `extended`. For v3 format descriptions,
1025    // the default is `standard`. For backwards compatibility, the default here needs to stay
1026    // `extended`.
1027    #[expect(deprecated)]
1028    enum YearRange {
1029        Standard = "standard",
1030        #[default]
1031        Extended = "extended",
1032    }
1033}
1034
1035/// Parse a modifier value using `FromStr`. Requires the modifier value to be valid UTF-8.
1036#[inline]
1037fn parse_from_modifier_value<T>(value: &Spanned<&str>) -> Result<T, Error>
1038where
1039    T: FromStr,
1040{
1041    value.parse::<T>().map_err(|_| Error {
1042        _inner: unused(value.span.error("invalid modifier value")),
1043        public: InvalidFormatDescription::InvalidModifier {
1044            value: (**value).to_owned(),
1045            index: value.span.start.byte as usize,
1046        },
1047    })
1048}