time/parsing/
component.rs

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