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