time/parsing/
parsable.rs

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