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::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
16/// Parse the "year" component of a `Date`.
17pub(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
83/// Parse the "month" component of a `Date`.
84pub(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
129/// Parse the "week number" component of a `Date`.
130pub(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
137/// Parse the "weekday" component of a `Date`.
138pub(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/// Parse the "ordinal" component of a `Date`.
328#[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/// Parse the "day" component of a `Date`.
338#[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/// Indicate whether the hour is "am" or "pm".
348#[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/// Parse the "hour" component of a `Time`.
357#[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/// Parse the "minute" component of a `Time`.
363#[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/// Parse the "second" component of a `Time`.
372#[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/// Parse the "period" component of a `Time`. Required if the hour is on a 12-hour clock.
381#[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
398/// Parse the "subsecond" component of a `Time`.
399pub(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
430/// Parse the "hour" component of a `UtcOffset`.
431///
432/// Returns the value and whether the value is negative. This is used for when "-0" is parsed.
433pub(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/// Parse the "minute" component of a `UtcOffset`.
447#[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/// Parse the "second" component of a `UtcOffset`.
459#[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/// Ignore the given number of bytes.
471#[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
481/// Parse the Unix timestamp component.
482pub(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/// Parse the `end` component, which represents the end of input. If any input is remaining, `None`
506/// is returned.
507#[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}