Skip to main content

time/parsing/
component.rs

1//! Parsing implementations for all [`Component`](crate::format_description::Component)s.
2
3use core::num::NonZero;
4
5use num_conv::prelude::*;
6
7use crate::format_description::{Period, modifier};
8use crate::parsing::ParsedItem;
9use crate::parsing::combinator::{
10    ExactlyNDigits, Sign, any_digit, exactly_n_digits_padded, n_to_m_digits, n_to_m_digits_padded,
11    opt, sign,
12};
13use crate::unit::*;
14use crate::{Month, Weekday};
15
16/// Parse the full calendar-based year.
17///
18/// This permits utilizing the full range of supported years, though at the cost of introducing
19/// parsing ambiguities.
20#[inline]
21pub(crate) fn parse_calendar_year_full_extended_range(
22    input: &[u8],
23    modifiers: modifier::CalendarYearFullExtendedRange,
24) -> Option<ParsedItem<'_, i32>> {
25    let ParsedItem(input, sign) = opt(sign)(input);
26
27    if let Some(sign) = sign {
28        let ParsedItem(input, year) = n_to_m_digits_padded::<4, 6, u32>(modifiers.padding)(input)?;
29
30        Some(ParsedItem(
31            input,
32            match sign {
33                Sign::Negative => -year.cast_signed(),
34                Sign::Positive => year.cast_signed(),
35            },
36        ))
37    } else if modifiers.sign_is_mandatory {
38        None
39    } else {
40        let ParsedItem(input, year) = exactly_n_digits_padded::<4, u32>(modifiers.padding)(input)?;
41        Some(ParsedItem(input, year.cast_signed()))
42    }
43}
44
45/// Parse the full calendar-based year.
46///
47/// This only supports four digits in order to avoid parsing ambiguities, so it cannot utilize the
48/// full range of supported years when the `large-dates` feature flag is enabled.
49#[inline]
50pub(crate) fn parse_calendar_year_full_standard_range(
51    input: &[u8],
52    modifiers: modifier::CalendarYearFullStandardRange,
53) -> Option<ParsedItem<'_, i32>> {
54    let ParsedItem(input, sign) = opt(sign)(input);
55
56    if let Some(sign) = sign {
57        let ParsedItem(input, year) = exactly_n_digits_padded::<4, u32>(modifiers.padding)(input)?;
58
59        Some(ParsedItem(
60            input,
61            match sign {
62                Sign::Negative => -year.cast_signed(),
63                Sign::Positive => year.cast_signed(),
64            },
65        ))
66    } else if modifiers.sign_is_mandatory {
67        None
68    } else {
69        let ParsedItem(input, year) = exactly_n_digits_padded::<4, u32>(modifiers.padding)(input)?;
70        Some(ParsedItem(input, year.cast_signed()))
71    }
72}
73
74/// Parse the full ISO-week based year.
75///
76/// This permits utilizing the full range of supported years, though at the cost of introducing
77/// parsing ambiguities.
78#[inline]
79pub(crate) fn parse_iso_year_full_extended_range(
80    input: &[u8],
81    modifiers: modifier::IsoYearFullExtendedRange,
82) -> Option<ParsedItem<'_, i32>> {
83    let ParsedItem(input, sign) = opt(sign)(input);
84
85    if let Some(sign) = sign {
86        let ParsedItem(input, year) = n_to_m_digits_padded::<4, 6, u32>(modifiers.padding)(input)?;
87
88        Some(ParsedItem(
89            input,
90            match sign {
91                Sign::Negative => -year.cast_signed(),
92                Sign::Positive => year.cast_signed(),
93            },
94        ))
95    } else if modifiers.sign_is_mandatory {
96        None
97    } else {
98        let ParsedItem(input, year) = exactly_n_digits_padded::<4, u32>(modifiers.padding)(input)?;
99        Some(ParsedItem(input, year.cast_signed()))
100    }
101}
102
103/// Parse the full ISO-week based year.
104///
105/// This only supports four digits in order to avoid parsing ambiguities, so it cannot utilize the
106/// full range of supported years when the `large-dates` feature flag is enabled.
107#[inline]
108pub(crate) fn parse_iso_year_full_standard_range(
109    input: &[u8],
110    modifiers: modifier::IsoYearFullStandardRange,
111) -> Option<ParsedItem<'_, i32>> {
112    let ParsedItem(input, sign) = opt(sign)(input);
113
114    if let Some(sign) = sign {
115        let ParsedItem(input, year) = exactly_n_digits_padded::<4, u32>(modifiers.padding)(input)?;
116
117        Some(ParsedItem(
118            input,
119            match sign {
120                Sign::Negative => -year.cast_signed(),
121                Sign::Positive => year.cast_signed(),
122            },
123        ))
124    } else if modifiers.sign_is_mandatory {
125        None
126    } else {
127        let ParsedItem(input, year) = exactly_n_digits_padded::<4, u32>(modifiers.padding)(input)?;
128        Some(ParsedItem(input, year.cast_signed()))
129    }
130}
131
132/// Parse all digits of the calendar-based year except the last two.
133///
134/// This permits utilizing the full range of supported years, though at the cost of introducing
135/// parsing ambiguities.
136#[inline]
137pub(crate) fn parse_calendar_year_century_extended_range(
138    input: &[u8],
139    modifiers: modifier::CalendarYearCenturyExtendedRange,
140) -> Option<ParsedItem<'_, (i16, bool)>> {
141    let ParsedItem(input, sign) = opt(sign)(input);
142
143    if let Some(sign) = sign {
144        let ParsedItem(input, year) = n_to_m_digits_padded::<2, 4, u16>(modifiers.padding)(input)?;
145
146        Some(ParsedItem(
147            input,
148            match sign {
149                Sign::Negative => (-year.cast_signed(), true),
150                Sign::Positive => (year.cast_signed(), false),
151            },
152        ))
153    } else if modifiers.sign_is_mandatory {
154        None
155    } else {
156        let ParsedItem(input, year) = n_to_m_digits_padded::<1, 2, u16>(modifiers.padding)(input)?;
157        Some(ParsedItem(input, (year.cast_signed(), false)))
158    }
159}
160
161/// Parse all digits of the calendar-based year except the last two.
162///
163/// This only supports two digits in order to avoid parsing ambiguities, so it cannot utilize the
164/// full range of supported years when the `large-dates` feature flag is enabled.
165#[inline]
166pub(crate) fn parse_calendar_year_century_standard_range(
167    input: &[u8],
168    modifiers: modifier::CalendarYearCenturyStandardRange,
169) -> Option<ParsedItem<'_, (i16, bool)>> {
170    let ParsedItem(input, sign) = opt(sign)(input);
171
172    if let Some(sign) = sign {
173        let ParsedItem(input, year) = exactly_n_digits_padded::<2, u16>(modifiers.padding)(input)?;
174
175        Some(ParsedItem(
176            input,
177            match sign {
178                Sign::Negative => (-year.cast_signed(), true),
179                Sign::Positive => (year.cast_signed(), false),
180            },
181        ))
182    } else if modifiers.sign_is_mandatory {
183        None
184    } else {
185        let ParsedItem(input, year) = n_to_m_digits_padded::<1, 2, u16>(modifiers.padding)(input)?;
186        Some(ParsedItem(input, (year.cast_signed(), false)))
187    }
188}
189
190/// Parse all digits of the ISO week-based year except the last two.
191///
192/// This permits utilizing the full range of supported years, though at the cost of introducing
193/// parsing ambiguities.
194#[inline]
195pub(crate) fn parse_iso_year_century_extended_range(
196    input: &[u8],
197    modifiers: modifier::IsoYearCenturyExtendedRange,
198) -> Option<ParsedItem<'_, (i16, bool)>> {
199    let ParsedItem(input, sign) = opt(sign)(input);
200
201    if let Some(sign) = sign {
202        let ParsedItem(input, year) = n_to_m_digits_padded::<2, 4, u16>(modifiers.padding)(input)?;
203
204        Some(ParsedItem(
205            input,
206            match sign {
207                Sign::Negative => (-year.cast_signed(), true),
208                Sign::Positive => (year.cast_signed(), false),
209            },
210        ))
211    } else if modifiers.sign_is_mandatory {
212        None
213    } else {
214        let ParsedItem(input, year) = n_to_m_digits_padded::<1, 2, u16>(modifiers.padding)(input)?;
215        Some(ParsedItem(input, (year.cast_signed(), false)))
216    }
217}
218
219/// Parse all digits of the ISO week-based year except the last two.
220///
221/// This only supports two digits in order to avoid parsing ambiguities, so it cannot utilize the
222/// full range of supported years when the `large-dates` feature flag is enabled.
223#[inline]
224pub(crate) fn parse_iso_year_century_standard_range(
225    input: &[u8],
226    modifiers: modifier::IsoYearCenturyStandardRange,
227) -> Option<ParsedItem<'_, (i16, bool)>> {
228    let ParsedItem(input, sign) = opt(sign)(input);
229
230    if let Some(sign) = sign {
231        let ParsedItem(input, year) = exactly_n_digits_padded::<2, u16>(modifiers.padding)(input)?;
232
233        Some(ParsedItem(
234            input,
235            match sign {
236                Sign::Negative => (-year.cast_signed(), true),
237                Sign::Positive => (year.cast_signed(), false),
238            },
239        ))
240    } else if modifiers.sign_is_mandatory {
241        None
242    } else {
243        let ParsedItem(input, year) = n_to_m_digits_padded::<1, 2, u16>(modifiers.padding)(input)?;
244        Some(ParsedItem(input, (year.cast_signed(), false)))
245    }
246}
247
248/// Parse the last two digits of the calendar-based year.
249#[inline]
250pub(crate) fn parse_calendar_year_last_two(
251    input: &[u8],
252    modifiers: modifier::CalendarYearLastTwo,
253) -> Option<ParsedItem<'_, u8>> {
254    exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
255}
256
257/// Parse the last two digits of the ISO week-based year.
258#[inline]
259pub(crate) fn parse_iso_year_last_two(
260    input: &[u8],
261    modifiers: modifier::IsoYearLastTwo,
262) -> Option<ParsedItem<'_, u8>> {
263    exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
264}
265
266/// Parse the "month" component of a `Date` in the abbreviated form (e.g. "Jan").
267#[inline]
268pub(crate) fn parse_month_short(
269    input: &[u8],
270    modifiers: modifier::MonthShort,
271) -> Option<ParsedItem<'_, Month>> {
272    let [first, second, third, rest @ ..] = input else {
273        return None;
274    };
275    let byte = if modifiers.case_sensitive {
276        u32::from_ne_bytes([0, *first, *second, *third])
277    } else {
278        u32::from_ne_bytes([
279            0,
280            first.to_ascii_uppercase(),
281            second.to_ascii_lowercase(),
282            third.to_ascii_lowercase(),
283        ])
284    };
285    const WEEKDAYS: [u32; 12] = [
286        u32::from_ne_bytes([0, b'J', b'a', b'n']),
287        u32::from_ne_bytes([0, b'F', b'e', b'b']),
288        u32::from_ne_bytes([0, b'M', b'a', b'r']),
289        u32::from_ne_bytes([0, b'A', b'p', b'r']),
290        u32::from_ne_bytes([0, b'M', b'a', b'y']),
291        u32::from_ne_bytes([0, b'J', b'u', b'n']),
292        u32::from_ne_bytes([0, b'J', b'u', b'l']),
293        u32::from_ne_bytes([0, b'A', b'u', b'g']),
294        u32::from_ne_bytes([0, b'S', b'e', b'p']),
295        u32::from_ne_bytes([0, b'O', b'c', b't']),
296        u32::from_ne_bytes([0, b'N', b'o', b'v']),
297        u32::from_ne_bytes([0, b'D', b'e', b'c']),
298    ];
299
300    let bitmask = ((WEEKDAYS[0] == byte) as u32) << 1
301        | ((WEEKDAYS[1] == byte) as u32) << 2
302        | ((WEEKDAYS[2] == byte) as u32) << 3
303        | ((WEEKDAYS[3] == byte) as u32) << 4
304        | ((WEEKDAYS[4] == byte) as u32) << 5
305        | ((WEEKDAYS[5] == byte) as u32) << 6
306        | ((WEEKDAYS[6] == byte) as u32) << 7
307        | ((WEEKDAYS[7] == byte) as u32) << 8
308        | ((WEEKDAYS[8] == byte) as u32) << 9
309        | ((WEEKDAYS[9] == byte) as u32) << 10
310        | ((WEEKDAYS[10] == byte) as u32) << 11
311        | ((WEEKDAYS[11] == byte) as u32) << 12;
312    if bitmask == 0 {
313        return None;
314    }
315    let index = if cfg!(target_endian = "little") {
316        bitmask.trailing_zeros() as u8
317    } else {
318        31 - bitmask.leading_zeros() as u8
319    };
320
321    // Safety: `index` cannot be greater than 12 because there are only 12 elements in the
322    // array that is converted to a bitmask. We know at least one element matched because
323    // the bitmask is non-zero.
324    let month = unsafe { Month::from_number(NonZero::new(index)?).unwrap_unchecked() };
325
326    Some(ParsedItem(rest, month))
327}
328
329/// Parse the "month" component of a `Date` in the long form (e.g. "January").
330#[inline]
331pub(crate) fn parse_month_long(
332    input: &[u8],
333    modifiers: modifier::MonthLong,
334) -> Option<ParsedItem<'_, Month>> {
335    use Month::*;
336
337    let ParsedItem(rest, month) = parse_month_short(
338        input,
339        modifier::MonthShort {
340            case_sensitive: modifiers.case_sensitive,
341        },
342    )?;
343
344    let expected_remaining = match month {
345        January => b"uary".as_slice(),
346        February => b"ruary".as_slice(),
347        March => b"ch".as_slice(),
348        April => b"il".as_slice(),
349        May => b"".as_slice(),
350        June => b"e".as_slice(),
351        July => b"y".as_slice(),
352        August => b"ust".as_slice(),
353        September => b"tember".as_slice(),
354        October => b"ober".as_slice(),
355        November | December => b"ember".as_slice(),
356    };
357
358    if modifiers.case_sensitive {
359        rest.strip_prefix(expected_remaining)
360            .map(|remaining| ParsedItem(remaining, month))
361    } else {
362        let (head, tail) = rest.split_at_checked(expected_remaining.len())?;
363        core::iter::zip(head, expected_remaining)
364            .all(|(a, b)| a.eq_ignore_ascii_case(b))
365            .then_some(ParsedItem(tail, month))
366    }
367}
368
369/// Parse the "month" component of a `Date` in the numerical format (e.g. "1" for January).
370#[inline]
371pub(crate) fn parse_month_numerical(
372    input: &[u8],
373    modifiers: modifier::MonthNumerical,
374) -> Option<ParsedItem<'_, Month>> {
375    exactly_n_digits_padded::<2, _>(modifiers.padding)(input)?
376        .flat_map(|n| Month::from_number(NonZero::new(n)?).ok())
377}
378
379/// Parse the "week number" component of a `Date`, where week 1 starts on the last Monday on or
380/// before January 4.
381#[inline]
382pub(crate) fn parse_week_number_iso(
383    input: &[u8],
384    modifiers: modifier::WeekNumberIso,
385) -> Option<ParsedItem<'_, u8>> {
386    exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
387}
388
389/// Parse the "week number" component of a `Date`, where week 1 starts on the first Sunday of the
390/// year.
391#[inline]
392pub(crate) fn parse_week_number_sunday(
393    input: &[u8],
394    modifiers: modifier::WeekNumberSunday,
395) -> Option<ParsedItem<'_, u8>> {
396    exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
397}
398
399/// Parse the "week number" component of a `Date`, where week 1 starts on the first Monday of the
400/// year.
401#[inline]
402pub(crate) fn parse_week_number_monday(
403    input: &[u8],
404    modifiers: modifier::WeekNumberMonday,
405) -> Option<ParsedItem<'_, u8>> {
406    exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
407}
408
409/// Parse the "weekday" component of a `Date` in the abbreviated form (e.g. "Mon").
410#[inline]
411pub(crate) fn parse_weekday_short(
412    input: &[u8],
413    modifiers: modifier::WeekdayShort,
414) -> Option<ParsedItem<'_, Weekday>> {
415    let [first, second, third, rest @ ..] = input else {
416        return None;
417    };
418    let byte = if modifiers.case_sensitive {
419        u32::from_ne_bytes([0, *first, *second, *third])
420    } else {
421        u32::from_ne_bytes([
422            0,
423            first.to_ascii_uppercase(),
424            second.to_ascii_lowercase(),
425            third.to_ascii_lowercase(),
426        ])
427    };
428    const WEEKDAYS: [u32; 7] = [
429        u32::from_ne_bytes([0, b'M', b'o', b'n']),
430        u32::from_ne_bytes([0, b'T', b'u', b'e']),
431        u32::from_ne_bytes([0, b'W', b'e', b'd']),
432        u32::from_ne_bytes([0, b'T', b'h', b'u']),
433        u32::from_ne_bytes([0, b'F', b'r', b'i']),
434        u32::from_ne_bytes([0, b'S', b'a', b't']),
435        u32::from_ne_bytes([0, b'S', b'u', b'n']),
436    ];
437
438    let bitmask = ((WEEKDAYS[0] == byte) as u32)
439        | ((WEEKDAYS[1] == byte) as u32) << 1
440        | ((WEEKDAYS[2] == byte) as u32) << 2
441        | ((WEEKDAYS[3] == byte) as u32) << 3
442        | ((WEEKDAYS[4] == byte) as u32) << 4
443        | ((WEEKDAYS[5] == byte) as u32) << 5
444        | ((WEEKDAYS[6] == byte) as u32) << 6;
445    if bitmask == 0 {
446        return None;
447    }
448    let index = if cfg!(target_endian = "little") {
449        bitmask.trailing_zeros()
450    } else {
451        31 - bitmask.leading_zeros()
452    };
453
454    if index > 6 {
455        return None;
456    }
457    // Safety: Values zero thru six are valid variants, while values greater than six have
458    // already been excluded above. We know at least one element matched because the bitmask
459    // is non-zero.
460    let weekday = unsafe { core::mem::transmute::<u8, Weekday>(index.truncate()) };
461
462    Some(ParsedItem(rest, weekday))
463}
464
465/// Parse the "weekday" component of a `Date` in the long form (e.g. "Monday").
466#[inline]
467pub(crate) fn parse_weekday_long(
468    input: &[u8],
469    modifiers: modifier::WeekdayLong,
470) -> Option<ParsedItem<'_, Weekday>> {
471    let ParsedItem(rest, weekday) = parse_weekday_short(
472        input,
473        modifier::WeekdayShort {
474            case_sensitive: modifiers.case_sensitive,
475        },
476    )?;
477
478    let expected_remaining = match weekday {
479        Weekday::Monday | Weekday::Friday | Weekday::Sunday => b"day".as_slice(),
480        Weekday::Tuesday => b"sday".as_slice(),
481        Weekday::Wednesday => b"nesday".as_slice(),
482        Weekday::Thursday => b"rsday".as_slice(),
483        Weekday::Saturday => b"urday".as_slice(),
484    };
485
486    if modifiers.case_sensitive {
487        rest.strip_prefix(expected_remaining)
488            .map(|remaining| ParsedItem(remaining, weekday))
489    } else {
490        let (head, tail) = rest.split_at_checked(expected_remaining.len())?;
491        core::iter::zip(head, expected_remaining)
492            .all(|(a, b)| a.eq_ignore_ascii_case(b))
493            .then_some(ParsedItem(tail, weekday))
494    }
495}
496
497/// Parse the weekday component of a `Date` in the numerical format, where Sunday is the first day
498/// of the week.`
499#[inline]
500pub(crate) fn parse_weekday_sunday(
501    input: &[u8],
502    modifiers: modifier::WeekdaySunday,
503) -> Option<ParsedItem<'_, Weekday>> {
504    let [digit, rest @ ..] = input else {
505        return None;
506    };
507    let mut digit = digit
508        .wrapping_sub(b'0')
509        .wrapping_sub(u8::from(modifiers.one_indexed));
510    if digit > 6 {
511        return None;
512    }
513
514    // Remap so that Sunday comes after Saturday, not before Monday.
515    digit = (digit + 6) % 7;
516
517    // Safety: Values zero thru six are valid variants.
518    let weekday = unsafe { core::mem::transmute::<u8, Weekday>(digit) };
519    Some(ParsedItem(rest, weekday))
520}
521
522/// Parse the weekday component of a `Date` in the numerical format, where Monday is the first day
523/// of the week.`
524#[inline]
525pub(crate) fn parse_weekday_monday(
526    input: &[u8],
527    modifiers: modifier::WeekdayMonday,
528) -> Option<ParsedItem<'_, Weekday>> {
529    let [digit, rest @ ..] = input else {
530        return None;
531    };
532    let digit = digit
533        .wrapping_sub(b'0')
534        .wrapping_sub(u8::from(modifiers.one_indexed));
535    if digit > 6 {
536        return None;
537    }
538
539    // Safety: Values zero thru six are valid variants.
540    let weekday = unsafe { core::mem::transmute::<u8, Weekday>(digit) };
541    Some(ParsedItem(rest, weekday))
542}
543
544/// Parse the "ordinal" component of a `Date`.
545#[inline]
546pub(crate) fn parse_ordinal(
547    input: &[u8],
548    modifiers: modifier::Ordinal,
549) -> Option<ParsedItem<'_, NonZero<u16>>> {
550    exactly_n_digits_padded::<3, _>(modifiers.padding)(input)
551        .and_then(|parsed| parsed.flat_map(NonZero::new))
552}
553
554/// Parse the "day" component of a `Date`.
555#[inline]
556pub(crate) fn parse_day(
557    input: &[u8],
558    modifiers: modifier::Day,
559) -> Option<ParsedItem<'_, NonZero<u8>>> {
560    exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
561        .and_then(|parsed| parsed.flat_map(NonZero::new))
562}
563
564/// Parse the "hour" component of a `Time` in the 12-hour format.
565#[inline]
566pub(crate) fn parse_hour_12(
567    input: &[u8],
568    modifiers: modifier::Hour12,
569) -> Option<ParsedItem<'_, u8>> {
570    exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
571}
572
573/// Parse the "hour" component of a `Time` in the 24-hour format.
574#[inline]
575pub(crate) fn parse_hour_24(
576    input: &[u8],
577    modifiers: modifier::Hour24,
578) -> Option<ParsedItem<'_, u8>> {
579    exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
580}
581
582/// Parse the "minute" component of a `Time`.
583#[inline]
584pub(crate) fn parse_minute(
585    input: &[u8],
586    modifiers: modifier::Minute,
587) -> Option<ParsedItem<'_, u8>> {
588    exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
589}
590
591/// Parse the "second" component of a `Time`.
592#[inline]
593pub(crate) fn parse_second(
594    input: &[u8],
595    modifiers: modifier::Second,
596) -> Option<ParsedItem<'_, u8>> {
597    exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
598}
599
600/// Parse the "period" component of a `Time`. Required if the hour is on a 12-hour clock.
601#[inline]
602pub(crate) fn parse_period(
603    input: &[u8],
604    modifiers: modifier::Period,
605) -> Option<ParsedItem<'_, Period>> {
606    let [first, second, rest @ ..] = input else {
607        return None;
608    };
609    let mut first = *first;
610    let mut second = *second;
611
612    if modifiers.is_uppercase && modifiers.case_sensitive {
613        match [first, second].as_slice() {
614            b"AM" => Some(ParsedItem(rest, Period::Am)),
615            b"PM" => Some(ParsedItem(rest, Period::Pm)),
616            _ => None,
617        }
618    } else {
619        first = first.to_ascii_lowercase();
620        second = second.to_ascii_lowercase();
621
622        match &[first, second] {
623            b"am" => Some(ParsedItem(rest, Period::Am)),
624            b"pm" => Some(ParsedItem(rest, Period::Pm)),
625            _ => None,
626        }
627    }
628}
629
630/// Parse the "subsecond" component of a `Time`.
631pub(crate) fn parse_subsecond(
632    input: &[u8],
633    modifiers: modifier::Subsecond,
634) -> Option<ParsedItem<'_, u32>> {
635    use modifier::SubsecondDigits::*;
636    Some(match modifiers.digits {
637        One => ExactlyNDigits::<1>::parse(input)?.map(|v| v.extend::<u32>() * 100_000_000),
638        Two => ExactlyNDigits::<2>::parse(input)?.map(|v| v.extend::<u32>() * 10_000_000),
639        Three => ExactlyNDigits::<3>::parse(input)?.map(|v| v.extend::<u32>() * 1_000_000),
640        Four => ExactlyNDigits::<4>::parse(input)?.map(|v| v.extend::<u32>() * 100_000),
641        Five => ExactlyNDigits::<5>::parse(input)?.map(|v| v * 10_000),
642        Six => ExactlyNDigits::<6>::parse(input)?.map(|v| v * 1_000),
643        Seven => ExactlyNDigits::<7>::parse(input)?.map(|v| v * 100),
644        Eight => ExactlyNDigits::<8>::parse(input)?.map(|v| v * 10),
645        Nine => ExactlyNDigits::<9>::parse(input)?,
646        OneOrMore => {
647            let ParsedItem(mut input, mut value) =
648                any_digit(input)?.map(|v| (v - b'0').extend::<u32>() * 100_000_000);
649
650            let mut multiplier = 10_000_000;
651            while let Some(ParsedItem(new_input, digit)) = any_digit(input) {
652                value += (digit - b'0').extend::<u32>() * multiplier;
653                input = new_input;
654                multiplier /= 10;
655            }
656
657            ParsedItem(input, value)
658        }
659    })
660}
661
662/// Parse the "hour" component of a `UtcOffset`.
663///
664/// Returns the value and whether the value is negative. This is used for when "-0" is parsed.
665#[inline]
666pub(crate) fn parse_offset_hour(
667    input: &[u8],
668    modifiers: modifier::OffsetHour,
669) -> Option<ParsedItem<'_, (i8, bool)>> {
670    let ParsedItem(input, sign) = opt(sign)(input);
671    let ParsedItem(input, hour) = exactly_n_digits_padded::<2, u8>(modifiers.padding)(input)?;
672    match sign {
673        Some(Sign::Negative) => Some(ParsedItem(input, (-hour.cast_signed(), true))),
674        None if modifiers.sign_is_mandatory => None,
675        _ => Some(ParsedItem(input, (hour.cast_signed(), false))),
676    }
677}
678
679/// Parse the "minute" component of a `UtcOffset`.
680#[inline]
681pub(crate) fn parse_offset_minute(
682    input: &[u8],
683    modifiers: modifier::OffsetMinute,
684) -> Option<ParsedItem<'_, i8>> {
685    Some(
686        exactly_n_digits_padded::<2, u8>(modifiers.padding)(input)?
687            .map(|offset_minute| offset_minute.cast_signed()),
688    )
689}
690
691/// Parse the "second" component of a `UtcOffset`.
692#[inline]
693pub(crate) fn parse_offset_second(
694    input: &[u8],
695    modifiers: modifier::OffsetSecond,
696) -> Option<ParsedItem<'_, i8>> {
697    Some(
698        exactly_n_digits_padded::<2, u8>(modifiers.padding)(input)?
699            .map(|offset_second| offset_second.cast_signed()),
700    )
701}
702
703/// Ignore the given number of bytes.
704#[inline]
705pub(crate) fn parse_ignore(
706    input: &[u8],
707    modifiers: modifier::Ignore,
708) -> Option<ParsedItem<'_, ()>> {
709    let modifier::Ignore { count } = modifiers;
710    let input = input.get((count.get().extend())..)?;
711    Some(ParsedItem(input, ()))
712}
713
714/// Parse the Unix timestamp component with second precision, returning the value in nanoseconds.
715#[inline]
716pub(crate) fn parse_unix_timestamp_second(
717    input: &[u8],
718    modifiers: modifier::UnixTimestampSecond,
719) -> Option<ParsedItem<'_, i128>> {
720    let ParsedItem(input, sign) = opt(sign)(input);
721    let ParsedItem(input, nano_timestamp) =
722        n_to_m_digits::<1, 14, u128>(input)?.map(|val| val * Nanosecond::per_t::<u128>(Second));
723
724    match sign {
725        Some(Sign::Negative) => Some(ParsedItem(input, -nano_timestamp.cast_signed())),
726        None if modifiers.sign_is_mandatory => None,
727        _ => Some(ParsedItem(input, nano_timestamp.cast_signed())),
728    }
729}
730
731/// Parse the Unix timestamp component with millisecond precision, returning the value in
732/// nanoseconds.
733#[inline]
734pub(crate) fn parse_unix_timestamp_millisecond(
735    input: &[u8],
736    modifiers: modifier::UnixTimestampMillisecond,
737) -> Option<ParsedItem<'_, i128>> {
738    let ParsedItem(input, sign) = opt(sign)(input);
739    let ParsedItem(input, nano_timestamp) = n_to_m_digits::<1, 17, u128>(input)?
740        .map(|val| val * Nanosecond::per_t::<u128>(Millisecond));
741
742    match sign {
743        Some(Sign::Negative) => Some(ParsedItem(input, -nano_timestamp.cast_signed())),
744        None if modifiers.sign_is_mandatory => None,
745        _ => Some(ParsedItem(input, nano_timestamp.cast_signed())),
746    }
747}
748
749/// Parse the Unix timestamp component with microsecond precision, returning the value in
750/// nanoseconds.
751#[inline]
752pub(crate) fn parse_unix_timestamp_microsecond(
753    input: &[u8],
754    modifiers: modifier::UnixTimestampMicrosecond,
755) -> Option<ParsedItem<'_, i128>> {
756    let ParsedItem(input, sign) = opt(sign)(input);
757    let ParsedItem(input, nano_timestamp) = n_to_m_digits::<1, 20, u128>(input)?
758        .map(|val| val * Nanosecond::per_t::<u128>(Microsecond));
759
760    match sign {
761        Some(Sign::Negative) => Some(ParsedItem(input, -nano_timestamp.cast_signed())),
762        None if modifiers.sign_is_mandatory => None,
763        _ => Some(ParsedItem(input, nano_timestamp.cast_signed())),
764    }
765}
766
767/// Parse the Unix timestamp component with nanosecond precision.
768#[inline]
769pub(crate) fn parse_unix_timestamp_nanosecond(
770    input: &[u8],
771    modifiers: modifier::UnixTimestampNanosecond,
772) -> Option<ParsedItem<'_, i128>> {
773    let ParsedItem(input, sign) = opt(sign)(input);
774    let ParsedItem(input, nano_timestamp) = n_to_m_digits::<1, 23, u128>(input)?;
775
776    match sign {
777        Some(Sign::Negative) => Some(ParsedItem(input, -nano_timestamp.cast_signed())),
778        None if modifiers.sign_is_mandatory => None,
779        _ => Some(ParsedItem(input, nano_timestamp.cast_signed())),
780    }
781}
782
783/// Parse the `end` component, which represents the end of input. If any input is remaining _and_
784/// trailing input is prohibited, `None` is returned. If trailing input is permitted, it is
785/// discarded.
786#[inline]
787pub(crate) fn parse_end(input: &[u8], end: modifier::End) -> Option<ParsedItem<'_, ()>> {
788    let modifier::End { trailing_input } = end;
789
790    if trailing_input == modifier::TrailingInput::Discard || input.is_empty() {
791        Some(ParsedItem(b"", ()))
792    } else {
793        None
794    }
795}