1use alloc::boxed::Box;
4use alloc::string::String;
5use core::num::NonZero;
6use core::str::{self, FromStr};
7
8use super::{Error, Span, Spanned, ast, unused};
9use crate::internal_macros::bug;
10
11#[inline]
13pub(super) fn parse<'a>(
14 ast_items: impl Iterator<Item = Result<ast::Item<'a>, Error>>,
15) -> impl Iterator<Item = Result<Item<'a>, Error>> {
16 ast_items.map(|ast_item| ast_item.and_then(Item::from_ast))
17}
18
19pub(super) enum Item<'a> {
21 Literal(&'a [u8]),
23 Component(Component),
25 Optional {
27 value: Box<[Self]>,
29 span: Span,
31 },
32 First {
34 value: Box<[Box<[Self]>]>,
36 span: Span,
38 },
39}
40
41impl Item<'_> {
42 pub(super) fn from_ast(ast_item: ast::Item<'_>) -> Result<Item<'_>, Error> {
44 Ok(match ast_item {
45 ast::Item::Component {
46 _opening_bracket: _,
47 _leading_whitespace: _,
48 name,
49 modifiers,
50 _trailing_whitespace: _,
51 _closing_bracket: _,
52 } => Item::Component(component_from_ast(&name, &modifiers)?),
53 ast::Item::Literal(Spanned { value, span: _ }) => Item::Literal(value),
54 ast::Item::EscapedBracket {
55 _first: _,
56 _second: _,
57 } => Item::Literal(b"["),
58 ast::Item::Optional {
59 opening_bracket,
60 _leading_whitespace: _,
61 _optional_kw: _,
62 _whitespace: _,
63 nested_format_description,
64 closing_bracket,
65 } => {
66 let items = nested_format_description
67 .items
68 .into_vec()
69 .into_iter()
70 .map(Item::from_ast)
71 .collect::<Result<_, _>>()?;
72 Item::Optional {
73 value: items,
74 span: opening_bracket.to(closing_bracket),
75 }
76 }
77 ast::Item::First {
78 opening_bracket,
79 _leading_whitespace: _,
80 _first_kw: _,
81 _whitespace: _,
82 nested_format_descriptions,
83 closing_bracket,
84 } => {
85 let items = nested_format_descriptions
86 .into_vec()
87 .into_iter()
88 .map(|nested_format_description| {
89 nested_format_description
90 .items
91 .into_vec()
92 .into_iter()
93 .map(Item::from_ast)
94 .collect()
95 })
96 .collect::<Result<_, _>>()?;
97 Item::First {
98 value: items,
99 span: opening_bracket.to(closing_bracket),
100 }
101 }
102 })
103 }
104}
105
106impl<'a> TryFrom<Item<'a>> for crate::format_description::BorrowedFormatItem<'a> {
107 type Error = Error;
108
109 #[inline]
110 fn try_from(item: Item<'a>) -> Result<Self, Self::Error> {
111 match item {
112 Item::Literal(literal) => Ok(Self::Literal(literal)),
113 Item::Component(component) => Ok(Self::Component(component.into())),
114 Item::Optional { value: _, span } => Err(Error {
115 _inner: unused(span.error(
116 "optional items are not supported in runtime-parsed format descriptions",
117 )),
118 public: crate::error::InvalidFormatDescription::NotSupported {
119 what: "optional item",
120 context: "runtime-parsed format descriptions",
121 index: span.start.byte as usize,
122 },
123 }),
124 Item::First { value: _, span } => Err(Error {
125 _inner: unused(span.error(
126 "'first' items are not supported in runtime-parsed format descriptions",
127 )),
128 public: crate::error::InvalidFormatDescription::NotSupported {
129 what: "'first' item",
130 context: "runtime-parsed format descriptions",
131 index: span.start.byte as usize,
132 },
133 }),
134 }
135 }
136}
137
138impl From<Item<'_>> for crate::format_description::OwnedFormatItem {
139 #[inline]
140 fn from(item: Item<'_>) -> Self {
141 match item {
142 Item::Literal(literal) => Self::Literal(literal.to_vec().into_boxed_slice()),
143 Item::Component(component) => Self::Component(component.into()),
144 Item::Optional { value, span: _ } => Self::Optional(Box::new(value.into())),
145 Item::First { value, span: _ } => {
146 Self::First(value.into_vec().into_iter().map(Into::into).collect())
147 }
148 }
149 }
150}
151
152impl<'a> From<Box<[Item<'a>]>> for crate::format_description::OwnedFormatItem {
153 #[inline]
154 fn from(items: Box<[Item<'a>]>) -> Self {
155 let items = items.into_vec();
156 match <[_; 1]>::try_from(items) {
157 Ok([item]) => item.into(),
158 Err(vec) => Self::Compound(vec.into_iter().map(Into::into).collect()),
159 }
160 }
161}
162
163macro_rules! component_definition {
165 (@if_required required then { $($then:tt)* } $(else { $($else:tt)* })?) => { $($then)* };
166 (@if_required then { $($then:tt)* } $(else { $($else:tt)* })?) => { $($($else)*)? };
167 (@if_from_str from_str then { $($then:tt)* } $(else { $($else:tt)* })?) => { $($then)* };
168 (@if_from_str then { $($then:tt)* } $(else { $($else:tt)* })?) => { $($($else)*)? };
169
170 ($vis:vis enum $name:ident {
171 $($variant:ident = $parse_variant:literal {$(
172 $(#[$required:tt])?
173 $field:ident = $parse_field:literal:
174 Option<$(#[$from_str:tt])? $field_type:ty>
175 => $target_field:ident
176 ),* $(,)?}),* $(,)?
177 }) => {
178 $vis enum $name {
179 $($variant($variant),)*
180 }
181
182 $($vis struct $variant {
183 $($field: Option<$field_type>),*
184 })*
185
186 $(impl $variant {
187 #[inline]
189 fn with_modifiers(
190 modifiers: &[ast::Modifier<'_>],
191 _component_span: Span,
192 ) -> Result<Self, Error>
193 {
194 #[allow(unused_mut)]
196 let mut this = Self {
197 $($field: None),*
198 };
199
200 for modifier in modifiers {
201 $(#[expect(clippy::string_lit_as_bytes)]
202 if modifier.key.eq_ignore_ascii_case($parse_field.as_bytes()) {
203 this.$field = component_definition!(@if_from_str $($from_str)?
204 then {
205 parse_from_modifier_value::<$field_type>(&modifier.value)?
206 } else {
207 <$field_type>::from_modifier_value(&modifier.value)?
208 });
209 continue;
210 })*
211 return Err(Error {
212 _inner: unused(modifier.key.span.error("invalid modifier key")),
213 public: crate::error::InvalidFormatDescription::InvalidModifier {
214 value: String::from_utf8_lossy(*modifier.key).into_owned(),
215 index: modifier.key.span.start.byte as usize,
216 }
217 });
218 }
219
220 $(component_definition! { @if_required $($required)? then {
221 if this.$field.is_none() {
222 return Err(Error {
223 _inner: unused(_component_span.error("missing required modifier")),
224 public:
225 crate::error::InvalidFormatDescription::MissingRequiredModifier {
226 name: $parse_field,
227 index: _component_span.start.byte as usize,
228 }
229 });
230 }
231 }})*
232
233 Ok(this)
234 }
235 })*
236
237 impl From<$name> for crate::format_description::Component {
238 #[inline]
239 fn from(component: $name) -> Self {
240 match component {$(
241 $name::$variant($variant { $($field),* }) => {
242 $crate::format_description::component::Component::$variant(
243 $crate::format_description::modifier::$variant {$(
244 $target_field: component_definition! { @if_required $($required)?
245 then {
246 match $field {
247 Some(value) => value.into(),
248 None => bug!("required modifier was not set"),
249 }
250 } else {
251 $field.unwrap_or_default().into()
252 }
253 }
254 ),*}
255 )
256 }
257 )*}
258 }
259 }
260
261 #[inline]
263 fn component_from_ast(
264 name: &Spanned<&[u8]>,
265 modifiers: &[ast::Modifier<'_>],
266 ) -> Result<Component, Error> {
267 $(#[expect(clippy::string_lit_as_bytes)]
268 if name.eq_ignore_ascii_case($parse_variant.as_bytes()) {
269 return Ok(Component::$variant($variant::with_modifiers(&modifiers, name.span)?));
270 })*
271 Err(Error {
272 _inner: unused(name.span.error("invalid component")),
273 public: crate::error::InvalidFormatDescription::InvalidComponentName {
274 name: String::from_utf8_lossy(name).into_owned(),
275 index: name.span.start.byte as usize,
276 },
277 })
278 }
279 }
280}
281
282pub(super) enum Component {
Day(Day),
End(End),
Hour(Hour),
Ignore(Ignore),
Minute(Minute),
Month(Month),
OffsetHour(OffsetHour),
OffsetMinute(OffsetMinute),
OffsetSecond(OffsetSecond),
Ordinal(Ordinal),
Period(Period),
Second(Second),
Subsecond(Subsecond),
UnixTimestamp(UnixTimestamp),
Weekday(Weekday),
WeekNumber(WeekNumber),
Year(Year),
}
pub(super) struct Day {
padding: Option<Padding>,
}
pub(super) struct End {
trailing_input: Option<TrailingInput>,
}
pub(super) struct Hour {
padding: Option<Padding>,
base: Option<HourBase>,
}
pub(super) struct Ignore {
count: Option<NonZero<u16>>,
}
pub(super) struct Minute {
padding: Option<Padding>,
}
pub(super) struct Month {
padding: Option<Padding>,
repr: Option<MonthRepr>,
case_sensitive: Option<MonthCaseSensitive>,
}
pub(super) struct OffsetHour {
sign_behavior: Option<SignBehavior>,
padding: Option<Padding>,
}
pub(super) struct OffsetMinute {
padding: Option<Padding>,
}
pub(super) struct OffsetSecond {
padding: Option<Padding>,
}
pub(super) struct Ordinal {
padding: Option<Padding>,
}
pub(super) struct Period {
case: Option<PeriodCase>,
case_sensitive: Option<PeriodCaseSensitive>,
}
pub(super) struct Second {
padding: Option<Padding>,
}
pub(super) struct Subsecond {
digits: Option<SubsecondDigits>,
}
pub(super) struct UnixTimestamp {
precision: Option<UnixTimestampPrecision>,
sign_behavior: Option<SignBehavior>,
}
pub(super) struct Weekday {
repr: Option<WeekdayRepr>,
one_indexed: Option<WeekdayOneIndexed>,
case_sensitive: Option<WeekdayCaseSensitive>,
}
pub(super) struct WeekNumber {
padding: Option<Padding>,
repr: Option<WeekNumberRepr>,
}
pub(super) struct Year {
padding: Option<Padding>,
repr: Option<YearRepr>,
range: Option<YearRange>,
base: Option<YearBase>,
sign_behavior: Option<SignBehavior>,
}
impl Day {
#[inline]
fn with_modifiers(modifiers: &[ast::Modifier<'_>], _component_span: Span)
-> Result<Self, Error> {
#[allow(unused_mut)]
let mut this = Self { padding: None };
for modifier in modifiers {
#[expect(clippy :: string_lit_as_bytes)]
if modifier.key.eq_ignore_ascii_case("padding".as_bytes()) {
this.padding =
<Padding>::from_modifier_value(&modifier.value)?;
continue;
}
return Err(Error {
_inner: unused(modifier.key.span.error("invalid modifier key")),
public: crate::error::InvalidFormatDescription::InvalidModifier {
value: String::from_utf8_lossy(*modifier.key).into_owned(),
index: modifier.key.span.start.byte as usize,
},
});
}
Ok(this)
}
}
impl End {
#[inline]
fn with_modifiers(modifiers: &[ast::Modifier<'_>], _component_span: Span)
-> Result<Self, Error> {
#[allow(unused_mut)]
let mut this = Self { trailing_input: None };
for modifier in modifiers {
#[expect(clippy :: string_lit_as_bytes)]
if modifier.key.eq_ignore_ascii_case("trailing_input".as_bytes())
{
this.trailing_input =
<TrailingInput>::from_modifier_value(&modifier.value)?;
continue;
}
return Err(Error {
_inner: unused(modifier.key.span.error("invalid modifier key")),
public: crate::error::InvalidFormatDescription::InvalidModifier {
value: String::from_utf8_lossy(*modifier.key).into_owned(),
index: modifier.key.span.start.byte as usize,
},
});
}
Ok(this)
}
}
impl Hour {
#[inline]
fn with_modifiers(modifiers: &[ast::Modifier<'_>], _component_span: Span)
-> Result<Self, Error> {
#[allow(unused_mut)]
let mut this = Self { padding: None, base: None };
for modifier in modifiers {
#[expect(clippy :: string_lit_as_bytes)]
if modifier.key.eq_ignore_ascii_case("padding".as_bytes()) {
this.padding =
<Padding>::from_modifier_value(&modifier.value)?;
continue;
}
#[expect(clippy :: string_lit_as_bytes)]
if modifier.key.eq_ignore_ascii_case("repr".as_bytes()) {
this.base = <HourBase>::from_modifier_value(&modifier.value)?;
continue;
}
return Err(Error {
_inner: unused(modifier.key.span.error("invalid modifier key")),
public: crate::error::InvalidFormatDescription::InvalidModifier {
value: String::from_utf8_lossy(*modifier.key).into_owned(),
index: modifier.key.span.start.byte as usize,
},
});
}
Ok(this)
}
}
impl Ignore {
#[inline]
fn with_modifiers(modifiers: &[ast::Modifier<'_>], _component_span: Span)
-> Result<Self, Error> {
#[allow(unused_mut)]
let mut this = Self { count: None };
for modifier in modifiers {
#[expect(clippy :: string_lit_as_bytes)]
if modifier.key.eq_ignore_ascii_case("count".as_bytes()) {
this.count =
parse_from_modifier_value::<NonZero<u16>>(&modifier.value)?;
continue;
}
return Err(Error {
_inner: unused(modifier.key.span.error("invalid modifier key")),
public: crate::error::InvalidFormatDescription::InvalidModifier {
value: String::from_utf8_lossy(*modifier.key).into_owned(),
index: modifier.key.span.start.byte as usize,
},
});
}
if this.count.is_none() {
return Err(Error {
_inner: unused(_component_span.error("missing required modifier")),
public: crate::error::InvalidFormatDescription::MissingRequiredModifier {
name: "count",
index: _component_span.start.byte as usize,
},
});
}
Ok(this)
}
}
impl Minute {
#[inline]
fn with_modifiers(modifiers: &[ast::Modifier<'_>], _component_span: Span)
-> Result<Self, Error> {
#[allow(unused_mut)]
let mut this = Self { padding: None };
for modifier in modifiers {
#[expect(clippy :: string_lit_as_bytes)]
if modifier.key.eq_ignore_ascii_case("padding".as_bytes()) {
this.padding =
<Padding>::from_modifier_value(&modifier.value)?;
continue;
}
return Err(Error {
_inner: unused(modifier.key.span.error("invalid modifier key")),
public: crate::error::InvalidFormatDescription::InvalidModifier {
value: String::from_utf8_lossy(*modifier.key).into_owned(),
index: modifier.key.span.start.byte as usize,
},
});
}
Ok(this)
}
}
impl Month {
#[inline]
fn with_modifiers(modifiers: &[ast::Modifier<'_>], _component_span: Span)
-> Result<Self, Error> {
#[allow(unused_mut)]
let mut this =
Self { padding: None, repr: None, case_sensitive: None };
for modifier in modifiers {
#[expect(clippy :: string_lit_as_bytes)]
if modifier.key.eq_ignore_ascii_case("padding".as_bytes()) {
this.padding =
<Padding>::from_modifier_value(&modifier.value)?;
continue;
}
#[expect(clippy :: string_lit_as_bytes)]
if modifier.key.eq_ignore_ascii_case("repr".as_bytes()) {
this.repr =
<MonthRepr>::from_modifier_value(&modifier.value)?;
continue;
}
#[expect(clippy :: string_lit_as_bytes)]
if modifier.key.eq_ignore_ascii_case("case_sensitive".as_bytes())
{
this.case_sensitive =
<MonthCaseSensitive>::from_modifier_value(&modifier.value)?;
continue;
}
return Err(Error {
_inner: unused(modifier.key.span.error("invalid modifier key")),
public: crate::error::InvalidFormatDescription::InvalidModifier {
value: String::from_utf8_lossy(*modifier.key).into_owned(),
index: modifier.key.span.start.byte as usize,
},
});
}
Ok(this)
}
}
impl OffsetHour {
#[inline]
fn with_modifiers(modifiers: &[ast::Modifier<'_>], _component_span: Span)
-> Result<Self, Error> {
#[allow(unused_mut)]
let mut this = Self { sign_behavior: None, padding: None };
for modifier in modifiers {
#[expect(clippy :: string_lit_as_bytes)]
if modifier.key.eq_ignore_ascii_case("sign".as_bytes()) {
this.sign_behavior =
<SignBehavior>::from_modifier_value(&modifier.value)?;
continue;
}
#[expect(clippy :: string_lit_as_bytes)]
if modifier.key.eq_ignore_ascii_case("padding".as_bytes()) {
this.padding =
<Padding>::from_modifier_value(&modifier.value)?;
continue;
}
return Err(Error {
_inner: unused(modifier.key.span.error("invalid modifier key")),
public: crate::error::InvalidFormatDescription::InvalidModifier {
value: String::from_utf8_lossy(*modifier.key).into_owned(),
index: modifier.key.span.start.byte as usize,
},
});
}
Ok(this)
}
}
impl OffsetMinute {
#[inline]
fn with_modifiers(modifiers: &[ast::Modifier<'_>], _component_span: Span)
-> Result<Self, Error> {
#[allow(unused_mut)]
let mut this = Self { padding: None };
for modifier in modifiers {
#[expect(clippy :: string_lit_as_bytes)]
if modifier.key.eq_ignore_ascii_case("padding".as_bytes()) {
this.padding =
<Padding>::from_modifier_value(&modifier.value)?;
continue;
}
return Err(Error {
_inner: unused(modifier.key.span.error("invalid modifier key")),
public: crate::error::InvalidFormatDescription::InvalidModifier {
value: String::from_utf8_lossy(*modifier.key).into_owned(),
index: modifier.key.span.start.byte as usize,
},
});
}
Ok(this)
}
}
impl OffsetSecond {
#[inline]
fn with_modifiers(modifiers: &[ast::Modifier<'_>], _component_span: Span)
-> Result<Self, Error> {
#[allow(unused_mut)]
let mut this = Self { padding: None };
for modifier in modifiers {
#[expect(clippy :: string_lit_as_bytes)]
if modifier.key.eq_ignore_ascii_case("padding".as_bytes()) {
this.padding =
<Padding>::from_modifier_value(&modifier.value)?;
continue;
}
return Err(Error {
_inner: unused(modifier.key.span.error("invalid modifier key")),
public: crate::error::InvalidFormatDescription::InvalidModifier {
value: String::from_utf8_lossy(*modifier.key).into_owned(),
index: modifier.key.span.start.byte as usize,
},
});
}
Ok(this)
}
}
impl Ordinal {
#[inline]
fn with_modifiers(modifiers: &[ast::Modifier<'_>], _component_span: Span)
-> Result<Self, Error> {
#[allow(unused_mut)]
let mut this = Self { padding: None };
for modifier in modifiers {
#[expect(clippy :: string_lit_as_bytes)]
if modifier.key.eq_ignore_ascii_case("padding".as_bytes()) {
this.padding =
<Padding>::from_modifier_value(&modifier.value)?;
continue;
}
return Err(Error {
_inner: unused(modifier.key.span.error("invalid modifier key")),
public: crate::error::InvalidFormatDescription::InvalidModifier {
value: String::from_utf8_lossy(*modifier.key).into_owned(),
index: modifier.key.span.start.byte as usize,
},
});
}
Ok(this)
}
}
impl Period {
#[inline]
fn with_modifiers(modifiers: &[ast::Modifier<'_>], _component_span: Span)
-> Result<Self, Error> {
#[allow(unused_mut)]
let mut this = Self { case: None, case_sensitive: None };
for modifier in modifiers {
#[expect(clippy :: string_lit_as_bytes)]
if modifier.key.eq_ignore_ascii_case("case".as_bytes()) {
this.case =
<PeriodCase>::from_modifier_value(&modifier.value)?;
continue;
}
#[expect(clippy :: string_lit_as_bytes)]
if modifier.key.eq_ignore_ascii_case("case_sensitive".as_bytes())
{
this.case_sensitive =
<PeriodCaseSensitive>::from_modifier_value(&modifier.value)?;
continue;
}
return Err(Error {
_inner: unused(modifier.key.span.error("invalid modifier key")),
public: crate::error::InvalidFormatDescription::InvalidModifier {
value: String::from_utf8_lossy(*modifier.key).into_owned(),
index: modifier.key.span.start.byte as usize,
},
});
}
Ok(this)
}
}
impl Second {
#[inline]
fn with_modifiers(modifiers: &[ast::Modifier<'_>], _component_span: Span)
-> Result<Self, Error> {
#[allow(unused_mut)]
let mut this = Self { padding: None };
for modifier in modifiers {
#[expect(clippy :: string_lit_as_bytes)]
if modifier.key.eq_ignore_ascii_case("padding".as_bytes()) {
this.padding =
<Padding>::from_modifier_value(&modifier.value)?;
continue;
}
return Err(Error {
_inner: unused(modifier.key.span.error("invalid modifier key")),
public: crate::error::InvalidFormatDescription::InvalidModifier {
value: String::from_utf8_lossy(*modifier.key).into_owned(),
index: modifier.key.span.start.byte as usize,
},
});
}
Ok(this)
}
}
impl Subsecond {
#[inline]
fn with_modifiers(modifiers: &[ast::Modifier<'_>], _component_span: Span)
-> Result<Self, Error> {
#[allow(unused_mut)]
let mut this = Self { digits: None };
for modifier in modifiers {
#[expect(clippy :: string_lit_as_bytes)]
if modifier.key.eq_ignore_ascii_case("digits".as_bytes()) {
this.digits =
<SubsecondDigits>::from_modifier_value(&modifier.value)?;
continue;
}
return Err(Error {
_inner: unused(modifier.key.span.error("invalid modifier key")),
public: crate::error::InvalidFormatDescription::InvalidModifier {
value: String::from_utf8_lossy(*modifier.key).into_owned(),
index: modifier.key.span.start.byte as usize,
},
});
}
Ok(this)
}
}
impl UnixTimestamp {
#[inline]
fn with_modifiers(modifiers: &[ast::Modifier<'_>], _component_span: Span)
-> Result<Self, Error> {
#[allow(unused_mut)]
let mut this = Self { precision: None, sign_behavior: None };
for modifier in modifiers {
#[expect(clippy :: string_lit_as_bytes)]
if modifier.key.eq_ignore_ascii_case("precision".as_bytes()) {
this.precision =
<UnixTimestampPrecision>::from_modifier_value(&modifier.value)?;
continue;
}
#[expect(clippy :: string_lit_as_bytes)]
if modifier.key.eq_ignore_ascii_case("sign".as_bytes()) {
this.sign_behavior =
<SignBehavior>::from_modifier_value(&modifier.value)?;
continue;
}
return Err(Error {
_inner: unused(modifier.key.span.error("invalid modifier key")),
public: crate::error::InvalidFormatDescription::InvalidModifier {
value: String::from_utf8_lossy(*modifier.key).into_owned(),
index: modifier.key.span.start.byte as usize,
},
});
}
Ok(this)
}
}
impl Weekday {
#[inline]
fn with_modifiers(modifiers: &[ast::Modifier<'_>], _component_span: Span)
-> Result<Self, Error> {
#[allow(unused_mut)]
let mut this =
Self { repr: None, one_indexed: None, case_sensitive: None };
for modifier in modifiers {
#[expect(clippy :: string_lit_as_bytes)]
if modifier.key.eq_ignore_ascii_case("repr".as_bytes()) {
this.repr =
<WeekdayRepr>::from_modifier_value(&modifier.value)?;
continue;
}
#[expect(clippy :: string_lit_as_bytes)]
if modifier.key.eq_ignore_ascii_case("one_indexed".as_bytes()) {
this.one_indexed =
<WeekdayOneIndexed>::from_modifier_value(&modifier.value)?;
continue;
}
#[expect(clippy :: string_lit_as_bytes)]
if modifier.key.eq_ignore_ascii_case("case_sensitive".as_bytes())
{
this.case_sensitive =
<WeekdayCaseSensitive>::from_modifier_value(&modifier.value)?;
continue;
}
return Err(Error {
_inner: unused(modifier.key.span.error("invalid modifier key")),
public: crate::error::InvalidFormatDescription::InvalidModifier {
value: String::from_utf8_lossy(*modifier.key).into_owned(),
index: modifier.key.span.start.byte as usize,
},
});
}
Ok(this)
}
}
impl WeekNumber {
#[inline]
fn with_modifiers(modifiers: &[ast::Modifier<'_>], _component_span: Span)
-> Result<Self, Error> {
#[allow(unused_mut)]
let mut this = Self { padding: None, repr: None };
for modifier in modifiers {
#[expect(clippy :: string_lit_as_bytes)]
if modifier.key.eq_ignore_ascii_case("padding".as_bytes()) {
this.padding =
<Padding>::from_modifier_value(&modifier.value)?;
continue;
}
#[expect(clippy :: string_lit_as_bytes)]
if modifier.key.eq_ignore_ascii_case("repr".as_bytes()) {
this.repr =
<WeekNumberRepr>::from_modifier_value(&modifier.value)?;
continue;
}
return Err(Error {
_inner: unused(modifier.key.span.error("invalid modifier key")),
public: crate::error::InvalidFormatDescription::InvalidModifier {
value: String::from_utf8_lossy(*modifier.key).into_owned(),
index: modifier.key.span.start.byte as usize,
},
});
}
Ok(this)
}
}
impl Year {
#[inline]
fn with_modifiers(modifiers: &[ast::Modifier<'_>], _component_span: Span)
-> Result<Self, Error> {
#[allow(unused_mut)]
let mut this =
Self {
padding: None,
repr: None,
range: None,
base: None,
sign_behavior: None,
};
for modifier in modifiers {
#[expect(clippy :: string_lit_as_bytes)]
if modifier.key.eq_ignore_ascii_case("padding".as_bytes()) {
this.padding =
<Padding>::from_modifier_value(&modifier.value)?;
continue;
}
#[expect(clippy :: string_lit_as_bytes)]
if modifier.key.eq_ignore_ascii_case("repr".as_bytes()) {
this.repr = <YearRepr>::from_modifier_value(&modifier.value)?;
continue;
}
#[expect(clippy :: string_lit_as_bytes)]
if modifier.key.eq_ignore_ascii_case("range".as_bytes()) {
this.range =
<YearRange>::from_modifier_value(&modifier.value)?;
continue;
}
#[expect(clippy :: string_lit_as_bytes)]
if modifier.key.eq_ignore_ascii_case("base".as_bytes()) {
this.base = <YearBase>::from_modifier_value(&modifier.value)?;
continue;
}
#[expect(clippy :: string_lit_as_bytes)]
if modifier.key.eq_ignore_ascii_case("sign".as_bytes()) {
this.sign_behavior =
<SignBehavior>::from_modifier_value(&modifier.value)?;
continue;
}
return Err(Error {
_inner: unused(modifier.key.span.error("invalid modifier key")),
public: crate::error::InvalidFormatDescription::InvalidModifier {
value: String::from_utf8_lossy(*modifier.key).into_owned(),
index: modifier.key.span.start.byte as usize,
},
});
}
Ok(this)
}
}
impl From<Component> for crate::format_description::Component {
#[inline]
fn from(component: Component) -> Self {
match component {
Component::Day(Day { padding }) => {
crate::format_description::component::Component::Day(crate::format_description::modifier::Day {
padding: padding.unwrap_or_default().into(),
})
}
Component::End(End { trailing_input }) => {
crate::format_description::component::Component::End(crate::format_description::modifier::End {
trailing_input: trailing_input.unwrap_or_default().into(),
})
}
Component::Hour(Hour { padding, base }) => {
crate::format_description::component::Component::Hour(crate::format_description::modifier::Hour {
padding: padding.unwrap_or_default().into(),
is_12_hour_clock: base.unwrap_or_default().into(),
})
}
Component::Ignore(Ignore { count }) => {
crate::format_description::component::Component::Ignore(crate::format_description::modifier::Ignore {
count: match count {
Some(value) => value.into(),
None => {
::core::panicking::panic_fmt(format_args!("internal error: required modifier was not set"));
}
},
})
}
Component::Minute(Minute { padding }) => {
crate::format_description::component::Component::Minute(crate::format_description::modifier::Minute {
padding: padding.unwrap_or_default().into(),
})
}
Component::Month(Month { padding, repr, case_sensitive }) => {
crate::format_description::component::Component::Month(crate::format_description::modifier::Month {
padding: padding.unwrap_or_default().into(),
repr: repr.unwrap_or_default().into(),
case_sensitive: case_sensitive.unwrap_or_default().into(),
})
}
Component::OffsetHour(OffsetHour { sign_behavior, padding }) => {
crate::format_description::component::Component::OffsetHour(crate::format_description::modifier::OffsetHour {
sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
padding: padding.unwrap_or_default().into(),
})
}
Component::OffsetMinute(OffsetMinute { padding }) => {
crate::format_description::component::Component::OffsetMinute(crate::format_description::modifier::OffsetMinute {
padding: padding.unwrap_or_default().into(),
})
}
Component::OffsetSecond(OffsetSecond { padding }) => {
crate::format_description::component::Component::OffsetSecond(crate::format_description::modifier::OffsetSecond {
padding: padding.unwrap_or_default().into(),
})
}
Component::Ordinal(Ordinal { padding }) => {
crate::format_description::component::Component::Ordinal(crate::format_description::modifier::Ordinal {
padding: padding.unwrap_or_default().into(),
})
}
Component::Period(Period { case, case_sensitive }) => {
crate::format_description::component::Component::Period(crate::format_description::modifier::Period {
is_uppercase: case.unwrap_or_default().into(),
case_sensitive: case_sensitive.unwrap_or_default().into(),
})
}
Component::Second(Second { padding }) => {
crate::format_description::component::Component::Second(crate::format_description::modifier::Second {
padding: padding.unwrap_or_default().into(),
})
}
Component::Subsecond(Subsecond { digits }) => {
crate::format_description::component::Component::Subsecond(crate::format_description::modifier::Subsecond {
digits: digits.unwrap_or_default().into(),
})
}
Component::UnixTimestamp(UnixTimestamp { precision, sign_behavior
}) => {
crate::format_description::component::Component::UnixTimestamp(crate::format_description::modifier::UnixTimestamp {
precision: precision.unwrap_or_default().into(),
sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
})
}
Component::Weekday(Weekday { repr, one_indexed, case_sensitive })
=> {
crate::format_description::component::Component::Weekday(crate::format_description::modifier::Weekday {
repr: repr.unwrap_or_default().into(),
one_indexed: one_indexed.unwrap_or_default().into(),
case_sensitive: case_sensitive.unwrap_or_default().into(),
})
}
Component::WeekNumber(WeekNumber { padding, repr }) => {
crate::format_description::component::Component::WeekNumber(crate::format_description::modifier::WeekNumber {
padding: padding.unwrap_or_default().into(),
repr: repr.unwrap_or_default().into(),
})
}
Component::Year(Year { padding, repr, range, base, sign_behavior
}) => {
crate::format_description::component::Component::Year(crate::format_description::modifier::Year {
padding: padding.unwrap_or_default().into(),
repr: repr.unwrap_or_default().into(),
range: range.unwrap_or_default().into(),
iso_week_based: base.unwrap_or_default().into(),
sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
})
}
}
}
}
#[inline]
fn component_from_ast(name: &Spanned<&[u8]>, modifiers: &[ast::Modifier<'_>])
-> Result<Component, Error> {
#[expect(clippy :: string_lit_as_bytes)]
if name.eq_ignore_ascii_case("day".as_bytes()) {
return Ok(Component::Day(Day::with_modifiers(&modifiers,
name.span)?));
}
#[expect(clippy :: string_lit_as_bytes)]
if name.eq_ignore_ascii_case("end".as_bytes()) {
return Ok(Component::End(End::with_modifiers(&modifiers,
name.span)?));
}
#[expect(clippy :: string_lit_as_bytes)]
if name.eq_ignore_ascii_case("hour".as_bytes()) {
return Ok(Component::Hour(Hour::with_modifiers(&modifiers,
name.span)?));
}
#[expect(clippy :: string_lit_as_bytes)]
if name.eq_ignore_ascii_case("ignore".as_bytes()) {
return Ok(Component::Ignore(Ignore::with_modifiers(&modifiers,
name.span)?));
}
#[expect(clippy :: string_lit_as_bytes)]
if name.eq_ignore_ascii_case("minute".as_bytes()) {
return Ok(Component::Minute(Minute::with_modifiers(&modifiers,
name.span)?));
}
#[expect(clippy :: string_lit_as_bytes)]
if name.eq_ignore_ascii_case("month".as_bytes()) {
return Ok(Component::Month(Month::with_modifiers(&modifiers,
name.span)?));
}
#[expect(clippy :: string_lit_as_bytes)]
if name.eq_ignore_ascii_case("offset_hour".as_bytes()) {
return Ok(Component::OffsetHour(OffsetHour::with_modifiers(&modifiers,
name.span)?));
}
#[expect(clippy :: string_lit_as_bytes)]
if name.eq_ignore_ascii_case("offset_minute".as_bytes()) {
return Ok(Component::OffsetMinute(OffsetMinute::with_modifiers(&modifiers,
name.span)?));
}
#[expect(clippy :: string_lit_as_bytes)]
if name.eq_ignore_ascii_case("offset_second".as_bytes()) {
return Ok(Component::OffsetSecond(OffsetSecond::with_modifiers(&modifiers,
name.span)?));
}
#[expect(clippy :: string_lit_as_bytes)]
if name.eq_ignore_ascii_case("ordinal".as_bytes()) {
return Ok(Component::Ordinal(Ordinal::with_modifiers(&modifiers,
name.span)?));
}
#[expect(clippy :: string_lit_as_bytes)]
if name.eq_ignore_ascii_case("period".as_bytes()) {
return Ok(Component::Period(Period::with_modifiers(&modifiers,
name.span)?));
}
#[expect(clippy :: string_lit_as_bytes)]
if name.eq_ignore_ascii_case("second".as_bytes()) {
return Ok(Component::Second(Second::with_modifiers(&modifiers,
name.span)?));
}
#[expect(clippy :: string_lit_as_bytes)]
if name.eq_ignore_ascii_case("subsecond".as_bytes()) {
return Ok(Component::Subsecond(Subsecond::with_modifiers(&modifiers,
name.span)?));
}
#[expect(clippy :: string_lit_as_bytes)]
if name.eq_ignore_ascii_case("unix_timestamp".as_bytes()) {
return Ok(Component::UnixTimestamp(UnixTimestamp::with_modifiers(&modifiers,
name.span)?));
}
#[expect(clippy :: string_lit_as_bytes)]
if name.eq_ignore_ascii_case("weekday".as_bytes()) {
return Ok(Component::Weekday(Weekday::with_modifiers(&modifiers,
name.span)?));
}
#[expect(clippy :: string_lit_as_bytes)]
if name.eq_ignore_ascii_case("week_number".as_bytes()) {
return Ok(Component::WeekNumber(WeekNumber::with_modifiers(&modifiers,
name.span)?));
}
#[expect(clippy :: string_lit_as_bytes)]
if name.eq_ignore_ascii_case("year".as_bytes()) {
return Ok(Component::Year(Year::with_modifiers(&modifiers,
name.span)?));
}
Err(Error {
_inner: unused(name.span.error("invalid component")),
public: crate::error::InvalidFormatDescription::InvalidComponentName {
name: String::from_utf8_lossy(name).into_owned(),
index: name.span.start.byte as usize,
},
})
}component_definition! {
284 pub(super) enum Component {
285 Day = "day" {
286 padding = "padding": Option<Padding> => padding,
287 },
288 End = "end" {
289 trailing_input = "trailing_input": Option<TrailingInput> => trailing_input,
290 },
291 Hour = "hour" {
292 padding = "padding": Option<Padding> => padding,
293 base = "repr": Option<HourBase> => is_12_hour_clock,
294 },
295 Ignore = "ignore" {
296 #[required]
297 count = "count": Option<#[from_str] NonZero<u16>> => count,
298 },
299 Minute = "minute" {
300 padding = "padding": Option<Padding> => padding,
301 },
302 Month = "month" {
303 padding = "padding": Option<Padding> => padding,
304 repr = "repr": Option<MonthRepr> => repr,
305 case_sensitive = "case_sensitive": Option<MonthCaseSensitive> => case_sensitive,
306 },
307 OffsetHour = "offset_hour" {
308 sign_behavior = "sign": Option<SignBehavior> => sign_is_mandatory,
309 padding = "padding": Option<Padding> => padding,
310 },
311 OffsetMinute = "offset_minute" {
312 padding = "padding": Option<Padding> => padding,
313 },
314 OffsetSecond = "offset_second" {
315 padding = "padding": Option<Padding> => padding,
316 },
317 Ordinal = "ordinal" {
318 padding = "padding": Option<Padding> => padding,
319 },
320 Period = "period" {
321 case = "case": Option<PeriodCase> => is_uppercase,
322 case_sensitive = "case_sensitive": Option<PeriodCaseSensitive> => case_sensitive,
323 },
324 Second = "second" {
325 padding = "padding": Option<Padding> => padding,
326 },
327 Subsecond = "subsecond" {
328 digits = "digits": Option<SubsecondDigits> => digits,
329 },
330 UnixTimestamp = "unix_timestamp" {
331 precision = "precision": Option<UnixTimestampPrecision> => precision,
332 sign_behavior = "sign": Option<SignBehavior> => sign_is_mandatory,
333 },
334 Weekday = "weekday" {
335 repr = "repr": Option<WeekdayRepr> => repr,
336 one_indexed = "one_indexed": Option<WeekdayOneIndexed> => one_indexed,
337 case_sensitive = "case_sensitive": Option<WeekdayCaseSensitive> => case_sensitive,
338 },
339 WeekNumber = "week_number" {
340 padding = "padding": Option<Padding> => padding,
341 repr = "repr": Option<WeekNumberRepr> => repr,
342 },
343 Year = "year" {
344 padding = "padding": Option<Padding> => padding,
345 repr = "repr": Option<YearRepr> => repr,
346 range = "range": Option<YearRange> => range,
347 base = "base": Option<YearBase> => iso_week_based,
348 sign_behavior = "sign": Option<SignBehavior> => sign_is_mandatory,
349 },
350 }
351}
352
353macro_rules! target_ty {
355 ($name:ident $type:ty) => {
356 $type
357 };
358 ($name:ident) => {
359 $crate::format_description::modifier::$name
360 };
361}
362
363macro_rules! target_value {
365 ($name:ident $variant:ident $value:expr) => {
366 $value
367 };
368 ($name:ident $variant:ident) => {
369 $crate::format_description::modifier::$name::$variant
370 };
371}
372
373macro_rules! modifier {
391 ($(
392 enum $name:ident $(($target_ty:ty))? {
393 $(
394 $(#[$attr:meta])?
395 $variant:ident $(($target_value:expr))? = $parse_variant:literal
396 ),* $(,)?
397 }
398 )+) => {$(
399 #[derive(Default)]
400 enum $name {
401 $($(#[$attr])? $variant),*
402 }
403
404 impl $name {
405 #[inline]
407 fn from_modifier_value(value: &Spanned<&[u8]>) -> Result<Option<Self>, Error> {
408 $(if value.eq_ignore_ascii_case($parse_variant) {
409 return Ok(Some(Self::$variant));
410 })*
411 Err(Error {
412 _inner: unused(value.span.error("invalid modifier value")),
413 public: crate::error::InvalidFormatDescription::InvalidModifier {
414 value: String::from_utf8_lossy(value).into_owned(),
415 index: value.span.start.byte as usize,
416 },
417 })
418 }
419 }
420
421 impl From<$name> for target_ty!($name $($target_ty)?) {
422 #[inline]
423 fn from(modifier: $name) -> Self {
424 match modifier {
425 $($name::$variant => target_value!($name $variant $($target_value)?)),*
426 }
427 }
428 }
429 )+};
430}
431
432#[automatically_derived]
impl ::core::default::Default for YearRange {
#[inline]
fn default() -> YearRange { Self::Extended }
}
impl YearRange {
#[inline]
fn from_modifier_value(value: &Spanned<&[u8]>)
-> Result<Option<Self>, Error> {
if value.eq_ignore_ascii_case(b"standard") {
return Ok(Some(Self::Standard));
}
if value.eq_ignore_ascii_case(b"extended") {
return Ok(Some(Self::Extended));
}
Err(Error {
_inner: unused(value.span.error("invalid modifier value")),
public: crate::error::InvalidFormatDescription::InvalidModifier {
value: String::from_utf8_lossy(value).into_owned(),
index: value.span.start.byte as usize,
},
})
}
}
impl From<YearRange> for crate::format_description::modifier::YearRange {
#[inline]
fn from(modifier: YearRange) -> Self {
match modifier {
YearRange::Standard =>
crate::format_description::modifier::YearRange::Standard,
YearRange::Extended =>
crate::format_description::modifier::YearRange::Extended,
}
}
}modifier! {
434 enum HourBase(bool) {
435 Twelve(true) = b"12",
436 #[default]
437 TwentyFour(false) = b"24",
438 }
439
440 enum MonthCaseSensitive(bool) {
441 False(false) = b"false",
442 #[default]
443 True(true) = b"true",
444 }
445
446 enum MonthRepr {
447 #[default]
448 Numerical = b"numerical",
449 Long = b"long",
450 Short = b"short",
451 }
452
453 enum Padding {
454 Space = b"space",
455 #[default]
456 Zero = b"zero",
457 None = b"none",
458 }
459
460 enum PeriodCase(bool) {
461 Lower(false) = b"lower",
462 #[default]
463 Upper(true) = b"upper",
464 }
465
466 enum PeriodCaseSensitive(bool) {
467 False(false) = b"false",
468 #[default]
469 True(true) = b"true",
470 }
471
472 enum SignBehavior(bool) {
473 #[default]
474 Automatic(false) = b"automatic",
475 Mandatory(true) = b"mandatory",
476 }
477
478 enum SubsecondDigits {
479 One = b"1",
480 Two = b"2",
481 Three = b"3",
482 Four = b"4",
483 Five = b"5",
484 Six = b"6",
485 Seven = b"7",
486 Eight = b"8",
487 Nine = b"9",
488 #[default]
489 OneOrMore = b"1+",
490 }
491
492 enum TrailingInput {
493 #[default]
494 Prohibit = b"prohibit",
495 Discard = b"discard",
496 }
497
498 enum UnixTimestampPrecision {
499 #[default]
500 Second = b"second",
501 Millisecond = b"millisecond",
502 Microsecond = b"microsecond",
503 Nanosecond = b"nanosecond",
504 }
505
506 enum WeekNumberRepr {
507 #[default]
508 Iso = b"iso",
509 Sunday = b"sunday",
510 Monday = b"monday",
511 }
512
513 enum WeekdayCaseSensitive(bool) {
514 False(false) = b"false",
515 #[default]
516 True(true) = b"true",
517 }
518
519 enum WeekdayOneIndexed(bool) {
520 False(false) = b"false",
521 #[default]
522 True(true) = b"true",
523 }
524
525 enum WeekdayRepr {
526 Short = b"short",
527 #[default]
528 Long = b"long",
529 Sunday = b"sunday",
530 Monday = b"monday",
531 }
532
533 enum YearBase(bool) {
534 #[default]
535 Calendar(false) = b"calendar",
536 IsoWeek(true) = b"iso_week",
537 }
538
539 enum YearRepr {
540 #[default]
541 Full = b"full",
542 Century = b"century",
543 LastTwo = b"last_two",
544 }
545
546 enum YearRange {
547 Standard = b"standard",
548 #[default]
549 Extended = b"extended",
550 }
551}
552
553#[inline]
555fn parse_from_modifier_value<T>(value: &Spanned<&[u8]>) -> Result<Option<T>, Error>
556where
557 T: FromStr,
558{
559 str::from_utf8(value)
560 .ok()
561 .and_then(|val| val.parse::<T>().ok())
562 .map(|val| Some(val))
563 .ok_or_else(|| Error {
564 _inner: unused(value.span.error("invalid modifier value")),
565 public: crate::error::InvalidFormatDescription::InvalidModifier {
566 value: String::from_utf8_lossy(value).into_owned(),
567 index: value.span.start.byte as usize,
568 },
569 })
570}