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