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