Skip to main content

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