time/parsing/
parsable.rs

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