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| {
235                                parsed.set_year(value.cast_signed().extend())
236                            })
237                        })
238                        .ok_or(InvalidComponent("year"))
239                );
240                try_likely_ok!(fws(input).ok_or(InvalidLiteral)).into_inner()
241            }
242            None => {
243                let input = try_likely_ok!(
244                    ExactlyNDigits::<2>::parse(input)
245                        .and_then(|item| {
246                            item.map(|year| year.extend::<u32>())
247                                .map(|year| if year < 50 { year + 2000 } else { year + 1900 })
248                                .map(|year| year.cast_signed())
249                                .consume_value(|value| parsed.set_year(value))
250                        })
251                        .ok_or(InvalidComponent("year"))
252                );
253                try_likely_ok!(cfws(input).ok_or(InvalidLiteral)).into_inner()
254            }
255        };
256
257        let input = try_likely_ok!(
258            ExactlyNDigits::<2>::parse(input)
259                .and_then(|item| item.consume_value(|value| parsed.set_hour_24(value)))
260                .ok_or(InvalidComponent("hour"))
261        );
262        let input = opt(cfws)(input).into_inner();
263        let input = try_likely_ok!(colon(input).ok_or(InvalidLiteral)).into_inner();
264        let input = opt(cfws)(input).into_inner();
265        let input = try_likely_ok!(
266            ExactlyNDigits::<2>::parse(input)
267                .and_then(|item| item.consume_value(|value| parsed.set_minute(value)))
268                .ok_or(InvalidComponent("minute"))
269        );
270
271        let input = if let Some(input) = colon(opt(cfws)(input).into_inner()) {
272            let input = input.into_inner(); // discard the colon
273            let input = opt(cfws)(input).into_inner();
274            let input = try_likely_ok!(
275                ExactlyNDigits::<2>::parse(input)
276                    .and_then(|item| item.consume_value(|value| parsed.set_second(value)))
277                    .ok_or(InvalidComponent("second"))
278            );
279            try_likely_ok!(cfws(input).ok_or(InvalidLiteral)).into_inner()
280        } else {
281            try_likely_ok!(cfws(input).ok_or(InvalidLiteral)).into_inner()
282        };
283
284        // The RFC explicitly allows leap seconds.
285        parsed.leap_second_allowed = true;
286
287        if let Some(zone_literal) = zone_literal(input) {
288            crate::hint::cold_path();
289            let input = try_likely_ok!(
290                zone_literal
291                    .consume_value(|value| parsed.set_offset_hour(value))
292                    .ok_or(InvalidComponent("offset hour"))
293            );
294            try_likely_ok!(
295                parsed
296                    .set_offset_minute_signed(0)
297                    .ok_or(InvalidComponent("offset minute"))
298            );
299            try_likely_ok!(
300                parsed
301                    .set_offset_second_signed(0)
302                    .ok_or(InvalidComponent("offset second"))
303            );
304            return Ok(input);
305        }
306
307        let ParsedItem(input, offset_sign) =
308            try_likely_ok!(sign(input).ok_or(InvalidComponent("offset hour")));
309        let input = try_likely_ok!(
310            ExactlyNDigits::<2>::parse(input)
311                .and_then(|item| {
312                    item.map(|offset_hour| match offset_sign {
313                        Sign::Negative => -offset_hour.cast_signed(),
314                        Sign::Positive => offset_hour.cast_signed(),
315                    })
316                    .consume_value(|value| parsed.set_offset_hour(value))
317                })
318                .ok_or(InvalidComponent("offset hour"))
319        );
320        let input = try_likely_ok!(
321            ExactlyNDigits::<2>::parse(input)
322                .and_then(|item| {
323                    item.consume_value(|value| parsed.set_offset_minute_signed(value.cast_signed()))
324                })
325                .ok_or(InvalidComponent("offset minute"))
326        );
327
328        let input = opt(cfws)(input).into_inner();
329
330        Ok(input)
331    }
332
333    fn parse_offset_date_time(&self, input: &[u8]) -> Result<OffsetDateTime, error::Parse> {
334        use crate::parsing::combinator::rfc::rfc2822::{cfws, fws, zone_literal};
335
336        let colon = ascii_char::<b':'>;
337        let comma = ascii_char::<b','>;
338
339        let input = opt(cfws)(input).into_inner();
340        let weekday = component::parse_weekday_short(
341            input,
342            modifier::WeekdayShort {
343                case_sensitive: false,
344            },
345        );
346        let input = if let Some(item) = weekday {
347            let input = item.discard_value();
348            let input = try_likely_ok!(comma(input).ok_or(InvalidLiteral)).into_inner();
349            opt(cfws)(input).into_inner()
350        } else {
351            input
352        };
353        let ParsedItem(input, day) =
354            try_likely_ok!(one_or_two_digits(input).ok_or(InvalidComponent("day")));
355        let input = try_likely_ok!(cfws(input).ok_or(InvalidLiteral)).into_inner();
356        let ParsedItem(input, month) = try_likely_ok!(
357            component::parse_month_short(
358                input,
359                modifier::MonthShort {
360                    case_sensitive: false,
361                },
362            )
363            .ok_or(InvalidComponent("month"))
364        );
365        let input = try_likely_ok!(cfws(input).ok_or(InvalidLiteral)).into_inner();
366        let (input, year) = match ExactlyNDigits::<4>::parse(input) {
367            Some(item) => {
368                let ParsedItem(input, year) = try_likely_ok!(
369                    item.flat_map(|year| if year >= 1900 { Some(year) } else { None })
370                        .ok_or(InvalidComponent("year"))
371                );
372                let input = try_likely_ok!(fws(input).ok_or(InvalidLiteral)).into_inner();
373                (input, year)
374            }
375            None => {
376                let ParsedItem(input, year) = try_likely_ok!(
377                    ExactlyNDigits::<2>::parse(input)
378                        .map(|item| {
379                            item.map(|year| year.extend::<u16>())
380                                .map(|year| if year < 50 { year + 2000 } else { year + 1900 })
381                        })
382                        .ok_or(InvalidComponent("year"))
383                );
384                let input = try_likely_ok!(cfws(input).ok_or(InvalidLiteral)).into_inner();
385                (input, year)
386            }
387        };
388
389        let ParsedItem(input, hour) =
390            try_likely_ok!(ExactlyNDigits::<2>::parse(input).ok_or(InvalidComponent("hour")));
391        let input = opt(cfws)(input).into_inner();
392        let input = try_likely_ok!(colon(input).ok_or(InvalidLiteral)).into_inner();
393        let input = opt(cfws)(input).into_inner();
394        let ParsedItem(input, minute) =
395            try_likely_ok!(ExactlyNDigits::<2>::parse(input).ok_or(InvalidComponent("minute")));
396
397        let (input, mut second) = if let Some(input) = colon(opt(cfws)(input).into_inner()) {
398            let input = input.into_inner(); // discard the colon
399            let input = opt(cfws)(input).into_inner();
400            let ParsedItem(input, second) =
401                try_likely_ok!(ExactlyNDigits::<2>::parse(input).ok_or(InvalidComponent("second")));
402            let input = try_likely_ok!(cfws(input).ok_or(InvalidLiteral)).into_inner();
403            (input, second)
404        } else {
405            (
406                try_likely_ok!(cfws(input).ok_or(InvalidLiteral)).into_inner(),
407                0,
408            )
409        };
410
411        let sign = sign(input);
412        let (input, offset_hour, offset_minute) = match sign {
413            None => {
414                let ParsedItem(input, offset_hour) =
415                    zone_literal(input).ok_or(InvalidComponent("offset hour"))?;
416                (input, offset_hour, 0)
417            }
418            Some(ParsedItem(input, offset_sign)) => {
419                let ParsedItem(input, offset_hour) = try_likely_ok!(
420                    ExactlyNDigits::<2>::parse(input)
421                        .map(|item| {
422                            item.map(|offset_hour| match offset_sign {
423                                Sign::Negative => -offset_hour.cast_signed(),
424                                Sign::Positive => offset_hour.cast_signed(),
425                            })
426                        })
427                        .ok_or(InvalidComponent("offset hour"))
428                );
429                let ParsedItem(input, offset_minute) = try_likely_ok!(
430                    ExactlyNDigits::<2>::parse(input).ok_or(InvalidComponent("offset minute"))
431                );
432                (input, offset_hour, offset_minute.cast_signed())
433            }
434        };
435
436        let input = opt(cfws)(input).into_inner();
437
438        if !input.is_empty() {
439            return Err(error::Parse::ParseFromDescription(
440                error::ParseFromDescription::UnexpectedTrailingCharacters,
441            ));
442        }
443
444        let mut nanosecond = 0;
445        let leap_second_input = if second == 60 {
446            second = 59;
447            nanosecond = 999_999_999;
448            true
449        } else {
450            false
451        };
452
453        let dt = try_likely_ok!(
454            (|| {
455                let date = try_likely_ok!(Date::from_calendar_date(
456                    year.cast_signed().extend(),
457                    month,
458                    day
459                ));
460                let time = try_likely_ok!(Time::from_hms_nano(hour, minute, second, nanosecond));
461                let offset = try_likely_ok!(UtcOffset::from_hms(offset_hour, offset_minute, 0));
462                Ok(OffsetDateTime::new_in_offset(date, time, offset))
463            })()
464            .map_err(TryFromParsed::ComponentRange)
465        );
466
467        if leap_second_input && !dt.is_valid_leap_second_stand_in() {
468            return Err(error::Parse::TryFromParsed(TryFromParsed::ComponentRange(
469                error::ComponentRange::conditional("second"),
470            )));
471        }
472
473        Ok(dt)
474    }
475}
476
477impl sealed::Sealed for Rfc3339 {
478    fn parse_into<'a>(
479        &self,
480        input: &'a [u8],
481        parsed: &mut Parsed,
482    ) -> Result<&'a [u8], error::Parse> {
483        let dash = ascii_char::<b'-'>;
484        let colon = ascii_char::<b':'>;
485
486        let input = try_likely_ok!(
487            ExactlyNDigits::<4>::parse(input)
488                .and_then(|item| {
489                    item.consume_value(|value| parsed.set_year(value.cast_signed().extend()))
490                })
491                .ok_or(InvalidComponent("year"))
492        );
493        let input = try_likely_ok!(dash(input).ok_or(InvalidLiteral)).into_inner();
494        let input = try_likely_ok!(
495            ExactlyNDigits::<2>::parse(input)
496                .and_then(
497                    |item| item.flat_map(|value| Month::from_number(NonZero::new(value)?).ok())
498                )
499                .and_then(|item| item.consume_value(|value| parsed.set_month(value)))
500                .ok_or(InvalidComponent("month"))
501        );
502        let input = try_likely_ok!(dash(input).ok_or(InvalidLiteral)).into_inner();
503        let input = try_likely_ok!(
504            ExactlyNDigits::<2>::parse(input)
505                .and_then(|item| item.consume_value(|value| parsed.set_day(NonZero::new(value)?)))
506                .ok_or(InvalidComponent("day"))
507        );
508
509        // RFC3339 allows any separator, not just `T`, not just `space`.
510        // cf. Section 5.6: Internet Date/Time Format:
511        //   NOTE: ISO 8601 defines date and time separated by "T".
512        //   Applications using this syntax may choose, for the sake of
513        //   readability, to specify a full-date and full-time separated by
514        //   (say) a space character.
515        // Specifically, rusqlite uses space separators.
516        let input = try_likely_ok!(input.get(1..).ok_or(InvalidComponent("separator")));
517
518        let input = try_likely_ok!(
519            ExactlyNDigits::<2>::parse(input)
520                .and_then(|item| item.consume_value(|value| parsed.set_hour_24(value)))
521                .ok_or(InvalidComponent("hour"))
522        );
523        let input = try_likely_ok!(colon(input).ok_or(InvalidLiteral)).into_inner();
524        let input = try_likely_ok!(
525            ExactlyNDigits::<2>::parse(input)
526                .and_then(|item| item.consume_value(|value| parsed.set_minute(value)))
527                .ok_or(InvalidComponent("minute"))
528        );
529        let input = try_likely_ok!(colon(input).ok_or(InvalidLiteral)).into_inner();
530        let input = try_likely_ok!(
531            ExactlyNDigits::<2>::parse(input)
532                .and_then(|item| item.consume_value(|value| parsed.set_second(value)))
533                .ok_or(InvalidComponent("second"))
534        );
535        let input = if let Some(ParsedItem(input, ())) = ascii_char::<b'.'>(input) {
536            let ParsedItem(mut input, mut value) =
537                try_likely_ok!(any_digit(input).ok_or(InvalidComponent("subsecond")))
538                    .map(|v| (v - b'0').extend::<u32>() * 100_000_000);
539
540            let mut multiplier = 10_000_000;
541            while let Some(ParsedItem(new_input, digit)) = any_digit(input) {
542                value += (digit - b'0').extend::<u32>() * multiplier;
543                input = new_input;
544                multiplier /= 10;
545            }
546
547            try_likely_ok!(
548                parsed
549                    .set_subsecond(value)
550                    .ok_or(InvalidComponent("subsecond"))
551            );
552            input
553        } else {
554            input
555        };
556
557        // The RFC explicitly allows leap seconds.
558        parsed.leap_second_allowed = true;
559
560        if let Some(ParsedItem(input, ())) = ascii_char_ignore_case::<b'Z'>(input) {
561            try_likely_ok!(
562                parsed
563                    .set_offset_hour(0)
564                    .ok_or(InvalidComponent("offset hour"))
565            );
566            try_likely_ok!(
567                parsed
568                    .set_offset_minute_signed(0)
569                    .ok_or(InvalidComponent("offset minute"))
570            );
571            try_likely_ok!(
572                parsed
573                    .set_offset_second_signed(0)
574                    .ok_or(InvalidComponent("offset second"))
575            );
576            return Ok(input);
577        }
578
579        let ParsedItem(input, offset_sign) =
580            try_likely_ok!(sign(input).ok_or(InvalidComponent("offset hour")));
581        let input = try_likely_ok!(
582            ExactlyNDigits::<2>::parse(input)
583                .and_then(|item| {
584                    item.filter(|&offset_hour| offset_hour <= 23)?
585                        .map(|offset_hour| match offset_sign {
586                            Sign::Negative => -offset_hour.cast_signed(),
587                            Sign::Positive => offset_hour.cast_signed(),
588                        })
589                        .consume_value(|value| parsed.set_offset_hour(value))
590                })
591                .ok_or(InvalidComponent("offset hour"))
592        );
593        let input = try_likely_ok!(colon(input).ok_or(InvalidLiteral)).into_inner();
594        let input = try_likely_ok!(
595            ExactlyNDigits::<2>::parse(input)
596                .and_then(|item| {
597                    item.map(|offset_minute| match offset_sign {
598                        Sign::Negative => -offset_minute.cast_signed(),
599                        Sign::Positive => offset_minute.cast_signed(),
600                    })
601                    .consume_value(|value| parsed.set_offset_minute_signed(value))
602                })
603                .ok_or(InvalidComponent("offset minute"))
604        );
605
606        Ok(input)
607    }
608
609    fn parse_offset_date_time(&self, input: &[u8]) -> Result<OffsetDateTime, error::Parse> {
610        let dash = ascii_char::<b'-'>;
611        let colon = ascii_char::<b':'>;
612
613        let ParsedItem(input, year) =
614            try_likely_ok!(ExactlyNDigits::<4>::parse(input).ok_or(InvalidComponent("year")));
615        let input = try_likely_ok!(dash(input).ok_or(InvalidLiteral)).into_inner();
616        let ParsedItem(input, month) = try_likely_ok!(
617            ExactlyNDigits::<2>::parse(input)
618                .and_then(|parsed| parsed.flat_map(NonZero::new))
619                .ok_or(InvalidComponent("month"))
620        );
621        let input = try_likely_ok!(dash(input).ok_or(InvalidLiteral)).into_inner();
622        let ParsedItem(input, day) =
623            try_likely_ok!(ExactlyNDigits::<2>::parse(input).ok_or(InvalidComponent("day")));
624
625        // RFC3339 allows any separator, not just `T`, not just `space`.
626        // cf. Section 5.6: Internet Date/Time Format:
627        //   NOTE: ISO 8601 defines date and time separated by "T".
628        //   Applications using this syntax may choose, for the sake of
629        //   readability, to specify a full-date and full-time separated by
630        //   (say) a space character.
631        // Specifically, rusqlite uses space separators.
632        let input = try_likely_ok!(input.get(1..).ok_or(InvalidComponent("separator")));
633
634        let ParsedItem(input, hour) =
635            try_likely_ok!(ExactlyNDigits::<2>::parse(input).ok_or(InvalidComponent("hour")));
636        let input = try_likely_ok!(colon(input).ok_or(InvalidLiteral)).into_inner();
637        let ParsedItem(input, minute) =
638            try_likely_ok!(ExactlyNDigits::<2>::parse(input).ok_or(InvalidComponent("minute")));
639        let input = try_likely_ok!(colon(input).ok_or(InvalidLiteral)).into_inner();
640        let ParsedItem(input, mut second) =
641            try_likely_ok!(ExactlyNDigits::<2>::parse(input).ok_or(InvalidComponent("second")));
642        let ParsedItem(input, mut nanosecond) =
643            if let Some(ParsedItem(input, ())) = ascii_char::<b'.'>(input) {
644                let ParsedItem(mut input, mut value) =
645                    try_likely_ok!(any_digit(input).ok_or(InvalidComponent("subsecond")))
646                        .map(|v| (v - b'0').extend::<u32>() * 100_000_000);
647
648                let mut multiplier = 10_000_000;
649                while let Some(ParsedItem(new_input, digit)) = any_digit(input) {
650                    value += (digit - b'0').extend::<u32>() * multiplier;
651                    input = new_input;
652                    multiplier /= 10;
653                }
654
655                ParsedItem(input, value)
656            } else {
657                ParsedItem(input, 0)
658            };
659        let ParsedItem(input, offset) = {
660            if let Some(ParsedItem(input, ())) = ascii_char_ignore_case::<b'Z'>(input) {
661                ParsedItem(input, UtcOffset::UTC)
662            } else {
663                let ParsedItem(input, offset_sign) =
664                    try_likely_ok!(sign(input).ok_or(InvalidComponent("offset hour")));
665                let ParsedItem(input, offset_hour) = try_likely_ok!(
666                    ExactlyNDigits::<2>::parse(input)
667                        .and_then(|parsed| parsed.filter(|&offset_hour| offset_hour <= 23))
668                        .ok_or(InvalidComponent("offset hour"))
669                );
670                let input = try_likely_ok!(colon(input).ok_or(InvalidLiteral)).into_inner();
671                let ParsedItem(input, offset_minute) = try_likely_ok!(
672                    ExactlyNDigits::<2>::parse(input).ok_or(InvalidComponent("offset minute"))
673                );
674                try_likely_ok!(
675                    match offset_sign {
676                        Sign::Negative => UtcOffset::from_hms(
677                            -offset_hour.cast_signed(),
678                            -offset_minute.cast_signed(),
679                            0,
680                        ),
681                        Sign::Positive => UtcOffset::from_hms(
682                            offset_hour.cast_signed(),
683                            offset_minute.cast_signed(),
684                            0,
685                        ),
686                    }
687                    .map(|offset| ParsedItem(input, offset))
688                    .map_err(TryFromParsed::ComponentRange)
689                )
690            }
691        };
692
693        if !input.is_empty() {
694            return Err(error::Parse::ParseFromDescription(
695                error::ParseFromDescription::UnexpectedTrailingCharacters,
696            ));
697        }
698
699        // The RFC explicitly permits leap seconds. We don't currently support them, so treat it as
700        // the preceding nanosecond. However, leap seconds can only occur as the last second of the
701        // month UTC.
702        let leap_second_input = if second == 60 {
703            second = 59;
704            nanosecond = 999_999_999;
705            true
706        } else {
707            false
708        };
709
710        let date = try_likely_ok!(
711            Month::from_number(month)
712                .and_then(|month| Date::from_calendar_date(year.cast_signed().extend(), month, day))
713                .map_err(TryFromParsed::ComponentRange)
714        );
715        let time = try_likely_ok!(
716            Time::from_hms_nano(hour, minute, second, nanosecond)
717                .map_err(TryFromParsed::ComponentRange)
718        );
719        let dt = OffsetDateTime::new_in_offset(date, time, offset);
720
721        if leap_second_input && !dt.is_valid_leap_second_stand_in() {
722            return Err(error::Parse::TryFromParsed(TryFromParsed::ComponentRange(
723                error::ComponentRange::conditional("second"),
724            )));
725        }
726
727        Ok(dt)
728    }
729}
730
731impl<const CONFIG: EncodedConfig> sealed::Sealed for Iso8601<CONFIG> {
732    #[inline]
733    fn parse_into<'a>(
734        &self,
735        mut input: &'a [u8],
736        parsed: &mut Parsed,
737    ) -> Result<&'a [u8], error::Parse> {
738        use crate::parsing::combinator::rfc::iso8601::ExtendedKind;
739
740        let mut extended_kind = ExtendedKind::Unknown;
741        let mut date_is_present = false;
742        let mut time_is_present = false;
743        let mut offset_is_present = false;
744        let mut first_error = None;
745
746        parsed.leap_second_allowed = true;
747
748        match Self::parse_date(parsed, &mut extended_kind)(input) {
749            Ok(new_input) => {
750                input = new_input;
751                date_is_present = true;
752            }
753            Err(err) => {
754                first_error.get_or_insert(err);
755            }
756        }
757
758        match Self::parse_time(parsed, &mut extended_kind, date_is_present)(input) {
759            Ok(new_input) => {
760                input = new_input;
761                time_is_present = true;
762            }
763            Err(err) => {
764                first_error.get_or_insert(err);
765            }
766        }
767
768        // If a date and offset are present, a time must be as well.
769        if !date_is_present || time_is_present {
770            match Self::parse_offset(parsed, &mut extended_kind)(input) {
771                Ok(new_input) => {
772                    input = new_input;
773                    offset_is_present = true;
774                }
775                Err(err) => {
776                    first_error.get_or_insert(err);
777                }
778            }
779        }
780
781        if !date_is_present && !time_is_present && !offset_is_present {
782            match first_error {
783                Some(err) => return Err(err),
784                None => bug!("an error should be present if no components were parsed"),
785            }
786        }
787
788        Ok(input)
789    }
790}