1use alloc::borrow::ToOwned as _;
4use core::num::NonZero;
5use core::str::{self, FromStr};
6
7use super::lexer_ast::Modifier;
8use super::{Error, OptionExt as _, Span, Spanned, SpannedValue as _, unused};
9use crate::error::InvalidFormatDescription;
10use crate::format_description::__private::FormatDescriptionV3Inner;
11use crate::hint;
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 hint::cold_path();
28 if version!(3..) && parsed.$field.is_some() {
29 break 'block Err(Error {
30 _inner: unused(modifier.key_span().error("duplicate modifier key")),
31 public: InvalidFormatDescription::DuplicateModifier {
32 name: stringify!($field),
33 index: modifier.key.location.byte as usize,
34 }
35 });
36 }
37 match <$modifier>::from_modifier_value::<VERSION>(
38 || modifier.value_span(),
39 modifier.value,
40 ) {
41 Ok(value) => {
42 parsed.$field = Some(
43 <<$modifier as ModifierValue>::Type>::from(value)
44 .spanned(modifier.value_span())
45 )
46 },
47 Err(err) => {
48 hint::cold_path();
49 break 'block Err(err)
50 },
51 }
52 continue;
53 })*
54
55 hint::cold_path();
56 break 'block Err(Error {
57 _inner: unused(modifier.key_span().error("invalid modifier key")),
58 public: InvalidFormatDescription::InvalidModifier {
59 value: (*modifier.key).to_owned(),
60 index: modifier.key.location.byte as usize,
61 }
62 });
63 }
64
65 Ok(parsed)
66 }
67 };
68}
69
70#[inline]
71pub(super) fn ident_eq<const VERSION: u8>(provided: &str, expected: &str) -> bool {
72 assert_version!();
73 if version!(3..) {
74 provided == expected
75 } else {
76 provided.len() == expected.len()
77 && core::iter::zip(provided.bytes(), expected.bytes())
78 .all(|(p, e)| p.to_ascii_lowercase() == e)
79 }
80}
81
82pub(super) fn parse_optional_format_modifier<const VERSION: u8>(
83 modifiers: &[Modifier<'_>],
84) -> Result<Spanned<bool>, Error> {
85 let modifiers = parse_modifiers!(VERSION, modifiers, struct {
86 format: OptionalFormat,
87 })?;
88
89 Ok(modifiers.format.transpose().map(|val| val.unwrap_or(true)))
90}
91
92macro_rules! component_definition {
94 (@if_required required then { $($then:tt)* } $(else { $($else:tt)* })?) => { $($then)* };
95 (@if_required then { $($then:tt)* } $(else { $($else:tt)* })?) => { $($($else)*)? };
96 (@if_from_str from_str then { $($then:tt)* } $(else { $($else:tt)* })?) => { $($then)* };
97 (@if_from_str then { $($then:tt)* } $(else { $($else:tt)* })?) => { $($($else)*)? };
98 (@if_year "year" $($then:tt)*) => { $($then)* };
99 (@if_year $lit:tt $($then:tt)*) => {};
100
101 ($vis:vis enum $name:ident {$(
102 $variant:ident = $parse_variant:tt {$(
103 $(#[$required:tt])?
104 $field:ident = $parse_field:literal:
105 Option<$(#[$from_str:tt])? $field_type:ty>
106 ),* $(,)?}
107 ),* $(,)?}) => {
108 $vis enum $name {
109 $($variant($variant),)*
110 }
111
112 $($vis struct $variant {
113 $($field: Spanned<Option<$field_type>>),*
114 })*
115
116 $(impl $variant {
117 #[inline]
119 fn with_modifiers<const VERSION: u8>(
120 modifiers: &[Modifier<'_>],
121 _component_span: Span,
122 ) -> Result<Self, Error>
123 {
124 assert_version!();
125
126 #[allow(unused_mut)]
128 let mut this = Self {
129 $($field: None.spanned(Span::DUMMY)),*
130 };
131
132 for modifier in modifiers {
133 $(if ident_eq::<VERSION>(&modifier.key, $parse_field) {
134 if version!(3..) && this.$field.is_some() {
135 hint::cold_path();
136 return Err(Error {
137 _inner: unused(modifier.key_span().error("duplicate modifier key")),
138 public: InvalidFormatDescription::DuplicateModifier {
139 name: stringify!($field),
140 index: modifier.key.location.byte as usize,
141 }
142 });
143 }
144 this.$field = Some(
145 component_definition!(@if_from_str $($from_str)?
146 then {
147 parse_from_modifier_value::<$field_type>(
148 || modifier.value_span(),
149 modifier.value,
150 )?
151 } else {
152 <$field_type>::from_modifier_value::<VERSION>(
153 || modifier.value_span(),
154 modifier.value,
155 )?
156 }
157 )
158 ).spanned(modifier.key_value_span());
159 continue;
160 })*
161
162 hint::cold_path();
163 return Err(Error {
164 _inner: unused(modifier.key_span().error("invalid modifier key")),
165 public: InvalidFormatDescription::InvalidModifier {
166 value: (*modifier.key).to_owned(),
167 index: modifier.key.location.byte as usize,
168 }
169 });
170 }
171
172 $(component_definition! { @if_required $($required)? then {
173 if this.$field.is_none() {
174 hint::cold_path();
175 return Err(Error {
176 _inner: unused(_component_span.error("missing required modifier")),
177 public:
178 InvalidFormatDescription::MissingRequiredModifier {
179 name: $parse_field,
180 index: _component_span.start.byte as usize,
181 }
182 });
183 }
184 }})*
185
186 Ok(this)
187 }
188 })*
189
190 #[inline]
192 pub(super) fn component_from_ast<const VERSION: u8>(
193 name: &Spanned<&str>,
194 modifiers: &[Modifier<'_>],
195 ) -> Result<AstComponent, Error> {
196 assert_version!();
197
198 $(if ident_eq::<VERSION>(&name, $parse_variant) {
199 #[allow(unused_mut)] let mut component = AstComponent::$variant(
201 try_likely_ok!($variant::with_modifiers::<VERSION>(&modifiers, name.span))
202 );
203 component_definition!(@if_year $parse_variant
204 #[cfg(feature = "large-dates")]
205 if version!(3..)
206 && let AstComponent::Year(y) = &mut component
207 && y.range.value.is_none()
208 {
209 y.range = Some(YearRange::Standard).spanned(Span::DUMMY);
210 }
211 );
212 return Ok(component);
213 })*
214
215 hint::cold_path();
216 Err(Error {
217 _inner: unused(name.span.error("invalid component")),
218 public: InvalidFormatDescription::InvalidComponentName {
219 name: (**name).to_owned(),
220 index: name.span.start.byte as usize,
221 },
222 })
223 }
224 }
225}
226
227component_definition! {
229 pub(super) enum AstComponent {
230 Day = "day" {
231 padding = "padding": Option<Padding>,
232 },
233 End = "end" {
234 trailing_input = "trailing_input": Option<TrailingInput>,
235 },
236 Hour = "hour" {
237 padding = "padding": Option<Padding>,
238 base = "repr": Option<HourBase>,
239 },
240 Ignore = "ignore" {
241 #[required]
242 count = "count": Option<#[from_str] NonZero<u16>>,
243 },
244 Minute = "minute" {
245 padding = "padding": Option<Padding>,
246 },
247 Month = "month" {
248 padding = "padding": Option<Padding>,
249 repr = "repr": Option<MonthRepr>,
250 case_sensitive = "case_sensitive": Option<MonthCaseSensitive>,
251 },
252 OffsetHour = "offset_hour" {
253 sign_behavior = "sign": Option<SignBehavior>,
254 padding = "padding": Option<Padding>,
255 },
256 OffsetMinute = "offset_minute" {
257 padding = "padding": Option<Padding>,
258 },
259 OffsetSecond = "offset_second" {
260 padding = "padding": Option<Padding>,
261 },
262 Ordinal = "ordinal" {
263 padding = "padding": Option<Padding>,
264 },
265 Period = "period" {
266 case = "case": Option<PeriodCase>,
267 case_sensitive = "case_sensitive": Option<PeriodCaseSensitive>,
268 },
269 Second = "second" {
270 padding = "padding": Option<Padding>,
271 },
272 Subsecond = "subsecond" {
273 digits = "digits": Option<SubsecondDigits>,
274 },
275 UnixTimestamp = "unix_timestamp" {
276 precision = "precision": Option<UnixTimestampPrecision>,
277 sign_behavior = "sign": Option<SignBehavior>,
278 },
279 Weekday = "weekday" {
280 repr = "repr": Option<WeekdayRepr>,
281 one_indexed = "one_indexed": Option<WeekdayOneIndexed>,
282 case_sensitive = "case_sensitive": Option<WeekdayCaseSensitive>,
283 },
284 WeekNumber = "week_number" {
285 padding = "padding": Option<Padding>,
286 repr = "repr": Option<WeekNumberRepr>,
287 },
288 Year = "year" {
289 padding = "padding": Option<Padding>,
290 repr = "repr": Option<YearRepr>,
291 range = "range": Option<YearRange>,
292 base = "base": Option<YearBase>,
293 sign_behavior = "sign": Option<SignBehavior>,
294 },
295 }
296}
297
298macro_rules! impl_from_ast_component_for {
299 ($([$reject_nonsensical:literal] $ty:ty),+ $(,)?) => {$(
300 impl TryFrom<AstComponent> for $ty {
301 type Error = Error;
302
303 #[inline]
304 fn try_from(component: AstComponent) -> Result<Self, Self::Error> {
305 macro_rules! reject_modifier {
306 ($modifier:ident, $modifier_str:literal, $context:literal) => {
307 if $reject_nonsensical && $modifier.value.is_some() {
308 hint::cold_path();
309 return Err(Error {
310 _inner: unused($modifier.span.error(concat!(
311 "the '",
312 $modifier_str,
313 "' modifier is not valid ",
314 $context
315 ))),
316 public: InvalidFormatDescription::InvalidModifierCombination {
317 modifier: $modifier_str,
318 context: $context,
319 index: $modifier.span.start.byte as usize,
320 },
321 });
322 }
323 };
324 }
325
326 use crate::format_description::modifier;
327 Ok(match component {
328 AstComponent::Day(Day { padding }) => Self::Day(modifier::Day {
329 padding: padding.unwrap_or_default().into(),
330 }),
331 AstComponent::End(End { trailing_input }) => Self::End(modifier::End {
332 trailing_input: trailing_input.unwrap_or_default().into(),
333 }),
334 AstComponent::Hour(Hour { padding, base }) => match base.unwrap_or_default() {
335 HourBase::Twelve => Self::Hour12(modifier::Hour12 {
336 padding: padding.unwrap_or_default().into(),
337 }),
338 HourBase::TwentyFour => Self::Hour24(modifier::Hour24 {
339 padding: padding.unwrap_or_default().into(),
340 }),
341 },
342 AstComponent::Ignore(Ignore { count }) => Self::Ignore(modifier::Ignore {
343 count: match *count {
344 Some(value) => value,
345 None => bug!("required modifier was not set"),
346 },
347 }),
348 AstComponent::Minute(Minute { padding }) => Self::Minute(modifier::Minute {
349 padding: padding.unwrap_or_default().into(),
350 }),
351 AstComponent::Month(Month {
352 padding,
353 repr,
354 case_sensitive,
355 }) => match repr.unwrap_or_default() {
356 MonthRepr::Numerical => {
357 reject_modifier!(
358 case_sensitive,
359 "case_sensitive",
360 "for numerical month"
361 );
362 Self::MonthNumerical(modifier::MonthNumerical {
363 padding: padding.unwrap_or_default().into(),
364 })
365 },
366 MonthRepr::Long => {
367 reject_modifier!(padding, "padding", "for long month");
368 Self::MonthLong(modifier::MonthLong {
369 case_sensitive: case_sensitive.unwrap_or_default().into(),
370 })
371 },
372 MonthRepr::Short => {
373 reject_modifier!(padding, "padding", "for short month");
374 Self::MonthShort(modifier::MonthShort {
375 case_sensitive: case_sensitive.unwrap_or_default().into(),
376 })
377 },
378 },
379 AstComponent::OffsetHour(OffsetHour {
380 sign_behavior,
381 padding,
382 }) => Self::OffsetHour(modifier::OffsetHour {
383 sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
384 padding: padding.unwrap_or_default().into(),
385 }),
386 AstComponent::OffsetMinute(OffsetMinute { padding }) => {
387 Self::OffsetMinute(modifier::OffsetMinute {
388 padding: padding.unwrap_or_default().into(),
389 })
390 }
391 AstComponent::OffsetSecond(OffsetSecond { padding }) => {
392 Self::OffsetSecond(modifier::OffsetSecond {
393 padding: padding.unwrap_or_default().into(),
394 })
395 }
396 AstComponent::Ordinal(Ordinal { padding }) => Self::Ordinal(modifier::Ordinal {
397 padding: padding.unwrap_or_default().into(),
398 }),
399 AstComponent::Period(Period {
400 case,
401 case_sensitive,
402 }) => Self::Period(modifier::Period {
403 is_uppercase: case.unwrap_or_default().into(),
404 case_sensitive: case_sensitive.unwrap_or_default().into(),
405 }),
406 AstComponent::Second(Second { padding }) => Self::Second(modifier::Second {
407 padding: padding.unwrap_or_default().into(),
408 }),
409 AstComponent::Subsecond(Subsecond { digits }) => {
410 Self::Subsecond(modifier::Subsecond {
411 digits: digits.unwrap_or_default().into(),
412 })
413 },
414 AstComponent::UnixTimestamp(UnixTimestamp {
415 precision,
416 sign_behavior,
417 }) => match precision.unwrap_or_default() {
418 UnixTimestampPrecision::Second => {
419 Self::UnixTimestampSecond(modifier::UnixTimestampSecond {
420 sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
421 })
422 }
423 UnixTimestampPrecision::Millisecond => {
424 Self::UnixTimestampMillisecond(modifier::UnixTimestampMillisecond {
425 sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
426 })
427 }
428 UnixTimestampPrecision::Microsecond => {
429 Self::UnixTimestampMicrosecond(modifier::UnixTimestampMicrosecond {
430 sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
431 })
432 }
433 UnixTimestampPrecision::Nanosecond => {
434 Self::UnixTimestampNanosecond(modifier::UnixTimestampNanosecond {
435 sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
436 })
437 }
438 },
439 AstComponent::Weekday(Weekday {
440 repr,
441 one_indexed,
442 case_sensitive,
443 }) => match repr.unwrap_or_default() {
444 WeekdayRepr::Short => {
445 reject_modifier!(one_indexed, "one_indexed", "for short weekday");
446 Self::WeekdayShort(modifier::WeekdayShort {
447 case_sensitive: case_sensitive.unwrap_or_default().into(),
448 })
449 },
450 WeekdayRepr::Long => {
451 reject_modifier!(one_indexed, "one_indexed", "for long weekday");
452 Self::WeekdayLong(modifier::WeekdayLong {
453 case_sensitive: case_sensitive.unwrap_or_default().into(),
454 })
455 },
456 WeekdayRepr::Sunday => {
457 reject_modifier!(
458 case_sensitive,
459 "case_sensitive",
460 "for numerical weekday"
461 );
462 Self::WeekdaySunday(modifier::WeekdaySunday {
463 one_indexed: one_indexed.unwrap_or_default().into(),
464 })
465 },
466 WeekdayRepr::Monday => {
467 reject_modifier!(
468 case_sensitive,
469 "case_sensitive",
470 "for numerical weekday"
471 );
472 Self::WeekdayMonday(modifier::WeekdayMonday {
473 one_indexed: one_indexed.unwrap_or_default().into(),
474 })
475 },
476 },
477 AstComponent::WeekNumber(WeekNumber { padding, repr }) => {
478 match repr.unwrap_or_default() {
479 WeekNumberRepr::Iso => {
480 Self::WeekNumberIso(modifier::WeekNumberIso {
481 padding: padding.unwrap_or_default().into(),
482 })
483 },
484 WeekNumberRepr::Sunday => {
485 Self::WeekNumberSunday(modifier::WeekNumberSunday {
486 padding: padding.unwrap_or_default().into(),
487 })
488 },
489 WeekNumberRepr::Monday => {
490 Self::WeekNumberMonday(modifier::WeekNumberMonday {
491 padding: padding.unwrap_or_default().into(),
492 })
493 },
494 }
495 }
496 AstComponent::Year(Year {
497 padding,
498 repr,
499 range,
500 base,
501 sign_behavior,
502 }) => {
503 #[cfg(not(feature = "large-dates"))]
504 reject_modifier!(
505 range,
506 "range",
507 "when the `large-dates` feature is not enabled"
508 );
509
510 match (
511 base.unwrap_or_default(),
512 repr.unwrap_or_default(),
513 range.unwrap_or_default(),
514 ) {
515 #[cfg(feature = "large-dates")]
516 (YearBase::Calendar, YearRepr::Full, YearRange::Extended) => {
517 Self::CalendarYearFullExtendedRange(
518 modifier::CalendarYearFullExtendedRange {
519 padding: padding.unwrap_or_default().into(),
520 sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
521 },
522 )
523 }
524 (YearBase::Calendar, YearRepr::Full, _) => {
525 Self::CalendarYearFullStandardRange(
526 modifier::CalendarYearFullStandardRange {
527 padding: padding.unwrap_or_default().into(),
528 sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
529 },
530 )
531 }
532 #[cfg(feature = "large-dates")]
533 (YearBase::Calendar, YearRepr::Century, YearRange::Extended) => {
534 Self::CalendarYearCenturyExtendedRange(
535 modifier::CalendarYearCenturyExtendedRange {
536 padding: padding.unwrap_or_default().into(),
537 sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
538 },
539 )
540 }
541 (YearBase::Calendar, YearRepr::Century, _) => {
542 Self::CalendarYearCenturyStandardRange(
543 modifier::CalendarYearCenturyStandardRange {
544 padding: padding.unwrap_or_default().into(),
545 sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
546 },
547 )
548 }
549 #[cfg(feature = "large-dates")]
550 (YearBase::IsoWeek, YearRepr::Full, YearRange::Extended) => {
551 Self::IsoYearFullExtendedRange(modifier::IsoYearFullExtendedRange {
552 padding: padding.unwrap_or_default().into(),
553 sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
554 })
555 }
556 (YearBase::IsoWeek, YearRepr::Full, _) => {
557 Self::IsoYearFullStandardRange(modifier::IsoYearFullStandardRange {
558 padding: padding.unwrap_or_default().into(),
559 sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
560 })
561 }
562 #[cfg(feature = "large-dates")]
563 (YearBase::IsoWeek, YearRepr::Century, YearRange::Extended) => {
564 Self::IsoYearCenturyExtendedRange(
565 modifier::IsoYearCenturyExtendedRange {
566 padding: padding.unwrap_or_default().into(),
567 sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
568 },
569 )
570 }
571 (YearBase::IsoWeek, YearRepr::Century, _) => {
572 Self::IsoYearCenturyStandardRange(
573 modifier::IsoYearCenturyStandardRange {
574 padding: padding.unwrap_or_default().into(),
575 sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
576 },
577 )
578 }
579 (YearBase::Calendar, YearRepr::LastTwo, _) => {
580 #[cfg(feature = "large-dates")]
581 reject_modifier!(range, "range", "when `repr:last_two` is used");
582 reject_modifier!(
583 sign_behavior,
584 "sign",
585 "when `repr:last_two` is used"
586 );
587 Self::CalendarYearLastTwo(modifier::CalendarYearLastTwo {
588 padding: padding.unwrap_or_default().into(),
589 })
590 }
591 (YearBase::IsoWeek, YearRepr::LastTwo, _) => {
592 #[cfg(feature = "large-dates")]
593 reject_modifier!(range, "range", "when `repr:last_two` is used");
594 reject_modifier!(
595 sign_behavior,
596 "sign",
597 "when `repr:last_two` is used"
598 );
599 Self::IsoYearLastTwo(modifier::IsoYearLastTwo {
600 padding: padding.unwrap_or_default().into(),
601 })
602 }
603 }
604 }
605 })
606 }
607 })+
608 }
609}
610
611impl_from_ast_component_for!(
612 [false] crate::format_description::Component,
613 [true] FormatDescriptionV3Inner<'_>,
614);
615
616macro_rules! target_ty {
618 ($name:ident $type:ty) => {
619 $type
620 };
621 ($name:ident) => {
622 $crate::format_description::modifier::$name
623 };
624}
625
626macro_rules! target_value {
628 ($name:ident $variant:ident $value:expr) => {
629 $value
630 };
631 ($name:ident $variant:ident) => {
632 $crate::format_description::modifier::$name::$variant
633 };
634}
635
636trait ModifierValue {
637 type Type;
638}
639
640macro_rules! modifier {
658 ($(
659 $(#[expect($expect_inner:meta)])?
660 enum $name:ident $(($target_ty:ty))? {
661 $(
662 $(#[$attr:meta])?
663 $variant:ident $(($target_value:expr))? = $parse_variant:literal
664 ),* $(,)?
665 }
666 )+) => {$(
667 #[derive(Default, Clone, Copy)]
668 enum $name {
669 $($(#[$attr])? $variant),*
670 }
671
672 impl $name {
673 #[inline]
675 fn from_modifier_value<const VERSION: u8>(
676 value_span: impl FnOnce() -> Span,
677 value: &str,
678 ) -> Result<Self, Error>
679 {
680 assert_version!();
681
682 $(if ident_eq::<VERSION>(&value, $parse_variant) {
683 return Ok(Self::$variant);
684 })*
685
686 hint::cold_path();
687 let span = value_span();
688 Err(Error {
689 _inner: unused(span.error("invalid modifier value")),
690 public: InvalidFormatDescription::InvalidModifier {
691 value: value.to_owned(),
692 index: span.start.byte as usize,
693 },
694 })
695 }
696 }
697
698 $(#[expect($expect_inner)])?
699 impl ModifierValue for $name {
700 type Type = target_ty!($name $($target_ty)?);
701 }
702
703 $(#[expect($expect_inner)])?
704 impl From<$name> for target_ty!($name $($target_ty)?) {
705 #[inline]
706 fn from(modifier: $name) -> Self {
707 match modifier {
708 $($name::$variant => target_value!($name $variant $($target_value)?)),*
709 }
710 }
711 }
712 )+};
713}
714
715modifier! {
717 enum HourBase(bool) {
718 Twelve(true) = "12",
719 #[default]
720 TwentyFour(false) = "24",
721 }
722
723 enum MonthCaseSensitive(bool) {
724 False(false) = "false",
725 #[default]
726 True(true) = "true",
727 }
728
729 #[expect(deprecated)]
730 enum MonthRepr {
731 #[default]
732 Numerical = "numerical",
733 Long = "long",
734 Short = "short",
735 }
736
737 enum OptionalFormat(bool) {
738 False(false) = "false",
739 #[default]
740 True(true) = "true",
741 }
742
743 enum Padding {
744 Space = "space",
745 #[default]
746 Zero = "zero",
747 None = "none",
748 }
749
750 enum PeriodCase(bool) {
751 Lower(false) = "lower",
752 #[default]
753 Upper(true) = "upper",
754 }
755
756 enum PeriodCaseSensitive(bool) {
757 False(false) = "false",
758 #[default]
759 True(true) = "true",
760 }
761
762 enum SignBehavior(bool) {
763 #[default]
764 Automatic(false) = "automatic",
765 Mandatory(true) = "mandatory",
766 }
767
768 enum SubsecondDigits {
769 One = "1",
770 Two = "2",
771 Three = "3",
772 Four = "4",
773 Five = "5",
774 Six = "6",
775 Seven = "7",
776 Eight = "8",
777 Nine = "9",
778 #[default]
779 OneOrMore = "1+",
780 }
781
782 enum TrailingInput {
783 #[default]
784 Prohibit = "prohibit",
785 Discard = "discard",
786 }
787
788 #[expect(deprecated)]
789 enum UnixTimestampPrecision {
790 #[default]
791 Second = "second",
792 Millisecond = "millisecond",
793 Microsecond = "microsecond",
794 Nanosecond = "nanosecond",
795 }
796
797 #[expect(deprecated)]
798 enum WeekNumberRepr {
799 #[default]
800 Iso = "iso",
801 Sunday = "sunday",
802 Monday = "monday",
803 }
804
805 enum WeekdayCaseSensitive(bool) {
806 False(false) = "false",
807 #[default]
808 True(true) = "true",
809 }
810
811 enum WeekdayOneIndexed(bool) {
812 False(false) = "false",
813 #[default]
814 True(true) = "true",
815 }
816
817 #[expect(deprecated)]
818 enum WeekdayRepr {
819 Short = "short",
820 #[default]
821 Long = "long",
822 Sunday = "sunday",
823 Monday = "monday",
824 }
825
826 enum YearBase(bool) {
827 #[default]
828 Calendar(false) = "calendar",
829 IsoWeek(true) = "iso_week",
830 }
831
832 #[expect(deprecated)]
833 enum YearRepr {
834 #[default]
835 Full = "full",
836 Century = "century",
837 LastTwo = "last_two",
838 }
839
840 #[expect(deprecated)]
844 enum YearRange {
845 Standard = "standard",
846 #[default]
847 Extended = "extended",
848 }
849}
850
851#[inline]
853fn parse_from_modifier_value<T>(value_span: impl FnOnce() -> Span, value: &str) -> Result<T, Error>
854where
855 T: FromStr,
856{
857 value.parse::<T>().map_err(|_| {
858 hint::cold_path();
859 let span = value_span();
860 Error {
861 _inner: unused(span.error("invalid modifier value")),
862 public: InvalidFormatDescription::InvalidModifier {
863 value: value.to_owned(),
864 index: span.start.byte as usize,
865 },
866 }
867 })
868}