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