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