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 "year" component of a `Date`.
267#[expect(deprecated)]
268pub(crate) fn parse_year(
269    input: &[u8],
270    modifiers: modifier::Year,
271) -> Option<ParsedItem<'_, (i32, bool)>> {
272    match modifiers.repr {
273        modifier::YearRepr::Full => {
274            let ParsedItem(input, sign) = opt(sign)(input);
275
276            if let Some(sign) = sign {
277                let ParsedItem(input, year) = if cfg!(feature = "large-dates")
278                    && modifiers.range == modifier::YearRange::Extended
279                {
280                    n_to_m_digits_padded::<4, 6, u32>(modifiers.padding)(input)?
281                } else {
282                    exactly_n_digits_padded::<4, u32>(modifiers.padding)(input)?
283                };
284
285                Some(ParsedItem(
286                    input,
287                    match sign {
288                        Sign::Negative => (-year.cast_signed(), true),
289                        Sign::Positive => (year.cast_signed(), false),
290                    },
291                ))
292            } else if modifiers.sign_is_mandatory {
293                None
294            } else {
295                let ParsedItem(input, year) =
296                    exactly_n_digits_padded::<4, u32>(modifiers.padding)(input)?;
297                Some(ParsedItem(input, (year.cast_signed(), false)))
298            }
299        }
300        modifier::YearRepr::Century => {
301            let ParsedItem(input, sign) = opt(sign)(input);
302
303            if let Some(sign) = sign {
304                let ParsedItem(input, year) = if cfg!(feature = "large-dates")
305                    && modifiers.range == modifier::YearRange::Extended
306                {
307                    n_to_m_digits_padded::<2, 4, u32>(modifiers.padding)(input)?
308                } else {
309                    exactly_n_digits_padded::<2, u32>(modifiers.padding)(input)?
310                };
311
312                Some(ParsedItem(
313                    input,
314                    match sign {
315                        Sign::Negative => (-year.cast_signed(), true),
316                        Sign::Positive => (year.cast_signed(), false),
317                    },
318                ))
319            } else if modifiers.sign_is_mandatory {
320                None
321            } else {
322                let ParsedItem(input, year) =
323                    n_to_m_digits_padded::<1, 2, u32>(modifiers.padding)(input)?;
324                Some(ParsedItem(input, (year.cast_signed(), false)))
325            }
326        }
327        modifier::YearRepr::LastTwo => Some(
328            exactly_n_digits_padded::<2, u32>(modifiers.padding)(input)?
329                .map(|v| (v.cast_signed(), false)),
330        ),
331    }
332}
333
334/// Parse the "month" component of a `Date` in the abbreviated form (e.g. "Jan").
335#[inline]
336pub(crate) fn parse_month_short(
337    input: &[u8],
338    modifiers: modifier::MonthShort,
339) -> Option<ParsedItem<'_, Month>> {
340    let [first, second, third, rest @ ..] = input else {
341        return None;
342    };
343    let byte = if modifiers.case_sensitive {
344        u32::from_ne_bytes([0, *first, *second, *third])
345    } else {
346        u32::from_ne_bytes([
347            0,
348            first.to_ascii_uppercase(),
349            second.to_ascii_lowercase(),
350            third.to_ascii_lowercase(),
351        ])
352    };
353    const WEEKDAYS: [u32; 12] = [
354        u32::from_ne_bytes([0, b'J', b'a', b'n']),
355        u32::from_ne_bytes([0, b'F', b'e', b'b']),
356        u32::from_ne_bytes([0, b'M', b'a', b'r']),
357        u32::from_ne_bytes([0, b'A', b'p', b'r']),
358        u32::from_ne_bytes([0, b'M', b'a', b'y']),
359        u32::from_ne_bytes([0, b'J', b'u', b'n']),
360        u32::from_ne_bytes([0, b'J', b'u', b'l']),
361        u32::from_ne_bytes([0, b'A', b'u', b'g']),
362        u32::from_ne_bytes([0, b'S', b'e', b'p']),
363        u32::from_ne_bytes([0, b'O', b'c', b't']),
364        u32::from_ne_bytes([0, b'N', b'o', b'v']),
365        u32::from_ne_bytes([0, b'D', b'e', b'c']),
366    ];
367
368    let bitmask = ((WEEKDAYS[0] == byte) as u32) << 1
369        | ((WEEKDAYS[1] == byte) as u32) << 2
370        | ((WEEKDAYS[2] == byte) as u32) << 3
371        | ((WEEKDAYS[3] == byte) as u32) << 4
372        | ((WEEKDAYS[4] == byte) as u32) << 5
373        | ((WEEKDAYS[5] == byte) as u32) << 6
374        | ((WEEKDAYS[6] == byte) as u32) << 7
375        | ((WEEKDAYS[7] == byte) as u32) << 8
376        | ((WEEKDAYS[8] == byte) as u32) << 9
377        | ((WEEKDAYS[9] == byte) as u32) << 10
378        | ((WEEKDAYS[10] == byte) as u32) << 11
379        | ((WEEKDAYS[11] == byte) as u32) << 12;
380    if bitmask == 0 {
381        return None;
382    }
383    let index = if cfg!(target_endian = "little") {
384        bitmask.trailing_zeros() as u8
385    } else {
386        31 - bitmask.leading_zeros() as u8
387    };
388
389    // Safety: `index` cannot be greater than 12 because there are only 12 elements in the
390    // array that is converted to a bitmask. We know at least one element matched because
391    // the bitmask is non-zero.
392    let month = unsafe { Month::from_number(NonZero::new(index)?).unwrap_unchecked() };
393
394    Some(ParsedItem(rest, month))
395}
396
397/// Parse the "month" component of a `Date` in the long form (e.g. "January").
398#[inline]
399pub(crate) fn parse_month_long(
400    input: &[u8],
401    modifiers: modifier::MonthLong,
402) -> Option<ParsedItem<'_, Month>> {
403    use Month::*;
404
405    let ParsedItem(rest, month) = parse_month_short(
406        input,
407        modifier::MonthShort {
408            case_sensitive: modifiers.case_sensitive,
409        },
410    )?;
411
412    let expected_remaining = match month {
413        January => b"uary".as_slice(),
414        February => b"ruary".as_slice(),
415        March => b"ch".as_slice(),
416        April => b"il".as_slice(),
417        May => b"".as_slice(),
418        June => b"e".as_slice(),
419        July => b"y".as_slice(),
420        August => b"ust".as_slice(),
421        September => b"tember".as_slice(),
422        October => b"ober".as_slice(),
423        November | December => b"ember".as_slice(),
424    };
425
426    if modifiers.case_sensitive {
427        rest.strip_prefix(expected_remaining)
428            .map(|remaining| ParsedItem(remaining, month))
429    } else {
430        let (head, tail) = rest.split_at_checked(expected_remaining.len())?;
431        core::iter::zip(head, expected_remaining)
432            .all(|(a, b)| a.eq_ignore_ascii_case(b))
433            .then_some(ParsedItem(tail, month))
434    }
435}
436
437/// Parse the "month" component of a `Date` in the numerical format (e.g. "1" for January).
438#[inline]
439pub(crate) fn parse_month_numerical(
440    input: &[u8],
441    modifiers: modifier::MonthNumerical,
442) -> Option<ParsedItem<'_, Month>> {
443    exactly_n_digits_padded::<2, _>(modifiers.padding)(input)?
444        .flat_map(|n| Month::from_number(NonZero::new(n)?).ok())
445}
446
447/// Parse the "week number" component of a `Date`, where week 1 starts on the last Monday on or
448/// before January 4.
449#[inline]
450pub(crate) fn parse_week_number_iso(
451    input: &[u8],
452    modifiers: modifier::WeekNumberIso,
453) -> Option<ParsedItem<'_, u8>> {
454    exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
455}
456
457/// Parse the "week number" component of a `Date`, where week 1 starts on the first Sunday of the
458/// year.
459#[inline]
460pub(crate) fn parse_week_number_sunday(
461    input: &[u8],
462    modifiers: modifier::WeekNumberSunday,
463) -> Option<ParsedItem<'_, u8>> {
464    exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
465}
466
467/// Parse the "week number" component of a `Date`, where week 1 starts on the first Monday of the
468/// year.
469#[inline]
470pub(crate) fn parse_week_number_monday(
471    input: &[u8],
472    modifiers: modifier::WeekNumberMonday,
473) -> Option<ParsedItem<'_, u8>> {
474    exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
475}
476
477/// Parse the "weekday" component of a `Date` in the abbreviated form (e.g. "Mon").
478#[inline]
479pub(crate) fn parse_weekday_short(
480    input: &[u8],
481    modifiers: modifier::WeekdayShort,
482) -> Option<ParsedItem<'_, Weekday>> {
483    let [first, second, third, rest @ ..] = input else {
484        return None;
485    };
486    let byte = if modifiers.case_sensitive {
487        u32::from_ne_bytes([0, *first, *second, *third])
488    } else {
489        u32::from_ne_bytes([
490            0,
491            first.to_ascii_uppercase(),
492            second.to_ascii_lowercase(),
493            third.to_ascii_lowercase(),
494        ])
495    };
496    const WEEKDAYS: [u32; 7] = [
497        u32::from_ne_bytes([0, b'M', b'o', b'n']),
498        u32::from_ne_bytes([0, b'T', b'u', b'e']),
499        u32::from_ne_bytes([0, b'W', b'e', b'd']),
500        u32::from_ne_bytes([0, b'T', b'h', b'u']),
501        u32::from_ne_bytes([0, b'F', b'r', b'i']),
502        u32::from_ne_bytes([0, b'S', b'a', b't']),
503        u32::from_ne_bytes([0, b'S', b'u', b'n']),
504    ];
505
506    let bitmask = ((WEEKDAYS[0] == byte) as u32)
507        | ((WEEKDAYS[1] == byte) as u32) << 1
508        | ((WEEKDAYS[2] == byte) as u32) << 2
509        | ((WEEKDAYS[3] == byte) as u32) << 3
510        | ((WEEKDAYS[4] == byte) as u32) << 4
511        | ((WEEKDAYS[5] == byte) as u32) << 5
512        | ((WEEKDAYS[6] == byte) as u32) << 6;
513    if bitmask == 0 {
514        return None;
515    }
516    let index = if cfg!(target_endian = "little") {
517        bitmask.trailing_zeros()
518    } else {
519        31 - bitmask.leading_zeros()
520    };
521
522    if index > 6 {
523        return None;
524    }
525    // Safety: Values zero thru six are valid variants, while values greater than six have
526    // already been excluded above. We know at least one element matched because the bitmask
527    // is non-zero.
528    let weekday = unsafe { core::mem::transmute::<u8, Weekday>(index.truncate()) };
529
530    Some(ParsedItem(rest, weekday))
531}
532
533/// Parse the "weekday" component of a `Date` in the long form (e.g. "Monday").
534#[inline]
535pub(crate) fn parse_weekday_long(
536    input: &[u8],
537    modifiers: modifier::WeekdayLong,
538) -> Option<ParsedItem<'_, Weekday>> {
539    let ParsedItem(rest, weekday) = parse_weekday_short(
540        input,
541        modifier::WeekdayShort {
542            case_sensitive: modifiers.case_sensitive,
543        },
544    )?;
545
546    let expected_remaining = match weekday {
547        Weekday::Monday | Weekday::Friday | Weekday::Sunday => b"day".as_slice(),
548        Weekday::Tuesday => b"sday".as_slice(),
549        Weekday::Wednesday => b"nesday".as_slice(),
550        Weekday::Thursday => b"rsday".as_slice(),
551        Weekday::Saturday => b"urday".as_slice(),
552    };
553
554    if modifiers.case_sensitive {
555        rest.strip_prefix(expected_remaining)
556            .map(|remaining| ParsedItem(remaining, weekday))
557    } else {
558        let (head, tail) = rest.split_at_checked(expected_remaining.len())?;
559        core::iter::zip(head, expected_remaining)
560            .all(|(a, b)| a.eq_ignore_ascii_case(b))
561            .then_some(ParsedItem(tail, weekday))
562    }
563}
564
565/// Parse the weekday component of a `Date` in the numerical format, where Sunday is the first day
566/// of the week.`
567#[inline]
568pub(crate) fn parse_weekday_sunday(
569    input: &[u8],
570    modifiers: modifier::WeekdaySunday,
571) -> Option<ParsedItem<'_, Weekday>> {
572    let [digit, rest @ ..] = input else {
573        return None;
574    };
575    let mut digit = digit
576        .wrapping_sub(b'0')
577        .wrapping_sub(u8::from(modifiers.one_indexed));
578    if digit > 6 {
579        return None;
580    }
581
582    // Remap so that Sunday comes after Saturday, not before Monday.
583    digit = (digit + 6) % 7;
584
585    // Safety: Values zero thru six are valid variants.
586    let weekday = unsafe { core::mem::transmute::<u8, Weekday>(digit) };
587    Some(ParsedItem(rest, weekday))
588}
589
590/// Parse the weekday component of a `Date` in the numerical format, where Monday is the first day
591/// of the week.`
592#[inline]
593pub(crate) fn parse_weekday_monday(
594    input: &[u8],
595    modifiers: modifier::WeekdayMonday,
596) -> Option<ParsedItem<'_, Weekday>> {
597    let [digit, rest @ ..] = input else {
598        return None;
599    };
600    let digit = digit
601        .wrapping_sub(b'0')
602        .wrapping_sub(u8::from(modifiers.one_indexed));
603    if digit > 6 {
604        return None;
605    }
606
607    // Safety: Values zero thru six are valid variants.
608    let weekday = unsafe { core::mem::transmute::<u8, Weekday>(digit) };
609    Some(ParsedItem(rest, weekday))
610}
611
612/// Parse the "ordinal" component of a `Date`.
613#[inline]
614pub(crate) fn parse_ordinal(
615    input: &[u8],
616    modifiers: modifier::Ordinal,
617) -> Option<ParsedItem<'_, NonZero<u16>>> {
618    exactly_n_digits_padded::<3, _>(modifiers.padding)(input)
619        .and_then(|parsed| parsed.flat_map(NonZero::new))
620}
621
622/// Parse the "day" component of a `Date`.
623#[inline]
624pub(crate) fn parse_day(
625    input: &[u8],
626    modifiers: modifier::Day,
627) -> Option<ParsedItem<'_, NonZero<u8>>> {
628    exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
629        .and_then(|parsed| parsed.flat_map(NonZero::new))
630}
631
632/// Parse the "hour" component of a `Time` in the 12-hour format.
633#[inline]
634pub(crate) fn parse_hour_12(
635    input: &[u8],
636    modifiers: modifier::Hour12,
637) -> Option<ParsedItem<'_, u8>> {
638    exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
639}
640
641/// Parse the "hour" component of a `Time` in the 24-hour format.
642#[inline]
643pub(crate) fn parse_hour_24(
644    input: &[u8],
645    modifiers: modifier::Hour24,
646) -> Option<ParsedItem<'_, u8>> {
647    exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
648}
649
650/// Parse the "minute" component of a `Time`.
651#[inline]
652pub(crate) fn parse_minute(
653    input: &[u8],
654    modifiers: modifier::Minute,
655) -> Option<ParsedItem<'_, u8>> {
656    exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
657}
658
659/// Parse the "second" component of a `Time`.
660#[inline]
661pub(crate) fn parse_second(
662    input: &[u8],
663    modifiers: modifier::Second,
664) -> Option<ParsedItem<'_, u8>> {
665    exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
666}
667
668/// Parse the "period" component of a `Time`. Required if the hour is on a 12-hour clock.
669#[inline]
670pub(crate) fn parse_period(
671    input: &[u8],
672    modifiers: modifier::Period,
673) -> Option<ParsedItem<'_, Period>> {
674    let [first, second, rest @ ..] = input else {
675        return None;
676    };
677    let mut first = *first;
678    let mut second = *second;
679
680    if modifiers.is_uppercase && modifiers.case_sensitive {
681        match [first, second].as_slice() {
682            b"AM" => Some(ParsedItem(rest, Period::Am)),
683            b"PM" => Some(ParsedItem(rest, Period::Pm)),
684            _ => None,
685        }
686    } else {
687        first = first.to_ascii_lowercase();
688        second = second.to_ascii_lowercase();
689
690        match &[first, second] {
691            b"am" => Some(ParsedItem(rest, Period::Am)),
692            b"pm" => Some(ParsedItem(rest, Period::Pm)),
693            _ => None,
694        }
695    }
696}
697
698/// Parse the "subsecond" component of a `Time`.
699pub(crate) fn parse_subsecond(
700    input: &[u8],
701    modifiers: modifier::Subsecond,
702) -> Option<ParsedItem<'_, u32>> {
703    use modifier::SubsecondDigits::*;
704    Some(match modifiers.digits {
705        One => ExactlyNDigits::<1>::parse(input)?.map(|v| v.extend::<u32>() * 100_000_000),
706        Two => ExactlyNDigits::<2>::parse(input)?.map(|v| v.extend::<u32>() * 10_000_000),
707        Three => ExactlyNDigits::<3>::parse(input)?.map(|v| v.extend::<u32>() * 1_000_000),
708        Four => ExactlyNDigits::<4>::parse(input)?.map(|v| v.extend::<u32>() * 100_000),
709        Five => ExactlyNDigits::<5>::parse(input)?.map(|v| v * 10_000),
710        Six => ExactlyNDigits::<6>::parse(input)?.map(|v| v * 1_000),
711        Seven => ExactlyNDigits::<7>::parse(input)?.map(|v| v * 100),
712        Eight => ExactlyNDigits::<8>::parse(input)?.map(|v| v * 10),
713        Nine => ExactlyNDigits::<9>::parse(input)?,
714        OneOrMore => {
715            let ParsedItem(mut input, mut value) =
716                any_digit(input)?.map(|v| (v - b'0').extend::<u32>() * 100_000_000);
717
718            let mut multiplier = 10_000_000;
719            while let Some(ParsedItem(new_input, digit)) = any_digit(input) {
720                value += (digit - b'0').extend::<u32>() * multiplier;
721                input = new_input;
722                multiplier /= 10;
723            }
724
725            ParsedItem(input, value)
726        }
727    })
728}
729
730/// Parse the "hour" component of a `UtcOffset`.
731///
732/// Returns the value and whether the value is negative. This is used for when "-0" is parsed.
733#[inline]
734pub(crate) fn parse_offset_hour(
735    input: &[u8],
736    modifiers: modifier::OffsetHour,
737) -> Option<ParsedItem<'_, (i8, bool)>> {
738    let ParsedItem(input, sign) = opt(sign)(input);
739    let ParsedItem(input, hour) = exactly_n_digits_padded::<2, u8>(modifiers.padding)(input)?;
740    match sign {
741        Some(Sign::Negative) => Some(ParsedItem(input, (-hour.cast_signed(), true))),
742        None if modifiers.sign_is_mandatory => None,
743        _ => Some(ParsedItem(input, (hour.cast_signed(), false))),
744    }
745}
746
747/// Parse the "minute" component of a `UtcOffset`.
748#[inline]
749pub(crate) fn parse_offset_minute(
750    input: &[u8],
751    modifiers: modifier::OffsetMinute,
752) -> Option<ParsedItem<'_, i8>> {
753    Some(
754        exactly_n_digits_padded::<2, u8>(modifiers.padding)(input)?
755            .map(|offset_minute| offset_minute.cast_signed()),
756    )
757}
758
759/// Parse the "second" component of a `UtcOffset`.
760#[inline]
761pub(crate) fn parse_offset_second(
762    input: &[u8],
763    modifiers: modifier::OffsetSecond,
764) -> Option<ParsedItem<'_, i8>> {
765    Some(
766        exactly_n_digits_padded::<2, u8>(modifiers.padding)(input)?
767            .map(|offset_second| offset_second.cast_signed()),
768    )
769}
770
771/// Ignore the given number of bytes.
772#[inline]
773pub(crate) fn parse_ignore(
774    input: &[u8],
775    modifiers: modifier::Ignore,
776) -> Option<ParsedItem<'_, ()>> {
777    let modifier::Ignore { count } = modifiers;
778    let input = input.get((count.get().extend())..)?;
779    Some(ParsedItem(input, ()))
780}
781
782/// Parse the Unix timestamp component with second precision, returning the value in nanoseconds.
783#[inline]
784pub(crate) fn parse_unix_timestamp_second(
785    input: &[u8],
786    modifiers: modifier::UnixTimestampSecond,
787) -> Option<ParsedItem<'_, i128>> {
788    let ParsedItem(input, sign) = opt(sign)(input);
789    let ParsedItem(input, nano_timestamp) =
790        n_to_m_digits::<1, 14, u128>(input)?.map(|val| val * Nanosecond::per_t::<u128>(Second));
791
792    match sign {
793        Some(Sign::Negative) => Some(ParsedItem(input, -nano_timestamp.cast_signed())),
794        None if modifiers.sign_is_mandatory => None,
795        _ => Some(ParsedItem(input, nano_timestamp.cast_signed())),
796    }
797}
798
799/// Parse the Unix timestamp component with millisecond precision, returning the value in
800/// nanoseconds.
801#[inline]
802pub(crate) fn parse_unix_timestamp_millisecond(
803    input: &[u8],
804    modifiers: modifier::UnixTimestampMillisecond,
805) -> Option<ParsedItem<'_, i128>> {
806    let ParsedItem(input, sign) = opt(sign)(input);
807    let ParsedItem(input, nano_timestamp) = n_to_m_digits::<1, 17, u128>(input)?
808        .map(|val| val * Nanosecond::per_t::<u128>(Millisecond));
809
810    match sign {
811        Some(Sign::Negative) => Some(ParsedItem(input, -nano_timestamp.cast_signed())),
812        None if modifiers.sign_is_mandatory => None,
813        _ => Some(ParsedItem(input, nano_timestamp.cast_signed())),
814    }
815}
816
817/// Parse the Unix timestamp component with microsecond precision, returning the value in
818/// nanoseconds.
819#[inline]
820pub(crate) fn parse_unix_timestamp_microsecond(
821    input: &[u8],
822    modifiers: modifier::UnixTimestampMicrosecond,
823) -> Option<ParsedItem<'_, i128>> {
824    let ParsedItem(input, sign) = opt(sign)(input);
825    let ParsedItem(input, nano_timestamp) = n_to_m_digits::<1, 20, u128>(input)?
826        .map(|val| val * Nanosecond::per_t::<u128>(Microsecond));
827
828    match sign {
829        Some(Sign::Negative) => Some(ParsedItem(input, -nano_timestamp.cast_signed())),
830        None if modifiers.sign_is_mandatory => None,
831        _ => Some(ParsedItem(input, nano_timestamp.cast_signed())),
832    }
833}
834
835/// Parse the Unix timestamp component with nanosecond precision.
836#[inline]
837pub(crate) fn parse_unix_timestamp_nanosecond(
838    input: &[u8],
839    modifiers: modifier::UnixTimestampNanosecond,
840) -> Option<ParsedItem<'_, i128>> {
841    let ParsedItem(input, sign) = opt(sign)(input);
842    let ParsedItem(input, nano_timestamp) = n_to_m_digits::<1, 23, u128>(input)?;
843
844    match sign {
845        Some(Sign::Negative) => Some(ParsedItem(input, -nano_timestamp.cast_signed())),
846        None if modifiers.sign_is_mandatory => None,
847        _ => Some(ParsedItem(input, nano_timestamp.cast_signed())),
848    }
849}
850
851/// Parse the `end` component, which represents the end of input. If any input is remaining _and_
852/// trailing input is prohibited, `None` is returned. If trailing input is permitted, it is
853/// discarded.
854#[inline]
855pub(crate) fn parse_end(input: &[u8], end: modifier::End) -> Option<ParsedItem<'_, ()>> {
856    let modifier::End { trailing_input } = end;
857
858    if trailing_input == modifier::TrailingInput::Discard || input.is_empty() {
859        Some(ParsedItem(b"", ()))
860    } else {
861        None
862    }
863}