Coverage Report

Created: 2025-07-23 07:29

/rust/registry/src/index.crates.io-6f17d22bba15001f/time-0.3.13/src/parsing/parsable.rs
Line
Count
Source (jump to first uncovered line)
1
//! A trait that can be used to parse an item from an input.
2
3
use core::ops::Deref;
4
5
use crate::error::TryFromParsed;
6
use crate::format_description::well_known::iso8601::EncodedConfig;
7
use crate::format_description::well_known::{Iso8601, Rfc2822, Rfc3339};
8
use crate::format_description::FormatItem;
9
use crate::parsing::{Parsed, ParsedItem};
10
use crate::{error, Date, Month, OffsetDateTime, PrimitiveDateTime, Time, UtcOffset, Weekday};
11
12
/// A type that can be parsed.
13
#[cfg_attr(__time_03_docs, doc(notable_trait))]
14
pub trait Parsable: sealed::Sealed {}
15
impl Parsable for FormatItem<'_> {}
16
impl Parsable for [FormatItem<'_>] {}
17
impl Parsable for Rfc2822 {}
18
impl Parsable for Rfc3339 {}
19
impl<const CONFIG: EncodedConfig> Parsable for Iso8601<CONFIG> {}
20
impl<T: Deref> Parsable for T where T::Target: Parsable {}
21
22
/// Seal the trait to prevent downstream users from implementing it, while still allowing it to
23
/// exist in generic bounds.
24
mod sealed {
25
26
    #[allow(clippy::wildcard_imports)]
27
    use super::*;
28
29
    /// Parse the item using a format description and an input.
30
    #[cfg_attr(__time_03_docs, doc(cfg(feature = "parsing")))]
31
    pub trait Sealed {
32
        /// Parse the item into the provided [`Parsed`] struct.
33
        ///
34
        /// This method can be used to parse a single component without parsing the full value.
35
        fn parse_into<'a>(
36
            &self,
37
            input: &'a [u8],
38
            parsed: &mut Parsed,
39
        ) -> Result<&'a [u8], error::Parse>;
40
41
        /// Parse the item into a new [`Parsed`] struct.
42
        ///
43
        /// This method can only be used to parse a complete value of a type. If any characters
44
        /// remain after parsing, an error will be returned.
45
0
        fn parse(&self, input: &[u8]) -> Result<Parsed, error::Parse> {
46
0
            let mut parsed = Parsed::new();
47
0
            if self.parse_into(input, &mut parsed)?.is_empty() {
48
0
                Ok(parsed)
49
            } else {
50
0
                Err(error::Parse::UnexpectedTrailingCharacters)
51
            }
52
0
        }
53
54
        /// Parse a [`Date`] from the format description.
55
0
        fn parse_date(&self, input: &[u8]) -> Result<Date, error::Parse> {
56
0
            Ok(self.parse(input)?.try_into()?)
57
0
        }
58
59
        /// Parse a [`Time`] from the format description.
60
0
        fn parse_time(&self, input: &[u8]) -> Result<Time, error::Parse> {
61
0
            Ok(self.parse(input)?.try_into()?)
62
0
        }
63
64
        /// Parse a [`UtcOffset`] from the format description.
65
0
        fn parse_offset(&self, input: &[u8]) -> Result<UtcOffset, error::Parse> {
66
0
            Ok(self.parse(input)?.try_into()?)
67
0
        }
68
69
        /// Parse a [`PrimitiveDateTime`] from the format description.
70
0
        fn parse_date_time(&self, input: &[u8]) -> Result<PrimitiveDateTime, error::Parse> {
71
0
            Ok(self.parse(input)?.try_into()?)
72
0
        }
73
74
        /// Parse a [`OffsetDateTime`] from the format description.
75
0
        fn parse_offset_date_time(&self, input: &[u8]) -> Result<OffsetDateTime, error::Parse> {
76
0
            Ok(self.parse(input)?.try_into()?)
77
0
        }
78
    }
79
}
80
81
// region: custom formats
82
impl sealed::Sealed for FormatItem<'_> {
83
0
    fn parse_into<'a>(
84
0
        &self,
85
0
        input: &'a [u8],
86
0
        parsed: &mut Parsed,
87
0
    ) -> Result<&'a [u8], error::Parse> {
88
0
        Ok(parsed.parse_item(input, self)?)
89
0
    }
90
}
91
92
impl sealed::Sealed for [FormatItem<'_>] {
93
0
    fn parse_into<'a>(
94
0
        &self,
95
0
        input: &'a [u8],
96
0
        parsed: &mut Parsed,
97
0
    ) -> Result<&'a [u8], error::Parse> {
98
0
        Ok(parsed.parse_items(input, self)?)
99
0
    }
100
}
101
102
impl<T: Deref> sealed::Sealed for T
103
where
104
    T::Target: sealed::Sealed,
105
{
106
0
    fn parse_into<'a>(
107
0
        &self,
108
0
        input: &'a [u8],
109
0
        parsed: &mut Parsed,
110
0
    ) -> Result<&'a [u8], error::Parse> {
111
0
        self.deref().parse_into(input, parsed)
112
0
    }
113
}
114
// endregion custom formats
115
116
// region: well-known formats
117
impl sealed::Sealed for Rfc2822 {
118
0
    fn parse_into<'a>(
119
0
        &self,
120
0
        input: &'a [u8],
121
0
        parsed: &mut Parsed,
122
0
    ) -> Result<&'a [u8], error::Parse> {
123
        use crate::error::ParseFromDescription::{InvalidComponent, InvalidLiteral};
124
        use crate::parsing::combinator::rfc::rfc2822::{cfws, fws};
125
        use crate::parsing::combinator::{
126
            ascii_char, exactly_n_digits, first_match, n_to_m_digits, opt, sign,
127
        };
128
129
0
        let colon = ascii_char::<b':'>;
130
0
        let comma = ascii_char::<b','>;
131
0
132
0
        let input = opt(fws)(input).into_inner();
133
0
        let input = first_match(
134
0
            [
135
0
                (b"Mon".as_slice(), Weekday::Monday),
136
0
                (b"Tue".as_slice(), Weekday::Tuesday),
137
0
                (b"Wed".as_slice(), Weekday::Wednesday),
138
0
                (b"Thu".as_slice(), Weekday::Thursday),
139
0
                (b"Fri".as_slice(), Weekday::Friday),
140
0
                (b"Sat".as_slice(), Weekday::Saturday),
141
0
                (b"Sun".as_slice(), Weekday::Sunday),
142
0
            ],
143
0
            false,
144
0
        )(input)
145
0
        .and_then(|item| item.consume_value(|value| parsed.set_weekday(value)))
146
0
        .ok_or(InvalidComponent("weekday"))?;
147
0
        let input = comma(input).ok_or(InvalidLiteral)?.into_inner();
148
0
        let input = cfws(input).ok_or(InvalidLiteral)?.into_inner();
149
0
        let input = n_to_m_digits::<_, 1, 2>(input)
150
0
            .and_then(|item| item.consume_value(|value| parsed.set_day(value)))
151
0
            .ok_or(InvalidComponent("day"))?;
152
0
        let input = cfws(input).ok_or(InvalidLiteral)?.into_inner();
153
0
        let input = first_match(
154
0
            [
155
0
                (b"Jan".as_slice(), Month::January),
156
0
                (b"Feb".as_slice(), Month::February),
157
0
                (b"Mar".as_slice(), Month::March),
158
0
                (b"Apr".as_slice(), Month::April),
159
0
                (b"May".as_slice(), Month::May),
160
0
                (b"Jun".as_slice(), Month::June),
161
0
                (b"Jul".as_slice(), Month::July),
162
0
                (b"Aug".as_slice(), Month::August),
163
0
                (b"Sep".as_slice(), Month::September),
164
0
                (b"Oct".as_slice(), Month::October),
165
0
                (b"Nov".as_slice(), Month::November),
166
0
                (b"Dec".as_slice(), Month::December),
167
0
            ],
168
0
            false,
169
0
        )(input)
170
0
        .and_then(|item| item.consume_value(|value| parsed.set_month(value)))
171
0
        .ok_or(InvalidComponent("month"))?;
172
0
        let input = cfws(input).ok_or(InvalidLiteral)?.into_inner();
173
0
        let input = match exactly_n_digits::<u32, 4>(input) {
174
0
            Some(item) => {
175
0
                let input = item
176
0
                    .flat_map(|year| if year >= 1900 { Some(year) } else { None })
177
0
                    .and_then(|item| item.consume_value(|value| parsed.set_year(value as _)))
178
0
                    .ok_or(InvalidComponent("year"))?;
179
0
                fws(input).ok_or(InvalidLiteral)?.into_inner()
180
            }
181
            None => {
182
0
                let input = exactly_n_digits::<u32, 2>(input)
183
0
                    .and_then(|item| {
184
0
                        item.map(|year| if year < 50 { year + 2000 } else { year + 1900 })
185
0
                            .map(|year| year as _)
186
0
                            .consume_value(|value| parsed.set_year(value))
187
0
                    })
188
0
                    .ok_or(InvalidComponent("year"))?;
189
0
                cfws(input).ok_or(InvalidLiteral)?.into_inner()
190
            }
191
        };
192
193
0
        let input = exactly_n_digits::<_, 2>(input)
194
0
            .and_then(|item| item.consume_value(|value| parsed.set_hour_24(value)))
195
0
            .ok_or(InvalidComponent("hour"))?;
196
0
        let input = opt(cfws)(input).into_inner();
197
0
        let input = colon(input).ok_or(InvalidLiteral)?.into_inner();
198
0
        let input = opt(cfws)(input).into_inner();
199
0
        let input = exactly_n_digits::<_, 2>(input)
200
0
            .and_then(|item| item.consume_value(|value| parsed.set_minute(value)))
201
0
            .ok_or(InvalidComponent("minute"))?;
202
203
0
        let input = if let Some(input) = colon(opt(cfws)(input).into_inner()) {
204
0
            let input = input.into_inner(); // discard the colon
205
0
            let input = opt(cfws)(input).into_inner();
206
0
            let input = exactly_n_digits::<_, 2>(input)
207
0
                .and_then(|item| item.consume_value(|value| parsed.set_second(value)))
208
0
                .ok_or(InvalidComponent("second"))?;
209
0
            cfws(input).ok_or(InvalidLiteral)?.into_inner()
210
        } else {
211
0
            cfws(input).ok_or(InvalidLiteral)?.into_inner()
212
        };
213
214
        // The RFC explicitly allows leap seconds.
215
0
        parsed.set_leap_second_allowed(true);
216
0
217
0
        #[allow(clippy::unnecessary_lazy_evaluations)] // rust-lang/rust-clippy#8522
218
0
        let zone_literal = first_match(
219
0
            [
220
0
                (b"UT".as_slice(), 0),
221
0
                (b"GMT".as_slice(), 0),
222
0
                (b"EST".as_slice(), -5),
223
0
                (b"EDT".as_slice(), -4),
224
0
                (b"CST".as_slice(), -6),
225
0
                (b"CDT".as_slice(), -5),
226
0
                (b"MST".as_slice(), -7),
227
0
                (b"MDT".as_slice(), -6),
228
0
                (b"PST".as_slice(), -8),
229
0
                (b"PDT".as_slice(), -7),
230
0
            ],
231
0
            false,
232
0
        )(input)
233
0
        .or_else(|| match input {
234
            [
235
0
                b'a'..=b'i' | b'k'..=b'z' | b'A'..=b'I' | b'K'..=b'Z',
236
0
                rest @ ..,
237
0
            ] => Some(ParsedItem(rest, 0)),
238
0
            _ => None,
239
0
        });
240
0
        if let Some(zone_literal) = zone_literal {
241
0
            let input = zone_literal
242
0
                .consume_value(|value| parsed.set_offset_hour(value))
243
0
                .ok_or(InvalidComponent("offset hour"))?;
244
0
            parsed
245
0
                .set_offset_minute_signed(0)
246
0
                .ok_or(InvalidComponent("offset minute"))?;
247
0
            parsed
248
0
                .set_offset_second_signed(0)
249
0
                .ok_or(InvalidComponent("offset second"))?;
250
0
            return Ok(input);
251
0
        }
252
253
0
        let ParsedItem(input, offset_sign) = sign(input).ok_or(InvalidComponent("offset hour"))?;
254
0
        let input = exactly_n_digits::<u8, 2>(input)
255
0
            .and_then(|item| {
256
0
                item.map(|offset_hour| {
257
0
                    if offset_sign == b'-' {
258
0
                        -(offset_hour as i8)
259
                    } else {
260
0
                        offset_hour as _
261
                    }
262
0
                })
263
0
                .consume_value(|value| parsed.set_offset_hour(value))
264
0
            })
265
0
            .ok_or(InvalidComponent("offset hour"))?;
266
0
        let input = exactly_n_digits::<u8, 2>(input)
267
0
            .and_then(|item| {
268
0
                item.consume_value(|value| parsed.set_offset_minute_signed(value as _))
269
0
            })
270
0
            .ok_or(InvalidComponent("offset minute"))?;
271
272
0
        Ok(input)
273
0
    }
274
275
0
    fn parse_offset_date_time(&self, input: &[u8]) -> Result<OffsetDateTime, error::Parse> {
276
        use crate::error::ParseFromDescription::{InvalidComponent, InvalidLiteral};
277
        use crate::parsing::combinator::rfc::rfc2822::{cfws, fws};
278
        use crate::parsing::combinator::{
279
            ascii_char, exactly_n_digits, first_match, n_to_m_digits, opt, sign,
280
        };
281
282
0
        let colon = ascii_char::<b':'>;
283
0
        let comma = ascii_char::<b','>;
284
0
285
0
        let input = opt(fws)(input).into_inner();
286
        // This parses the weekday, but we don't actually use the value anywhere. Because of this,
287
        // just return `()` to avoid unnecessary generated code.
288
0
        let ParsedItem(input, ()) = first_match(
289
0
            [
290
0
                (b"Mon".as_slice(), ()),
291
0
                (b"Tue".as_slice(), ()),
292
0
                (b"Wed".as_slice(), ()),
293
0
                (b"Thu".as_slice(), ()),
294
0
                (b"Fri".as_slice(), ()),
295
0
                (b"Sat".as_slice(), ()),
296
0
                (b"Sun".as_slice(), ()),
297
0
            ],
298
0
            false,
299
0
        )(input)
300
0
        .ok_or(InvalidComponent("weekday"))?;
301
0
        let input = comma(input).ok_or(InvalidLiteral)?.into_inner();
302
0
        let input = cfws(input).ok_or(InvalidLiteral)?.into_inner();
303
0
        let ParsedItem(input, day) =
304
0
            n_to_m_digits::<_, 1, 2>(input).ok_or(InvalidComponent("day"))?;
305
0
        let input = cfws(input).ok_or(InvalidLiteral)?.into_inner();
306
0
        let ParsedItem(input, month) = first_match(
307
0
            [
308
0
                (b"Jan".as_slice(), Month::January),
309
0
                (b"Feb".as_slice(), Month::February),
310
0
                (b"Mar".as_slice(), Month::March),
311
0
                (b"Apr".as_slice(), Month::April),
312
0
                (b"May".as_slice(), Month::May),
313
0
                (b"Jun".as_slice(), Month::June),
314
0
                (b"Jul".as_slice(), Month::July),
315
0
                (b"Aug".as_slice(), Month::August),
316
0
                (b"Sep".as_slice(), Month::September),
317
0
                (b"Oct".as_slice(), Month::October),
318
0
                (b"Nov".as_slice(), Month::November),
319
0
                (b"Dec".as_slice(), Month::December),
320
0
            ],
321
0
            false,
322
0
        )(input)
323
0
        .ok_or(InvalidComponent("month"))?;
324
0
        let input = cfws(input).ok_or(InvalidLiteral)?.into_inner();
325
0
        let (input, year) = match exactly_n_digits::<u32, 4>(input) {
326
0
            Some(item) => {
327
0
                let ParsedItem(input, year) = item
328
0
                    .flat_map(|year| if year >= 1900 { Some(year) } else { None })
329
0
                    .ok_or(InvalidComponent("year"))?;
330
0
                let input = fws(input).ok_or(InvalidLiteral)?.into_inner();
331
0
                (input, year)
332
            }
333
            None => {
334
0
                let ParsedItem(input, year) = exactly_n_digits::<u32, 2>(input)
335
0
                    .map(|item| item.map(|year| if year < 50 { year + 2000 } else { year + 1900 }))
336
0
                    .ok_or(InvalidComponent("year"))?;
337
0
                let input = cfws(input).ok_or(InvalidLiteral)?.into_inner();
338
0
                (input, year)
339
            }
340
        };
341
342
0
        let ParsedItem(input, hour) =
343
0
            exactly_n_digits::<_, 2>(input).ok_or(InvalidComponent("hour"))?;
344
0
        let input = opt(cfws)(input).into_inner();
345
0
        let input = colon(input).ok_or(InvalidLiteral)?.into_inner();
346
0
        let input = opt(cfws)(input).into_inner();
347
0
        let ParsedItem(input, minute) =
348
0
            exactly_n_digits::<_, 2>(input).ok_or(InvalidComponent("minute"))?;
349
350
0
        let (input, mut second) = if let Some(input) = colon(opt(cfws)(input).into_inner()) {
351
0
            let input = input.into_inner(); // discard the colon
352
0
            let input = opt(cfws)(input).into_inner();
353
0
            let ParsedItem(input, second) =
354
0
                exactly_n_digits::<_, 2>(input).ok_or(InvalidComponent("second"))?;
355
0
            let input = cfws(input).ok_or(InvalidLiteral)?.into_inner();
356
0
            (input, second)
357
        } else {
358
0
            (cfws(input).ok_or(InvalidLiteral)?.into_inner(), 0)
359
        };
360
361
        #[allow(clippy::unnecessary_lazy_evaluations)] // rust-lang/rust-clippy#8522
362
0
        let zone_literal = first_match(
363
0
            [
364
0
                (b"UT".as_slice(), 0),
365
0
                (b"GMT".as_slice(), 0),
366
0
                (b"EST".as_slice(), -5),
367
0
                (b"EDT".as_slice(), -4),
368
0
                (b"CST".as_slice(), -6),
369
0
                (b"CDT".as_slice(), -5),
370
0
                (b"MST".as_slice(), -7),
371
0
                (b"MDT".as_slice(), -6),
372
0
                (b"PST".as_slice(), -8),
373
0
                (b"PDT".as_slice(), -7),
374
0
            ],
375
0
            false,
376
0
        )(input)
377
0
        .or_else(|| match input {
378
            [
379
0
                b'a'..=b'i' | b'k'..=b'z' | b'A'..=b'I' | b'K'..=b'Z',
380
0
                rest @ ..,
381
0
            ] => Some(ParsedItem(rest, 0)),
382
0
            _ => None,
383
0
        });
384
385
0
        let (input, offset_hour, offset_minute) = if let Some(zone_literal) = zone_literal {
386
0
            let ParsedItem(input, offset_hour) = zone_literal;
387
0
            (input, offset_hour, 0)
388
        } else {
389
0
            let ParsedItem(input, offset_sign) =
390
0
                sign(input).ok_or(InvalidComponent("offset hour"))?;
391
0
            let ParsedItem(input, offset_hour) = exactly_n_digits::<u8, 2>(input)
392
0
                .map(|item| {
393
0
                    item.map(|offset_hour| {
394
0
                        if offset_sign == b'-' {
395
0
                            -(offset_hour as i8)
396
                        } else {
397
0
                            offset_hour as _
398
                        }
399
0
                    })
400
0
                })
401
0
                .ok_or(InvalidComponent("offset hour"))?;
402
0
            let ParsedItem(input, offset_minute) =
403
0
                exactly_n_digits::<u8, 2>(input).ok_or(InvalidComponent("offset minute"))?;
404
0
            (input, offset_hour, offset_minute as i8)
405
        };
406
407
0
        if !input.is_empty() {
408
0
            return Err(error::Parse::UnexpectedTrailingCharacters);
409
0
        }
410
0
411
0
        let mut nanosecond = 0;
412
0
        let leap_second_input = if second == 60 {
413
0
            second = 59;
414
0
            nanosecond = 999_999_999;
415
0
            true
416
        } else {
417
0
            false
418
        };
419
420
0
        let dt = (|| {
421
0
            let date = Date::from_calendar_date(year as _, month, day)?;
422
0
            let time = Time::from_hms_nano(hour, minute, second, nanosecond)?;
423
0
            let offset = UtcOffset::from_hms(offset_hour, offset_minute, 0)?;
424
0
            Ok(date.with_time(time).assume_offset(offset))
425
0
        })()
426
0
        .map_err(TryFromParsed::ComponentRange)?;
427
428
0
        if leap_second_input && !dt.is_valid_leap_second_stand_in() {
429
0
            return Err(error::Parse::TryFromParsed(TryFromParsed::ComponentRange(
430
0
                error::ComponentRange {
431
0
                    name: "second",
432
0
                    minimum: 0,
433
0
                    maximum: 59,
434
0
                    value: 60,
435
0
                    conditional_range: true,
436
0
                },
437
0
            )));
438
0
        }
439
0
440
0
        Ok(dt)
441
0
    }
442
}
443
444
impl sealed::Sealed for Rfc3339 {
445
0
    fn parse_into<'a>(
446
0
        &self,
447
0
        input: &'a [u8],
448
0
        parsed: &mut Parsed,
449
0
    ) -> Result<&'a [u8], error::Parse> {
450
        use crate::error::ParseFromDescription::{InvalidComponent, InvalidLiteral};
451
        use crate::parsing::combinator::{
452
            any_digit, ascii_char, ascii_char_ignore_case, exactly_n_digits, sign,
453
        };
454
455
0
        let dash = ascii_char::<b'-'>;
456
0
        let colon = ascii_char::<b':'>;
457
458
0
        let input = exactly_n_digits::<u32, 4>(input)
459
0
            .and_then(|item| item.consume_value(|value| parsed.set_year(value as _)))
460
0
            .ok_or(InvalidComponent("year"))?;
461
0
        let input = dash(input).ok_or(InvalidLiteral)?.into_inner();
462
0
        let input = exactly_n_digits::<_, 2>(input)
463
0
            .and_then(|item| item.flat_map(|value| Month::from_number(value).ok()))
464
0
            .and_then(|item| item.consume_value(|value| parsed.set_month(value)))
465
0
            .ok_or(InvalidComponent("month"))?;
466
0
        let input = dash(input).ok_or(InvalidLiteral)?.into_inner();
467
0
        let input = exactly_n_digits::<_, 2>(input)
468
0
            .and_then(|item| item.consume_value(|value| parsed.set_day(value)))
469
0
            .ok_or(InvalidComponent("day"))?;
470
0
        let input = ascii_char_ignore_case::<b'T'>(input)
471
0
            .ok_or(InvalidLiteral)?
472
0
            .into_inner();
473
0
        let input = exactly_n_digits::<_, 2>(input)
474
0
            .and_then(|item| item.consume_value(|value| parsed.set_hour_24(value)))
475
0
            .ok_or(InvalidComponent("hour"))?;
476
0
        let input = colon(input).ok_or(InvalidLiteral)?.into_inner();
477
0
        let input = exactly_n_digits::<_, 2>(input)
478
0
            .and_then(|item| item.consume_value(|value| parsed.set_minute(value)))
479
0
            .ok_or(InvalidComponent("minute"))?;
480
0
        let input = colon(input).ok_or(InvalidLiteral)?.into_inner();
481
0
        let input = exactly_n_digits::<_, 2>(input)
482
0
            .and_then(|item| item.consume_value(|value| parsed.set_second(value)))
483
0
            .ok_or(InvalidComponent("second"))?;
484
0
        let input = if let Some(ParsedItem(input, ())) = ascii_char::<b'.'>(input) {
485
0
            let ParsedItem(mut input, mut value) = any_digit(input)
486
0
                .ok_or(InvalidComponent("subsecond"))?
487
0
                .map(|v| (v - b'0') as u32 * 100_000_000);
488
0
489
0
            let mut multiplier = 10_000_000;
490
0
            while let Some(ParsedItem(new_input, digit)) = any_digit(input) {
491
0
                value += (digit - b'0') as u32 * multiplier;
492
0
                input = new_input;
493
0
                multiplier /= 10;
494
0
            }
495
496
0
            parsed
497
0
                .set_subsecond(value)
498
0
                .ok_or(InvalidComponent("subsecond"))?;
499
0
            input
500
        } else {
501
0
            input
502
        };
503
504
        // The RFC explicitly allows leap seconds.
505
0
        parsed.set_leap_second_allowed(true);
506
507
0
        if let Some(ParsedItem(input, ())) = ascii_char_ignore_case::<b'Z'>(input) {
508
0
            parsed
509
0
                .set_offset_hour(0)
510
0
                .ok_or(InvalidComponent("offset hour"))?;
511
0
            parsed
512
0
                .set_offset_minute_signed(0)
513
0
                .ok_or(InvalidComponent("offset minute"))?;
514
0
            parsed
515
0
                .set_offset_second_signed(0)
516
0
                .ok_or(InvalidComponent("offset second"))?;
517
0
            return Ok(input);
518
0
        }
519
520
0
        let ParsedItem(input, offset_sign) = sign(input).ok_or(InvalidComponent("offset hour"))?;
521
0
        let input = exactly_n_digits::<u8, 2>(input)
522
0
            .and_then(|item| {
523
0
                item.map(|offset_hour| {
524
0
                    if offset_sign == b'-' {
525
0
                        -(offset_hour as i8)
526
                    } else {
527
0
                        offset_hour as _
528
                    }
529
0
                })
530
0
                .consume_value(|value| parsed.set_offset_hour(value))
531
0
            })
532
0
            .ok_or(InvalidComponent("offset hour"))?;
533
0
        let input = colon(input).ok_or(InvalidLiteral)?.into_inner();
534
0
        let input = exactly_n_digits::<u8, 2>(input)
535
0
            .and_then(|item| {
536
0
                item.map(|offset_minute| {
537
0
                    if offset_sign == b'-' {
538
0
                        -(offset_minute as i8)
539
                    } else {
540
0
                        offset_minute as _
541
                    }
542
0
                })
543
0
                .consume_value(|value| parsed.set_offset_minute_signed(value))
544
0
            })
545
0
            .ok_or(InvalidComponent("offset minute"))?;
546
547
0
        Ok(input)
548
0
    }
549
550
0
    fn parse_offset_date_time(&self, input: &[u8]) -> Result<OffsetDateTime, error::Parse> {
551
        use crate::error::ParseFromDescription::{InvalidComponent, InvalidLiteral};
552
        use crate::parsing::combinator::{
553
            any_digit, ascii_char, ascii_char_ignore_case, exactly_n_digits, sign,
554
        };
555
556
0
        let dash = ascii_char::<b'-'>;
557
0
        let colon = ascii_char::<b':'>;
558
559
0
        let ParsedItem(input, year) =
560
0
            exactly_n_digits::<u32, 4>(input).ok_or(InvalidComponent("year"))?;
561
0
        let input = dash(input).ok_or(InvalidLiteral)?.into_inner();
562
0
        let ParsedItem(input, month) =
563
0
            exactly_n_digits::<_, 2>(input).ok_or(InvalidComponent("month"))?;
564
0
        let input = dash(input).ok_or(InvalidLiteral)?.into_inner();
565
0
        let ParsedItem(input, day) =
566
0
            exactly_n_digits::<_, 2>(input).ok_or(InvalidComponent("day"))?;
567
0
        let input = ascii_char_ignore_case::<b'T'>(input)
568
0
            .ok_or(InvalidLiteral)?
569
0
            .into_inner();
570
0
        let ParsedItem(input, hour) =
571
0
            exactly_n_digits::<_, 2>(input).ok_or(InvalidComponent("hour"))?;
572
0
        let input = colon(input).ok_or(InvalidLiteral)?.into_inner();
573
0
        let ParsedItem(input, minute) =
574
0
            exactly_n_digits::<_, 2>(input).ok_or(InvalidComponent("minute"))?;
575
0
        let input = colon(input).ok_or(InvalidLiteral)?.into_inner();
576
0
        let ParsedItem(input, mut second) =
577
0
            exactly_n_digits::<_, 2>(input).ok_or(InvalidComponent("second"))?;
578
0
        let ParsedItem(input, mut nanosecond) =
579
0
            if let Some(ParsedItem(input, ())) = ascii_char::<b'.'>(input) {
580
0
                let ParsedItem(mut input, mut value) = any_digit(input)
581
0
                    .ok_or(InvalidComponent("subsecond"))?
582
0
                    .map(|v| (v - b'0') as u32 * 100_000_000);
583
0
584
0
                let mut multiplier = 10_000_000;
585
0
                while let Some(ParsedItem(new_input, digit)) = any_digit(input) {
586
0
                    value += (digit - b'0') as u32 * multiplier;
587
0
                    input = new_input;
588
0
                    multiplier /= 10;
589
0
                }
590
591
0
                ParsedItem(input, value)
592
            } else {
593
0
                ParsedItem(input, 0)
594
            };
595
0
        let ParsedItem(input, offset) = {
596
0
            if let Some(ParsedItem(input, ())) = ascii_char_ignore_case::<b'Z'>(input) {
597
0
                ParsedItem(input, UtcOffset::UTC)
598
            } else {
599
0
                let ParsedItem(input, offset_sign) =
600
0
                    sign(input).ok_or(InvalidComponent("offset hour"))?;
601
0
                let ParsedItem(input, offset_hour) =
602
0
                    exactly_n_digits::<u8, 2>(input).ok_or(InvalidComponent("offset hour"))?;
603
0
                let input = colon(input).ok_or(InvalidLiteral)?.into_inner();
604
0
                let ParsedItem(input, offset_minute) =
605
0
                    exactly_n_digits::<u8, 2>(input).ok_or(InvalidComponent("offset minute"))?;
606
                UtcOffset::from_hms(
607
0
                    if offset_sign == b'-' {
608
0
                        -(offset_hour as i8)
609
                    } else {
610
0
                        offset_hour as _
611
                    },
612
0
                    if offset_sign == b'-' {
613
0
                        -(offset_minute as i8)
614
                    } else {
615
0
                        offset_minute as _
616
                    },
617
                    0,
618
                )
619
0
                .map(|offset| ParsedItem(input, offset))
620
0
                .map_err(|mut err| {
621
0
                    // Provide the user a more accurate error.
622
0
                    if err.name == "hours" {
623
0
                        err.name = "offset hour";
624
0
                    } else if err.name == "minutes" {
625
0
                        err.name = "offset minute";
626
0
                    }
627
0
                    err
628
0
                })
629
0
                .map_err(TryFromParsed::ComponentRange)?
630
            }
631
        };
632
633
0
        if !input.is_empty() {
634
0
            return Err(error::Parse::UnexpectedTrailingCharacters);
635
0
        }
636
637
        // The RFC explicitly permits leap seconds. We don't currently support them, so treat it as
638
        // the preceding nanosecond. However, leap seconds can only occur as the last second of the
639
        // month UTC.
640
0
        let leap_second_input = if second == 60 {
641
0
            second = 59;
642
0
            nanosecond = 999_999_999;
643
0
            true
644
        } else {
645
0
            false
646
        };
647
648
0
        let dt = Month::from_number(month)
649
0
            .and_then(|month| Date::from_calendar_date(year as _, month, day))
650
0
            .and_then(|date| date.with_hms_nano(hour, minute, second, nanosecond))
651
0
            .map(|date| date.assume_offset(offset))
652
0
            .map_err(TryFromParsed::ComponentRange)?;
653
654
0
        if leap_second_input && !dt.is_valid_leap_second_stand_in() {
655
0
            return Err(error::Parse::TryFromParsed(TryFromParsed::ComponentRange(
656
0
                error::ComponentRange {
657
0
                    name: "second",
658
0
                    minimum: 0,
659
0
                    maximum: 59,
660
0
                    value: 60,
661
0
                    conditional_range: true,
662
0
                },
663
0
            )));
664
0
        }
665
0
666
0
        Ok(dt)
667
0
    }
668
}
669
670
impl<const CONFIG: EncodedConfig> sealed::Sealed for Iso8601<CONFIG> {
671
0
    fn parse_into<'a>(
672
0
        &self,
673
0
        mut input: &'a [u8],
674
0
        parsed: &mut Parsed,
675
0
    ) -> Result<&'a [u8], error::Parse> {
676
        use crate::parsing::combinator::rfc::iso8601::ExtendedKind;
677
678
0
        let mut extended_kind = ExtendedKind::Unknown;
679
0
        let mut date_is_present = false;
680
0
        let mut time_is_present = false;
681
0
        let mut offset_is_present = false;
682
0
        let mut first_error = None;
683
0
684
0
        match Self::parse_date(parsed, &mut extended_kind)(input) {
685
0
            Ok(new_input) => {
686
0
                input = new_input;
687
0
                date_is_present = true;
688
0
            }
689
0
            Err(err) => {
690
0
                first_error.get_or_insert(err);
691
0
            }
692
        }
693
694
0
        match Self::parse_time(parsed, &mut extended_kind, date_is_present)(input) {
695
0
            Ok(new_input) => {
696
0
                input = new_input;
697
0
                time_is_present = true;
698
0
            }
699
0
            Err(err) => {
700
0
                first_error.get_or_insert(err);
701
0
            }
702
        }
703
704
        // If a date and offset are present, a time must be as well.
705
0
        if !date_is_present || time_is_present {
706
0
            match Self::parse_offset(parsed, &mut extended_kind)(input) {
707
0
                Ok(new_input) => {
708
0
                    input = new_input;
709
0
                    offset_is_present = true;
710
0
                }
711
0
                Err(err) => {
712
0
                    first_error.get_or_insert(err);
713
0
                }
714
            }
715
0
        }
716
717
0
        if !date_is_present && !time_is_present && !offset_is_present {
718
0
            match first_error {
719
0
                Some(err) => return Err(err),
720
0
                None => unreachable!("an error should be present if no components were parsed"),
721
            }
722
0
        }
723
0
724
0
        Ok(input)
725
0
    }
726
}
727
// endregion well-known formats