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