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