Coverage Report

Created: 2025-10-10 07:11

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