Coverage Report

Created: 2026-06-30 07:02

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/time-0.3.47/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::{Period, modifier};
9
use crate::parsing::ParsedItem;
10
use crate::parsing::combinator::{
11
    ExactlyNDigits, Sign, any_digit, exactly_n_digits_padded, n_to_m_digits, n_to_m_digits_padded,
12
    opt, sign,
13
};
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
                Some(ParsedItem(
35
0
                    input,
36
0
                    match sign {
37
0
                        Sign::Negative => (-year.cast_signed(), true),
38
0
                        Sign::Positive => (year.cast_signed(), false),
39
                    },
40
                ))
41
0
            } else if modifiers.sign_is_mandatory {
42
0
                None
43
            } else {
44
0
                let ParsedItem(input, year) =
45
0
                    exactly_n_digits_padded::<4, u32>(modifiers.padding)(input)?;
46
0
                Some(ParsedItem(input, (year.cast_signed(), false)))
47
            }
48
        }
49
        modifier::YearRepr::Century => {
50
0
            let ParsedItem(input, sign) = opt(sign)(input);
51
52
0
            if let Some(sign) = sign {
53
0
                let ParsedItem(input, year) = if cfg!(feature = "large-dates")
54
0
                    && modifiers.range == modifier::YearRange::Extended
55
                {
56
0
                    n_to_m_digits_padded::<2, 4, u32>(modifiers.padding)(input)?
57
                } else {
58
0
                    exactly_n_digits_padded::<2, u32>(modifiers.padding)(input)?
59
                };
60
61
                Some(ParsedItem(
62
0
                    input,
63
0
                    match sign {
64
0
                        Sign::Negative => (-year.cast_signed(), true),
65
0
                        Sign::Positive => (year.cast_signed(), false),
66
                    },
67
                ))
68
0
            } else if modifiers.sign_is_mandatory {
69
0
                None
70
            } else {
71
0
                let ParsedItem(input, year) =
72
0
                    n_to_m_digits_padded::<1, 2, u32>(modifiers.padding)(input)?;
73
0
                Some(ParsedItem(input, (year.cast_signed(), false)))
74
            }
75
        }
76
        modifier::YearRepr::LastTwo => Some(
77
0
            exactly_n_digits_padded::<2, u32>(modifiers.padding)(input)?
78
0
                .map(|v| (v.cast_signed(), false)),
79
        ),
80
    }
81
0
}
82
83
/// Parse the "month" component of a `Date`.
84
#[inline]
85
0
pub(crate) fn parse_month(
86
0
    input: &[u8],
87
0
    modifiers: modifier::Month,
88
0
) -> Option<ParsedItem<'_, Month>> {
89
    use Month::*;
90
0
    match modifiers.repr {
91
        modifier::MonthRepr::Numerical => {
92
0
            exactly_n_digits_padded::<2, _>(modifiers.padding)(input)?
93
0
                .flat_map(|n| Month::from_number(NonZero::new(n)?).ok())
94
        }
95
        modifier::MonthRepr::Long | modifier::MonthRepr::Short => {
96
0
            let [first, second, third, rest @ ..] = input else {
97
0
                return None;
98
            };
99
0
            let byte = if modifiers.case_sensitive {
100
0
                u32::from_ne_bytes([0, *first, *second, *third])
101
            } else {
102
0
                u32::from_ne_bytes([
103
0
                    0,
104
0
                    first.to_ascii_uppercase(),
105
0
                    second.to_ascii_lowercase(),
106
0
                    third.to_ascii_lowercase(),
107
0
                ])
108
            };
109
            const WEEKDAYS: [u32; 12] = [
110
                u32::from_ne_bytes([0, b'J', b'a', b'n']),
111
                u32::from_ne_bytes([0, b'F', b'e', b'b']),
112
                u32::from_ne_bytes([0, b'M', b'a', b'r']),
113
                u32::from_ne_bytes([0, b'A', b'p', b'r']),
114
                u32::from_ne_bytes([0, b'M', b'a', b'y']),
115
                u32::from_ne_bytes([0, b'J', b'u', b'n']),
116
                u32::from_ne_bytes([0, b'J', b'u', b'l']),
117
                u32::from_ne_bytes([0, b'A', b'u', b'g']),
118
                u32::from_ne_bytes([0, b'S', b'e', b'p']),
119
                u32::from_ne_bytes([0, b'O', b'c', b't']),
120
                u32::from_ne_bytes([0, b'N', b'o', b'v']),
121
                u32::from_ne_bytes([0, b'D', b'e', b'c']),
122
            ];
123
124
0
            let bitmask = ((WEEKDAYS[0] == byte) as u32) << 1
125
0
                | ((WEEKDAYS[1] == byte) as u32) << 2
126
0
                | ((WEEKDAYS[2] == byte) as u32) << 3
127
0
                | ((WEEKDAYS[3] == byte) as u32) << 4
128
0
                | ((WEEKDAYS[4] == byte) as u32) << 5
129
0
                | ((WEEKDAYS[5] == byte) as u32) << 6
130
0
                | ((WEEKDAYS[6] == byte) as u32) << 7
131
0
                | ((WEEKDAYS[7] == byte) as u32) << 8
132
0
                | ((WEEKDAYS[8] == byte) as u32) << 9
133
0
                | ((WEEKDAYS[9] == byte) as u32) << 10
134
0
                | ((WEEKDAYS[10] == byte) as u32) << 11
135
0
                | ((WEEKDAYS[11] == byte) as u32) << 12;
136
0
            if bitmask == 0 {
137
0
                return None;
138
0
            }
139
0
            let index = if cfg!(target_endian = "little") {
140
0
                bitmask.trailing_zeros() as u8
141
            } else {
142
0
                31 - bitmask.leading_zeros() as u8
143
            };
144
145
            // Safety: `index` cannot be greater than 12 because there are only 12 elements in the
146
            // array that is converted to a bitmask. We know at least one element matched because
147
            // the bitmask is non-zero.
148
0
            let month = unsafe { Month::from_number(NonZero::new(index)?).unwrap_unchecked() };
149
150
            // For the "short" repr, we've already validated the full text expected. For the "long"
151
            // repr, we need to validate the remaining characters.
152
0
            if modifiers.repr == modifier::MonthRepr::Short {
153
0
                return Some(ParsedItem(rest, month));
154
0
            }
155
156
0
            let expected_remaining = match month {
157
0
                January => b"uary".as_slice(),
158
0
                February => b"ruary".as_slice(),
159
0
                March => b"ch".as_slice(),
160
0
                April => b"il".as_slice(),
161
0
                May => b"".as_slice(),
162
0
                June => b"e".as_slice(),
163
0
                July => b"y".as_slice(),
164
0
                August => b"ust".as_slice(),
165
0
                September => b"tember".as_slice(),
166
0
                October => b"ober".as_slice(),
167
0
                November | December => b"ember".as_slice(),
168
            };
169
170
0
            if modifiers.case_sensitive {
171
0
                rest.strip_prefix(expected_remaining)
172
0
                    .map(|remaining| ParsedItem(remaining, month))
173
            } else {
174
0
                let (head, tail) = rest.split_at_checked(expected_remaining.len())?;
175
0
                core::iter::zip(head, expected_remaining)
176
0
                    .all(|(a, b)| a.eq_ignore_ascii_case(b))
177
0
                    .then_some(ParsedItem(tail, month))
178
            }
179
        }
180
    }
181
0
}
182
183
/// Parse the "week number" component of a `Date`.
184
0
pub(crate) fn parse_week_number(
185
0
    input: &[u8],
186
0
    modifiers: modifier::WeekNumber,
187
0
) -> Option<ParsedItem<'_, u8>> {
188
0
    exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
189
0
}
190
191
/// Parse the "weekday" component of a `Date`.
192
#[inline]
193
0
pub(crate) fn parse_weekday(
194
0
    input: &[u8],
195
0
    modifiers: modifier::Weekday,
196
0
) -> Option<ParsedItem<'_, Weekday>> {
197
0
    match modifiers.repr {
198
        modifier::WeekdayRepr::Long | modifier::WeekdayRepr::Short => {
199
0
            let [first, second, third, rest @ ..] = input else {
200
0
                return None;
201
            };
202
0
            let byte = if modifiers.case_sensitive {
203
0
                u32::from_ne_bytes([0, *first, *second, *third])
204
            } else {
205
0
                u32::from_ne_bytes([
206
0
                    0,
207
0
                    first.to_ascii_uppercase(),
208
0
                    second.to_ascii_lowercase(),
209
0
                    third.to_ascii_lowercase(),
210
0
                ])
211
            };
212
            const WEEKDAYS: [u32; 7] = [
213
                u32::from_ne_bytes([0, b'M', b'o', b'n']),
214
                u32::from_ne_bytes([0, b'T', b'u', b'e']),
215
                u32::from_ne_bytes([0, b'W', b'e', b'd']),
216
                u32::from_ne_bytes([0, b'T', b'h', b'u']),
217
                u32::from_ne_bytes([0, b'F', b'r', b'i']),
218
                u32::from_ne_bytes([0, b'S', b'a', b't']),
219
                u32::from_ne_bytes([0, b'S', b'u', b'n']),
220
            ];
221
222
0
            let bitmask = ((WEEKDAYS[0] == byte) as u32)
223
0
                | ((WEEKDAYS[1] == byte) as u32) << 1
224
0
                | ((WEEKDAYS[2] == byte) as u32) << 2
225
0
                | ((WEEKDAYS[3] == byte) as u32) << 3
226
0
                | ((WEEKDAYS[4] == byte) as u32) << 4
227
0
                | ((WEEKDAYS[5] == byte) as u32) << 5
228
0
                | ((WEEKDAYS[6] == byte) as u32) << 6;
229
0
            if bitmask == 0 {
230
0
                return None;
231
0
            }
232
0
            let index = if cfg!(target_endian = "little") {
233
0
                bitmask.trailing_zeros()
234
            } else {
235
0
                31 - bitmask.leading_zeros()
236
            };
237
238
0
            if index > 6 {
239
0
                return None;
240
0
            }
241
            // Safety: Values zero thru six are valid variants, while values greater than six have
242
            // already been excluded above. We know at least one element matched because the bitmask
243
            // is non-zero.
244
0
            let weekday = unsafe { core::mem::transmute::<u8, Weekday>(index.truncate()) };
245
246
            // For the "short" repr, we've already validated the full text expected. For the "long"
247
            // repr, we need to validate the remaining characters.
248
0
            if modifiers.repr == modifier::WeekdayRepr::Short {
249
0
                return Some(ParsedItem(rest, weekday));
250
0
            }
251
252
0
            let expected_remaining = match weekday {
253
0
                Weekday::Monday | Weekday::Friday | Weekday::Sunday => b"day".as_slice(),
254
0
                Weekday::Tuesday => b"sday".as_slice(),
255
0
                Weekday::Wednesday => b"nesday".as_slice(),
256
0
                Weekday::Thursday => b"rsday".as_slice(),
257
0
                Weekday::Saturday => b"urday".as_slice(),
258
            };
259
260
0
            if modifiers.case_sensitive {
261
0
                rest.strip_prefix(expected_remaining)
262
0
                    .map(|remaining| ParsedItem(remaining, weekday))
263
            } else {
264
0
                let (head, tail) = rest.split_at_checked(expected_remaining.len())?;
265
0
                core::iter::zip(head, expected_remaining)
266
0
                    .all(|(a, b)| a.eq_ignore_ascii_case(b))
267
0
                    .then_some(ParsedItem(tail, weekday))
268
            }
269
        }
270
        modifier::WeekdayRepr::Sunday | modifier::WeekdayRepr::Monday => {
271
0
            let [digit, rest @ ..] = input else {
272
0
                return None;
273
            };
274
0
            let mut digit = digit
275
0
                .wrapping_sub(b'0')
276
0
                .wrapping_sub(u8::from(modifiers.one_indexed));
277
0
            if digit > 6 {
278
0
                return None;
279
0
            }
280
281
0
            if modifiers.repr == modifier::WeekdayRepr::Sunday {
282
0
                // Remap so that Sunday comes after Saturday, not before Monday.
283
0
                digit = (digit + 6) % 7;
284
0
            }
285
            // Safety: Values zero thru six are valid variants.
286
0
            let weekday = unsafe { core::mem::transmute::<u8, Weekday>(digit) };
287
0
            Some(ParsedItem(rest, weekday))
288
        }
289
    }
290
0
}
291
292
/// Parse the "ordinal" component of a `Date`.
293
#[inline]
294
0
pub(crate) fn parse_ordinal(
295
0
    input: &[u8],
296
0
    modifiers: modifier::Ordinal,
297
0
) -> Option<ParsedItem<'_, NonZero<u16>>> {
298
0
    exactly_n_digits_padded::<3, _>(modifiers.padding)(input)
299
0
        .and_then(|parsed| parsed.flat_map(NonZero::new))
300
0
}
301
302
/// Parse the "day" component of a `Date`.
303
#[inline]
304
0
pub(crate) fn parse_day(
305
0
    input: &[u8],
306
0
    modifiers: modifier::Day,
307
0
) -> Option<ParsedItem<'_, NonZero<u8>>> {
308
0
    exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
309
0
        .and_then(|parsed| parsed.flat_map(NonZero::new))
310
0
}
311
312
/// Parse the "hour" component of a `Time`.
313
#[inline]
314
0
pub(crate) fn parse_hour(input: &[u8], modifiers: modifier::Hour) -> Option<ParsedItem<'_, u8>> {
315
0
    exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
316
0
}
317
318
/// Parse the "minute" component of a `Time`.
319
#[inline]
320
0
pub(crate) fn parse_minute(
321
0
    input: &[u8],
322
0
    modifiers: modifier::Minute,
323
0
) -> Option<ParsedItem<'_, u8>> {
324
0
    exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
325
0
}
326
327
/// Parse the "second" component of a `Time`.
328
#[inline]
329
0
pub(crate) fn parse_second(
330
0
    input: &[u8],
331
0
    modifiers: modifier::Second,
332
0
) -> Option<ParsedItem<'_, u8>> {
333
0
    exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
334
0
}
335
336
/// Parse the "period" component of a `Time`. Required if the hour is on a 12-hour clock.
337
#[inline]
338
0
pub(crate) fn parse_period(
339
0
    input: &[u8],
340
0
    modifiers: modifier::Period,
341
0
) -> Option<ParsedItem<'_, Period>> {
342
0
    let [first, second, rest @ ..] = input else {
343
0
        return None;
344
    };
345
0
    let mut first = *first;
346
0
    let mut second = *second;
347
348
0
    if modifiers.is_uppercase && modifiers.case_sensitive {
349
0
        match [first, second].as_slice() {
350
0
            b"AM" => Some(ParsedItem(rest, Period::Am)),
351
0
            b"PM" => Some(ParsedItem(rest, Period::Pm)),
352
0
            _ => None,
353
        }
354
    } else {
355
0
        first = first.to_ascii_lowercase();
356
0
        second = second.to_ascii_lowercase();
357
358
0
        match &[first, second] {
359
0
            b"am" => Some(ParsedItem(rest, Period::Am)),
360
0
            b"pm" => Some(ParsedItem(rest, Period::Pm)),
361
0
            _ => None,
362
        }
363
    }
364
0
}
365
366
/// Parse the "subsecond" component of a `Time`.
367
0
pub(crate) fn parse_subsecond(
368
0
    input: &[u8],
369
0
    modifiers: modifier::Subsecond,
370
0
) -> Option<ParsedItem<'_, u32>> {
371
    use modifier::SubsecondDigits::*;
372
0
    Some(match modifiers.digits {
373
0
        One => ExactlyNDigits::<1>::parse(input)?.map(|v| v.extend::<u32>() * 100_000_000),
374
0
        Two => ExactlyNDigits::<2>::parse(input)?.map(|v| v.extend::<u32>() * 10_000_000),
375
0
        Three => ExactlyNDigits::<3>::parse(input)?.map(|v| v.extend::<u32>() * 1_000_000),
376
0
        Four => ExactlyNDigits::<4>::parse(input)?.map(|v| v.extend::<u32>() * 100_000),
377
0
        Five => ExactlyNDigits::<5>::parse(input)?.map(|v| v * 10_000),
378
0
        Six => ExactlyNDigits::<6>::parse(input)?.map(|v| v * 1_000),
379
0
        Seven => ExactlyNDigits::<7>::parse(input)?.map(|v| v * 100),
380
0
        Eight => ExactlyNDigits::<8>::parse(input)?.map(|v| v * 10),
381
0
        Nine => ExactlyNDigits::<9>::parse(input)?,
382
        OneOrMore => {
383
0
            let ParsedItem(mut input, mut value) =
384
0
                any_digit(input)?.map(|v| (v - b'0').extend::<u32>() * 100_000_000);
385
386
0
            let mut multiplier = 10_000_000;
387
0
            while let Some(ParsedItem(new_input, digit)) = any_digit(input) {
388
0
                value += (digit - b'0').extend::<u32>() * multiplier;
389
0
                input = new_input;
390
0
                multiplier /= 10;
391
0
            }
392
393
0
            ParsedItem(input, value)
394
        }
395
    })
396
0
}
397
398
/// Parse the "hour" component of a `UtcOffset`.
399
///
400
/// Returns the value and whether the value is negative. This is used for when "-0" is parsed.
401
#[inline]
402
0
pub(crate) fn parse_offset_hour(
403
0
    input: &[u8],
404
0
    modifiers: modifier::OffsetHour,
405
0
) -> Option<ParsedItem<'_, (i8, bool)>> {
406
0
    let ParsedItem(input, sign) = opt(sign)(input);
407
0
    let ParsedItem(input, hour) = exactly_n_digits_padded::<2, u8>(modifiers.padding)(input)?;
408
0
    match sign {
409
0
        Some(Sign::Negative) => Some(ParsedItem(input, (-hour.cast_signed(), true))),
410
0
        None if modifiers.sign_is_mandatory => None,
411
0
        _ => Some(ParsedItem(input, (hour.cast_signed(), false))),
412
    }
413
0
}
414
415
/// Parse the "minute" component of a `UtcOffset`.
416
#[inline]
417
0
pub(crate) fn parse_offset_minute(
418
0
    input: &[u8],
419
0
    modifiers: modifier::OffsetMinute,
420
0
) -> Option<ParsedItem<'_, i8>> {
421
    Some(
422
0
        exactly_n_digits_padded::<2, u8>(modifiers.padding)(input)?
423
0
            .map(|offset_minute| offset_minute.cast_signed()),
424
    )
425
0
}
426
427
/// Parse the "second" component of a `UtcOffset`.
428
#[inline]
429
0
pub(crate) fn parse_offset_second(
430
0
    input: &[u8],
431
0
    modifiers: modifier::OffsetSecond,
432
0
) -> Option<ParsedItem<'_, i8>> {
433
    Some(
434
0
        exactly_n_digits_padded::<2, u8>(modifiers.padding)(input)?
435
0
            .map(|offset_second| offset_second.cast_signed()),
436
    )
437
0
}
438
439
/// Ignore the given number of bytes.
440
#[inline]
441
0
pub(crate) fn parse_ignore(
442
0
    input: &[u8],
443
0
    modifiers: modifier::Ignore,
444
0
) -> Option<ParsedItem<'_, ()>> {
445
0
    let modifier::Ignore { count } = modifiers;
446
0
    let input = input.get((count.get().extend())..)?;
447
0
    Some(ParsedItem(input, ()))
448
0
}
449
450
/// Parse the Unix timestamp component.
451
0
pub(crate) fn parse_unix_timestamp(
452
0
    input: &[u8],
453
0
    modifiers: modifier::UnixTimestamp,
454
0
) -> Option<ParsedItem<'_, i128>> {
455
0
    let ParsedItem(input, sign) = opt(sign)(input);
456
0
    let ParsedItem(input, nano_timestamp) = match modifiers.precision {
457
        modifier::UnixTimestampPrecision::Second => {
458
0
            n_to_m_digits::<1, 14, u128>(input)?.map(|val| val * Nanosecond::per_t::<u128>(Second))
459
        }
460
0
        modifier::UnixTimestampPrecision::Millisecond => n_to_m_digits::<1, 17, u128>(input)?
461
0
            .map(|val| val * Nanosecond::per_t::<u128>(Millisecond)),
462
0
        modifier::UnixTimestampPrecision::Microsecond => n_to_m_digits::<1, 20, u128>(input)?
463
0
            .map(|val| val * Nanosecond::per_t::<u128>(Microsecond)),
464
0
        modifier::UnixTimestampPrecision::Nanosecond => n_to_m_digits::<1, 23, _>(input)?,
465
    };
466
467
0
    match sign {
468
0
        Some(Sign::Negative) => Some(ParsedItem(input, -nano_timestamp.cast_signed())),
469
0
        None if modifiers.sign_is_mandatory => None,
470
0
        _ => Some(ParsedItem(input, nano_timestamp.cast_signed())),
471
    }
472
0
}
473
474
/// Parse the `end` component, which represents the end of input. If any input is remaining _and_
475
/// trailing input is prohibited, `None` is returned. If trailing input is permitted, it is
476
/// discarded.
477
#[inline]
478
0
pub(crate) fn parse_end(input: &[u8], end: modifier::End) -> Option<ParsedItem<'_, ()>> {
479
0
    let modifier::End { trailing_input } = end;
480
481
0
    if trailing_input == modifier::TrailingInput::Discard || input.is_empty() {
482
0
        Some(ParsedItem(b"", ()))
483
    } else {
484
0
        None
485
    }
486
0
}