1use core::num::{NonZeroU16, NonZeroU8};
4
5use num_conv::prelude::*;
6
7use crate::convert::*;
8use crate::format_description::modifier;
9use crate::parsing::combinator::{
10 any_digit, exactly_n_digits, exactly_n_digits_padded, first_match, n_to_m_digits,
11 n_to_m_digits_padded, opt, sign,
12};
13use crate::parsing::ParsedItem;
14use crate::{Month, Weekday};
15
16pub(crate) fn parse_year(
19 input: &[u8],
20 modifiers: modifier::Year,
21) -> Option<ParsedItem<'_, (i32, bool)>> {
22 match modifiers.repr {
23 modifier::YearRepr::Full => {
24 let ParsedItem(input, sign) = opt(sign)(input);
25
26 if let Some(sign) = sign {
27 let ParsedItem(input, year) = if cfg!(feature = "large-dates")
28 && modifiers.range == modifier::YearRange::Extended
29 {
30 n_to_m_digits_padded::<4, 6, u32>(modifiers.padding)(input)?
31 } else {
32 exactly_n_digits_padded::<4, u32>(modifiers.padding)(input)?
33 };
34
35 Some(if sign == b'-' {
36 ParsedItem(input, (-year.cast_signed(), true))
37 } else {
38 ParsedItem(input, (year.cast_signed(), false))
39 })
40 } else if modifiers.sign_is_mandatory {
41 None
42 } else {
43 let ParsedItem(input, year) =
44 exactly_n_digits_padded::<4, u32>(modifiers.padding)(input)?;
45 Some(ParsedItem(input, (year.cast_signed(), false)))
46 }
47 }
48 modifier::YearRepr::Century => {
49 let ParsedItem(input, sign) = opt(sign)(input);
50
51 if let Some(sign) = sign {
52 let ParsedItem(input, year) = if cfg!(feature = "large-dates")
53 && modifiers.range == modifier::YearRange::Extended
54 {
55 n_to_m_digits_padded::<2, 4, u32>(modifiers.padding)(input)?
56 } else {
57 exactly_n_digits_padded::<2, u32>(modifiers.padding)(input)?
58 };
59
60 Some(if sign == b'-' {
61 ParsedItem(input, (-year.cast_signed(), true))
62 } else {
63 ParsedItem(input, (year.cast_signed(), false))
64 })
65 } else if modifiers.sign_is_mandatory {
66 None
67 } else {
68 let ParsedItem(input, year) =
69 n_to_m_digits_padded::<1, 2, u32>(modifiers.padding)(input)?;
70 Some(ParsedItem(input, (year.cast_signed(), false)))
71 }
72 }
73 modifier::YearRepr::LastTwo => Some(
74 exactly_n_digits_padded::<2, u32>(modifiers.padding)(input)?
75 .map(|v| (v.cast_signed(), false)),
76 ),
77 }
78}
79
80pub(crate) fn parse_month(
82 input: &[u8],
83 modifiers: modifier::Month,
84) -> Option<ParsedItem<'_, Month>> {
85 use Month::*;
86 let ParsedItem(remaining, value) = first_match(
87 match modifiers.repr {
88 modifier::MonthRepr::Numerical => {
89 return exactly_n_digits_padded::<2, _>(modifiers.padding)(input)?
90 .flat_map(|n| Month::from_number(n).ok());
91 }
92 modifier::MonthRepr::Long => [
93 (b"January".as_slice(), January),
94 (b"February".as_slice(), February),
95 (b"March".as_slice(), March),
96 (b"April".as_slice(), April),
97 (b"May".as_slice(), May),
98 (b"June".as_slice(), June),
99 (b"July".as_slice(), July),
100 (b"August".as_slice(), August),
101 (b"September".as_slice(), September),
102 (b"October".as_slice(), October),
103 (b"November".as_slice(), November),
104 (b"December".as_slice(), December),
105 ],
106 modifier::MonthRepr::Short => [
107 (b"Jan".as_slice(), January),
108 (b"Feb".as_slice(), February),
109 (b"Mar".as_slice(), March),
110 (b"Apr".as_slice(), April),
111 (b"May".as_slice(), May),
112 (b"Jun".as_slice(), June),
113 (b"Jul".as_slice(), July),
114 (b"Aug".as_slice(), August),
115 (b"Sep".as_slice(), September),
116 (b"Oct".as_slice(), October),
117 (b"Nov".as_slice(), November),
118 (b"Dec".as_slice(), December),
119 ],
120 },
121 modifiers.case_sensitive,
122 )(input)?;
123 Some(ParsedItem(remaining, value))
124}
125
126pub(crate) fn parse_week_number(
128 input: &[u8],
129 modifiers: modifier::WeekNumber,
130) -> Option<ParsedItem<'_, u8>> {
131 exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
132}
133
134pub(crate) fn parse_weekday(
136 input: &[u8],
137 modifiers: modifier::Weekday,
138) -> Option<ParsedItem<'_, Weekday>> {
139 first_match(
140 match (modifiers.repr, modifiers.one_indexed) {
141 (modifier::WeekdayRepr::Short, _) => [
142 (b"Mon".as_slice(), Weekday::Monday),
143 (b"Tue".as_slice(), Weekday::Tuesday),
144 (b"Wed".as_slice(), Weekday::Wednesday),
145 (b"Thu".as_slice(), Weekday::Thursday),
146 (b"Fri".as_slice(), Weekday::Friday),
147 (b"Sat".as_slice(), Weekday::Saturday),
148 (b"Sun".as_slice(), Weekday::Sunday),
149 ],
150 (modifier::WeekdayRepr::Long, _) => [
151 (b"Monday".as_slice(), Weekday::Monday),
152 (b"Tuesday".as_slice(), Weekday::Tuesday),
153 (b"Wednesday".as_slice(), Weekday::Wednesday),
154 (b"Thursday".as_slice(), Weekday::Thursday),
155 (b"Friday".as_slice(), Weekday::Friday),
156 (b"Saturday".as_slice(), Weekday::Saturday),
157 (b"Sunday".as_slice(), Weekday::Sunday),
158 ],
159 (modifier::WeekdayRepr::Sunday, false) => [
160 (b"1".as_slice(), Weekday::Monday),
161 (b"2".as_slice(), Weekday::Tuesday),
162 (b"3".as_slice(), Weekday::Wednesday),
163 (b"4".as_slice(), Weekday::Thursday),
164 (b"5".as_slice(), Weekday::Friday),
165 (b"6".as_slice(), Weekday::Saturday),
166 (b"0".as_slice(), Weekday::Sunday),
167 ],
168 (modifier::WeekdayRepr::Sunday, true) => [
169 (b"2".as_slice(), Weekday::Monday),
170 (b"3".as_slice(), Weekday::Tuesday),
171 (b"4".as_slice(), Weekday::Wednesday),
172 (b"5".as_slice(), Weekday::Thursday),
173 (b"6".as_slice(), Weekday::Friday),
174 (b"7".as_slice(), Weekday::Saturday),
175 (b"1".as_slice(), Weekday::Sunday),
176 ],
177 (modifier::WeekdayRepr::Monday, false) => [
178 (b"0".as_slice(), Weekday::Monday),
179 (b"1".as_slice(), Weekday::Tuesday),
180 (b"2".as_slice(), Weekday::Wednesday),
181 (b"3".as_slice(), Weekday::Thursday),
182 (b"4".as_slice(), Weekday::Friday),
183 (b"5".as_slice(), Weekday::Saturday),
184 (b"6".as_slice(), Weekday::Sunday),
185 ],
186 (modifier::WeekdayRepr::Monday, true) => [
187 (b"1".as_slice(), Weekday::Monday),
188 (b"2".as_slice(), Weekday::Tuesday),
189 (b"3".as_slice(), Weekday::Wednesday),
190 (b"4".as_slice(), Weekday::Thursday),
191 (b"5".as_slice(), Weekday::Friday),
192 (b"6".as_slice(), Weekday::Saturday),
193 (b"7".as_slice(), Weekday::Sunday),
194 ],
195 },
196 modifiers.case_sensitive,
197 )(input)
198}
199
200pub(crate) fn parse_ordinal(
202 input: &[u8],
203 modifiers: modifier::Ordinal,
204) -> Option<ParsedItem<'_, NonZeroU16>> {
205 exactly_n_digits_padded::<3, _>(modifiers.padding)(input)
206}
207
208pub(crate) fn parse_day(
210 input: &[u8],
211 modifiers: modifier::Day,
212) -> Option<ParsedItem<'_, NonZeroU8>> {
213 exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
214}
215#[derive(Debug, Clone, Copy, PartialEq, Eq)]
220pub(crate) enum Period {
221 #[allow(clippy::missing_docs_in_private_items)]
222 Am,
223 #[allow(clippy::missing_docs_in_private_items)]
224 Pm,
225}
226
227pub(crate) fn parse_hour(input: &[u8], modifiers: modifier::Hour) -> Option<ParsedItem<'_, u8>> {
229 exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
230}
231
232pub(crate) fn parse_minute(
234 input: &[u8],
235 modifiers: modifier::Minute,
236) -> Option<ParsedItem<'_, u8>> {
237 exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
238}
239
240pub(crate) fn parse_second(
242 input: &[u8],
243 modifiers: modifier::Second,
244) -> Option<ParsedItem<'_, u8>> {
245 exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
246}
247
248pub(crate) fn parse_period(
250 input: &[u8],
251 modifiers: modifier::Period,
252) -> Option<ParsedItem<'_, Period>> {
253 first_match(
254 if modifiers.is_uppercase {
255 [
256 (b"AM".as_slice(), Period::Am),
257 (b"PM".as_slice(), Period::Pm),
258 ]
259 } else {
260 [
261 (b"am".as_slice(), Period::Am),
262 (b"pm".as_slice(), Period::Pm),
263 ]
264 },
265 modifiers.case_sensitive,
266 )(input)
267}
268
269pub(crate) fn parse_subsecond(
271 input: &[u8],
272 modifiers: modifier::Subsecond,
273) -> Option<ParsedItem<'_, u32>> {
274 use modifier::SubsecondDigits::*;
275 Some(match modifiers.digits {
276 One => exactly_n_digits::<1, u32>(input)?.map(|v| v * 100_000_000),
277 Two => exactly_n_digits::<2, u32>(input)?.map(|v| v * 10_000_000),
278 Three => exactly_n_digits::<3, u32>(input)?.map(|v| v * 1_000_000),
279 Four => exactly_n_digits::<4, u32>(input)?.map(|v| v * 100_000),
280 Five => exactly_n_digits::<5, u32>(input)?.map(|v| v * 10_000),
281 Six => exactly_n_digits::<6, u32>(input)?.map(|v| v * 1_000),
282 Seven => exactly_n_digits::<7, u32>(input)?.map(|v| v * 100),
283 Eight => exactly_n_digits::<8, u32>(input)?.map(|v| v * 10),
284 Nine => exactly_n_digits::<9, _>(input)?,
285 OneOrMore => {
286 let ParsedItem(mut input, mut value) =
287 any_digit(input)?.map(|v| (v - b'0').extend::<u32>() * 100_000_000);
288
289 let mut multiplier = 10_000_000;
290 while let Some(ParsedItem(new_input, digit)) = any_digit(input) {
291 value += (digit - b'0').extend::<u32>() * multiplier;
292 input = new_input;
293 multiplier /= 10;
294 }
295
296 ParsedItem(input, value)
297 }
298 })
299}
300pub(crate) fn parse_offset_hour(
307 input: &[u8],
308 modifiers: modifier::OffsetHour,
309) -> Option<ParsedItem<'_, (i8, bool)>> {
310 let ParsedItem(input, sign) = opt(sign)(input);
311 let ParsedItem(input, hour) = exactly_n_digits_padded::<2, u8>(modifiers.padding)(input)?;
312 match sign {
313 Some(b'-') => Some(ParsedItem(input, (-hour.cast_signed(), true))),
314 None if modifiers.sign_is_mandatory => None,
315 _ => Some(ParsedItem(input, (hour.cast_signed(), false))),
316 }
317}
318
319pub(crate) fn parse_offset_minute(
321 input: &[u8],
322 modifiers: modifier::OffsetMinute,
323) -> Option<ParsedItem<'_, i8>> {
324 Some(
325 exactly_n_digits_padded::<2, u8>(modifiers.padding)(input)?
326 .map(|offset_minute| offset_minute.cast_signed()),
327 )
328}
329
330pub(crate) fn parse_offset_second(
332 input: &[u8],
333 modifiers: modifier::OffsetSecond,
334) -> Option<ParsedItem<'_, i8>> {
335 Some(
336 exactly_n_digits_padded::<2, u8>(modifiers.padding)(input)?
337 .map(|offset_second| offset_second.cast_signed()),
338 )
339}
340pub(crate) fn parse_ignore(
344 input: &[u8],
345 modifiers: modifier::Ignore,
346) -> Option<ParsedItem<'_, ()>> {
347 let modifier::Ignore { count } = modifiers;
348 let input = input.get((count.get().extend())..)?;
349 Some(ParsedItem(input, ()))
350}
351
352pub(crate) fn parse_unix_timestamp(
354 input: &[u8],
355 modifiers: modifier::UnixTimestamp,
356) -> Option<ParsedItem<'_, i128>> {
357 let ParsedItem(input, sign) = opt(sign)(input);
358 let ParsedItem(input, nano_timestamp) = match modifiers.precision {
359 modifier::UnixTimestampPrecision::Second => n_to_m_digits::<1, 14, u128>(input)?
360 .map(|val| val * Nanosecond::per(Second).extend::<u128>()),
361 modifier::UnixTimestampPrecision::Millisecond => n_to_m_digits::<1, 17, u128>(input)?
362 .map(|val| val * Nanosecond::per(Millisecond).extend::<u128>()),
363 modifier::UnixTimestampPrecision::Microsecond => n_to_m_digits::<1, 20, u128>(input)?
364 .map(|val| val * Nanosecond::per(Microsecond).extend::<u128>()),
365 modifier::UnixTimestampPrecision::Nanosecond => n_to_m_digits::<1, 23, _>(input)?,
366 };
367
368 match sign {
369 Some(b'-') => Some(ParsedItem(input, -nano_timestamp.cast_signed())),
370 None if modifiers.sign_is_mandatory => None,
371 _ => Some(ParsedItem(input, nano_timestamp.cast_signed())),
372 }
373}
374
375pub(crate) const fn parse_end(input: &[u8], end: modifier::End) -> Option<ParsedItem<'_, ()>> {
378 let modifier::End {} = end;
379
380 if input.is_empty() {
381 Some(ParsedItem(input, ()))
382 } else {
383 None
384 }
385}