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