time/parsing/
parsable.rs

1//! A trait that can be used to parse an item from an input.
2
3use core::ops::Deref;
4
5use num_conv::prelude::*;
6
7use crate::error::TryFromParsed;
8use crate::format_description::well_known::iso8601::EncodedConfig;
9use crate::format_description::well_known::{Iso8601, Rfc2822, Rfc3339};
10use crate::format_description::BorrowedFormatItem;
11#[cfg(feature = "alloc")]
12use crate::format_description::OwnedFormatItem;
13use crate::internal_macros::bug;
14use crate::parsing::{Parsed, ParsedItem};
15use crate::{error, Date, Month, OffsetDateTime, Time, UtcOffset, Weekday};
16
17/// A type that can be parsed.
18#[cfg_attr(docsrs, doc(notable_trait))]
19#[doc(alias = "Parseable")]
20pub trait Parsable: sealed::Sealed {}
21impl Parsable for BorrowedFormatItem<'_> {}
22impl Parsable for [BorrowedFormatItem<'_>] {}
23#[cfg(feature = "alloc")]
24impl Parsable for OwnedFormatItem {}
25#[cfg(feature = "alloc")]
26impl Parsable for [OwnedFormatItem] {}
27impl Parsable for Rfc2822 {}
28impl Parsable for Rfc3339 {}
29impl<const CONFIG: EncodedConfig> Parsable for Iso8601<CONFIG> {}
30impl<T: Deref> Parsable for T where T::Target: Parsable {}
31
32/// Seal the trait to prevent downstream users from implementing it, while still allowing it to
33/// exist in generic bounds.
34mod sealed {
35    use super::*;
36    use crate::{PrimitiveDateTime, UtcDateTime};
37
38    /// Parse the item using a format description and an input.
39    pub trait Sealed {
40        /// Parse the item into the provided [`Parsed`] struct.
41        ///
42        /// This method can be used to parse a single component without parsing the full value.
43        fn parse_into<'a>(
44            &self,
45            input: &'a [u8],
46            parsed: &mut Parsed,
47        ) -> Result<&'a [u8], error::Parse>;
48
49        /// Parse the item into a new [`Parsed`] struct.
50        ///
51        /// This method can only be used to parse a complete value of a type. If any characters
52        /// remain after parsing, an error will be returned.
53        #[inline]
54        fn parse(&self, input: &[u8]) -> Result<Parsed, error::Parse> {
55            let mut parsed = Parsed::new();
56            if self.parse_into(input, &mut parsed)?.is_empty() {
57                Ok(parsed)
58            } else {
59                Err(error::Parse::ParseFromDescription(
60                    error::ParseFromDescription::UnexpectedTrailingCharacters,
61                ))
62            }
63        }
64
65        /// Parse a [`Date`] from the format description.
66        #[inline]
67        fn parse_date(&self, input: &[u8]) -> Result<Date, error::Parse> {
68            Ok(self.parse(input)?.try_into()?)
69        }
70
71        /// Parse a [`Time`] from the format description.
72        #[inline]
73        fn parse_time(&self, input: &[u8]) -> Result<Time, error::Parse> {
74            Ok(self.parse(input)?.try_into()?)
75        }
76
77        /// Parse a [`UtcOffset`] from the format description.
78        #[inline]
79        fn parse_offset(&self, input: &[u8]) -> Result<UtcOffset, error::Parse> {
80            Ok(self.parse(input)?.try_into()?)
81        }
82
83        /// Parse a [`PrimitiveDateTime`] from the format description.
84        #[inline]
85        fn parse_primitive_date_time(
86            &self,
87            input: &[u8],
88        ) -> Result<PrimitiveDateTime, error::Parse> {
89            Ok(self.parse(input)?.try_into()?)
90        }
91
92        /// Parse a [`UtcDateTime`] from the format description.
93        #[inline]
94        fn parse_utc_date_time(&self, input: &[u8]) -> Result<UtcDateTime, error::Parse> {
95            Ok(self.parse(input)?.try_into()?)
96        }
97
98        /// Parse a [`OffsetDateTime`] from the format description.
99        #[inline]
100        fn parse_offset_date_time(&self, input: &[u8]) -> Result<OffsetDateTime, error::Parse> {
101            Ok(self.parse(input)?.try_into()?)
102        }
103    }
104}
105
106impl sealed::Sealed for BorrowedFormatItem<'_> {
107    #[inline]
108    fn parse_into<'a>(
109        &self,
110        input: &'a [u8],
111        parsed: &mut Parsed,
112    ) -> Result<&'a [u8], error::Parse> {
113        Ok(parsed.parse_item(input, self)?)
114    }
115}
116
117impl sealed::Sealed for [BorrowedFormatItem<'_>] {
118    #[inline]
119    fn parse_into<'a>(
120        &self,
121        input: &'a [u8],
122        parsed: &mut Parsed,
123    ) -> Result<&'a [u8], error::Parse> {
124        Ok(parsed.parse_items(input, self)?)
125    }
126}
127
128#[cfg(feature = "alloc")]
129impl sealed::Sealed for OwnedFormatItem {
130    #[inline]
131    fn parse_into<'a>(
132        &self,
133        input: &'a [u8],
134        parsed: &mut Parsed,
135    ) -> Result<&'a [u8], error::Parse> {
136        Ok(parsed.parse_item(input, self)?)
137    }
138}
139
140#[cfg(feature = "alloc")]
141impl sealed::Sealed for [OwnedFormatItem] {
142    #[inline]
143    fn parse_into<'a>(
144        &self,
145        input: &'a [u8],
146        parsed: &mut Parsed,
147    ) -> Result<&'a [u8], error::Parse> {
148        Ok(parsed.parse_items(input, self)?)
149    }
150}
151
152impl<T> sealed::Sealed for T
153where
154    T: Deref<Target: sealed::Sealed>,
155{
156    #[inline]
157    fn parse_into<'a>(
158        &self,
159        input: &'a [u8],
160        parsed: &mut Parsed,
161    ) -> Result<&'a [u8], error::Parse> {
162        self.deref().parse_into(input, parsed)
163    }
164}
165
166impl sealed::Sealed for Rfc2822 {
167    fn parse_into<'a>(
168        &self,
169        input: &'a [u8],
170        parsed: &mut Parsed,
171    ) -> Result<&'a [u8], error::Parse> {
172        use crate::error::ParseFromDescription::{InvalidComponent, InvalidLiteral};
173        use crate::parsing::combinator::rfc::rfc2822::{cfws, fws};
174        use crate::parsing::combinator::{
175            ascii_char, exactly_n_digits, first_match, n_to_m_digits, opt, sign,
176        };
177
178        let colon = ascii_char::<b':'>;
179        let comma = ascii_char::<b','>;
180
181        let input = opt(cfws)(input).into_inner();
182        let weekday = first_match(
183            [
184                (b"Mon".as_slice(), Weekday::Monday),
185                (b"Tue".as_slice(), Weekday::Tuesday),
186                (b"Wed".as_slice(), Weekday::Wednesday),
187                (b"Thu".as_slice(), Weekday::Thursday),
188                (b"Fri".as_slice(), Weekday::Friday),
189                (b"Sat".as_slice(), Weekday::Saturday),
190                (b"Sun".as_slice(), Weekday::Sunday),
191            ],
192            false,
193        )(input);
194        let input = if let Some(item) = weekday {
195            let input = item
196                .consume_value(|value| parsed.set_weekday(value))
197                .ok_or(InvalidComponent("weekday"))?;
198            let input = comma(input).ok_or(InvalidLiteral)?.into_inner();
199            opt(cfws)(input).into_inner()
200        } else {
201            input
202        };
203        let input = n_to_m_digits::<1, 2, _>(input)
204            .and_then(|item| item.consume_value(|value| parsed.set_day(value)))
205            .ok_or(InvalidComponent("day"))?;
206        let input = cfws(input).ok_or(InvalidLiteral)?.into_inner();
207        let input = first_match(
208            [
209                (b"Jan".as_slice(), Month::January),
210                (b"Feb".as_slice(), Month::February),
211                (b"Mar".as_slice(), Month::March),
212                (b"Apr".as_slice(), Month::April),
213                (b"May".as_slice(), Month::May),
214                (b"Jun".as_slice(), Month::June),
215                (b"Jul".as_slice(), Month::July),
216                (b"Aug".as_slice(), Month::August),
217                (b"Sep".as_slice(), Month::September),
218                (b"Oct".as_slice(), Month::October),
219                (b"Nov".as_slice(), Month::November),
220                (b"Dec".as_slice(), Month::December),
221            ],
222            false,
223        )(input)
224        .and_then(|item| item.consume_value(|value| parsed.set_month(value)))
225        .ok_or(InvalidComponent("month"))?;
226        let input = cfws(input).ok_or(InvalidLiteral)?.into_inner();
227        let input = match exactly_n_digits::<4, u32>(input) {
228            Some(item) => {
229                let input = item
230                    .flat_map(|year| if year >= 1900 { Some(year) } else { None })
231                    .and_then(|item| {
232                        item.consume_value(|value| parsed.set_year(value.cast_signed()))
233                    })
234                    .ok_or(InvalidComponent("year"))?;
235                fws(input).ok_or(InvalidLiteral)?.into_inner()
236            }
237            None => {
238                let input = exactly_n_digits::<2, u32>(input)
239                    .and_then(|item| {
240                        item.map(|year| if year < 50 { year + 2000 } else { year + 1900 })
241                            .map(|year| year.cast_signed())
242                            .consume_value(|value| parsed.set_year(value))
243                    })
244                    .ok_or(InvalidComponent("year"))?;
245                cfws(input).ok_or(InvalidLiteral)?.into_inner()
246            }
247        };
248
249        let input = exactly_n_digits::<2, _>(input)
250            .and_then(|item| item.consume_value(|value| parsed.set_hour_24(value)))
251            .ok_or(InvalidComponent("hour"))?;
252        let input = opt(cfws)(input).into_inner();
253        let input = colon(input).ok_or(InvalidLiteral)?.into_inner();
254        let input = opt(cfws)(input).into_inner();
255        let input = exactly_n_digits::<2, _>(input)
256            .and_then(|item| item.consume_value(|value| parsed.set_minute(value)))
257            .ok_or(InvalidComponent("minute"))?;
258
259        let input = if let Some(input) = colon(opt(cfws)(input).into_inner()) {
260            let input = input.into_inner(); // discard the colon
261            let input = opt(cfws)(input).into_inner();
262            let input = exactly_n_digits::<2, _>(input)
263                .and_then(|item| item.consume_value(|value| parsed.set_second(value)))
264                .ok_or(InvalidComponent("second"))?;
265            cfws(input).ok_or(InvalidLiteral)?.into_inner()
266        } else {
267            cfws(input).ok_or(InvalidLiteral)?.into_inner()
268        };
269
270        // The RFC explicitly allows leap seconds.
271        parsed.leap_second_allowed = true;
272
273        #[expect(
274            clippy::unnecessary_lazy_evaluations,
275            reason = "rust-lang/rust-clippy#8522"
276        )]
277        let zone_literal = first_match(
278            [
279                (b"UT".as_slice(), 0),
280                (b"GMT".as_slice(), 0),
281                (b"EST".as_slice(), -5),
282                (b"EDT".as_slice(), -4),
283                (b"CST".as_slice(), -6),
284                (b"CDT".as_slice(), -5),
285                (b"MST".as_slice(), -7),
286                (b"MDT".as_slice(), -6),
287                (b"PST".as_slice(), -8),
288                (b"PDT".as_slice(), -7),
289            ],
290            false,
291        )(input)
292        .or_else(|| match input {
293            [b'a'..=b'i' | b'k'..=b'z' | b'A'..=b'I' | b'K'..=b'Z', rest @ ..] => {
294                Some(ParsedItem(rest, 0))
295            }
296            _ => None,
297        });
298        if let Some(zone_literal) = zone_literal {
299            let input = zone_literal
300                .consume_value(|value| parsed.set_offset_hour(value))
301                .ok_or(InvalidComponent("offset hour"))?;
302            parsed
303                .set_offset_minute_signed(0)
304                .ok_or(InvalidComponent("offset minute"))?;
305            parsed
306                .set_offset_second_signed(0)
307                .ok_or(InvalidComponent("offset second"))?;
308            return Ok(input);
309        }
310
311        let ParsedItem(input, offset_sign) = sign(input).ok_or(InvalidComponent("offset hour"))?;
312        let input = exactly_n_digits::<2, u8>(input)
313            .and_then(|item| {
314                item.map(|offset_hour| {
315                    if offset_sign == b'-' {
316                        -offset_hour.cast_signed()
317                    } else {
318                        offset_hour.cast_signed()
319                    }
320                })
321                .consume_value(|value| parsed.set_offset_hour(value))
322            })
323            .ok_or(InvalidComponent("offset hour"))?;
324        let input = exactly_n_digits::<2, u8>(input)
325            .and_then(|item| {
326                item.consume_value(|value| parsed.set_offset_minute_signed(value.cast_signed()))
327            })
328            .ok_or(InvalidComponent("offset minute"))?;
329
330        let input = opt(cfws)(input).into_inner();
331
332        Ok(input)
333    }
334
335    fn parse_offset_date_time(&self, input: &[u8]) -> Result<OffsetDateTime, error::Parse> {
336        use crate::error::ParseFromDescription::{InvalidComponent, InvalidLiteral};
337        use crate::parsing::combinator::rfc::rfc2822::{cfws, fws};
338        use crate::parsing::combinator::{
339            ascii_char, exactly_n_digits, first_match, n_to_m_digits, opt, sign,
340        };
341
342        let colon = ascii_char::<b':'>;
343        let comma = ascii_char::<b','>;
344
345        let input = opt(cfws)(input).into_inner();
346        // This parses the weekday, but we don't actually use the value anywhere. Because of this,
347        // just return `()` to avoid unnecessary generated code.
348        let weekday = first_match(
349            [
350                (b"Mon".as_slice(), ()),
351                (b"Tue".as_slice(), ()),
352                (b"Wed".as_slice(), ()),
353                (b"Thu".as_slice(), ()),
354                (b"Fri".as_slice(), ()),
355                (b"Sat".as_slice(), ()),
356                (b"Sun".as_slice(), ()),
357            ],
358            false,
359        )(input);
360        let input = if let Some(item) = weekday {
361            let input = item.into_inner();
362            let input = comma(input).ok_or(InvalidLiteral)?.into_inner();
363            opt(cfws)(input).into_inner()
364        } else {
365            input
366        };
367        let ParsedItem(input, day) =
368            n_to_m_digits::<1, 2, _>(input).ok_or(InvalidComponent("day"))?;
369        let input = cfws(input).ok_or(InvalidLiteral)?.into_inner();
370        let ParsedItem(input, month) = first_match(
371            [
372                (b"Jan".as_slice(), Month::January),
373                (b"Feb".as_slice(), Month::February),
374                (b"Mar".as_slice(), Month::March),
375                (b"Apr".as_slice(), Month::April),
376                (b"May".as_slice(), Month::May),
377                (b"Jun".as_slice(), Month::June),
378                (b"Jul".as_slice(), Month::July),
379                (b"Aug".as_slice(), Month::August),
380                (b"Sep".as_slice(), Month::September),
381                (b"Oct".as_slice(), Month::October),
382                (b"Nov".as_slice(), Month::November),
383                (b"Dec".as_slice(), Month::December),
384            ],
385            false,
386        )(input)
387        .ok_or(InvalidComponent("month"))?;
388        let input = cfws(input).ok_or(InvalidLiteral)?.into_inner();
389        let (input, year) = match exactly_n_digits::<4, u32>(input) {
390            Some(item) => {
391                let ParsedItem(input, year) = item
392                    .flat_map(|year| if year >= 1900 { Some(year) } else { None })
393                    .ok_or(InvalidComponent("year"))?;
394                let input = fws(input).ok_or(InvalidLiteral)?.into_inner();
395                (input, year)
396            }
397            None => {
398                let ParsedItem(input, year) = exactly_n_digits::<2, u32>(input)
399                    .map(|item| item.map(|year| if year < 50 { year + 2000 } else { year + 1900 }))
400                    .ok_or(InvalidComponent("year"))?;
401                let input = cfws(input).ok_or(InvalidLiteral)?.into_inner();
402                (input, year)
403            }
404        };
405
406        let ParsedItem(input, hour) =
407            exactly_n_digits::<2, _>(input).ok_or(InvalidComponent("hour"))?;
408        let input = opt(cfws)(input).into_inner();
409        let input = colon(input).ok_or(InvalidLiteral)?.into_inner();
410        let input = opt(cfws)(input).into_inner();
411        let ParsedItem(input, minute) =
412            exactly_n_digits::<2, _>(input).ok_or(InvalidComponent("minute"))?;
413
414        let (input, mut second) = if let Some(input) = colon(opt(cfws)(input).into_inner()) {
415            let input = input.into_inner(); // discard the colon
416            let input = opt(cfws)(input).into_inner();
417            let ParsedItem(input, second) =
418                exactly_n_digits::<2, _>(input).ok_or(InvalidComponent("second"))?;
419            let input = cfws(input).ok_or(InvalidLiteral)?.into_inner();
420            (input, second)
421        } else {
422            (cfws(input).ok_or(InvalidLiteral)?.into_inner(), 0)
423        };
424
425        #[expect(
426            clippy::unnecessary_lazy_evaluations,
427            reason = "rust-lang/rust-clippy#8522"
428        )]
429        let zone_literal = first_match(
430            [
431                (b"UT".as_slice(), 0),
432                (b"GMT".as_slice(), 0),
433                (b"EST".as_slice(), -5),
434                (b"EDT".as_slice(), -4),
435                (b"CST".as_slice(), -6),
436                (b"CDT".as_slice(), -5),
437                (b"MST".as_slice(), -7),
438                (b"MDT".as_slice(), -6),
439                (b"PST".as_slice(), -8),
440                (b"PDT".as_slice(), -7),
441            ],
442            false,
443        )(input)
444        .or_else(|| match input {
445            [b'a'..=b'i' | b'k'..=b'z' | b'A'..=b'I' | b'K'..=b'Z', rest @ ..] => {
446                Some(ParsedItem(rest, 0))
447            }
448            _ => None,
449        });
450
451        let (input, offset_hour, offset_minute) = if let Some(zone_literal) = zone_literal {
452            let ParsedItem(input, offset_hour) = zone_literal;
453            (input, offset_hour, 0)
454        } else {
455            let ParsedItem(input, offset_sign) =
456                sign(input).ok_or(InvalidComponent("offset hour"))?;
457            let ParsedItem(input, offset_hour) = exactly_n_digits::<2, u8>(input)
458                .map(|item| {
459                    item.map(|offset_hour| {
460                        if offset_sign == b'-' {
461                            -offset_hour.cast_signed()
462                        } else {
463                            offset_hour.cast_signed()
464                        }
465                    })
466                })
467                .ok_or(InvalidComponent("offset hour"))?;
468            let ParsedItem(input, offset_minute) =
469                exactly_n_digits::<2, u8>(input).ok_or(InvalidComponent("offset minute"))?;
470            (input, offset_hour, offset_minute.cast_signed())
471        };
472
473        let input = opt(cfws)(input).into_inner();
474
475        if !input.is_empty() {
476            return Err(error::Parse::ParseFromDescription(
477                error::ParseFromDescription::UnexpectedTrailingCharacters,
478            ));
479        }
480
481        let mut nanosecond = 0;
482        let leap_second_input = if second == 60 {
483            second = 59;
484            nanosecond = 999_999_999;
485            true
486        } else {
487            false
488        };
489
490        let dt = (|| {
491            let date = Date::from_calendar_date(year.cast_signed(), month, day)?;
492            let time = Time::from_hms_nano(hour, minute, second, nanosecond)?;
493            let offset = UtcOffset::from_hms(offset_hour, offset_minute, 0)?;
494            Ok(OffsetDateTime::new_in_offset(date, time, offset))
495        })()
496        .map_err(TryFromParsed::ComponentRange)?;
497
498        if leap_second_input && !dt.is_valid_leap_second_stand_in() {
499            return Err(error::Parse::TryFromParsed(TryFromParsed::ComponentRange(
500                error::ComponentRange {
501                    name: "second",
502                    minimum: 0,
503                    maximum: 59,
504                    value: 60,
505                    conditional_message: Some("because leap seconds are not supported"),
506                },
507            )));
508        }
509
510        Ok(dt)
511    }
512}
513
514impl sealed::Sealed for Rfc3339 {
515    fn parse_into<'a>(
516        &self,
517        input: &'a [u8],
518        parsed: &mut Parsed,
519    ) -> Result<&'a [u8], error::Parse> {
520        use crate::error::ParseFromDescription::{InvalidComponent, InvalidLiteral};
521        use crate::parsing::combinator::{
522            any_digit, ascii_char, ascii_char_ignore_case, exactly_n_digits, sign,
523        };
524
525        let dash = ascii_char::<b'-'>;
526        let colon = ascii_char::<b':'>;
527
528        let input = exactly_n_digits::<4, u32>(input)
529            .and_then(|item| item.consume_value(|value| parsed.set_year(value.cast_signed())))
530            .ok_or(InvalidComponent("year"))?;
531        let input = dash(input).ok_or(InvalidLiteral)?.into_inner();
532        let input = exactly_n_digits::<2, _>(input)
533            .and_then(|item| item.flat_map(|value| Month::from_number(value).ok()))
534            .and_then(|item| item.consume_value(|value| parsed.set_month(value)))
535            .ok_or(InvalidComponent("month"))?;
536        let input = dash(input).ok_or(InvalidLiteral)?.into_inner();
537        let input = exactly_n_digits::<2, _>(input)
538            .and_then(|item| item.consume_value(|value| parsed.set_day(value)))
539            .ok_or(InvalidComponent("day"))?;
540
541        // RFC3339 allows any separator, not just `T`, not just `space`.
542        // cf. Section 5.6: Internet Date/Time Format:
543        //   NOTE: ISO 8601 defines date and time separated by "T".
544        //   Applications using this syntax may choose, for the sake of
545        //   readability, to specify a full-date and full-time separated by
546        //   (say) a space character.
547        // Specifically, rusqlite uses space separators.
548        let input = input.get(1..).ok_or(InvalidComponent("separator"))?;
549
550        let input = exactly_n_digits::<2, _>(input)
551            .and_then(|item| item.consume_value(|value| parsed.set_hour_24(value)))
552            .ok_or(InvalidComponent("hour"))?;
553        let input = colon(input).ok_or(InvalidLiteral)?.into_inner();
554        let input = exactly_n_digits::<2, _>(input)
555            .and_then(|item| item.consume_value(|value| parsed.set_minute(value)))
556            .ok_or(InvalidComponent("minute"))?;
557        let input = colon(input).ok_or(InvalidLiteral)?.into_inner();
558        let input = exactly_n_digits::<2, _>(input)
559            .and_then(|item| item.consume_value(|value| parsed.set_second(value)))
560            .ok_or(InvalidComponent("second"))?;
561        let input = if let Some(ParsedItem(input, ())) = ascii_char::<b'.'>(input) {
562            let ParsedItem(mut input, mut value) = any_digit(input)
563                .ok_or(InvalidComponent("subsecond"))?
564                .map(|v| (v - b'0').extend::<u32>() * 100_000_000);
565
566            let mut multiplier = 10_000_000;
567            while let Some(ParsedItem(new_input, digit)) = any_digit(input) {
568                value += (digit - b'0').extend::<u32>() * multiplier;
569                input = new_input;
570                multiplier /= 10;
571            }
572
573            parsed
574                .set_subsecond(value)
575                .ok_or(InvalidComponent("subsecond"))?;
576            input
577        } else {
578            input
579        };
580
581        // The RFC explicitly allows leap seconds.
582        parsed.leap_second_allowed = true;
583
584        if let Some(ParsedItem(input, ())) = ascii_char_ignore_case::<b'Z'>(input) {
585            parsed
586                .set_offset_hour(0)
587                .ok_or(InvalidComponent("offset hour"))?;
588            parsed
589                .set_offset_minute_signed(0)
590                .ok_or(InvalidComponent("offset minute"))?;
591            parsed
592                .set_offset_second_signed(0)
593                .ok_or(InvalidComponent("offset second"))?;
594            return Ok(input);
595        }
596
597        let ParsedItem(input, offset_sign) = sign(input).ok_or(InvalidComponent("offset hour"))?;
598        let input = exactly_n_digits::<2, u8>(input)
599            .and_then(|item| {
600                item.filter(|&offset_hour| offset_hour <= 23)?
601                    .map(|offset_hour| {
602                        if offset_sign == b'-' {
603                            -offset_hour.cast_signed()
604                        } else {
605                            offset_hour.cast_signed()
606                        }
607                    })
608                    .consume_value(|value| parsed.set_offset_hour(value))
609            })
610            .ok_or(InvalidComponent("offset hour"))?;
611        let input = colon(input).ok_or(InvalidLiteral)?.into_inner();
612        let input = exactly_n_digits::<2, u8>(input)
613            .and_then(|item| {
614                item.map(|offset_minute| {
615                    if offset_sign == b'-' {
616                        -offset_minute.cast_signed()
617                    } else {
618                        offset_minute.cast_signed()
619                    }
620                })
621                .consume_value(|value| parsed.set_offset_minute_signed(value))
622            })
623            .ok_or(InvalidComponent("offset minute"))?;
624
625        Ok(input)
626    }
627
628    fn parse_offset_date_time(&self, input: &[u8]) -> Result<OffsetDateTime, error::Parse> {
629        use crate::error::ParseFromDescription::{InvalidComponent, InvalidLiteral};
630        use crate::parsing::combinator::{
631            any_digit, ascii_char, ascii_char_ignore_case, exactly_n_digits, sign,
632        };
633
634        let dash = ascii_char::<b'-'>;
635        let colon = ascii_char::<b':'>;
636
637        let ParsedItem(input, year) =
638            exactly_n_digits::<4, u32>(input).ok_or(InvalidComponent("year"))?;
639        let input = dash(input).ok_or(InvalidLiteral)?.into_inner();
640        let ParsedItem(input, month) =
641            exactly_n_digits::<2, _>(input).ok_or(InvalidComponent("month"))?;
642        let input = dash(input).ok_or(InvalidLiteral)?.into_inner();
643        let ParsedItem(input, day) =
644            exactly_n_digits::<2, _>(input).ok_or(InvalidComponent("day"))?;
645
646        // RFC3339 allows any separator, not just `T`, not just `space`.
647        // cf. Section 5.6: Internet Date/Time Format:
648        //   NOTE: ISO 8601 defines date and time separated by "T".
649        //   Applications using this syntax may choose, for the sake of
650        //   readability, to specify a full-date and full-time separated by
651        //   (say) a space character.
652        // Specifically, rusqlite uses space separators.
653        let input = input.get(1..).ok_or(InvalidComponent("separator"))?;
654
655        let ParsedItem(input, hour) =
656            exactly_n_digits::<2, _>(input).ok_or(InvalidComponent("hour"))?;
657        let input = colon(input).ok_or(InvalidLiteral)?.into_inner();
658        let ParsedItem(input, minute) =
659            exactly_n_digits::<2, _>(input).ok_or(InvalidComponent("minute"))?;
660        let input = colon(input).ok_or(InvalidLiteral)?.into_inner();
661        let ParsedItem(input, mut second) =
662            exactly_n_digits::<2, _>(input).ok_or(InvalidComponent("second"))?;
663        let ParsedItem(input, mut nanosecond) =
664            if let Some(ParsedItem(input, ())) = ascii_char::<b'.'>(input) {
665                let ParsedItem(mut input, mut value) = any_digit(input)
666                    .ok_or(InvalidComponent("subsecond"))?
667                    .map(|v| (v - b'0').extend::<u32>() * 100_000_000);
668
669                let mut multiplier = 10_000_000;
670                while let Some(ParsedItem(new_input, digit)) = any_digit(input) {
671                    value += (digit - b'0').extend::<u32>() * multiplier;
672                    input = new_input;
673                    multiplier /= 10;
674                }
675
676                ParsedItem(input, value)
677            } else {
678                ParsedItem(input, 0)
679            };
680        let ParsedItem(input, offset) = {
681            if let Some(ParsedItem(input, ())) = ascii_char_ignore_case::<b'Z'>(input) {
682                ParsedItem(input, UtcOffset::UTC)
683            } else {
684                let ParsedItem(input, offset_sign) =
685                    sign(input).ok_or(InvalidComponent("offset hour"))?;
686                let ParsedItem(input, offset_hour) = exactly_n_digits::<2, u8>(input)
687                    .and_then(|parsed| parsed.filter(|&offset_hour| offset_hour <= 23))
688                    .ok_or(InvalidComponent("offset hour"))?;
689                let input = colon(input).ok_or(InvalidLiteral)?.into_inner();
690                let ParsedItem(input, offset_minute) =
691                    exactly_n_digits::<2, u8>(input).ok_or(InvalidComponent("offset minute"))?;
692                UtcOffset::from_hms(
693                    if offset_sign == b'-' {
694                        -offset_hour.cast_signed()
695                    } else {
696                        offset_hour.cast_signed()
697                    },
698                    if offset_sign == b'-' {
699                        -offset_minute.cast_signed()
700                    } else {
701                        offset_minute.cast_signed()
702                    },
703                    0,
704                )
705                .map(|offset| ParsedItem(input, offset))
706                .map_err(|mut err| {
707                    // Provide the user a more accurate error.
708                    if err.name == "hours" {
709                        err.name = "offset hour";
710                    } else if err.name == "minutes" {
711                        err.name = "offset minute";
712                    }
713                    err
714                })
715                .map_err(TryFromParsed::ComponentRange)?
716            }
717        };
718
719        if !input.is_empty() {
720            return Err(error::Parse::ParseFromDescription(
721                error::ParseFromDescription::UnexpectedTrailingCharacters,
722            ));
723        }
724
725        // The RFC explicitly permits leap seconds. We don't currently support them, so treat it as
726        // the preceding nanosecond. However, leap seconds can only occur as the last second of the
727        // month UTC.
728        let leap_second_input = if second == 60 {
729            second = 59;
730            nanosecond = 999_999_999;
731            true
732        } else {
733            false
734        };
735
736        let date = Month::from_number(month)
737            .and_then(|month| Date::from_calendar_date(year.cast_signed(), month, day))
738            .map_err(TryFromParsed::ComponentRange)?;
739        let time = Time::from_hms_nano(hour, minute, second, nanosecond)
740            .map_err(TryFromParsed::ComponentRange)?;
741        let dt = OffsetDateTime::new_in_offset(date, time, offset);
742
743        if leap_second_input && !dt.is_valid_leap_second_stand_in() {
744            return Err(error::Parse::TryFromParsed(TryFromParsed::ComponentRange(
745                error::ComponentRange {
746                    name: "second",
747                    minimum: 0,
748                    maximum: 59,
749                    value: 60,
750                    conditional_message: Some("because leap seconds are not supported"),
751                },
752            )));
753        }
754
755        Ok(dt)
756    }
757}
758
759impl<const CONFIG: EncodedConfig> sealed::Sealed for Iso8601<CONFIG> {
760    #[inline]
761    fn parse_into<'a>(
762        &self,
763        mut input: &'a [u8],
764        parsed: &mut Parsed,
765    ) -> Result<&'a [u8], error::Parse> {
766        use crate::parsing::combinator::rfc::iso8601::ExtendedKind;
767
768        let mut extended_kind = ExtendedKind::Unknown;
769        let mut date_is_present = false;
770        let mut time_is_present = false;
771        let mut offset_is_present = false;
772        let mut first_error = None;
773
774        parsed.leap_second_allowed = true;
775
776        match Self::parse_date(parsed, &mut extended_kind)(input) {
777            Ok(new_input) => {
778                input = new_input;
779                date_is_present = true;
780            }
781            Err(err) => {
782                first_error.get_or_insert(err);
783            }
784        }
785
786        match Self::parse_time(parsed, &mut extended_kind, date_is_present)(input) {
787            Ok(new_input) => {
788                input = new_input;
789                time_is_present = true;
790            }
791            Err(err) => {
792                first_error.get_or_insert(err);
793            }
794        }
795
796        // If a date and offset are present, a time must be as well.
797        if !date_is_present || time_is_present {
798            match Self::parse_offset(parsed, &mut extended_kind)(input) {
799                Ok(new_input) => {
800                    input = new_input;
801                    offset_is_present = true;
802                }
803                Err(err) => {
804                    first_error.get_or_insert(err);
805                }
806            }
807        }
808
809        if !date_is_present && !time_is_present && !offset_is_present {
810            match first_error {
811                Some(err) => return Err(err),
812                None => bug!("an error should be present if no components were parsed"),
813            }
814        }
815
816        Ok(input)
817    }
818}