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 if version!(3..)
205 && let AstComponent::Year(y) = &mut component
206 && y.range.value.is_none()
207 {
208 y.range = Some(YearRange::Standard).spanned(Span::DUMMY);
209 }
210 );
211 return Ok(component);
212 })*
213
214 hint::cold_path();
215 Err(Error {
216 _inner: unused(name.span.error("invalid component")),
217 public: InvalidFormatDescription::InvalidComponentName {
218 name: (**name).to_owned(),
219 index: name.span.start.byte as usize,
220 },
221 })
222 }
223 }
224}
225
226component_definition! {
228 pub(super) enum AstComponent {
229 Day = "day" {
230 padding = "padding": Option<Padding>,
231 },
232 End = "end" {
233 trailing_input = "trailing_input": Option<TrailingInput>,
234 },
235 Hour = "hour" {
236 padding = "padding": Option<Padding>,
237 base = "repr": Option<HourBase>,
238 },
239 Ignore = "ignore" {
240 #[required]
241 count = "count": Option<#[from_str] NonZero<u16>>,
242 },
243 Minute = "minute" {
244 padding = "padding": Option<Padding>,
245 },
246 Month = "month" {
247 padding = "padding": Option<Padding>,
248 repr = "repr": Option<MonthRepr>,
249 case_sensitive = "case_sensitive": Option<MonthCaseSensitive>,
250 },
251 OffsetHour = "offset_hour" {
252 sign_behavior = "sign": Option<SignBehavior>,
253 padding = "padding": Option<Padding>,
254 },
255 OffsetMinute = "offset_minute" {
256 padding = "padding": Option<Padding>,
257 },
258 OffsetSecond = "offset_second" {
259 padding = "padding": Option<Padding>,
260 },
261 Ordinal = "ordinal" {
262 padding = "padding": Option<Padding>,
263 },
264 Period = "period" {
265 case = "case": Option<PeriodCase>,
266 case_sensitive = "case_sensitive": Option<PeriodCaseSensitive>,
267 },
268 Second = "second" {
269 padding = "padding": Option<Padding>,
270 },
271 Subsecond = "subsecond" {
272 digits = "digits": Option<SubsecondDigits>,
273 },
274 UnixTimestamp = "unix_timestamp" {
275 precision = "precision": Option<UnixTimestampPrecision>,
276 sign_behavior = "sign": Option<SignBehavior>,
277 },
278 Weekday = "weekday" {
279 repr = "repr": Option<WeekdayRepr>,
280 one_indexed = "one_indexed": Option<WeekdayOneIndexed>,
281 case_sensitive = "case_sensitive": Option<WeekdayCaseSensitive>,
282 },
283 WeekNumber = "week_number" {
284 padding = "padding": Option<Padding>,
285 repr = "repr": Option<WeekNumberRepr>,
286 },
287 Year = "year" {
288 padding = "padding": Option<Padding>,
289 repr = "repr": Option<YearRepr>,
290 range = "range": Option<YearRange>,
291 base = "base": Option<YearBase>,
292 sign_behavior = "sign": Option<SignBehavior>,
293 },
294 }
295}
296
297macro_rules! impl_from_ast_component_for {
298 ($([$reject_nonsensical:literal] $ty:ty),+ $(,)?) => {$(
299 impl TryFrom<AstComponent> for $ty {
300 type Error = Error;
301
302 #[inline]
303 fn try_from(component: AstComponent) -> Result<Self, Self::Error> {
304 macro_rules! reject_modifier {
305 ($modifier:ident, $modifier_str:literal, $context:literal) => {
306 if $reject_nonsensical && $modifier.value.is_some() {
307 hint::cold_path();
308 return Err(Error {
309 _inner: unused($modifier.span.error(concat!(
310 "the '",
311 $modifier_str,
312 "' modifier is not valid ",
313 $context
314 ))),
315 public: InvalidFormatDescription::InvalidModifierCombination {
316 modifier: $modifier_str,
317 context: $context,
318 index: $modifier.span.start.byte as usize,
319 },
320 });
321 }
322 };
323 }
324
325 use crate::format_description::modifier;
326 Ok(match component {
327 AstComponent::Day(Day { padding }) => Self::Day(modifier::Day {
328 padding: padding.unwrap_or_default().into(),
329 }),
330 AstComponent::End(End { trailing_input }) => Self::End(modifier::End {
331 trailing_input: trailing_input.unwrap_or_default().into(),
332 }),
333 AstComponent::Hour(Hour { padding, base }) => match base.unwrap_or_default() {
334 HourBase::Twelve => Self::Hour12(modifier::Hour12 {
335 padding: padding.unwrap_or_default().into(),
336 }),
337 HourBase::TwentyFour => Self::Hour24(modifier::Hour24 {
338 padding: padding.unwrap_or_default().into(),
339 }),
340 },
341 AstComponent::Ignore(Ignore { count }) => Self::Ignore(modifier::Ignore {
342 count: match *count {
343 Some(value) => value,
344 None => bug!("required modifier was not set"),
345 },
346 }),
347 AstComponent::Minute(Minute { padding }) => Self::Minute(modifier::Minute {
348 padding: padding.unwrap_or_default().into(),
349 }),
350 AstComponent::Month(Month {
351 padding,
352 repr,
353 case_sensitive,
354 }) => match repr.unwrap_or_default() {
355 MonthRepr::Numerical => {
356 reject_modifier!(
357 case_sensitive,
358 "case_sensitive",
359 "for numerical month"
360 );
361 Self::MonthNumerical(modifier::MonthNumerical {
362 padding: padding.unwrap_or_default().into(),
363 })
364 },
365 MonthRepr::Long => {
366 reject_modifier!(padding, "padding", "for long month");
367 Self::MonthLong(modifier::MonthLong {
368 case_sensitive: case_sensitive.unwrap_or_default().into(),
369 })
370 },
371 MonthRepr::Short => {
372 reject_modifier!(padding, "padding", "for short month");
373 Self::MonthShort(modifier::MonthShort {
374 case_sensitive: case_sensitive.unwrap_or_default().into(),
375 })
376 },
377 },
378 AstComponent::OffsetHour(OffsetHour {
379 sign_behavior,
380 padding,
381 }) => Self::OffsetHour(modifier::OffsetHour {
382 sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
383 padding: padding.unwrap_or_default().into(),
384 }),
385 AstComponent::OffsetMinute(OffsetMinute { padding }) => {
386 Self::OffsetMinute(modifier::OffsetMinute {
387 padding: padding.unwrap_or_default().into(),
388 })
389 }
390 AstComponent::OffsetSecond(OffsetSecond { padding }) => {
391 Self::OffsetSecond(modifier::OffsetSecond {
392 padding: padding.unwrap_or_default().into(),
393 })
394 }
395 AstComponent::Ordinal(Ordinal { padding }) => Self::Ordinal(modifier::Ordinal {
396 padding: padding.unwrap_or_default().into(),
397 }),
398 AstComponent::Period(Period {
399 case,
400 case_sensitive,
401 }) => Self::Period(modifier::Period {
402 is_uppercase: case.unwrap_or_default().into(),
403 case_sensitive: case_sensitive.unwrap_or_default().into(),
404 }),
405 AstComponent::Second(Second { padding }) => Self::Second(modifier::Second {
406 padding: padding.unwrap_or_default().into(),
407 }),
408 AstComponent::Subsecond(Subsecond { digits }) => {
409 Self::Subsecond(modifier::Subsecond {
410 digits: digits.unwrap_or_default().into(),
411 })
412 },
413 AstComponent::UnixTimestamp(UnixTimestamp {
414 precision,
415 sign_behavior,
416 }) => match precision.unwrap_or_default() {
417 UnixTimestampPrecision::Second => {
418 Self::UnixTimestampSecond(modifier::UnixTimestampSecond {
419 sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
420 })
421 }
422 UnixTimestampPrecision::Millisecond => {
423 Self::UnixTimestampMillisecond(modifier::UnixTimestampMillisecond {
424 sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
425 })
426 }
427 UnixTimestampPrecision::Microsecond => {
428 Self::UnixTimestampMicrosecond(modifier::UnixTimestampMicrosecond {
429 sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
430 })
431 }
432 UnixTimestampPrecision::Nanosecond => {
433 Self::UnixTimestampNanosecond(modifier::UnixTimestampNanosecond {
434 sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
435 })
436 }
437 },
438 AstComponent::Weekday(Weekday {
439 repr,
440 one_indexed,
441 case_sensitive,
442 }) => match repr.unwrap_or_default() {
443 WeekdayRepr::Short => {
444 reject_modifier!(one_indexed, "one_indexed", "for short weekday");
445 Self::WeekdayShort(modifier::WeekdayShort {
446 case_sensitive: case_sensitive.unwrap_or_default().into(),
447 })
448 },
449 WeekdayRepr::Long => {
450 reject_modifier!(one_indexed, "one_indexed", "for long weekday");
451 Self::WeekdayLong(modifier::WeekdayLong {
452 case_sensitive: case_sensitive.unwrap_or_default().into(),
453 })
454 },
455 WeekdayRepr::Sunday => {
456 reject_modifier!(
457 case_sensitive,
458 "case_sensitive",
459 "for numerical weekday"
460 );
461 Self::WeekdaySunday(modifier::WeekdaySunday {
462 one_indexed: one_indexed.unwrap_or_default().into(),
463 })
464 },
465 WeekdayRepr::Monday => {
466 reject_modifier!(
467 case_sensitive,
468 "case_sensitive",
469 "for numerical weekday"
470 );
471 Self::WeekdayMonday(modifier::WeekdayMonday {
472 one_indexed: one_indexed.unwrap_or_default().into(),
473 })
474 },
475 },
476 AstComponent::WeekNumber(WeekNumber { padding, repr }) => {
477 match repr.unwrap_or_default() {
478 WeekNumberRepr::Iso => {
479 Self::WeekNumberIso(modifier::WeekNumberIso {
480 padding: padding.unwrap_or_default().into(),
481 })
482 },
483 WeekNumberRepr::Sunday => {
484 Self::WeekNumberSunday(modifier::WeekNumberSunday {
485 padding: padding.unwrap_or_default().into(),
486 })
487 },
488 WeekNumberRepr::Monday => {
489 Self::WeekNumberMonday(modifier::WeekNumberMonday {
490 padding: padding.unwrap_or_default().into(),
491 })
492 },
493 }
494 }
495 AstComponent::Year(Year {
496 padding,
497 repr,
498 range,
499 base,
500 sign_behavior,
501 }) => {
502 #[cfg(not(feature = "large-dates"))]
503 reject_modifier!(
504 range,
505 "range",
506 "when the `large-dates` feature is not enabled"
507 );
508
509 match (
510 base.unwrap_or_default(),
511 repr.unwrap_or_default(),
512 range.unwrap_or_default(),
513 ) {
514 #[cfg(feature = "large-dates")]
515 (YearBase::Calendar, YearRepr::Full, YearRange::Extended) => {
516 Self::CalendarYearFullExtendedRange(
517 modifier::CalendarYearFullExtendedRange {
518 padding: padding.unwrap_or_default().into(),
519 sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
520 },
521 )
522 }
523 (YearBase::Calendar, YearRepr::Full, _) => {
524 Self::CalendarYearFullStandardRange(
525 modifier::CalendarYearFullStandardRange {
526 padding: padding.unwrap_or_default().into(),
527 sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
528 },
529 )
530 }
531 #[cfg(feature = "large-dates")]
532 (YearBase::Calendar, YearRepr::Century, YearRange::Extended) => {
533 Self::CalendarYearCenturyExtendedRange(
534 modifier::CalendarYearCenturyExtendedRange {
535 padding: padding.unwrap_or_default().into(),
536 sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
537 },
538 )
539 }
540 (YearBase::Calendar, YearRepr::Century, _) => {
541 Self::CalendarYearCenturyStandardRange(
542 modifier::CalendarYearCenturyStandardRange {
543 padding: padding.unwrap_or_default().into(),
544 sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
545 },
546 )
547 }
548 #[cfg(feature = "large-dates")]
549 (YearBase::IsoWeek, YearRepr::Full, YearRange::Extended) => {
550 Self::IsoYearFullExtendedRange(modifier::IsoYearFullExtendedRange {
551 padding: padding.unwrap_or_default().into(),
552 sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
553 })
554 }
555 (YearBase::IsoWeek, YearRepr::Full, _) => {
556 Self::IsoYearFullStandardRange(modifier::IsoYearFullStandardRange {
557 padding: padding.unwrap_or_default().into(),
558 sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
559 })
560 }
561 #[cfg(feature = "large-dates")]
562 (YearBase::IsoWeek, YearRepr::Century, YearRange::Extended) => {
563 Self::IsoYearCenturyExtendedRange(
564 modifier::IsoYearCenturyExtendedRange {
565 padding: padding.unwrap_or_default().into(),
566 sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
567 },
568 )
569 }
570 (YearBase::IsoWeek, YearRepr::Century, _) => {
571 Self::IsoYearCenturyStandardRange(
572 modifier::IsoYearCenturyStandardRange {
573 padding: padding.unwrap_or_default().into(),
574 sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
575 },
576 )
577 }
578 (YearBase::Calendar, YearRepr::LastTwo, _) => {
579 #[cfg(feature = "large-dates")]
580 reject_modifier!(range, "range", "when `repr:last_two` is used");
581 reject_modifier!(
582 sign_behavior,
583 "sign",
584 "when `repr:last_two` is used"
585 );
586 Self::CalendarYearLastTwo(modifier::CalendarYearLastTwo {
587 padding: padding.unwrap_or_default().into(),
588 })
589 }
590 (YearBase::IsoWeek, YearRepr::LastTwo, _) => {
591 #[cfg(feature = "large-dates")]
592 reject_modifier!(range, "range", "when `repr:last_two` is used");
593 reject_modifier!(
594 sign_behavior,
595 "sign",
596 "when `repr:last_two` is used"
597 );
598 Self::IsoYearLastTwo(modifier::IsoYearLastTwo {
599 padding: padding.unwrap_or_default().into(),
600 })
601 }
602 }
603 }
604 })
605 }
606 })+
607 }
608}
609
610impl_from_ast_component_for!(
611 [false] crate::format_description::Component,
612 [true] FormatDescriptionV3Inner<'_>,
613);
614
615macro_rules! target_ty {
617 ($name:ident $type:ty) => {
618 $type
619 };
620 ($name:ident) => {
621 $crate::format_description::modifier::$name
622 };
623}
624
625macro_rules! target_value {
627 ($name:ident $variant:ident $value:expr) => {
628 $value
629 };
630 ($name:ident $variant:ident) => {
631 $crate::format_description::modifier::$name::$variant
632 };
633}
634
635trait ModifierValue {
636 type Type;
637}
638
639macro_rules! modifier {
657 ($(
658 $(#[expect($expect_inner:meta)])?
659 enum $name:ident $(($target_ty:ty))? {
660 $(
661 $(#[$attr:meta])?
662 $variant:ident $(($target_value:expr))? = $parse_variant:literal
663 ),* $(,)?
664 }
665 )+) => {$(
666 #[derive(Default, Clone, Copy)]
667 enum $name {
668 $($(#[$attr])? $variant),*
669 }
670
671 impl $name {
672 #[inline]
674 fn from_modifier_value<const VERSION: u8>(
675 value_span: impl FnOnce() -> Span,
676 value: &str,
677 ) -> Result<Self, Error>
678 {
679 assert_version!();
680
681 $(if ident_eq::<VERSION>(&value, $parse_variant) {
682 return Ok(Self::$variant);
683 })*
684
685 hint::cold_path();
686 let span = value_span();
687 Err(Error {
688 _inner: unused(span.error("invalid modifier value")),
689 public: InvalidFormatDescription::InvalidModifier {
690 value: value.to_owned(),
691 index: span.start.byte as usize,
692 },
693 })
694 }
695 }
696
697 $(#[expect($expect_inner)])?
698 impl ModifierValue for $name {
699 type Type = target_ty!($name $($target_ty)?);
700 }
701
702 $(#[expect($expect_inner)])?
703 impl From<$name> for target_ty!($name $($target_ty)?) {
704 #[inline]
705 fn from(modifier: $name) -> Self {
706 match modifier {
707 $($name::$variant => target_value!($name $variant $($target_value)?)),*
708 }
709 }
710 }
711 )+};
712}
713
714modifier! {
716 enum HourBase(bool) {
717 Twelve(true) = "12",
718 #[default]
719 TwentyFour(false) = "24",
720 }
721
722 enum MonthCaseSensitive(bool) {
723 False(false) = "false",
724 #[default]
725 True(true) = "true",
726 }
727
728 #[expect(deprecated)]
729 enum MonthRepr {
730 #[default]
731 Numerical = "numerical",
732 Long = "long",
733 Short = "short",
734 }
735
736 enum OptionalFormat(bool) {
737 False(false) = "false",
738 #[default]
739 True(true) = "true",
740 }
741
742 enum Padding {
743 Space = "space",
744 #[default]
745 Zero = "zero",
746 None = "none",
747 }
748
749 enum PeriodCase(bool) {
750 Lower(false) = "lower",
751 #[default]
752 Upper(true) = "upper",
753 }
754
755 enum PeriodCaseSensitive(bool) {
756 False(false) = "false",
757 #[default]
758 True(true) = "true",
759 }
760
761 enum SignBehavior(bool) {
762 #[default]
763 Automatic(false) = "automatic",
764 Mandatory(true) = "mandatory",
765 }
766
767 enum SubsecondDigits {
768 One = "1",
769 Two = "2",
770 Three = "3",
771 Four = "4",
772 Five = "5",
773 Six = "6",
774 Seven = "7",
775 Eight = "8",
776 Nine = "9",
777 #[default]
778 OneOrMore = "1+",
779 }
780
781 enum TrailingInput {
782 #[default]
783 Prohibit = "prohibit",
784 Discard = "discard",
785 }
786
787 #[expect(deprecated)]
788 enum UnixTimestampPrecision {
789 #[default]
790 Second = "second",
791 Millisecond = "millisecond",
792 Microsecond = "microsecond",
793 Nanosecond = "nanosecond",
794 }
795
796 #[expect(deprecated)]
797 enum WeekNumberRepr {
798 #[default]
799 Iso = "iso",
800 Sunday = "sunday",
801 Monday = "monday",
802 }
803
804 enum WeekdayCaseSensitive(bool) {
805 False(false) = "false",
806 #[default]
807 True(true) = "true",
808 }
809
810 enum WeekdayOneIndexed(bool) {
811 False(false) = "false",
812 #[default]
813 True(true) = "true",
814 }
815
816 #[expect(deprecated)]
817 enum WeekdayRepr {
818 Short = "short",
819 #[default]
820 Long = "long",
821 Sunday = "sunday",
822 Monday = "monday",
823 }
824
825 enum YearBase(bool) {
826 #[default]
827 Calendar(false) = "calendar",
828 IsoWeek(true) = "iso_week",
829 }
830
831 #[expect(deprecated)]
832 enum YearRepr {
833 #[default]
834 Full = "full",
835 Century = "century",
836 LastTwo = "last_two",
837 }
838
839 #[expect(deprecated)]
843 enum YearRange {
844 Standard = "standard",
845 #[default]
846 Extended = "extended",
847 }
848}
849
850#[inline]
852fn parse_from_modifier_value<T>(value_span: impl FnOnce() -> Span, value: &str) -> Result<T, Error>
853where
854 T: FromStr,
855{
856 value.parse::<T>().map_err(|_| {
857 hint::cold_path();
858 let span = value_span();
859 Error {
860 _inner: unused(span.error("invalid modifier value")),
861 public: InvalidFormatDescription::InvalidModifier {
862 value: value.to_owned(),
863 index: span.start.byte as usize,
864 },
865 }
866 })
867}