Coverage Report

Created: 2026-01-08 06:10

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/time-0.3.44/src/parsing/component.rs
Line
Count
Source
1
//! Parsing implementations for all [`Component`](crate::format_description::Component)s.
2
3
use core::num::NonZero;
4
5
use num_conv::prelude::*;
6
7
use crate::convert::*;
8
use crate::format_description::modifier;
9
use 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
};
13
use crate::parsing::ParsedItem;
14
use crate::{Month, Weekday};
15
16
/// Parse the "year" component of a `Date`.
17
0
pub(crate) fn parse_year(
18
0
    input: &[u8],
19
0
    modifiers: modifier::Year,
20
0
) -> Option<ParsedItem<'_, (i32, bool)>> {
21
0
    match modifiers.repr {
22
        modifier::YearRepr::Full => {
23
0
            let ParsedItem(input, sign) = opt(sign)(input);
24
25
0
            if let Some(sign) = sign {
26
0
                let ParsedItem(input, year) = if cfg!(feature = "large-dates")
27
0
                    && modifiers.range == modifier::YearRange::Extended
28
                {
29
0
                    n_to_m_digits_padded::<4, 6, u32>(modifiers.padding)(input)?
30
                } else {
31
0
                    exactly_n_digits_padded::<4, u32>(modifiers.padding)(input)?
32
                };
33
34
0
                Some(if sign == b'-' {
35
0
                    ParsedItem(input, (-year.cast_signed(), true))
36
                } else {
37
0
                    ParsedItem(input, (year.cast_signed(), false))
38
                })
39
0
            } else if modifiers.sign_is_mandatory {
40
0
                None
41
            } else {
42
0
                let ParsedItem(input, year) =
43
0
                    exactly_n_digits_padded::<4, u32>(modifiers.padding)(input)?;
44
0
                Some(ParsedItem(input, (year.cast_signed(), false)))
45
            }
46
        }
47
        modifier::YearRepr::Century => {
48
0
            let ParsedItem(input, sign) = opt(sign)(input);
49
50
0
            if let Some(sign) = sign {
51
0
                let ParsedItem(input, year) = if cfg!(feature = "large-dates")
52
0
                    && modifiers.range == modifier::YearRange::Extended
53
                {
54
0
                    n_to_m_digits_padded::<2, 4, u32>(modifiers.padding)(input)?
55
                } else {
56
0
                    exactly_n_digits_padded::<2, u32>(modifiers.padding)(input)?
57
                };
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
                    n_to_m_digits_padded::<1, 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
        ),
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
#[inline]
201
0
pub(crate) fn parse_ordinal(
202
0
    input: &[u8],
203
0
    modifiers: modifier::Ordinal,
204
0
) -> Option<ParsedItem<'_, NonZero<u16>>> {
205
0
    exactly_n_digits_padded::<3, _>(modifiers.padding)(input)
206
0
}
207
208
/// Parse the "day" component of a `Date`.
209
#[inline]
210
0
pub(crate) fn parse_day(
211
0
    input: &[u8],
212
0
    modifiers: modifier::Day,
213
0
) -> Option<ParsedItem<'_, NonZero<u8>>> {
214
0
    exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
215
0
}
216
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
#[inline]
228
0
pub(crate) fn parse_hour(input: &[u8], modifiers: modifier::Hour) -> Option<ParsedItem<'_, u8>> {
229
0
    exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
230
0
}
231
232
/// Parse the "minute" component of a `Time`.
233
#[inline]
234
0
pub(crate) fn parse_minute(
235
0
    input: &[u8],
236
0
    modifiers: modifier::Minute,
237
0
) -> Option<ParsedItem<'_, u8>> {
238
0
    exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
239
0
}
240
241
/// Parse the "second" component of a `Time`.
242
#[inline]
243
0
pub(crate) fn parse_second(
244
0
    input: &[u8],
245
0
    modifiers: modifier::Second,
246
0
) -> Option<ParsedItem<'_, u8>> {
247
0
    exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
248
0
}
249
250
/// Parse the "period" component of a `Time`. Required if the hour is on a 12-hour clock.
251
#[inline]
252
0
pub(crate) fn parse_period(
253
0
    input: &[u8],
254
0
    modifiers: modifier::Period,
255
0
) -> Option<ParsedItem<'_, Period>> {
256
0
    first_match(
257
0
        if modifiers.is_uppercase {
258
0
            [
259
0
                (b"AM".as_slice(), Period::Am),
260
0
                (b"PM".as_slice(), Period::Pm),
261
0
            ]
262
        } else {
263
0
            [
264
0
                (b"am".as_slice(), Period::Am),
265
0
                (b"pm".as_slice(), Period::Pm),
266
0
            ]
267
        },
268
0
        modifiers.case_sensitive,
269
0
    )(input)
270
0
}
271
272
/// Parse the "subsecond" component of a `Time`.
273
0
pub(crate) fn parse_subsecond(
274
0
    input: &[u8],
275
0
    modifiers: modifier::Subsecond,
276
0
) -> Option<ParsedItem<'_, u32>> {
277
    use modifier::SubsecondDigits::*;
278
0
    Some(match modifiers.digits {
279
0
        One => exactly_n_digits::<1, u32>(input)?.map(|v| v * 100_000_000),
280
0
        Two => exactly_n_digits::<2, u32>(input)?.map(|v| v * 10_000_000),
281
0
        Three => exactly_n_digits::<3, u32>(input)?.map(|v| v * 1_000_000),
282
0
        Four => exactly_n_digits::<4, u32>(input)?.map(|v| v * 100_000),
283
0
        Five => exactly_n_digits::<5, u32>(input)?.map(|v| v * 10_000),
284
0
        Six => exactly_n_digits::<6, u32>(input)?.map(|v| v * 1_000),
285
0
        Seven => exactly_n_digits::<7, u32>(input)?.map(|v| v * 100),
286
0
        Eight => exactly_n_digits::<8, u32>(input)?.map(|v| v * 10),
287
0
        Nine => exactly_n_digits::<9, _>(input)?,
288
        OneOrMore => {
289
0
            let ParsedItem(mut input, mut value) =
290
0
                any_digit(input)?.map(|v| (v - b'0').extend::<u32>() * 100_000_000);
291
292
0
            let mut multiplier = 10_000_000;
293
0
            while let Some(ParsedItem(new_input, digit)) = any_digit(input) {
294
0
                value += (digit - b'0').extend::<u32>() * multiplier;
295
0
                input = new_input;
296
0
                multiplier /= 10;
297
0
            }
298
299
0
            ParsedItem(input, value)
300
        }
301
    })
302
0
}
303
304
/// Parse the "hour" component of a `UtcOffset`.
305
///
306
/// Returns the value and whether the value is negative. This is used for when "-0" is parsed.
307
0
pub(crate) fn parse_offset_hour(
308
0
    input: &[u8],
309
0
    modifiers: modifier::OffsetHour,
310
0
) -> Option<ParsedItem<'_, (i8, bool)>> {
311
0
    let ParsedItem(input, sign) = opt(sign)(input);
312
0
    let ParsedItem(input, hour) = exactly_n_digits_padded::<2, u8>(modifiers.padding)(input)?;
313
0
    match sign {
314
0
        Some(b'-') => Some(ParsedItem(input, (-hour.cast_signed(), true))),
315
0
        None if modifiers.sign_is_mandatory => None,
316
0
        _ => Some(ParsedItem(input, (hour.cast_signed(), false))),
317
    }
318
0
}
319
320
/// Parse the "minute" component of a `UtcOffset`.
321
#[inline]
322
0
pub(crate) fn parse_offset_minute(
323
0
    input: &[u8],
324
0
    modifiers: modifier::OffsetMinute,
325
0
) -> Option<ParsedItem<'_, i8>> {
326
    Some(
327
0
        exactly_n_digits_padded::<2, u8>(modifiers.padding)(input)?
328
0
            .map(|offset_minute| offset_minute.cast_signed()),
329
    )
330
0
}
331
332
/// Parse the "second" component of a `UtcOffset`.
333
#[inline]
334
0
pub(crate) fn parse_offset_second(
335
0
    input: &[u8],
336
0
    modifiers: modifier::OffsetSecond,
337
0
) -> Option<ParsedItem<'_, i8>> {
338
    Some(
339
0
        exactly_n_digits_padded::<2, u8>(modifiers.padding)(input)?
340
0
            .map(|offset_second| offset_second.cast_signed()),
341
    )
342
0
}
343
344
/// Ignore the given number of bytes.
345
#[inline]
346
0
pub(crate) fn parse_ignore(
347
0
    input: &[u8],
348
0
    modifiers: modifier::Ignore,
349
0
) -> Option<ParsedItem<'_, ()>> {
350
0
    let modifier::Ignore { count } = modifiers;
351
0
    let input = input.get((count.get().extend())..)?;
352
0
    Some(ParsedItem(input, ()))
353
0
}
354
355
/// Parse the Unix timestamp component.
356
0
pub(crate) fn parse_unix_timestamp(
357
0
    input: &[u8],
358
0
    modifiers: modifier::UnixTimestamp,
359
0
) -> Option<ParsedItem<'_, i128>> {
360
0
    let ParsedItem(input, sign) = opt(sign)(input);
361
0
    let ParsedItem(input, nano_timestamp) = match modifiers.precision {
362
        modifier::UnixTimestampPrecision::Second => {
363
0
            n_to_m_digits::<1, 14, u128>(input)?.map(|val| val * Nanosecond::per_t::<u128>(Second))
364
        }
365
0
        modifier::UnixTimestampPrecision::Millisecond => n_to_m_digits::<1, 17, u128>(input)?
366
0
            .map(|val| val * Nanosecond::per_t::<u128>(Millisecond)),
367
0
        modifier::UnixTimestampPrecision::Microsecond => n_to_m_digits::<1, 20, u128>(input)?
368
0
            .map(|val| val * Nanosecond::per_t::<u128>(Microsecond)),
369
0
        modifier::UnixTimestampPrecision::Nanosecond => n_to_m_digits::<1, 23, _>(input)?,
370
    };
371
372
0
    match sign {
373
0
        Some(b'-') => Some(ParsedItem(input, -nano_timestamp.cast_signed())),
374
0
        None if modifiers.sign_is_mandatory => None,
375
0
        _ => Some(ParsedItem(input, nano_timestamp.cast_signed())),
376
    }
377
0
}
378
379
/// Parse the `end` component, which represents the end of input. If any input is remaining, `None`
380
/// is returned.
381
#[inline]
382
0
pub(crate) const fn parse_end(input: &[u8], end: modifier::End) -> Option<ParsedItem<'_, ()>> {
383
0
    let modifier::End {} = end;
384
385
0
    if input.is_empty() {
386
0
        Some(ParsedItem(input, ()))
387
    } else {
388
0
        None
389
    }
390
0
}