Coverage Report

Created: 2026-08-14 06:28

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/time-0.3.55/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::format_description::{Period, modifier};
8
use crate::parsing::ParsedItem;
9
use crate::parsing::combinator::{
10
    ExactlyNDigits, Sign, any_digit, exactly_n_digits_padded, n_to_m_digits, n_to_m_digits_padded,
11
    opt, sign,
12
};
13
use crate::unit::*;
14
use crate::{Month, Weekday};
15
16
/// Parse the full calendar-based year.
17
///
18
/// This permits utilizing the full range of supported years, though at the cost of introducing
19
/// parsing ambiguities.
20
#[inline]
21
0
pub(crate) fn parse_calendar_year_full_extended_range(
22
0
    input: &[u8],
23
0
    modifiers: modifier::CalendarYearFullExtendedRange,
24
0
) -> Option<ParsedItem<'_, i32>> {
25
0
    let ParsedItem(input, sign) = opt(sign)(input);
26
27
0
    if let Some(sign) = sign {
28
0
        let ParsedItem(input, year) = n_to_m_digits_padded::<4, 6, u32>(modifiers.padding)(input)?;
29
30
        Some(ParsedItem(
31
0
            input,
32
0
            match sign {
33
0
                Sign::Negative => -year.cast_signed(),
34
0
                Sign::Positive => year.cast_signed(),
35
            },
36
        ))
37
0
    } else if modifiers.sign_is_mandatory {
38
0
        None
39
    } else {
40
0
        let ParsedItem(input, year) = exactly_n_digits_padded::<4, u32>(modifiers.padding)(input)?;
41
0
        Some(ParsedItem(input, year.cast_signed()))
42
    }
43
0
}
44
45
/// Parse the full calendar-based year.
46
///
47
/// This only supports four digits in order to avoid parsing ambiguities, so it cannot utilize the
48
/// full range of supported years when the `large-dates` feature flag is enabled.
49
#[inline]
50
0
pub(crate) fn parse_calendar_year_full_standard_range(
51
0
    input: &[u8],
52
0
    modifiers: modifier::CalendarYearFullStandardRange,
53
0
) -> Option<ParsedItem<'_, i32>> {
54
0
    let ParsedItem(input, sign) = opt(sign)(input);
55
56
0
    if let Some(sign) = sign {
57
0
        let ParsedItem(input, year) = exactly_n_digits_padded::<4, u32>(modifiers.padding)(input)?;
58
59
        Some(ParsedItem(
60
0
            input,
61
0
            match sign {
62
0
                Sign::Negative => -year.cast_signed(),
63
0
                Sign::Positive => year.cast_signed(),
64
            },
65
        ))
66
0
    } else if modifiers.sign_is_mandatory {
67
0
        None
68
    } else {
69
0
        let ParsedItem(input, year) = exactly_n_digits_padded::<4, u32>(modifiers.padding)(input)?;
70
0
        Some(ParsedItem(input, year.cast_signed()))
71
    }
72
0
}
73
74
/// Parse the full ISO-week based year.
75
///
76
/// This permits utilizing the full range of supported years, though at the cost of introducing
77
/// parsing ambiguities.
78
#[inline]
79
0
pub(crate) fn parse_iso_year_full_extended_range(
80
0
    input: &[u8],
81
0
    modifiers: modifier::IsoYearFullExtendedRange,
82
0
) -> Option<ParsedItem<'_, i32>> {
83
0
    let ParsedItem(input, sign) = opt(sign)(input);
84
85
0
    if let Some(sign) = sign {
86
0
        let ParsedItem(input, year) = n_to_m_digits_padded::<4, 6, u32>(modifiers.padding)(input)?;
87
88
        Some(ParsedItem(
89
0
            input,
90
0
            match sign {
91
0
                Sign::Negative => -year.cast_signed(),
92
0
                Sign::Positive => year.cast_signed(),
93
            },
94
        ))
95
0
    } else if modifiers.sign_is_mandatory {
96
0
        None
97
    } else {
98
0
        let ParsedItem(input, year) = exactly_n_digits_padded::<4, u32>(modifiers.padding)(input)?;
99
0
        Some(ParsedItem(input, year.cast_signed()))
100
    }
101
0
}
102
103
/// Parse the full ISO-week based year.
104
///
105
/// This only supports four digits in order to avoid parsing ambiguities, so it cannot utilize the
106
/// full range of supported years when the `large-dates` feature flag is enabled.
107
#[inline]
108
0
pub(crate) fn parse_iso_year_full_standard_range(
109
0
    input: &[u8],
110
0
    modifiers: modifier::IsoYearFullStandardRange,
111
0
) -> Option<ParsedItem<'_, i32>> {
112
0
    let ParsedItem(input, sign) = opt(sign)(input);
113
114
0
    if let Some(sign) = sign {
115
0
        let ParsedItem(input, year) = exactly_n_digits_padded::<4, u32>(modifiers.padding)(input)?;
116
117
        Some(ParsedItem(
118
0
            input,
119
0
            match sign {
120
0
                Sign::Negative => -year.cast_signed(),
121
0
                Sign::Positive => year.cast_signed(),
122
            },
123
        ))
124
0
    } else if modifiers.sign_is_mandatory {
125
0
        None
126
    } else {
127
0
        let ParsedItem(input, year) = exactly_n_digits_padded::<4, u32>(modifiers.padding)(input)?;
128
0
        Some(ParsedItem(input, year.cast_signed()))
129
    }
130
0
}
131
132
/// Parse all digits of the calendar-based year except the last two.
133
///
134
/// This permits utilizing the full range of supported years, though at the cost of introducing
135
/// parsing ambiguities.
136
#[inline]
137
0
pub(crate) fn parse_calendar_year_century_extended_range(
138
0
    input: &[u8],
139
0
    modifiers: modifier::CalendarYearCenturyExtendedRange,
140
0
) -> Option<ParsedItem<'_, (i16, bool)>> {
141
0
    let ParsedItem(input, sign) = opt(sign)(input);
142
143
0
    if let Some(sign) = sign {
144
0
        let ParsedItem(input, year) = n_to_m_digits_padded::<2, 4, u16>(modifiers.padding)(input)?;
145
146
        Some(ParsedItem(
147
0
            input,
148
0
            match sign {
149
0
                Sign::Negative => (-year.cast_signed(), true),
150
0
                Sign::Positive => (year.cast_signed(), false),
151
            },
152
        ))
153
0
    } else if modifiers.sign_is_mandatory {
154
0
        None
155
    } else {
156
0
        let ParsedItem(input, year) = n_to_m_digits_padded::<1, 2, u16>(modifiers.padding)(input)?;
157
0
        Some(ParsedItem(input, (year.cast_signed(), false)))
158
    }
159
0
}
160
161
/// Parse all digits of the calendar-based year except the last two.
162
///
163
/// This only supports two digits in order to avoid parsing ambiguities, so it cannot utilize the
164
/// full range of supported years when the `large-dates` feature flag is enabled.
165
#[inline]
166
0
pub(crate) fn parse_calendar_year_century_standard_range(
167
0
    input: &[u8],
168
0
    modifiers: modifier::CalendarYearCenturyStandardRange,
169
0
) -> Option<ParsedItem<'_, (i16, bool)>> {
170
0
    let ParsedItem(input, sign) = opt(sign)(input);
171
172
0
    if let Some(sign) = sign {
173
0
        let ParsedItem(input, year) = exactly_n_digits_padded::<2, u16>(modifiers.padding)(input)?;
174
175
        Some(ParsedItem(
176
0
            input,
177
0
            match sign {
178
0
                Sign::Negative => (-year.cast_signed(), true),
179
0
                Sign::Positive => (year.cast_signed(), false),
180
            },
181
        ))
182
0
    } else if modifiers.sign_is_mandatory {
183
0
        None
184
    } else {
185
0
        let ParsedItem(input, year) = n_to_m_digits_padded::<1, 2, u16>(modifiers.padding)(input)?;
186
0
        Some(ParsedItem(input, (year.cast_signed(), false)))
187
    }
188
0
}
189
190
/// Parse all digits of the ISO week-based year except the last two.
191
///
192
/// This permits utilizing the full range of supported years, though at the cost of introducing
193
/// parsing ambiguities.
194
#[inline]
195
0
pub(crate) fn parse_iso_year_century_extended_range(
196
0
    input: &[u8],
197
0
    modifiers: modifier::IsoYearCenturyExtendedRange,
198
0
) -> Option<ParsedItem<'_, (i16, bool)>> {
199
0
    let ParsedItem(input, sign) = opt(sign)(input);
200
201
0
    if let Some(sign) = sign {
202
0
        let ParsedItem(input, year) = n_to_m_digits_padded::<2, 4, u16>(modifiers.padding)(input)?;
203
204
        Some(ParsedItem(
205
0
            input,
206
0
            match sign {
207
0
                Sign::Negative => (-year.cast_signed(), true),
208
0
                Sign::Positive => (year.cast_signed(), false),
209
            },
210
        ))
211
0
    } else if modifiers.sign_is_mandatory {
212
0
        None
213
    } else {
214
0
        let ParsedItem(input, year) = n_to_m_digits_padded::<1, 2, u16>(modifiers.padding)(input)?;
215
0
        Some(ParsedItem(input, (year.cast_signed(), false)))
216
    }
217
0
}
218
219
/// Parse all digits of the ISO week-based year except the last two.
220
///
221
/// This only supports two digits in order to avoid parsing ambiguities, so it cannot utilize the
222
/// full range of supported years when the `large-dates` feature flag is enabled.
223
#[inline]
224
0
pub(crate) fn parse_iso_year_century_standard_range(
225
0
    input: &[u8],
226
0
    modifiers: modifier::IsoYearCenturyStandardRange,
227
0
) -> Option<ParsedItem<'_, (i16, bool)>> {
228
0
    let ParsedItem(input, sign) = opt(sign)(input);
229
230
0
    if let Some(sign) = sign {
231
0
        let ParsedItem(input, year) = exactly_n_digits_padded::<2, u16>(modifiers.padding)(input)?;
232
233
        Some(ParsedItem(
234
0
            input,
235
0
            match sign {
236
0
                Sign::Negative => (-year.cast_signed(), true),
237
0
                Sign::Positive => (year.cast_signed(), false),
238
            },
239
        ))
240
0
    } else if modifiers.sign_is_mandatory {
241
0
        None
242
    } else {
243
0
        let ParsedItem(input, year) = n_to_m_digits_padded::<1, 2, u16>(modifiers.padding)(input)?;
244
0
        Some(ParsedItem(input, (year.cast_signed(), false)))
245
    }
246
0
}
247
248
/// Parse the last two digits of the calendar-based year.
249
#[inline]
250
0
pub(crate) fn parse_calendar_year_last_two(
251
0
    input: &[u8],
252
0
    modifiers: modifier::CalendarYearLastTwo,
253
0
) -> Option<ParsedItem<'_, u8>> {
254
0
    exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
255
0
}
256
257
/// Parse the last two digits of the ISO week-based year.
258
#[inline]
259
0
pub(crate) fn parse_iso_year_last_two(
260
0
    input: &[u8],
261
0
    modifiers: modifier::IsoYearLastTwo,
262
0
) -> Option<ParsedItem<'_, u8>> {
263
0
    exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
264
0
}
265
266
/// Parse the "month" component of a `Date` in the abbreviated form (e.g. "Jan").
267
#[inline]
268
0
pub(crate) fn parse_month_short(
269
0
    input: &[u8],
270
0
    modifiers: modifier::MonthShort,
271
0
) -> Option<ParsedItem<'_, Month>> {
272
0
    let [first, second, third, rest @ ..] = input else {
273
0
        return None;
274
    };
275
0
    let byte = if modifiers.case_sensitive {
276
0
        u32::from_ne_bytes([0, *first, *second, *third])
277
    } else {
278
0
        u32::from_ne_bytes([
279
0
            0,
280
0
            first.to_ascii_uppercase(),
281
0
            second.to_ascii_lowercase(),
282
0
            third.to_ascii_lowercase(),
283
0
        ])
284
    };
285
    const WEEKDAYS: [u32; 12] = [
286
        u32::from_ne_bytes([0, b'J', b'a', b'n']),
287
        u32::from_ne_bytes([0, b'F', b'e', b'b']),
288
        u32::from_ne_bytes([0, b'M', b'a', b'r']),
289
        u32::from_ne_bytes([0, b'A', b'p', b'r']),
290
        u32::from_ne_bytes([0, b'M', b'a', b'y']),
291
        u32::from_ne_bytes([0, b'J', b'u', b'n']),
292
        u32::from_ne_bytes([0, b'J', b'u', b'l']),
293
        u32::from_ne_bytes([0, b'A', b'u', b'g']),
294
        u32::from_ne_bytes([0, b'S', b'e', b'p']),
295
        u32::from_ne_bytes([0, b'O', b'c', b't']),
296
        u32::from_ne_bytes([0, b'N', b'o', b'v']),
297
        u32::from_ne_bytes([0, b'D', b'e', b'c']),
298
    ];
299
300
0
    let bitmask = ((WEEKDAYS[0] == byte) as u32) << 1
301
0
        | ((WEEKDAYS[1] == byte) as u32) << 2
302
0
        | ((WEEKDAYS[2] == byte) as u32) << 3
303
0
        | ((WEEKDAYS[3] == byte) as u32) << 4
304
0
        | ((WEEKDAYS[4] == byte) as u32) << 5
305
0
        | ((WEEKDAYS[5] == byte) as u32) << 6
306
0
        | ((WEEKDAYS[6] == byte) as u32) << 7
307
0
        | ((WEEKDAYS[7] == byte) as u32) << 8
308
0
        | ((WEEKDAYS[8] == byte) as u32) << 9
309
0
        | ((WEEKDAYS[9] == byte) as u32) << 10
310
0
        | ((WEEKDAYS[10] == byte) as u32) << 11
311
0
        | ((WEEKDAYS[11] == byte) as u32) << 12;
312
0
    if bitmask == 0 {
313
0
        return None;
314
0
    }
315
0
    let index = if cfg!(target_endian = "little") {
316
0
        bitmask.trailing_zeros() as u8
317
    } else {
318
0
        31 - bitmask.leading_zeros() as u8
319
    };
320
321
    // Safety: `index` cannot be greater than 12 because there are only 12 elements in the
322
    // array that is converted to a bitmask. We know at least one element matched because
323
    // the bitmask is non-zero.
324
0
    let month = unsafe { Month::from_number(NonZero::new(index)?).unwrap_unchecked() };
325
326
0
    Some(ParsedItem(rest, month))
327
0
}
328
329
/// Parse the "month" component of a `Date` in the long form (e.g. "January").
330
#[inline]
331
0
pub(crate) fn parse_month_long(
332
0
    input: &[u8],
333
0
    modifiers: modifier::MonthLong,
334
0
) -> Option<ParsedItem<'_, Month>> {
335
    use Month::*;
336
337
0
    let ParsedItem(rest, month) = parse_month_short(
338
0
        input,
339
0
        modifier::MonthShort {
340
0
            case_sensitive: modifiers.case_sensitive,
341
0
        },
342
0
    )?;
343
344
0
    let expected_remaining = match month {
345
0
        January => b"uary".as_slice(),
346
0
        February => b"ruary".as_slice(),
347
0
        March => b"ch".as_slice(),
348
0
        April => b"il".as_slice(),
349
0
        May => b"".as_slice(),
350
0
        June => b"e".as_slice(),
351
0
        July => b"y".as_slice(),
352
0
        August => b"ust".as_slice(),
353
0
        September => b"tember".as_slice(),
354
0
        October => b"ober".as_slice(),
355
0
        November | December => b"ember".as_slice(),
356
    };
357
358
0
    if modifiers.case_sensitive {
359
0
        rest.strip_prefix(expected_remaining)
360
0
            .map(|remaining| ParsedItem(remaining, month))
361
    } else {
362
0
        let (head, tail) = rest.split_at_checked(expected_remaining.len())?;
363
0
        core::iter::zip(head, expected_remaining)
364
0
            .all(|(a, b)| a.eq_ignore_ascii_case(b))
365
0
            .then_some(ParsedItem(tail, month))
366
    }
367
0
}
368
369
/// Parse the "month" component of a `Date` in the numerical format (e.g. "1" for January).
370
#[inline]
371
0
pub(crate) fn parse_month_numerical(
372
0
    input: &[u8],
373
0
    modifiers: modifier::MonthNumerical,
374
0
) -> Option<ParsedItem<'_, Month>> {
375
0
    exactly_n_digits_padded::<2, _>(modifiers.padding)(input)?
376
0
        .flat_map(|n| Month::from_number(NonZero::new(n)?).ok())
377
0
}
378
379
/// Parse the "week number" component of a `Date`, where week 1 starts on the last Monday on or
380
/// before January 4.
381
#[inline]
382
0
pub(crate) fn parse_week_number_iso(
383
0
    input: &[u8],
384
0
    modifiers: modifier::WeekNumberIso,
385
0
) -> Option<ParsedItem<'_, u8>> {
386
0
    exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
387
0
}
388
389
/// Parse the "week number" component of a `Date`, where week 1 starts on the first Sunday of the
390
/// year.
391
#[inline]
392
0
pub(crate) fn parse_week_number_sunday(
393
0
    input: &[u8],
394
0
    modifiers: modifier::WeekNumberSunday,
395
0
) -> Option<ParsedItem<'_, u8>> {
396
0
    exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
397
0
}
398
399
/// Parse the "week number" component of a `Date`, where week 1 starts on the first Monday of the
400
/// year.
401
#[inline]
402
0
pub(crate) fn parse_week_number_monday(
403
0
    input: &[u8],
404
0
    modifiers: modifier::WeekNumberMonday,
405
0
) -> Option<ParsedItem<'_, u8>> {
406
0
    exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
407
0
}
408
409
/// Parse the "weekday" component of a `Date` in the abbreviated form (e.g. "Mon").
410
#[inline]
411
0
pub(crate) fn parse_weekday_short(
412
0
    input: &[u8],
413
0
    modifiers: modifier::WeekdayShort,
414
0
) -> Option<ParsedItem<'_, Weekday>> {
415
0
    let [first, second, third, rest @ ..] = input else {
416
0
        return None;
417
    };
418
0
    let byte = if modifiers.case_sensitive {
419
0
        u32::from_ne_bytes([0, *first, *second, *third])
420
    } else {
421
0
        u32::from_ne_bytes([
422
0
            0,
423
0
            first.to_ascii_uppercase(),
424
0
            second.to_ascii_lowercase(),
425
0
            third.to_ascii_lowercase(),
426
0
        ])
427
    };
428
    const WEEKDAYS: [u32; 7] = [
429
        u32::from_ne_bytes([0, b'M', b'o', b'n']),
430
        u32::from_ne_bytes([0, b'T', b'u', b'e']),
431
        u32::from_ne_bytes([0, b'W', b'e', b'd']),
432
        u32::from_ne_bytes([0, b'T', b'h', b'u']),
433
        u32::from_ne_bytes([0, b'F', b'r', b'i']),
434
        u32::from_ne_bytes([0, b'S', b'a', b't']),
435
        u32::from_ne_bytes([0, b'S', b'u', b'n']),
436
    ];
437
438
0
    let bitmask = ((WEEKDAYS[0] == byte) as u32)
439
0
        | ((WEEKDAYS[1] == byte) as u32) << 1
440
0
        | ((WEEKDAYS[2] == byte) as u32) << 2
441
0
        | ((WEEKDAYS[3] == byte) as u32) << 3
442
0
        | ((WEEKDAYS[4] == byte) as u32) << 4
443
0
        | ((WEEKDAYS[5] == byte) as u32) << 5
444
0
        | ((WEEKDAYS[6] == byte) as u32) << 6;
445
0
    if bitmask == 0 {
446
0
        return None;
447
0
    }
448
0
    let index = if cfg!(target_endian = "little") {
449
0
        bitmask.trailing_zeros()
450
    } else {
451
0
        31 - bitmask.leading_zeros()
452
    };
453
454
0
    if index > 6 {
455
0
        return None;
456
0
    }
457
    // Safety: Values zero thru six are valid variants, while values greater than six have
458
    // already been excluded above. We know at least one element matched because the bitmask
459
    // is non-zero.
460
0
    let weekday = unsafe { core::mem::transmute::<u8, Weekday>(index.truncate()) };
461
462
0
    Some(ParsedItem(rest, weekday))
463
0
}
464
465
/// Parse the "weekday" component of a `Date` in the long form (e.g. "Monday").
466
#[inline]
467
0
pub(crate) fn parse_weekday_long(
468
0
    input: &[u8],
469
0
    modifiers: modifier::WeekdayLong,
470
0
) -> Option<ParsedItem<'_, Weekday>> {
471
0
    let ParsedItem(rest, weekday) = parse_weekday_short(
472
0
        input,
473
0
        modifier::WeekdayShort {
474
0
            case_sensitive: modifiers.case_sensitive,
475
0
        },
476
0
    )?;
477
478
0
    let expected_remaining = match weekday {
479
0
        Weekday::Monday | Weekday::Friday | Weekday::Sunday => b"day".as_slice(),
480
0
        Weekday::Tuesday => b"sday".as_slice(),
481
0
        Weekday::Wednesday => b"nesday".as_slice(),
482
0
        Weekday::Thursday => b"rsday".as_slice(),
483
0
        Weekday::Saturday => b"urday".as_slice(),
484
    };
485
486
0
    if modifiers.case_sensitive {
487
0
        rest.strip_prefix(expected_remaining)
488
0
            .map(|remaining| ParsedItem(remaining, weekday))
489
    } else {
490
0
        let (head, tail) = rest.split_at_checked(expected_remaining.len())?;
491
0
        core::iter::zip(head, expected_remaining)
492
0
            .all(|(a, b)| a.eq_ignore_ascii_case(b))
493
0
            .then_some(ParsedItem(tail, weekday))
494
    }
495
0
}
496
497
/// Parse the weekday component of a `Date` in the numerical format, where Sunday is the first day
498
/// of the week.`
499
#[inline]
500
0
pub(crate) fn parse_weekday_sunday(
501
0
    input: &[u8],
502
0
    modifiers: modifier::WeekdaySunday,
503
0
) -> Option<ParsedItem<'_, Weekday>> {
504
0
    let [digit, rest @ ..] = input else {
505
0
        return None;
506
    };
507
0
    let mut digit = digit
508
0
        .wrapping_sub(b'0')
509
0
        .wrapping_sub(u8::from(modifiers.one_indexed));
510
0
    if digit > 6 {
511
0
        return None;
512
0
    }
513
514
    // Remap so that Sunday comes after Saturday, not before Monday.
515
0
    digit = (digit + 6) % 7;
516
517
    // Safety: Values zero thru six are valid variants.
518
0
    let weekday = unsafe { core::mem::transmute::<u8, Weekday>(digit) };
519
0
    Some(ParsedItem(rest, weekday))
520
0
}
521
522
/// Parse the weekday component of a `Date` in the numerical format, where Monday is the first day
523
/// of the week.`
524
#[inline]
525
0
pub(crate) fn parse_weekday_monday(
526
0
    input: &[u8],
527
0
    modifiers: modifier::WeekdayMonday,
528
0
) -> Option<ParsedItem<'_, Weekday>> {
529
0
    let [digit, rest @ ..] = input else {
530
0
        return None;
531
    };
532
0
    let digit = digit
533
0
        .wrapping_sub(b'0')
534
0
        .wrapping_sub(u8::from(modifiers.one_indexed));
535
0
    if digit > 6 {
536
0
        return None;
537
0
    }
538
539
    // Safety: Values zero thru six are valid variants.
540
0
    let weekday = unsafe { core::mem::transmute::<u8, Weekday>(digit) };
541
0
    Some(ParsedItem(rest, weekday))
542
0
}
543
544
/// Parse the "ordinal" component of a `Date`.
545
#[inline]
546
0
pub(crate) fn parse_ordinal(
547
0
    input: &[u8],
548
0
    modifiers: modifier::Ordinal,
549
0
) -> Option<ParsedItem<'_, NonZero<u16>>> {
550
0
    exactly_n_digits_padded::<3, _>(modifiers.padding)(input)
551
0
        .and_then(|parsed| parsed.flat_map(NonZero::new))
552
0
}
553
554
/// Parse the "day" component of a `Date`.
555
#[inline]
556
0
pub(crate) fn parse_day(
557
0
    input: &[u8],
558
0
    modifiers: modifier::Day,
559
0
) -> Option<ParsedItem<'_, NonZero<u8>>> {
560
0
    exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
561
0
        .and_then(|parsed| parsed.flat_map(NonZero::new))
562
0
}
563
564
/// Parse the "hour" component of a `Time` in the 12-hour format.
565
#[inline]
566
0
pub(crate) fn parse_hour_12(
567
0
    input: &[u8],
568
0
    modifiers: modifier::Hour12,
569
0
) -> Option<ParsedItem<'_, u8>> {
570
0
    exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
571
0
}
572
573
/// Parse the "hour" component of a `Time` in the 24-hour format.
574
#[inline]
575
0
pub(crate) fn parse_hour_24(
576
0
    input: &[u8],
577
0
    modifiers: modifier::Hour24,
578
0
) -> Option<ParsedItem<'_, u8>> {
579
0
    exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
580
0
}
581
582
/// Parse the "minute" component of a `Time`.
583
#[inline]
584
0
pub(crate) fn parse_minute(
585
0
    input: &[u8],
586
0
    modifiers: modifier::Minute,
587
0
) -> Option<ParsedItem<'_, u8>> {
588
0
    exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
589
0
}
590
591
/// Parse the "second" component of a `Time`.
592
#[inline]
593
0
pub(crate) fn parse_second(
594
0
    input: &[u8],
595
0
    modifiers: modifier::Second,
596
0
) -> Option<ParsedItem<'_, u8>> {
597
0
    exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
598
0
}
599
600
/// Parse the "period" component of a `Time`. Required if the hour is on a 12-hour clock.
601
#[inline]
602
0
pub(crate) fn parse_period(
603
0
    input: &[u8],
604
0
    modifiers: modifier::Period,
605
0
) -> Option<ParsedItem<'_, Period>> {
606
0
    let [first, second, rest @ ..] = input else {
607
0
        return None;
608
    };
609
0
    let mut first = *first;
610
0
    let mut second = *second;
611
612
0
    if modifiers.is_uppercase && modifiers.case_sensitive {
613
0
        match [first, second].as_slice() {
614
0
            b"AM" => Some(ParsedItem(rest, Period::Am)),
615
0
            b"PM" => Some(ParsedItem(rest, Period::Pm)),
616
0
            _ => None,
617
        }
618
    } else {
619
0
        first = first.to_ascii_lowercase();
620
0
        second = second.to_ascii_lowercase();
621
622
0
        match &[first, second] {
623
0
            b"am" => Some(ParsedItem(rest, Period::Am)),
624
0
            b"pm" => Some(ParsedItem(rest, Period::Pm)),
625
0
            _ => None,
626
        }
627
    }
628
0
}
629
630
/// Parse the "subsecond" component of a `Time`.
631
0
pub(crate) fn parse_subsecond(
632
0
    input: &[u8],
633
0
    modifiers: modifier::Subsecond,
634
0
) -> Option<ParsedItem<'_, u32>> {
635
    use modifier::SubsecondDigits::*;
636
0
    Some(match modifiers.digits {
637
0
        One => ExactlyNDigits::<1>::parse(input)?.map(|v| v.widen::<u32>() * 100_000_000),
638
0
        Two => ExactlyNDigits::<2>::parse(input)?.map(|v| v.widen::<u32>() * 10_000_000),
639
0
        Three => ExactlyNDigits::<3>::parse(input)?.map(|v| v.widen::<u32>() * 1_000_000),
640
0
        Four => ExactlyNDigits::<4>::parse(input)?.map(|v| v.widen::<u32>() * 100_000),
641
0
        Five => ExactlyNDigits::<5>::parse(input)?.map(|v| v * 10_000),
642
0
        Six => ExactlyNDigits::<6>::parse(input)?.map(|v| v * 1_000),
643
0
        Seven => ExactlyNDigits::<7>::parse(input)?.map(|v| v * 100),
644
0
        Eight => ExactlyNDigits::<8>::parse(input)?.map(|v| v * 10),
645
0
        Nine => ExactlyNDigits::<9>::parse(input)?,
646
        OneOrMore => {
647
            const MULTIPLIERS: [u32; 8] =
648
                [10_000_000, 1_000_000, 100_000, 10_000, 1_000, 100, 10, 1];
649
650
            // Consume the first digit, which is mandatory.
651
0
            let ParsedItem(mut input, mut value) =
652
0
                any_digit(input)?.map(|v| (v - b'0').widen::<u32>() * 100_000_000);
653
654
            // Consume digits 2 thru 9, all of which are optional.
655
0
            for multiplier in MULTIPLIERS {
656
0
                if let Some(ParsedItem(new_input, digit)) = any_digit(input) {
657
0
                    value += (digit - b'0').widen::<u32>() * multiplier;
658
0
                    input = new_input;
659
0
                } else {
660
0
                    break;
661
                }
662
            }
663
664
            // Consume any remaining digits, up to 32 total to avoid consuming unbounded user input.
665
            // As these digits are past the nanosecond precision supported, they don't need to be
666
            // involved in calculating the value.
667
0
            for _ in 10..33 {
668
0
                if let Some(ParsedItem(new_input, _)) = any_digit(input) {
669
0
                    input = new_input;
670
0
                } else {
671
0
                    break;
672
                }
673
            }
674
675
0
            ParsedItem(input, value)
676
        }
677
    })
678
0
}
679
680
/// Parse the "hour" component of a `UtcOffset`.
681
///
682
/// Returns the value and whether the value is negative. This is used for when "-0" is parsed.
683
#[inline]
684
0
pub(crate) fn parse_offset_hour(
685
0
    input: &[u8],
686
0
    modifiers: modifier::OffsetHour,
687
0
) -> Option<ParsedItem<'_, (i8, bool)>> {
688
0
    let ParsedItem(input, sign) = opt(sign)(input);
689
0
    let ParsedItem(input, hour) = exactly_n_digits_padded::<2, u8>(modifiers.padding)(input)?;
690
0
    match sign {
691
0
        Some(Sign::Negative) => Some(ParsedItem(input, (-hour.cast_signed(), true))),
692
0
        None if modifiers.sign_is_mandatory => None,
693
0
        _ => Some(ParsedItem(input, (hour.cast_signed(), false))),
694
    }
695
0
}
696
697
/// Parse the "minute" component of a `UtcOffset`.
698
#[inline]
699
0
pub(crate) fn parse_offset_minute(
700
0
    input: &[u8],
701
0
    modifiers: modifier::OffsetMinute,
702
0
) -> Option<ParsedItem<'_, i8>> {
703
    Some(
704
0
        exactly_n_digits_padded::<2, u8>(modifiers.padding)(input)?
705
0
            .map(|offset_minute| offset_minute.cast_signed()),
706
    )
707
0
}
708
709
/// Parse the "second" component of a `UtcOffset`.
710
#[inline]
711
0
pub(crate) fn parse_offset_second(
712
0
    input: &[u8],
713
0
    modifiers: modifier::OffsetSecond,
714
0
) -> Option<ParsedItem<'_, i8>> {
715
    Some(
716
0
        exactly_n_digits_padded::<2, u8>(modifiers.padding)(input)?
717
0
            .map(|offset_second| offset_second.cast_signed()),
718
    )
719
0
}
720
721
/// Ignore the given number of bytes.
722
#[inline]
723
0
pub(crate) fn parse_ignore(
724
0
    input: &[u8],
725
0
    modifiers: modifier::Ignore,
726
0
) -> Option<ParsedItem<'_, ()>> {
727
0
    let modifier::Ignore { count } = modifiers;
728
0
    let input = input.get((count.get().widen())..)?;
729
0
    Some(ParsedItem(input, ()))
730
0
}
731
732
/// Parse the Unix timestamp component with second precision, returning the value in nanoseconds.
733
#[inline]
734
0
pub(crate) fn parse_unix_timestamp_second(
735
0
    input: &[u8],
736
0
    modifiers: modifier::UnixTimestampSecond,
737
0
) -> Option<ParsedItem<'_, i128>> {
738
0
    let ParsedItem(input, sign) = opt(sign)(input);
739
0
    let ParsedItem(input, nano_timestamp) =
740
0
        n_to_m_digits::<1, 14, u128>(input)?.map(|val| val * Nanosecond::per_t::<u128>(Second));
741
742
0
    match sign {
743
0
        Some(Sign::Negative) => Some(ParsedItem(input, -nano_timestamp.cast_signed())),
744
0
        None if modifiers.sign_is_mandatory => None,
745
0
        _ => Some(ParsedItem(input, nano_timestamp.cast_signed())),
746
    }
747
0
}
748
749
/// Parse the Unix timestamp component with millisecond precision, returning the value in
750
/// nanoseconds.
751
#[inline]
752
0
pub(crate) fn parse_unix_timestamp_millisecond(
753
0
    input: &[u8],
754
0
    modifiers: modifier::UnixTimestampMillisecond,
755
0
) -> Option<ParsedItem<'_, i128>> {
756
0
    let ParsedItem(input, sign) = opt(sign)(input);
757
0
    let ParsedItem(input, nano_timestamp) = n_to_m_digits::<1, 17, u128>(input)?
758
0
        .map(|val| val * Nanosecond::per_t::<u128>(Millisecond));
759
760
0
    match sign {
761
0
        Some(Sign::Negative) => Some(ParsedItem(input, -nano_timestamp.cast_signed())),
762
0
        None if modifiers.sign_is_mandatory => None,
763
0
        _ => Some(ParsedItem(input, nano_timestamp.cast_signed())),
764
    }
765
0
}
766
767
/// Parse the Unix timestamp component with microsecond precision, returning the value in
768
/// nanoseconds.
769
#[inline]
770
0
pub(crate) fn parse_unix_timestamp_microsecond(
771
0
    input: &[u8],
772
0
    modifiers: modifier::UnixTimestampMicrosecond,
773
0
) -> Option<ParsedItem<'_, i128>> {
774
0
    let ParsedItem(input, sign) = opt(sign)(input);
775
0
    let ParsedItem(input, nano_timestamp) = n_to_m_digits::<1, 20, u128>(input)?
776
0
        .map(|val| val * Nanosecond::per_t::<u128>(Microsecond));
777
778
0
    match sign {
779
0
        Some(Sign::Negative) => Some(ParsedItem(input, -nano_timestamp.cast_signed())),
780
0
        None if modifiers.sign_is_mandatory => None,
781
0
        _ => Some(ParsedItem(input, nano_timestamp.cast_signed())),
782
    }
783
0
}
784
785
/// Parse the Unix timestamp component with nanosecond precision.
786
#[inline]
787
0
pub(crate) fn parse_unix_timestamp_nanosecond(
788
0
    input: &[u8],
789
0
    modifiers: modifier::UnixTimestampNanosecond,
790
0
) -> Option<ParsedItem<'_, i128>> {
791
0
    let ParsedItem(input, sign) = opt(sign)(input);
792
0
    let ParsedItem(input, nano_timestamp) = n_to_m_digits::<1, 23, u128>(input)?;
793
794
0
    match sign {
795
0
        Some(Sign::Negative) => Some(ParsedItem(input, -nano_timestamp.cast_signed())),
796
0
        None if modifiers.sign_is_mandatory => None,
797
0
        _ => Some(ParsedItem(input, nano_timestamp.cast_signed())),
798
    }
799
0
}
800
801
/// Parse the `end` component, which represents the end of input. If any input is remaining _and_
802
/// trailing input is prohibited, `None` is returned. If trailing input is permitted, it is
803
/// discarded.
804
#[inline]
805
0
pub(crate) fn parse_end(input: &[u8], end: modifier::End) -> Option<ParsedItem<'_, ()>> {
806
0
    let modifier::End { trailing_input } = end;
807
808
0
    if trailing_input == modifier::TrailingInput::Discard || input.is_empty() {
809
0
        Some(ParsedItem(b"", ()))
810
    } else {
811
0
        None
812
    }
813
0
}