Coverage Report

Created: 2024-12-17 06:15

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