Skip to main content

time_macros/format_description/
format_item.rs

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