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, sign,
18};
19use crate::parsing::{Parsed, ParsedItem, component};
20use crate::{Date, Month, OffsetDateTime, PrivateMethod, Time, UtcOffset, error};
21
22/// A type that can be parsed.
23#[cfg_attr(docsrs, doc(notable_trait))]
24#[doc(alias = "Parseable")]
25pub trait Parsable: sealed::Sealed {}
26impl Parsable for FormatDescriptionV3<'_> {}
27impl Parsable for BorrowedFormatItem<'_> {}
28impl Parsable for [BorrowedFormatItem<'_>] {}
29#[cfg(feature = "alloc")]
30impl Parsable for OwnedFormatItem {}
31#[cfg(feature = "alloc")]
32impl Parsable for [OwnedFormatItem] {}
33impl Parsable for Rfc2822 {}
34impl Parsable for Rfc3339 {}
35impl<const CONFIG: EncodedConfig> Parsable for Iso8601<CONFIG> {}
36impl<T> Parsable for T where T: Deref<Target: Parsable> {}
37
38/// Seal the trait to prevent downstream users from implementing it, while still allowing it to
39/// exist in generic bounds.
40mod sealed {
41    use super::*;
42    use crate::{PlainDateTime, Timestamp, UtcDateTime};
43
44    /// Parse the item using a format description and an input.
45    #[expect(
46        private_interfaces,
47        reason = "not intended to be used by downstream users"
48    )]
49    pub trait Sealed {
50        /// Parse the item into the provided [`Parsed`] struct.
51        ///
52        /// This method can be used to parse a single component without parsing the full value.
53        fn parse_into<'a>(
54            &self,
55            input: &'a [u8],
56            parsed: &mut Parsed,
57            _: PrivateMethod,
58        ) -> Result<&'a [u8], error::Parse>;
59
60        /// # **DO NOT USE THIS METHOD**
61        ///
62        /// This method is for internal use only, has never been part of the public API, and will be
63        /// removed in a future release. If you are relying on the existence of this method, your
64        /// code will be broken in the future. The removal of this method will not be considered a
65        /// breaking change due to the internal nature and the fact that it was never documented as
66        /// part of the public API.
67        ///
68        /// You should use the `parse` method on the target type instead. For example, to parse a
69        /// [`Date`], use [`Date::parse`].
70        #[deprecated(
71            since = "0.3.53",
72            note = "use the `parse` method on the target type; this method has never been part of \
73                    the public API and will be removed in a future release"
74        )]
75        #[doc(hidden)]
76        fn parse(&self, input: &[u8]) -> Result<Parsed, error::Parse> {
77            self.parse_internal(input, None, PrivateMethod)
78        }
79
80        /// Parse the items into a [`Parsed`] struct, using the provided defaults for any components
81        /// that are not present in the input.
82        ///
83        /// This method can only be used to parse a complete value of a type. If any characters
84        /// remain after parsing, an error will be returned.
85        #[inline]
86        fn parse_internal(
87            &self,
88            input: &[u8],
89            defaults: Option<Parsed>,
90            _: PrivateMethod,
91        ) -> Result<Parsed, error::Parse> {
92            let mut parsed = defaults.unwrap_or_default();
93            if self
94                .parse_into(input, &mut parsed, PrivateMethod)?
95                .is_empty()
96            {
97                Ok(parsed)
98            } else {
99                Err(error::Parse::ParseFromDescription(
100                    error::ParseFromDescription::UnexpectedTrailingCharacters,
101                ))
102            }
103        }
104
105        /// Parse a [`Date`] from the format description.
106        #[inline]
107        fn parse_date(
108            &self,
109            input: &[u8],
110            defaults: Option<Parsed>,
111            _: PrivateMethod,
112        ) -> Result<Date, error::Parse> {
113            Ok(self
114                .parse_internal(input, defaults, PrivateMethod)?
115                .try_into()?)
116        }
117
118        /// Parse a [`Time`] from the format description.
119        #[inline]
120        fn parse_time(
121            &self,
122            input: &[u8],
123            defaults: Option<Parsed>,
124            _: PrivateMethod,
125        ) -> Result<Time, error::Parse> {
126            Ok(self
127                .parse_internal(input, defaults, PrivateMethod)?
128                .try_into()?)
129        }
130
131        /// Parse a [`UtcOffset`] from the format description.
132        #[inline]
133        fn parse_offset(
134            &self,
135            input: &[u8],
136            defaults: Option<Parsed>,
137            _: PrivateMethod,
138        ) -> Result<UtcOffset, error::Parse> {
139            Ok(self
140                .parse_internal(input, defaults, PrivateMethod)?
141                .try_into()?)
142        }
143
144        /// Parse a [`PlainDateTime`] from the format description.
145        #[inline]
146        fn parse_plain_date_time(
147            &self,
148            input: &[u8],
149            defaults: Option<Parsed>,
150            _: PrivateMethod,
151        ) -> Result<PlainDateTime, error::Parse> {
152            Ok(self
153                .parse_internal(input, defaults, PrivateMethod)?
154                .try_into()?)
155        }
156
157        /// Parse a [`UtcDateTime`] from the format description.
158        #[inline]
159        fn parse_utc_date_time(
160            &self,
161            input: &[u8],
162            defaults: Option<Parsed>,
163            _: PrivateMethod,
164        ) -> Result<UtcDateTime, error::Parse> {
165            Ok(self
166                .parse_internal(input, defaults, PrivateMethod)?
167                .try_into()?)
168        }
169
170        /// Parse a [`OffsetDateTime`] from the format description.
171        #[inline]
172        fn parse_offset_date_time(
173            &self,
174            input: &[u8],
175            defaults: Option<Parsed>,
176            _: PrivateMethod,
177        ) -> Result<OffsetDateTime, error::Parse> {
178            Ok(self
179                .parse_internal(input, defaults, PrivateMethod)?
180                .try_into()?)
181        }
182
183        /// Parse a [`Timestamp`] from the format description.
184        #[inline]
185        fn parse_timestamp(
186            &self,
187            input: &[u8],
188            defaults: Option<Parsed>,
189            _: PrivateMethod,
190        ) -> Result<Timestamp, error::Parse> {
191            Ok(self
192                .parse_internal(input, defaults, PrivateMethod)?
193                .try_into()?)
194        }
195    }
196}
197
198#[expect(
199    private_interfaces,
200    reason = "not intended to be used by downstream users"
201)]
202impl sealed::Sealed for FormatDescriptionV3<'_> {
203    #[inline]
204    fn parse_into<'a>(
205        &self,
206        input: &'a [u8],
207        parsed: &mut Parsed,
208        _: PrivateMethod,
209    ) -> Result<&'a [u8], error::Parse> {
210        Ok(parsed.parse_v3_inner(input, &self.inner)?)
211    }
212}
213
214#[expect(
215    private_interfaces,
216    reason = "not intended to be used by downstream users"
217)]
218impl sealed::Sealed for BorrowedFormatItem<'_> {
219    #[inline]
220    fn parse_into<'a>(
221        &self,
222        input: &'a [u8],
223        parsed: &mut Parsed,
224        _: PrivateMethod,
225    ) -> Result<&'a [u8], error::Parse> {
226        Ok(parsed.parse_item(input, self)?)
227    }
228}
229
230#[expect(
231    private_interfaces,
232    reason = "not intended to be used by downstream users"
233)]
234impl sealed::Sealed for [BorrowedFormatItem<'_>] {
235    #[inline]
236    fn parse_into<'a>(
237        &self,
238        input: &'a [u8],
239        parsed: &mut Parsed,
240        _: PrivateMethod,
241    ) -> Result<&'a [u8], error::Parse> {
242        Ok(parsed.parse_items(input, self)?)
243    }
244}
245
246#[cfg(feature = "alloc")]
247#[expect(
248    private_interfaces,
249    reason = "not intended to be used by downstream users"
250)]
251impl sealed::Sealed for OwnedFormatItem {
252    #[inline]
253    fn parse_into<'a>(
254        &self,
255        input: &'a [u8],
256        parsed: &mut Parsed,
257        _: PrivateMethod,
258    ) -> Result<&'a [u8], error::Parse> {
259        Ok(parsed.parse_item(input, self)?)
260    }
261}
262
263#[cfg(feature = "alloc")]
264#[expect(
265    private_interfaces,
266    reason = "not intended to be used by downstream users"
267)]
268impl sealed::Sealed for [OwnedFormatItem] {
269    #[inline]
270    fn parse_into<'a>(
271        &self,
272        input: &'a [u8],
273        parsed: &mut Parsed,
274        _: PrivateMethod,
275    ) -> Result<&'a [u8], error::Parse> {
276        Ok(parsed.parse_items(input, self)?)
277    }
278}
279
280#[expect(
281    private_interfaces,
282    reason = "not intended to be used by downstream users"
283)]
284impl<T> sealed::Sealed for T
285where
286    T: Deref<Target: sealed::Sealed>,
287{
288    #[inline]
289    fn parse_into<'a>(
290        &self,
291        input: &'a [u8],
292        parsed: &mut Parsed,
293        _: PrivateMethod,
294    ) -> Result<&'a [u8], error::Parse> {
295        self.deref().parse_into(input, parsed, PrivateMethod)
296    }
297}
298
299#[expect(
300    private_interfaces,
301    reason = "not intended to be used by downstream users"
302)]
303impl sealed::Sealed for Rfc2822 {
304    fn parse_into<'a>(
305        &self,
306        input: &'a [u8],
307        parsed: &mut Parsed,
308        _: PrivateMethod,
309    ) -> Result<&'a [u8], error::Parse> {
310        use crate::parsing::combinator::rfc::rfc2822::{
311            cfws, fws, opt_cfws, opt_cfws_colon_opt_cfws, zone_literal,
312        };
313
314        let comma = ascii_char::<b','>;
315
316        let input = opt_cfws(input).into_inner();
317        let weekday = component::parse_weekday_short(
318            input,
319            modifier::WeekdayShort {
320                case_sensitive: false,
321            },
322        );
323        let input = if let Some(item) = weekday {
324            let input = try_likely_ok!(
325                item.consume_value(|value| parsed.set_weekday(value))
326                    .ok_or(InvalidComponent("weekday"))
327            );
328            let input = try_likely_ok!(comma(input).ok_or(InvalidLiteral)).into_inner();
329            opt_cfws(input).into_inner()
330        } else {
331            input
332        };
333        let input = try_likely_ok!(
334            one_or_two_digits(input)
335                .and_then(|item| item.consume_value(|value| parsed.set_day(NonZero::new(value)?)))
336                .ok_or(InvalidComponent("day"))
337        );
338        let input = try_likely_ok!(cfws(input).ok_or(InvalidLiteral)).into_inner();
339        let input = try_likely_ok!(
340            component::parse_month_short(
341                input,
342                modifier::MonthShort {
343                    case_sensitive: false,
344                },
345            )
346            .and_then(|item| item.consume_value(|value| parsed.set_month(value)))
347            .ok_or(InvalidComponent("month"))
348        );
349        let input = try_likely_ok!(cfws(input).ok_or(InvalidLiteral)).into_inner();
350        let input = if let Some(ParsedItem(input, year_val)) = ExactlyNDigits::<4>::parse(input) {
351            if year_val < 1900 {
352                return Err(error::Parse::ParseFromDescription(InvalidComponent("year")));
353            }
354            try_likely_ok!(
355                parsed
356                    .set_year(year_val.cast_signed().widen())
357                    .ok_or(InvalidComponent("year"))
358            );
359            try_likely_ok!(fws(input).ok_or(InvalidLiteral)).into_inner()
360        } else {
361            crate::hint::cold_path();
362            let ParsedItem(input, year) = try_likely_ok!(
363                ExactlyNDigits::<2>::parse(input)
364                    .map(|item| {
365                        item.map(|year| year.widen::<u32>())
366                            .map(|year| if year < 50 { year + 2000 } else { year + 1900 })
367                    })
368                    .ok_or(InvalidComponent("year"))
369            );
370            try_likely_ok!(
371                parsed
372                    .set_year(year.cast_signed())
373                    .ok_or(InvalidComponent("year"))
374            );
375            try_likely_ok!(cfws(input).ok_or(InvalidLiteral)).into_inner()
376        };
377
378        let ParsedItem(input, hour) =
379            try_likely_ok!(ExactlyNDigits::<2>::parse(input).ok_or(InvalidComponent("hour")));
380        try_likely_ok!(parsed.set_hour_24(hour).ok_or(InvalidComponent("hour")));
381        let input =
382            try_likely_ok!(opt_cfws_colon_opt_cfws(input).ok_or(InvalidLiteral)).into_inner();
383        let ParsedItem(input, minute) =
384            try_likely_ok!(ExactlyNDigits::<2>::parse(input).ok_or(InvalidComponent("minute")));
385        try_likely_ok!(parsed.set_minute(minute).ok_or(InvalidComponent("minute")));
386
387        let input = if let Some(input) =
388            opt_cfws_colon_opt_cfws(input).map(|item| item.into_inner())
389        {
390            let ParsedItem(input, second) =
391                try_likely_ok!(ExactlyNDigits::<2>::parse(input).ok_or(InvalidComponent("second")));
392            try_likely_ok!(parsed.set_second(second).ok_or(InvalidComponent("second")));
393            try_likely_ok!(cfws(input).ok_or(InvalidLiteral)).into_inner()
394        } else {
395            try_likely_ok!(cfws(input).ok_or(InvalidLiteral)).into_inner()
396        };
397
398        // The RFC explicitly allows leap seconds.
399        parsed.leap_second_allowed = true;
400
401        if let Some(zone_literal) = zone_literal(input) {
402            crate::hint::cold_path();
403            let input = try_likely_ok!(
404                zone_literal
405                    .consume_value(|value| parsed.set_offset_hour(value))
406                    .ok_or(InvalidComponent("offset hour"))
407            );
408            try_likely_ok!(
409                parsed
410                    .set_offset_minute_signed(0)
411                    .ok_or(InvalidComponent("offset minute"))
412            );
413            try_likely_ok!(
414                parsed
415                    .set_offset_second_signed(0)
416                    .ok_or(InvalidComponent("offset second"))
417            );
418            return Ok(input);
419        }
420
421        let ParsedItem(input, offset_sign) =
422            try_likely_ok!(sign(input).ok_or(InvalidComponent("offset hour")));
423        let input = try_likely_ok!(
424            ExactlyNDigits::<2>::parse(input)
425                .and_then(|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                    .consume_value(|value| parsed.set_offset_hour(value))
431                })
432                .ok_or(InvalidComponent("offset hour"))
433        );
434        let input = try_likely_ok!(
435            ExactlyNDigits::<2>::parse(input)
436                .and_then(|item| {
437                    item.consume_value(|value| parsed.set_offset_minute_signed(value.cast_signed()))
438                })
439                .ok_or(InvalidComponent("offset minute"))
440        );
441
442        let input = opt_cfws(input).into_inner();
443
444        Ok(input)
445    }
446
447    fn parse_offset_date_time(
448        &self,
449        input: &[u8],
450        defaults: Option<Parsed>,
451        _: PrivateMethod,
452    ) -> Result<OffsetDateTime, error::Parse> {
453        use crate::parsing::combinator::rfc::rfc2822::{
454            cfws, fws, opt_cfws, opt_cfws_colon_opt_cfws, zone_literal,
455        };
456
457        if let Some(mut defaults) = defaults {
458            crate::hint::cold_path();
459            return self
460                .parse_into(input, &mut defaults, PrivateMethod)
461                .and_then(|remaining| {
462                    if remaining.is_empty() {
463                        defaults.try_into().map_err(error::Parse::TryFromParsed)
464                    } else {
465                        Err(error::Parse::ParseFromDescription(
466                            error::ParseFromDescription::UnexpectedTrailingCharacters,
467                        ))
468                    }
469                });
470        }
471
472        let comma = ascii_char::<b','>;
473
474        let input = opt_cfws(input).into_inner();
475        let weekday = component::parse_weekday_short(
476            input,
477            modifier::WeekdayShort {
478                case_sensitive: false,
479            },
480        );
481        let input = if let Some(item) = weekday {
482            let input = item.discard_value();
483            let input = try_likely_ok!(comma(input).ok_or(InvalidLiteral)).into_inner();
484            opt_cfws(input).into_inner()
485        } else {
486            input
487        };
488        let ParsedItem(input, day) =
489            try_likely_ok!(one_or_two_digits(input).ok_or(InvalidComponent("day")));
490        let input = try_likely_ok!(cfws(input).ok_or(InvalidLiteral)).into_inner();
491        let ParsedItem(input, month) = try_likely_ok!(
492            component::parse_month_short(
493                input,
494                modifier::MonthShort {
495                    case_sensitive: false,
496                },
497            )
498            .ok_or(InvalidComponent("month"))
499        );
500        let input = try_likely_ok!(cfws(input).ok_or(InvalidLiteral)).into_inner();
501        let (input, year) =
502            if let Some(ParsedItem(input, year_val)) = ExactlyNDigits::<4>::parse(input) {
503                if year_val < 1900 {
504                    return Err(error::Parse::ParseFromDescription(InvalidComponent("year")));
505                }
506
507                let input = try_likely_ok!(fws(input).ok_or(InvalidLiteral)).into_inner();
508                (input, year_val)
509            } else {
510                crate::hint::cold_path();
511                let ParsedItem(input, year) = try_likely_ok!(
512                    ExactlyNDigits::<2>::parse(input)
513                        .map(|item| {
514                            item.map(|year| year.widen::<u16>())
515                                .map(|year| if year < 50 { year + 2000 } else { year + 1900 })
516                        })
517                        .ok_or(InvalidComponent("year"))
518                );
519                let input = try_likely_ok!(cfws(input).ok_or(InvalidLiteral)).into_inner();
520                (input, year)
521            };
522
523        let ParsedItem(input, hour) =
524            try_likely_ok!(ExactlyNDigits::<2>::parse(input).ok_or(InvalidComponent("hour")));
525        let input =
526            try_likely_ok!(opt_cfws_colon_opt_cfws(input).ok_or(InvalidLiteral)).into_inner();
527        let ParsedItem(input, minute) =
528            try_likely_ok!(ExactlyNDigits::<2>::parse(input).ok_or(InvalidComponent("minute")));
529
530        let (input, mut second) = if let Some(input) =
531            opt_cfws_colon_opt_cfws(input).map(|item| item.into_inner())
532        {
533            let ParsedItem(input, second) =
534                try_likely_ok!(ExactlyNDigits::<2>::parse(input).ok_or(InvalidComponent("second")));
535            let input = try_likely_ok!(cfws(input).ok_or(InvalidLiteral)).into_inner();
536            (input, second)
537        } else {
538            (
539                try_likely_ok!(cfws(input).ok_or(InvalidLiteral)).into_inner(),
540                0,
541            )
542        };
543
544        let sign = sign(input);
545        let (input, offset_hour, offset_minute) = match sign {
546            None => {
547                crate::hint::cold_path();
548                let ParsedItem(input, offset_hour) =
549                    zone_literal(input).ok_or(InvalidComponent("offset hour"))?;
550                (input, offset_hour, 0)
551            }
552            Some(ParsedItem(input, offset_sign)) => {
553                let ParsedItem(input, offset_hour) = try_likely_ok!(
554                    ExactlyNDigits::<2>::parse(input)
555                        .map(|item| {
556                            item.map(|offset_hour| match offset_sign {
557                                Sign::Negative => -offset_hour.cast_signed(),
558                                Sign::Positive => offset_hour.cast_signed(),
559                            })
560                        })
561                        .ok_or(InvalidComponent("offset hour"))
562                );
563                let ParsedItem(input, offset_minute) = try_likely_ok!(
564                    ExactlyNDigits::<2>::parse(input).ok_or(InvalidComponent("offset minute"))
565                );
566                (input, offset_hour, offset_minute.cast_signed())
567            }
568        };
569
570        let input = opt_cfws(input).into_inner();
571
572        if !input.is_empty() {
573            return Err(error::Parse::ParseFromDescription(
574                error::ParseFromDescription::UnexpectedTrailingCharacters,
575            ));
576        }
577
578        let mut nanosecond = 0;
579        let leap_second_input = if second == 60 {
580            second = 59;
581            nanosecond = 999_999_999;
582            true
583        } else {
584            false
585        };
586
587        let dt = try_likely_ok!(
588            (|| {
589                let date = try_likely_ok!(Date::from_calendar_date(
590                    year.cast_signed().widen(),
591                    month,
592                    day
593                ));
594                let time = try_likely_ok!(Time::from_hms_nano(hour, minute, second, nanosecond));
595                let offset = try_likely_ok!(UtcOffset::from_hms(offset_hour, offset_minute, 0));
596                Ok(OffsetDateTime::new_in_offset(date, time, offset))
597            })()
598            .map_err(TryFromParsed::ComponentRange)
599        );
600
601        if leap_second_input && !dt.is_valid_leap_second_stand_in() {
602            return Err(error::Parse::TryFromParsed(TryFromParsed::ComponentRange(
603                error::ComponentRange::conditional("second"),
604            )));
605        }
606
607        Ok(dt)
608    }
609}
610
611#[expect(
612    private_interfaces,
613    reason = "not intended to be used by downstream users"
614)]
615impl sealed::Sealed for Rfc3339 {
616    fn parse_into<'a>(
617        &self,
618        input: &'a [u8],
619        parsed: &mut Parsed,
620        _: PrivateMethod,
621    ) -> Result<&'a [u8], error::Parse> {
622        let dash = ascii_char::<b'-'>;
623        let colon = ascii_char::<b':'>;
624
625        let input = try_likely_ok!(
626            ExactlyNDigits::<4>::parse(input)
627                .and_then(|item| {
628                    item.consume_value(|value| parsed.set_year(value.cast_signed().widen()))
629                })
630                .ok_or(InvalidComponent("year"))
631        );
632        let input = try_likely_ok!(dash(input).ok_or(InvalidLiteral)).into_inner();
633        let input = try_likely_ok!(
634            ExactlyNDigits::<2>::parse(input)
635                .and_then(
636                    |item| item.flat_map(|value| Month::from_number(NonZero::new(value)?).ok())
637                )
638                .and_then(|item| item.consume_value(|value| parsed.set_month(value)))
639                .ok_or(InvalidComponent("month"))
640        );
641        let input = try_likely_ok!(dash(input).ok_or(InvalidLiteral)).into_inner();
642        let input = try_likely_ok!(
643            ExactlyNDigits::<2>::parse(input)
644                .and_then(|item| item.consume_value(|value| parsed.set_day(NonZero::new(value)?)))
645                .ok_or(InvalidComponent("day"))
646        );
647
648        // RFC3339 allows any separator, not just `T`, not just `space`.
649        // cf. Section 5.6: Internet Date/Time Format:
650        //   NOTE: ISO 8601 defines date and time separated by "T".
651        //   Applications using this syntax may choose, for the sake of
652        //   readability, to specify a full-date and full-time separated by
653        //   (say) a space character.
654        // Specifically, rusqlite uses space separators.
655        let input = try_likely_ok!(input.get(1..).ok_or(InvalidComponent("separator")));
656
657        let input = try_likely_ok!(
658            ExactlyNDigits::<2>::parse(input)
659                .and_then(|item| item.consume_value(|value| parsed.set_hour_24(value)))
660                .ok_or(InvalidComponent("hour"))
661        );
662        let input = try_likely_ok!(colon(input).ok_or(InvalidLiteral)).into_inner();
663        let input = try_likely_ok!(
664            ExactlyNDigits::<2>::parse(input)
665                .and_then(|item| item.consume_value(|value| parsed.set_minute(value)))
666                .ok_or(InvalidComponent("minute"))
667        );
668        let input = try_likely_ok!(colon(input).ok_or(InvalidLiteral)).into_inner();
669        let input = try_likely_ok!(
670            ExactlyNDigits::<2>::parse(input)
671                .and_then(|item| item.consume_value(|value| parsed.set_second(value)))
672                .ok_or(InvalidComponent("second"))
673        );
674        let input = if let Some(ParsedItem(input, ())) = ascii_char::<b'.'>(input) {
675            let ParsedItem(mut input, mut value) =
676                try_likely_ok!(any_digit(input).ok_or(InvalidComponent("subsecond")))
677                    .map(|v| (v - b'0').widen::<u32>() * 100_000_000);
678
679            let mut multiplier = 10_000_000;
680            while let Some(ParsedItem(new_input, digit)) = any_digit(input) {
681                value += (digit - b'0').widen::<u32>() * multiplier;
682                input = new_input;
683                multiplier /= 10;
684            }
685
686            try_likely_ok!(
687                parsed
688                    .set_subsecond(value)
689                    .ok_or(InvalidComponent("subsecond"))
690            );
691            input
692        } else {
693            input
694        };
695
696        // The RFC explicitly allows leap seconds.
697        parsed.leap_second_allowed = true;
698
699        if let Some(ParsedItem(input, ())) = ascii_char_ignore_case::<b'Z'>(input) {
700            try_likely_ok!(
701                parsed
702                    .set_offset_hour(0)
703                    .ok_or(InvalidComponent("offset hour"))
704            );
705            try_likely_ok!(
706                parsed
707                    .set_offset_minute_signed(0)
708                    .ok_or(InvalidComponent("offset minute"))
709            );
710            try_likely_ok!(
711                parsed
712                    .set_offset_second_signed(0)
713                    .ok_or(InvalidComponent("offset second"))
714            );
715            return Ok(input);
716        }
717
718        let ParsedItem(input, offset_sign) =
719            try_likely_ok!(sign(input).ok_or(InvalidComponent("offset hour")));
720        let input = try_likely_ok!(
721            ExactlyNDigits::<2>::parse(input)
722                .and_then(|item| {
723                    item.filter(|&offset_hour| offset_hour <= 23)?
724                        .map(|offset_hour| match offset_sign {
725                            Sign::Negative => -offset_hour.cast_signed(),
726                            Sign::Positive => offset_hour.cast_signed(),
727                        })
728                        .consume_value(|value| parsed.set_offset_hour(value))
729                })
730                .ok_or(InvalidComponent("offset hour"))
731        );
732        let input = try_likely_ok!(colon(input).ok_or(InvalidLiteral)).into_inner();
733        let input = try_likely_ok!(
734            ExactlyNDigits::<2>::parse(input)
735                .and_then(|item| {
736                    item.map(|offset_minute| match offset_sign {
737                        Sign::Negative => -offset_minute.cast_signed(),
738                        Sign::Positive => offset_minute.cast_signed(),
739                    })
740                    .consume_value(|value| parsed.set_offset_minute_signed(value))
741                })
742                .ok_or(InvalidComponent("offset minute"))
743        );
744
745        Ok(input)
746    }
747
748    fn parse_offset_date_time(
749        &self,
750        input: &[u8],
751        defaults: Option<Parsed>,
752        _: PrivateMethod,
753    ) -> Result<OffsetDateTime, error::Parse> {
754        if let Some(mut defaults) = defaults {
755            crate::hint::cold_path();
756            return self
757                .parse_into(input, &mut defaults, PrivateMethod)
758                .and_then(|remaining| {
759                    if remaining.is_empty() {
760                        defaults.try_into().map_err(error::Parse::TryFromParsed)
761                    } else {
762                        Err(error::Parse::ParseFromDescription(
763                            error::ParseFromDescription::UnexpectedTrailingCharacters,
764                        ))
765                    }
766                });
767        }
768
769        let dash = ascii_char::<b'-'>;
770        let colon = ascii_char::<b':'>;
771
772        let ParsedItem(input, year) =
773            try_likely_ok!(ExactlyNDigits::<4>::parse(input).ok_or(InvalidComponent("year")));
774        let input = try_likely_ok!(dash(input).ok_or(InvalidLiteral)).into_inner();
775        let ParsedItem(input, month) = try_likely_ok!(
776            ExactlyNDigits::<2>::parse(input)
777                .and_then(|parsed| parsed.flat_map(NonZero::new))
778                .ok_or(InvalidComponent("month"))
779        );
780        let input = try_likely_ok!(dash(input).ok_or(InvalidLiteral)).into_inner();
781        let ParsedItem(input, day) =
782            try_likely_ok!(ExactlyNDigits::<2>::parse(input).ok_or(InvalidComponent("day")));
783
784        // RFC3339 allows any separator, not just `T`, not just `space`.
785        // cf. Section 5.6: Internet Date/Time Format:
786        //   NOTE: ISO 8601 defines date and time separated by "T".
787        //   Applications using this syntax may choose, for the sake of
788        //   readability, to specify a full-date and full-time separated by
789        //   (say) a space character.
790        // Specifically, rusqlite uses space separators.
791        let input = try_likely_ok!(input.get(1..).ok_or(InvalidComponent("separator")));
792
793        let ParsedItem(input, hour) =
794            try_likely_ok!(ExactlyNDigits::<2>::parse(input).ok_or(InvalidComponent("hour")));
795        let input = try_likely_ok!(colon(input).ok_or(InvalidLiteral)).into_inner();
796        let ParsedItem(input, minute) =
797            try_likely_ok!(ExactlyNDigits::<2>::parse(input).ok_or(InvalidComponent("minute")));
798        let input = try_likely_ok!(colon(input).ok_or(InvalidLiteral)).into_inner();
799        let ParsedItem(input, mut second) =
800            try_likely_ok!(ExactlyNDigits::<2>::parse(input).ok_or(InvalidComponent("second")));
801        let ParsedItem(input, mut nanosecond) =
802            if let Some(ParsedItem(input, ())) = ascii_char::<b'.'>(input) {
803                let ParsedItem(mut input, mut value) =
804                    try_likely_ok!(any_digit(input).ok_or(InvalidComponent("subsecond")))
805                        .map(|v| (v - b'0').widen::<u32>() * 100_000_000);
806
807                let mut multiplier = 10_000_000;
808                while let Some(ParsedItem(new_input, digit)) = any_digit(input) {
809                    value += (digit - b'0').widen::<u32>() * multiplier;
810                    input = new_input;
811                    multiplier /= 10;
812                }
813
814                ParsedItem(input, value)
815            } else {
816                ParsedItem(input, 0)
817            };
818        let ParsedItem(input, offset) = {
819            if let Some(ParsedItem(input, ())) = ascii_char_ignore_case::<b'Z'>(input) {
820                ParsedItem(input, UtcOffset::UTC)
821            } else {
822                let ParsedItem(input, offset_sign) =
823                    try_likely_ok!(sign(input).ok_or(InvalidComponent("offset hour")));
824                let ParsedItem(input, offset_hour) = try_likely_ok!(
825                    ExactlyNDigits::<2>::parse(input)
826                        .and_then(|parsed| parsed.filter(|&offset_hour| offset_hour <= 23))
827                        .ok_or(InvalidComponent("offset hour"))
828                );
829                let input = try_likely_ok!(colon(input).ok_or(InvalidLiteral)).into_inner();
830                let ParsedItem(input, offset_minute) = try_likely_ok!(
831                    ExactlyNDigits::<2>::parse(input).ok_or(InvalidComponent("offset minute"))
832                );
833                try_likely_ok!(
834                    match offset_sign {
835                        Sign::Negative => UtcOffset::from_hms(
836                            -offset_hour.cast_signed(),
837                            -offset_minute.cast_signed(),
838                            0,
839                        ),
840                        Sign::Positive => UtcOffset::from_hms(
841                            offset_hour.cast_signed(),
842                            offset_minute.cast_signed(),
843                            0,
844                        ),
845                    }
846                    .map(|offset| ParsedItem(input, offset))
847                    .map_err(TryFromParsed::ComponentRange)
848                )
849            }
850        };
851
852        if !input.is_empty() {
853            return Err(error::Parse::ParseFromDescription(
854                error::ParseFromDescription::UnexpectedTrailingCharacters,
855            ));
856        }
857
858        // The RFC explicitly permits leap seconds. We don't currently support them, so treat it as
859        // the preceding nanosecond. However, leap seconds can only occur as the last second of the
860        // month UTC.
861        let leap_second_input = if second == 60 {
862            second = 59;
863            nanosecond = 999_999_999;
864            true
865        } else {
866            false
867        };
868
869        let date = try_likely_ok!(
870            Month::from_number(month)
871                .and_then(|month| Date::from_calendar_date(year.cast_signed().widen(), month, day))
872                .map_err(TryFromParsed::ComponentRange)
873        );
874        let time = try_likely_ok!(
875            Time::from_hms_nano(hour, minute, second, nanosecond)
876                .map_err(TryFromParsed::ComponentRange)
877        );
878        let dt = OffsetDateTime::new_in_offset(date, time, offset);
879
880        if leap_second_input && !dt.is_valid_leap_second_stand_in() {
881            return Err(error::Parse::TryFromParsed(TryFromParsed::ComponentRange(
882                error::ComponentRange::conditional("second"),
883            )));
884        }
885
886        Ok(dt)
887    }
888}
889
890#[expect(
891    private_interfaces,
892    reason = "not intended to be used by downstream users"
893)]
894impl<const CONFIG: EncodedConfig> sealed::Sealed for Iso8601<CONFIG> {
895    #[inline]
896    fn parse_into<'a>(
897        &self,
898        mut input: &'a [u8],
899        parsed: &mut Parsed,
900        _: PrivateMethod,
901    ) -> Result<&'a [u8], error::Parse> {
902        use crate::parsing::combinator::rfc::iso8601::ExtendedKind;
903
904        let mut extended_kind = ExtendedKind::Unknown;
905        let mut date_is_present = false;
906        let mut time_is_present = false;
907        let mut offset_is_present = false;
908        let mut first_error = None;
909
910        parsed.leap_second_allowed = true;
911
912        match Self::parse_date(parsed, &mut extended_kind)(input) {
913            Ok(new_input) => {
914                input = new_input;
915                date_is_present = true;
916            }
917            Err(err) => {
918                first_error.get_or_insert(err);
919            }
920        }
921
922        match Self::parse_time(parsed, &mut extended_kind, date_is_present)(input) {
923            Ok(new_input) => {
924                input = new_input;
925                time_is_present = true;
926            }
927            Err(err) => {
928                first_error.get_or_insert(err);
929            }
930        }
931
932        // If a date and offset are present, a time must be as well.
933        if !date_is_present || time_is_present {
934            match Self::parse_offset(parsed, &mut extended_kind)(input) {
935                Ok(new_input) => {
936                    input = new_input;
937                    offset_is_present = true;
938                }
939                Err(err) => {
940                    first_error.get_or_insert(err);
941                }
942            }
943        }
944
945        if !date_is_present && !time_is_present && !offset_is_present {
946            match first_error {
947                Some(err) => return Err(err),
948                None => bug!("an error should be present if no components were parsed"),
949            }
950        }
951
952        Ok(input)
953    }
954}