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