1use core::num::NonZero;
4
5use num_conv::prelude::*;
6
7use crate::convert::*;
8use crate::format_description::modifier;
9use crate::parsing::ParsedItem;
10use crate::parsing::combinator::{
11 ExactlyNDigits, Sign, any_digit, exactly_n_digits_padded, first_match, n_to_m_digits,
12 n_to_m_digits_padded, opt, sign,
13};
14use crate::{Month, Weekday};
15
16pub(crate) fn parse_year(
18 input: &[u8],
19 modifiers: modifier::Year,
20) -> Option<ParsedItem<'_, (i32, bool)>> {
21 match modifiers.repr {
22 modifier::YearRepr::Full => {
23 let ParsedItem(input, sign) = opt(sign)(input);
24
25 if let Some(sign) = sign {
26 let ParsedItem(input, year) = if cfg!(feature = "large-dates")
27 && modifiers.range == modifier::YearRange::Extended
28 {
29 n_to_m_digits_padded::<4, 6, u32>(modifiers.padding)(input)?
30 } else {
31 exactly_n_digits_padded::<4, u32>(modifiers.padding)(input)?
32 };
33
34 Some(ParsedItem(
35 input,
36 match sign {
37 Sign::Negative => (-year.cast_signed(), true),
38 Sign::Positive => (year.cast_signed(), false),
39 },
40 ))
41 } else if modifiers.sign_is_mandatory {
42 None
43 } else {
44 let ParsedItem(input, year) =
45 exactly_n_digits_padded::<4, u32>(modifiers.padding)(input)?;
46 Some(ParsedItem(input, (year.cast_signed(), false)))
47 }
48 }
49 modifier::YearRepr::Century => {
50 let ParsedItem(input, sign) = opt(sign)(input);
51
52 if let Some(sign) = sign {
53 let ParsedItem(input, year) = if cfg!(feature = "large-dates")
54 && modifiers.range == modifier::YearRange::Extended
55 {
56 n_to_m_digits_padded::<2, 4, u32>(modifiers.padding)(input)?
57 } else {
58 exactly_n_digits_padded::<2, u32>(modifiers.padding)(input)?
59 };
60
61 Some(ParsedItem(
62 input,
63 match sign {
64 Sign::Negative => (-year.cast_signed(), true),
65 Sign::Positive => (year.cast_signed(), false),
66 },
67 ))
68 } else if modifiers.sign_is_mandatory {
69 None
70 } else {
71 let ParsedItem(input, year) =
72 n_to_m_digits_padded::<1, 2, u32>(modifiers.padding)(input)?;
73 Some(ParsedItem(input, (year.cast_signed(), false)))
74 }
75 }
76 modifier::YearRepr::LastTwo => Some(
77 exactly_n_digits_padded::<2, u32>(modifiers.padding)(input)?
78 .map(|v| (v.cast_signed(), false)),
79 ),
80 }
81}
82
83pub(crate) fn parse_month(
85 input: &[u8],
86 modifiers: modifier::Month,
87) -> Option<ParsedItem<'_, Month>> {
88 use Month::*;
89 let ParsedItem(remaining, value) = first_match(
90 match modifiers.repr {
91 modifier::MonthRepr::Numerical => {
92 return exactly_n_digits_padded::<2, _>(modifiers.padding)(input)?
93 .flat_map(|n| Month::from_number(NonZero::new(n)?).ok());
94 }
95 modifier::MonthRepr::Long => [
96 (b"January".as_slice(), January),
97 (b"February".as_slice(), February),
98 (b"March".as_slice(), March),
99 (b"April".as_slice(), April),
100 (b"May".as_slice(), May),
101 (b"June".as_slice(), June),
102 (b"July".as_slice(), July),
103 (b"August".as_slice(), August),
104 (b"September".as_slice(), September),
105 (b"October".as_slice(), October),
106 (b"November".as_slice(), November),
107 (b"December".as_slice(), December),
108 ],
109 modifier::MonthRepr::Short => [
110 (b"Jan".as_slice(), January),
111 (b"Feb".as_slice(), February),
112 (b"Mar".as_slice(), March),
113 (b"Apr".as_slice(), April),
114 (b"May".as_slice(), May),
115 (b"Jun".as_slice(), June),
116 (b"Jul".as_slice(), July),
117 (b"Aug".as_slice(), August),
118 (b"Sep".as_slice(), September),
119 (b"Oct".as_slice(), October),
120 (b"Nov".as_slice(), November),
121 (b"Dec".as_slice(), December),
122 ],
123 },
124 modifiers.case_sensitive,
125 )(input)?;
126 Some(ParsedItem(remaining, value))
127}
128
129pub(crate) fn parse_week_number(
131 input: &[u8],
132 modifiers: modifier::WeekNumber,
133) -> Option<ParsedItem<'_, u8>> {
134 exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
135}
136
137pub(crate) fn parse_weekday(
139 input: &[u8],
140 modifiers: modifier::Weekday,
141) -> Option<ParsedItem<'_, Weekday>> {
142 match (modifiers.repr, modifiers.one_indexed) {
143 (modifier::WeekdayRepr::Short, _) if modifiers.case_sensitive => match input {
144 [b'M', b'o', b'n', rest @ ..] => Some(ParsedItem(rest, Weekday::Monday)),
145 [b'T', b'u', b'e', rest @ ..] => Some(ParsedItem(rest, Weekday::Tuesday)),
146 [b'W', b'e', b'd', rest @ ..] => Some(ParsedItem(rest, Weekday::Wednesday)),
147 [b'T', b'h', b'u', rest @ ..] => Some(ParsedItem(rest, Weekday::Thursday)),
148 [b'F', b'r', b'i', rest @ ..] => Some(ParsedItem(rest, Weekday::Friday)),
149 [b'S', b'a', b't', rest @ ..] => Some(ParsedItem(rest, Weekday::Saturday)),
150 [b'S', b'u', b'n', rest @ ..] => Some(ParsedItem(rest, Weekday::Sunday)),
151 _ => None,
152 },
153 (modifier::WeekdayRepr::Short, _) => match input {
154 [b'M' | b'm', b'O' | b'o', b'N' | b'n', rest @ ..] => {
155 Some(ParsedItem(rest, Weekday::Monday))
156 }
157 [b'T' | b't', b'U' | b'u', b'E' | b'e', rest @ ..] => {
158 Some(ParsedItem(rest, Weekday::Tuesday))
159 }
160 [b'W' | b'w', b'E' | b'e', b'D' | b'd', rest @ ..] => {
161 Some(ParsedItem(rest, Weekday::Wednesday))
162 }
163 [b'T' | b't', b'H' | b'h', b'U' | b'u', rest @ ..] => {
164 Some(ParsedItem(rest, Weekday::Thursday))
165 }
166 [b'F' | b'f', b'R' | b'r', b'I' | b'i', rest @ ..] => {
167 Some(ParsedItem(rest, Weekday::Friday))
168 }
169 [b'S' | b's', b'A' | b'a', b'T' | b't', rest @ ..] => {
170 Some(ParsedItem(rest, Weekday::Saturday))
171 }
172 [b'S' | b's', b'U' | b'u', b'N' | b'n', rest @ ..] => {
173 Some(ParsedItem(rest, Weekday::Sunday))
174 }
175 _ => None,
176 },
177 (modifier::WeekdayRepr::Long, _) if modifiers.case_sensitive => match input {
178 [b'M', b'o', b'n', b'd', b'a', b'y', rest @ ..] => {
179 Some(ParsedItem(rest, Weekday::Monday))
180 }
181 [b'T', b'u', b'e', b's', b'd', b'a', b'y', rest @ ..] => {
182 Some(ParsedItem(rest, Weekday::Tuesday))
183 }
184 [
185 b'W',
186 b'e',
187 b'd',
188 b'n',
189 b'e',
190 b's',
191 b'd',
192 b'a',
193 b'y',
194 rest @ ..,
195 ] => Some(ParsedItem(rest, Weekday::Wednesday)),
196 [b'T', b'h', b'u', b'r', b's', b'd', b'a', b'y', rest @ ..] => {
197 Some(ParsedItem(rest, Weekday::Thursday))
198 }
199 [b'F', b'r', b'i', b'd', b'a', b'y', rest @ ..] => {
200 Some(ParsedItem(rest, Weekday::Friday))
201 }
202 [b'S', b'a', b't', b'u', b'r', b'd', b'a', b'y', rest @ ..] => {
203 Some(ParsedItem(rest, Weekday::Saturday))
204 }
205 [b'S', b'u', b'n', b'd', b'a', b'y', rest @ ..] => {
206 Some(ParsedItem(rest, Weekday::Sunday))
207 }
208 _ => None,
209 },
210 (modifier::WeekdayRepr::Long, _) => match input {
211 [
212 b'M' | b'm',
213 b'O' | b'o',
214 b'N' | b'n',
215 b'D' | b'd',
216 b'A' | b'a',
217 b'Y' | b'y',
218 rest @ ..,
219 ] => Some(ParsedItem(rest, Weekday::Monday)),
220 [
221 b'T' | b't',
222 b'U' | b'u',
223 b'E' | b'e',
224 b'S' | b's',
225 b'D' | b'd',
226 b'A' | b'a',
227 b'Y' | b'y',
228 rest @ ..,
229 ] => Some(ParsedItem(rest, Weekday::Tuesday)),
230 [
231 b'W' | b'w',
232 b'E' | b'e',
233 b'D' | b'd',
234 b'N' | b'n',
235 b'E' | b'e',
236 b'S' | b's',
237 b'D' | b'd',
238 b'A' | b'a',
239 b'Y' | b'y',
240 rest @ ..,
241 ] => Some(ParsedItem(rest, Weekday::Wednesday)),
242 [
243 b'T' | b't',
244 b'H' | b'h',
245 b'U' | b'u',
246 b'R' | b'r',
247 b'S' | b's',
248 b'D' | b'd',
249 b'A' | b'a',
250 b'Y' | b'y',
251 rest @ ..,
252 ] => Some(ParsedItem(rest, Weekday::Thursday)),
253 [
254 b'F' | b'f',
255 b'R' | b'r',
256 b'I' | b'i',
257 b'D' | b'd',
258 b'A' | b'a',
259 b'Y' | b'y',
260 rest @ ..,
261 ] => Some(ParsedItem(rest, Weekday::Friday)),
262 [
263 b'S' | b's',
264 b'A' | b'a',
265 b'T' | b't',
266 b'U' | b'u',
267 b'R' | b'r',
268 b'D' | b'd',
269 b'A' | b'a',
270 b'Y' | b'y',
271 rest @ ..,
272 ] => Some(ParsedItem(rest, Weekday::Saturday)),
273 [
274 b'S' | b's',
275 b'U' | b'u',
276 b'N' | b'n',
277 b'D' | b'd',
278 b'A' | b'a',
279 b'Y' | b'y',
280 rest @ ..,
281 ] => Some(ParsedItem(rest, Weekday::Sunday)),
282 _ => None,
283 },
284 (modifier::WeekdayRepr::Sunday, false) => match input {
285 [b'1', rest @ ..] => Some(ParsedItem(rest, Weekday::Monday)),
286 [b'2', rest @ ..] => Some(ParsedItem(rest, Weekday::Tuesday)),
287 [b'3', rest @ ..] => Some(ParsedItem(rest, Weekday::Wednesday)),
288 [b'4', rest @ ..] => Some(ParsedItem(rest, Weekday::Thursday)),
289 [b'5', rest @ ..] => Some(ParsedItem(rest, Weekday::Friday)),
290 [b'6', rest @ ..] => Some(ParsedItem(rest, Weekday::Saturday)),
291 [b'0', rest @ ..] => Some(ParsedItem(rest, Weekday::Sunday)),
292 _ => None,
293 },
294 (modifier::WeekdayRepr::Sunday, true) => match input {
295 [b'2', rest @ ..] => Some(ParsedItem(rest, Weekday::Monday)),
296 [b'3', rest @ ..] => Some(ParsedItem(rest, Weekday::Tuesday)),
297 [b'4', rest @ ..] => Some(ParsedItem(rest, Weekday::Wednesday)),
298 [b'5', rest @ ..] => Some(ParsedItem(rest, Weekday::Thursday)),
299 [b'6', rest @ ..] => Some(ParsedItem(rest, Weekday::Friday)),
300 [b'7', rest @ ..] => Some(ParsedItem(rest, Weekday::Saturday)),
301 [b'1', rest @ ..] => Some(ParsedItem(rest, Weekday::Sunday)),
302 _ => None,
303 },
304 (modifier::WeekdayRepr::Monday, false) => match input {
305 [b'0', rest @ ..] => Some(ParsedItem(rest, Weekday::Monday)),
306 [b'1', rest @ ..] => Some(ParsedItem(rest, Weekday::Tuesday)),
307 [b'2', rest @ ..] => Some(ParsedItem(rest, Weekday::Wednesday)),
308 [b'3', rest @ ..] => Some(ParsedItem(rest, Weekday::Thursday)),
309 [b'4', rest @ ..] => Some(ParsedItem(rest, Weekday::Friday)),
310 [b'5', rest @ ..] => Some(ParsedItem(rest, Weekday::Saturday)),
311 [b'6', rest @ ..] => Some(ParsedItem(rest, Weekday::Sunday)),
312 _ => None,
313 },
314 (modifier::WeekdayRepr::Monday, true) => match input {
315 [b'1', rest @ ..] => Some(ParsedItem(rest, Weekday::Monday)),
316 [b'2', rest @ ..] => Some(ParsedItem(rest, Weekday::Tuesday)),
317 [b'3', rest @ ..] => Some(ParsedItem(rest, Weekday::Wednesday)),
318 [b'4', rest @ ..] => Some(ParsedItem(rest, Weekday::Thursday)),
319 [b'5', rest @ ..] => Some(ParsedItem(rest, Weekday::Friday)),
320 [b'6', rest @ ..] => Some(ParsedItem(rest, Weekday::Saturday)),
321 [b'7', rest @ ..] => Some(ParsedItem(rest, Weekday::Sunday)),
322 _ => None,
323 },
324 }
325}
326
327#[inline]
329pub(crate) fn parse_ordinal(
330 input: &[u8],
331 modifiers: modifier::Ordinal,
332) -> Option<ParsedItem<'_, NonZero<u16>>> {
333 exactly_n_digits_padded::<3, _>(modifiers.padding)(input)
334 .and_then(|parsed| parsed.flat_map(NonZero::new))
335}
336
337#[inline]
339pub(crate) fn parse_day(
340 input: &[u8],
341 modifiers: modifier::Day,
342) -> Option<ParsedItem<'_, NonZero<u8>>> {
343 exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
344 .and_then(|parsed| parsed.flat_map(NonZero::new))
345}
346
347#[derive(Debug, Clone, Copy, PartialEq, Eq)]
349pub(crate) enum Period {
350 #[allow(clippy::missing_docs_in_private_items)]
351 Am,
352 #[allow(clippy::missing_docs_in_private_items)]
353 Pm,
354}
355
356#[inline]
358pub(crate) fn parse_hour(input: &[u8], modifiers: modifier::Hour) -> Option<ParsedItem<'_, u8>> {
359 exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
360}
361
362#[inline]
364pub(crate) fn parse_minute(
365 input: &[u8],
366 modifiers: modifier::Minute,
367) -> Option<ParsedItem<'_, u8>> {
368 exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
369}
370
371#[inline]
373pub(crate) fn parse_second(
374 input: &[u8],
375 modifiers: modifier::Second,
376) -> Option<ParsedItem<'_, u8>> {
377 exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
378}
379
380#[inline]
382pub(crate) fn parse_period(
383 input: &[u8],
384 modifiers: modifier::Period,
385) -> Option<ParsedItem<'_, Period>> {
386 let (rest, period) = match (modifiers.is_uppercase, modifiers.case_sensitive, input) {
387 (true, _, [b'A', b'M', rest @ ..]) => (rest, Period::Am),
388 (true, _, [b'P', b'M', rest @ ..]) => (rest, Period::Pm),
389 (false, _, [b'a', b'm', rest @ ..]) => (rest, Period::Am),
390 (false, _, [b'p', b'm', rest @ ..]) => (rest, Period::Pm),
391 (_, false, [b'A' | b'a', b'M' | b'm', rest @ ..]) => (rest, Period::Am),
392 (_, false, [b'P' | b'p', b'M' | b'm', rest @ ..]) => (rest, Period::Pm),
393 _ => return None,
394 };
395 Some(ParsedItem(rest, period))
396}
397
398pub(crate) fn parse_subsecond(
400 input: &[u8],
401 modifiers: modifier::Subsecond,
402) -> Option<ParsedItem<'_, u32>> {
403 use modifier::SubsecondDigits::*;
404 Some(match modifiers.digits {
405 One => ExactlyNDigits::<1>::parse(input)?.map(|v| v.extend::<u32>() * 100_000_000),
406 Two => ExactlyNDigits::<2>::parse(input)?.map(|v| v.extend::<u32>() * 10_000_000),
407 Three => ExactlyNDigits::<3>::parse(input)?.map(|v| v.extend::<u32>() * 1_000_000),
408 Four => ExactlyNDigits::<4>::parse(input)?.map(|v| v.extend::<u32>() * 100_000),
409 Five => ExactlyNDigits::<5>::parse(input)?.map(|v| v * 10_000),
410 Six => ExactlyNDigits::<6>::parse(input)?.map(|v| v * 1_000),
411 Seven => ExactlyNDigits::<7>::parse(input)?.map(|v| v * 100),
412 Eight => ExactlyNDigits::<8>::parse(input)?.map(|v| v * 10),
413 Nine => ExactlyNDigits::<9>::parse(input)?,
414 OneOrMore => {
415 let ParsedItem(mut input, mut value) =
416 any_digit(input)?.map(|v| (v - b'0').extend::<u32>() * 100_000_000);
417
418 let mut multiplier = 10_000_000;
419 while let Some(ParsedItem(new_input, digit)) = any_digit(input) {
420 value += (digit - b'0').extend::<u32>() * multiplier;
421 input = new_input;
422 multiplier /= 10;
423 }
424
425 ParsedItem(input, value)
426 }
427 })
428}
429
430pub(crate) fn parse_offset_hour(
434 input: &[u8],
435 modifiers: modifier::OffsetHour,
436) -> Option<ParsedItem<'_, (i8, bool)>> {
437 let ParsedItem(input, sign) = opt(sign)(input);
438 let ParsedItem(input, hour) = exactly_n_digits_padded::<2, u8>(modifiers.padding)(input)?;
439 match sign {
440 Some(Sign::Negative) => Some(ParsedItem(input, (-hour.cast_signed(), true))),
441 None if modifiers.sign_is_mandatory => None,
442 _ => Some(ParsedItem(input, (hour.cast_signed(), false))),
443 }
444}
445
446#[inline]
448pub(crate) fn parse_offset_minute(
449 input: &[u8],
450 modifiers: modifier::OffsetMinute,
451) -> Option<ParsedItem<'_, i8>> {
452 Some(
453 exactly_n_digits_padded::<2, u8>(modifiers.padding)(input)?
454 .map(|offset_minute| offset_minute.cast_signed()),
455 )
456}
457
458#[inline]
460pub(crate) fn parse_offset_second(
461 input: &[u8],
462 modifiers: modifier::OffsetSecond,
463) -> Option<ParsedItem<'_, i8>> {
464 Some(
465 exactly_n_digits_padded::<2, u8>(modifiers.padding)(input)?
466 .map(|offset_second| offset_second.cast_signed()),
467 )
468}
469
470#[inline]
472pub(crate) fn parse_ignore(
473 input: &[u8],
474 modifiers: modifier::Ignore,
475) -> Option<ParsedItem<'_, ()>> {
476 let modifier::Ignore { count } = modifiers;
477 let input = input.get((count.get().extend())..)?;
478 Some(ParsedItem(input, ()))
479}
480
481pub(crate) fn parse_unix_timestamp(
483 input: &[u8],
484 modifiers: modifier::UnixTimestamp,
485) -> Option<ParsedItem<'_, i128>> {
486 let ParsedItem(input, sign) = opt(sign)(input);
487 let ParsedItem(input, nano_timestamp) = match modifiers.precision {
488 modifier::UnixTimestampPrecision::Second => {
489 n_to_m_digits::<1, 14, u128>(input)?.map(|val| val * Nanosecond::per_t::<u128>(Second))
490 }
491 modifier::UnixTimestampPrecision::Millisecond => n_to_m_digits::<1, 17, u128>(input)?
492 .map(|val| val * Nanosecond::per_t::<u128>(Millisecond)),
493 modifier::UnixTimestampPrecision::Microsecond => n_to_m_digits::<1, 20, u128>(input)?
494 .map(|val| val * Nanosecond::per_t::<u128>(Microsecond)),
495 modifier::UnixTimestampPrecision::Nanosecond => n_to_m_digits::<1, 23, _>(input)?,
496 };
497
498 match sign {
499 Some(Sign::Negative) => Some(ParsedItem(input, -nano_timestamp.cast_signed())),
500 None if modifiers.sign_is_mandatory => None,
501 _ => Some(ParsedItem(input, nano_timestamp.cast_signed())),
502 }
503}
504
505#[inline]
508pub(crate) const fn parse_end(input: &[u8], end: modifier::End) -> Option<ParsedItem<'_, ()>> {
509 let modifier::End {} = end;
510
511 if input.is_empty() {
512 Some(ParsedItem(input, ()))
513 } else {
514 None
515 }
516}