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