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