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