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