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