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