jiff/fmt/temporal/
parser.rs

1use crate::{
2    civil::{Date, DateTime, Time},
3    error::{err, Error, ErrorContext},
4    fmt::{
5        offset::{self, ParsedOffset},
6        rfc9557::{self, ParsedAnnotations},
7        temporal::Pieces,
8        util::{
9            fractional_time_to_duration, fractional_time_to_span,
10            parse_temporal_fraction,
11        },
12        Parsed,
13    },
14    span::Span,
15    tz::{
16        AmbiguousZoned, Disambiguation, Offset, OffsetConflict, TimeZone,
17        TimeZoneDatabase,
18    },
19    util::{escape, parse, t},
20    SignedDuration, Timestamp, Unit, Zoned,
21};
22
23/// The datetime components parsed from a string.
24#[derive(Debug)]
25pub(super) struct ParsedDateTime<'i> {
26    /// The original input that the datetime was parsed from.
27    input: escape::Bytes<'i>,
28    /// A required civil date.
29    date: ParsedDate<'i>,
30    /// An optional civil time.
31    time: Option<ParsedTime<'i>>,
32    /// An optional UTC offset.
33    offset: Option<ParsedOffset>,
34    /// An optional RFC 9557 annotations parsed.
35    ///
36    /// An empty `ParsedAnnotations` is valid and possible, so this bakes
37    /// optionality into the type and doesn't need to be an `Option` itself.
38    annotations: ParsedAnnotations<'i>,
39}
40
41impl<'i> ParsedDateTime<'i> {
42    #[inline(always)]
43    pub(super) fn to_pieces(&self) -> Result<Pieces<'i>, Error> {
44        let mut pieces = Pieces::from(self.date.date);
45        if let Some(ref time) = self.time {
46            pieces = pieces.with_time(time.time);
47        }
48        if let Some(ref offset) = self.offset {
49            pieces = pieces.with_offset(offset.to_pieces_offset()?);
50        }
51        if let Some(ann) = self.annotations.to_time_zone_annotation()? {
52            pieces = pieces.with_time_zone_annotation(ann);
53        }
54        Ok(pieces)
55    }
56
57    #[inline(always)]
58    pub(super) fn to_zoned(
59        &self,
60        db: &TimeZoneDatabase,
61        offset_conflict: OffsetConflict,
62        disambiguation: Disambiguation,
63    ) -> Result<Zoned, Error> {
64        self.to_ambiguous_zoned(db, offset_conflict)?
65            .disambiguate(disambiguation)
66    }
67
68    #[inline(always)]
69    pub(super) fn to_ambiguous_zoned(
70        &self,
71        db: &TimeZoneDatabase,
72        offset_conflict: OffsetConflict,
73    ) -> Result<AmbiguousZoned, Error> {
74        let time = self.time.as_ref().map_or(Time::midnight(), |p| p.time);
75        let dt = DateTime::from_parts(self.date.date, time);
76
77        // We always require a time zone when parsing a zoned instant.
78        let tz_annotation =
79            self.annotations.to_time_zone_annotation()?.ok_or_else(|| {
80                err!(
81                    "failed to find time zone in square brackets \
82                     in {:?}, which is required for parsing a zoned instant",
83                    self.input,
84                )
85            })?;
86        let tz = tz_annotation.to_time_zone_with(db)?;
87
88        // If there's no offset, then our only choice, regardless of conflict
89        // resolution preference, is to use the time zone. That is, there is no
90        // possible conflict.
91        let Some(ref parsed_offset) = self.offset else {
92            return Ok(tz.into_ambiguous_zoned(dt));
93        };
94        if parsed_offset.is_zulu() {
95            // When `Z` is used, that means the offset to local time is not
96            // known. In this case, there really can't be a conflict because
97            // there is an explicit acknowledgment that the offset could be
98            // anything. So we just always accept `Z` as if it were `UTC` and
99            // respect that. If we didn't have this special check, we'd fall
100            // below and the `Z` would just be treated as `+00:00`, which would
101            // likely result in `OffsetConflict::Reject` raising an error.
102            // (Unless the actual correct offset at the time is `+00:00` for
103            // the time zone parsed.)
104            return OffsetConflict::AlwaysOffset
105                .resolve(dt, Offset::UTC, tz)
106                .with_context(|| {
107                    err!("parsing {input:?} failed", input = self.input)
108                });
109        }
110        let offset = parsed_offset.to_offset()?;
111        let is_equal = |parsed: Offset, candidate: Offset| {
112            // If they're equal down to the second, then no amount of rounding
113            // or whatever should change that.
114            if parsed == candidate {
115                return true;
116            }
117            // If the candidate offset we're considering is a whole minute,
118            // then we never need rounding.
119            if candidate.part_seconds_ranged() == 0 {
120                return parsed == candidate;
121            }
122            let Ok(candidate) = candidate.round(Unit::Minute) else {
123                // This is a degenerate case and this is the only sensible
124                // thing to do.
125                return parsed == candidate;
126            };
127            parsed == candidate
128        };
129        offset_conflict.resolve_with(dt, offset, tz, is_equal).with_context(
130            || err!("parsing {input:?} failed", input = self.input),
131        )
132    }
133
134    #[inline(always)]
135    pub(super) fn to_timestamp(&self) -> Result<Timestamp, Error> {
136        let time = self.time.as_ref().map(|p| p.time).ok_or_else(|| {
137            err!(
138                "failed to find time component in {:?}, \
139                 which is required for parsing a timestamp",
140                self.input,
141            )
142        })?;
143        let parsed_offset = self.offset.as_ref().ok_or_else(|| {
144            err!(
145                "failed to find offset component in {:?}, \
146                 which is required for parsing a timestamp",
147                self.input,
148            )
149        })?;
150        let offset = parsed_offset.to_offset()?;
151        let dt = DateTime::from_parts(self.date.date, time);
152        let timestamp = offset.to_timestamp(dt).with_context(|| {
153            err!(
154                "failed to convert civil datetime to timestamp \
155                 with offset {offset}",
156            )
157        })?;
158        Ok(timestamp)
159    }
160
161    #[inline(always)]
162    pub(super) fn to_datetime(&self) -> Result<DateTime, Error> {
163        if self.offset.as_ref().map_or(false, |o| o.is_zulu()) {
164            return Err(err!(
165                "cannot parse civil date from string with a Zulu \
166                 offset, parse as a `Timestamp` and convert to a civil \
167                 datetime instead",
168            ));
169        }
170        Ok(DateTime::from_parts(self.date.date, self.time()))
171    }
172
173    #[inline(always)]
174    pub(super) fn to_date(&self) -> Result<Date, Error> {
175        if self.offset.as_ref().map_or(false, |o| o.is_zulu()) {
176            return Err(err!(
177                "cannot parse civil date from string with a Zulu \
178                 offset, parse as a `Timestamp` and convert to a civil \
179                 date instead",
180            ));
181        }
182        Ok(self.date.date)
183    }
184
185    #[inline(always)]
186    fn time(&self) -> Time {
187        self.time.as_ref().map(|p| p.time).unwrap_or(Time::midnight())
188    }
189}
190
191impl<'i> core::fmt::Display for ParsedDateTime<'i> {
192    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
193        core::fmt::Display::fmt(&self.input, f)
194    }
195}
196
197/// The result of parsing a Gregorian calendar civil date.
198#[derive(Debug)]
199pub(super) struct ParsedDate<'i> {
200    /// The original input that the date was parsed from.
201    input: escape::Bytes<'i>,
202    /// The actual parsed date.
203    date: Date,
204}
205
206impl<'i> core::fmt::Display for ParsedDate<'i> {
207    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
208        core::fmt::Display::fmt(&self.input, f)
209    }
210}
211
212/// The result of parsing a 24-hour civil time.
213#[derive(Debug)]
214pub(super) struct ParsedTime<'i> {
215    /// The original input that the time was parsed from.
216    input: escape::Bytes<'i>,
217    /// The actual parsed time.
218    time: Time,
219    /// Whether the time was parsed in extended format or not.
220    extended: bool,
221}
222
223impl<'i> ParsedTime<'i> {
224    pub(super) fn to_time(&self) -> Time {
225        self.time
226    }
227}
228
229impl<'i> core::fmt::Display for ParsedTime<'i> {
230    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
231        core::fmt::Display::fmt(&self.input, f)
232    }
233}
234
235#[derive(Debug)]
236pub(super) struct ParsedTimeZone<'i> {
237    /// The original input that the time zone was parsed from.
238    input: escape::Bytes<'i>,
239    /// The kind of time zone parsed.
240    kind: ParsedTimeZoneKind<'i>,
241}
242
243impl<'i> core::fmt::Display for ParsedTimeZone<'i> {
244    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
245        core::fmt::Display::fmt(&self.input, f)
246    }
247}
248
249#[derive(Debug)]
250pub(super) enum ParsedTimeZoneKind<'i> {
251    Named(&'i str),
252    Offset(ParsedOffset),
253    #[cfg(feature = "alloc")]
254    Posix(crate::tz::posix::ReasonablePosixTimeZone),
255}
256
257impl<'i> ParsedTimeZone<'i> {
258    pub(super) fn into_time_zone(
259        self,
260        db: &TimeZoneDatabase,
261    ) -> Result<TimeZone, Error> {
262        match self.kind {
263            ParsedTimeZoneKind::Named(iana_name) => {
264                let tz = db.get(iana_name).with_context(|| {
265                    err!(
266                        "parsed apparent IANA time zone identifier \
267                         {iana_name} from {input}, but the tzdb lookup \
268                         failed",
269                        input = self.input,
270                    )
271                })?;
272                Ok(tz)
273            }
274            ParsedTimeZoneKind::Offset(poff) => {
275                let offset = poff.to_offset().with_context(|| {
276                    err!(
277                        "offset successfully parsed from {input}, \
278                         but failed to convert to numeric `Offset`",
279                        input = self.input,
280                    )
281                })?;
282                Ok(TimeZone::fixed(offset))
283            }
284            #[cfg(feature = "alloc")]
285            ParsedTimeZoneKind::Posix(posix_tz) => {
286                Ok(TimeZone::from_reasonable_posix_tz(posix_tz))
287            }
288        }
289    }
290}
291
292/// A parser for Temporal datetimes.
293#[derive(Debug)]
294pub(super) struct DateTimeParser {
295    /// There are currently no configuration options for this parser.
296    _priv: (),
297}
298
299impl DateTimeParser {
300    /// Create a new Temporal datetime parser with the default configuration.
301    pub(super) const fn new() -> DateTimeParser {
302        DateTimeParser { _priv: () }
303    }
304
305    // TemporalDateTimeString[Zoned] :::
306    //   AnnotatedDateTime[?Zoned]
307    //
308    // AnnotatedDateTime[Zoned] :::
309    //   [~Zoned] DateTime TimeZoneAnnotation[opt] Annotations[opt]
310    //   [+Zoned] DateTime TimeZoneAnnotation Annotations[opt]
311    //
312    // DateTime :::
313    //   Date
314    //   Date DateTimeSeparator TimeSpec DateTimeUTCOffset[opt]
315    #[inline(always)]
316    pub(super) fn parse_temporal_datetime<'i>(
317        &self,
318        input: &'i [u8],
319    ) -> Result<Parsed<'i, ParsedDateTime<'i>>, Error> {
320        let mkslice = parse::slicer(input);
321        let Parsed { value: date, input } = self.parse_date_spec(input)?;
322        if input.is_empty() {
323            let value = ParsedDateTime {
324                input: escape::Bytes(mkslice(input)),
325                date,
326                time: None,
327                offset: None,
328                annotations: ParsedAnnotations::none(),
329            };
330            return Ok(Parsed { value, input });
331        }
332        let (time, offset, input) = if !matches!(input[0], b' ' | b'T' | b't')
333        {
334            (None, None, input)
335        } else {
336            let input = &input[1..];
337            // If there's a separator, then we must parse a time and we are
338            // *allowed* to parse an offset. But without a separator, we don't
339            // support offsets. Just annotations (which are parsed below).
340            let Parsed { value: time, input } = self.parse_time_spec(input)?;
341            let Parsed { value: offset, input } = self.parse_offset(input)?;
342            (Some(time), offset, input)
343        };
344        let Parsed { value: annotations, input } =
345            self.parse_annotations(input)?;
346        let value = ParsedDateTime {
347            input: escape::Bytes(mkslice(input)),
348            date,
349            time,
350            offset,
351            annotations,
352        };
353        Ok(Parsed { value, input })
354    }
355
356    // TemporalTimeString :::
357    //   AnnotatedTime
358    //   AnnotatedDateTimeTimeRequired
359    //
360    // AnnotatedTime :::
361    //   TimeDesignator TimeSpec
362    //                  DateTimeUTCOffset[opt]
363    //                  TimeZoneAnnotation[opt]
364    //                  Annotations[opt]
365    //   TimeSpecWithOptionalOffsetNotAmbiguous TimeZoneAnnotation[opt]
366    //                                          Annotations[opt]
367    //
368    // TimeSpecWithOptionalOffsetNotAmbiguous :::
369    //   TimeSpec DateTimeUTCOffsetopt (but not one of ValidMonthDay or DateSpecYearMonth)
370    //
371    // TimeDesignator ::: one of
372    //   T t
373    #[inline(always)]
374    pub(super) fn parse_temporal_time<'i>(
375        &self,
376        mut input: &'i [u8],
377    ) -> Result<Parsed<'i, ParsedTime<'i>>, Error> {
378        let mkslice = parse::slicer(input);
379
380        if input.starts_with(b"T") || input.starts_with(b"t") {
381            input = &input[1..];
382            let Parsed { value: time, input } = self.parse_time_spec(input)?;
383            let Parsed { value: offset, input } = self.parse_offset(input)?;
384            if offset.map_or(false, |o| o.is_zulu()) {
385                return Err(err!(
386                    "cannot parse civil time from string with a Zulu \
387                     offset, parse as a `Timestamp` and convert to a civil \
388                     time instead",
389                ));
390            }
391            let Parsed { input, .. } = self.parse_annotations(input)?;
392            return Ok(Parsed { value: time, input });
393        }
394        // We now look for a full datetime and extract the time from that.
395        // We do this before looking for a non-T time-only component because
396        // otherwise things like `2024-06-01T01:02:03` end up having `2024-06`
397        // parsed as a `HHMM-OFFSET` time, and then result in an "ambiguous"
398        // error.
399        //
400        // This is largely a result of us trying to parse a time off of the
401        // beginning of the input without assuming that the time must consume
402        // the entire input.
403        if let Ok(parsed) = self.parse_temporal_datetime(input) {
404            let Parsed { value: dt, input } = parsed;
405            if dt.offset.map_or(false, |o| o.is_zulu()) {
406                return Err(err!(
407                    "cannot parse plain time from full datetime string with a \
408                     Zulu offset, parse as a `Timestamp` and convert to a \
409                     plain time instead",
410                ));
411            }
412            let Some(time) = dt.time else {
413                return Err(err!(
414                    "successfully parsed date from {parsed:?}, but \
415                     no time component was found",
416                    parsed = dt.input,
417                ));
418            };
419            return Ok(Parsed { value: time, input });
420        }
421
422        // At this point, we look for something that is a time that doesn't
423        // start with a `T`. We need to check that it isn't ambiguous with a
424        // possible date.
425        let Parsed { value: time, input } = self.parse_time_spec(input)?;
426        let Parsed { value: offset, input } = self.parse_offset(input)?;
427        if offset.map_or(false, |o| o.is_zulu()) {
428            return Err(err!(
429                "cannot parse plain time from string with a Zulu \
430                 offset, parse as a `Timestamp` and convert to a plain \
431                 time instead",
432            ));
433        }
434        // The possible ambiguities occur with the time AND the
435        // optional offset, so try to parse what we have so far as
436        // either a "month-day" or a "year-month." If either succeeds,
437        // then the time is ambiguous and we can report an error.
438        //
439        // ... but this can only happen when the time was parsed in
440        // "basic" mode. i.e., without the `:` separators.
441        if !time.extended {
442            let possibly_ambiguous = mkslice(input);
443            if self.parse_month_day(possibly_ambiguous).is_ok() {
444                return Err(err!(
445                    "parsed time from {parsed:?} is ambiguous \
446                             with a month-day date",
447                    parsed = escape::Bytes(possibly_ambiguous),
448                ));
449            }
450            if self.parse_year_month(possibly_ambiguous).is_ok() {
451                return Err(err!(
452                    "parsed time from {parsed:?} is ambiguous \
453                             with a year-month date",
454                    parsed = escape::Bytes(possibly_ambiguous),
455                ));
456            }
457        }
458        // OK... carry on.
459        let Parsed { input, .. } = self.parse_annotations(input)?;
460        Ok(Parsed { value: time, input })
461    }
462
463    #[inline(always)]
464    pub(super) fn parse_time_zone<'i>(
465        &self,
466        mut input: &'i [u8],
467    ) -> Result<Parsed<'i, ParsedTimeZone<'i>>, Error> {
468        let Some(first) = input.first().copied() else {
469            return Err(err!("an empty string is not a valid time zone"));
470        };
471        let original = escape::Bytes(input);
472        if matches!(first, b'+' | b'-') {
473            static P: offset::Parser = offset::Parser::new()
474                .zulu(false)
475                .subminute(true)
476                .subsecond(false);
477            let Parsed { value: offset, input } = P.parse(input)?;
478            let kind = ParsedTimeZoneKind::Offset(offset);
479            let value = ParsedTimeZone { input: original, kind };
480            return Ok(Parsed { value, input });
481        }
482
483        // Creates a "named" parsed time zone, generally meant to
484        // be an IANA time zone identifier. We do this in a couple
485        // different cases below, hence the helper function.
486        let mknamed = |consumed, remaining| {
487            let Ok(tzid) = core::str::from_utf8(consumed) else {
488                return Err(err!(
489                    "found plausible IANA time zone identifier \
490                     {input:?}, but it is not valid UTF-8",
491                    input = escape::Bytes(consumed),
492                ));
493            };
494            let kind = ParsedTimeZoneKind::Named(tzid);
495            let value = ParsedTimeZone { input: original, kind };
496            Ok(Parsed { value, input: remaining })
497        };
498        // This part get tricky. The common case is absolutely an IANA time
499        // zone identifer. So we try to parse something that looks like an IANA
500        // tz id.
501        //
502        // In theory, IANA tz ids can never be valid POSIX TZ strings, since
503        // POSIX TZ strings minimally require an offset in them (e.g., `EST5`)
504        // and IANA tz ids aren't supposed to contain numbers. But there are
505        // some legacy IANA tz ids (`EST5EDT`) that do contain numbers.
506        //
507        // However, the legacy IANA tz ids, like `EST5EDT`, are pretty much
508        // nonsense as POSIX TZ strings since there is no DST transition rule.
509        // So in cases of nonsense tz ids, we assume they are IANA tz ids.
510        let mkconsumed = parse::slicer(input);
511        let mut saw_number = false;
512        loop {
513            let Some(byte) = input.first().copied() else { break };
514            if byte.is_ascii_whitespace() {
515                break;
516            }
517            saw_number = saw_number || byte.is_ascii_digit();
518            input = &input[1..];
519        }
520        let consumed = mkconsumed(input);
521        if !saw_number {
522            return mknamed(consumed, input);
523        }
524        #[cfg(not(feature = "alloc"))]
525        {
526            Err(err!(
527                "cannot parsed time zones other than fixed offsets \
528                 without the `alloc` crate feature enabled",
529            ))
530        }
531        #[cfg(feature = "alloc")]
532        {
533            match crate::tz::posix::IanaTz::parse_v3plus_prefix(consumed) {
534                Ok((iana_tz, input)) => {
535                    let kind = ParsedTimeZoneKind::Posix(iana_tz.into_tz());
536                    let value = ParsedTimeZone { input: original, kind };
537                    Ok(Parsed { value, input })
538                }
539                // We get here for invalid POSIX tz strings, or even if they
540                // are valid but not "reasonable", i.e., `EST5EDT`. Which in
541                // that case would end up doing an IANA tz lookup. (And it
542                // might hit because `EST5EDT` is a legacy IANA tz id. Lol.)
543                Err(_) => mknamed(consumed, input),
544            }
545        }
546    }
547
548    // Date :::
549    //   DateYear - DateMonth - DateDay
550    //   DateYear DateMonth DateDay
551    #[inline(always)]
552    fn parse_date_spec<'i>(
553        &self,
554        input: &'i [u8],
555    ) -> Result<Parsed<'i, ParsedDate<'i>>, Error> {
556        let mkslice = parse::slicer(input);
557        let original = escape::Bytes(input);
558
559        // Parse year component.
560        let Parsed { value: year, input } =
561            self.parse_year(input).with_context(|| {
562                err!("failed to parse year in date {original:?}")
563            })?;
564        let extended = input.starts_with(b"-");
565
566        // Parse optional separator.
567        let Parsed { input, .. } = self
568            .parse_date_separator(input, extended)
569            .context("failed to parse separator after year")?;
570
571        // Parse month component.
572        let Parsed { value: month, input } =
573            self.parse_month(input).with_context(|| {
574                err!("failed to parse month in date {original:?}")
575            })?;
576
577        // Parse optional separator.
578        let Parsed { input, .. } = self
579            .parse_date_separator(input, extended)
580            .context("failed to parse separator after month")?;
581
582        // Parse day component.
583        let Parsed { value: day, input } =
584            self.parse_day(input).with_context(|| {
585                err!("failed to parse day in date {original:?}")
586            })?;
587
588        let date = Date::new_ranged(year, month, day).with_context(|| {
589            err!("date parsed from {original:?} is not valid")
590        })?;
591        let value = ParsedDate { input: escape::Bytes(mkslice(input)), date };
592        Ok(Parsed { value, input })
593    }
594
595    // TimeSpec :::
596    //   TimeHour
597    //   TimeHour : TimeMinute
598    //   TimeHour TimeMinute
599    //   TimeHour : TimeMinute : TimeSecond TimeFraction[opt]
600    //   TimeHour TimeMinute TimeSecond TimeFraction[opt]
601    #[inline(always)]
602    fn parse_time_spec<'i>(
603        &self,
604        input: &'i [u8],
605    ) -> Result<Parsed<'i, ParsedTime<'i>>, Error> {
606        let mkslice = parse::slicer(input);
607        let original = escape::Bytes(input);
608
609        // Parse hour component.
610        let Parsed { value: hour, input } =
611            self.parse_hour(input).with_context(|| {
612                err!("failed to parse hour in time {original:?}")
613            })?;
614        let extended = input.starts_with(b":");
615
616        // Parse optional minute component.
617        let Parsed { value: has_minute, input } =
618            self.parse_time_separator(input, extended);
619        if !has_minute {
620            let time = Time::new_ranged(
621                hour,
622                t::Minute::N::<0>(),
623                t::Second::N::<0>(),
624                t::SubsecNanosecond::N::<0>(),
625            );
626            let value = ParsedTime {
627                input: escape::Bytes(mkslice(input)),
628                time,
629                extended,
630            };
631            return Ok(Parsed { value, input });
632        }
633        let Parsed { value: minute, input } =
634            self.parse_minute(input).with_context(|| {
635                err!("failed to parse minute in time {original:?}")
636            })?;
637
638        // Parse optional second component.
639        let Parsed { value: has_second, input } =
640            self.parse_time_separator(input, extended);
641        if !has_second {
642            let time = Time::new_ranged(
643                hour,
644                minute,
645                t::Second::N::<0>(),
646                t::SubsecNanosecond::N::<0>(),
647            );
648            let value = ParsedTime {
649                input: escape::Bytes(mkslice(input)),
650                time,
651                extended,
652            };
653            return Ok(Parsed { value, input });
654        }
655        let Parsed { value: second, input } =
656            self.parse_second(input).with_context(|| {
657                err!("failed to parse second in time {original:?}")
658            })?;
659
660        // Parse an optional fractional component.
661        let Parsed { value: nanosecond, input } =
662            parse_temporal_fraction(input).with_context(|| {
663                err!(
664                    "failed to parse fractional nanoseconds \
665                     in time {original:?}",
666                )
667            })?;
668
669        let time = Time::new_ranged(
670            hour,
671            minute,
672            second,
673            nanosecond.unwrap_or(t::SubsecNanosecond::N::<0>()),
674        );
675        let value = ParsedTime {
676            input: escape::Bytes(mkslice(input)),
677            time,
678            extended,
679        };
680        Ok(Parsed { value, input })
681    }
682
683    // ValidMonthDay :::
684    //   DateMonth -[opt] 0 NonZeroDigit
685    //   DateMonth -[opt] 1 DecimalDigit
686    //   DateMonth -[opt] 2 DecimalDigit
687    //   DateMonth -[opt] 30 but not one of 0230 or 02-30
688    //   DateMonthWithThirtyOneDays -opt 31
689    //
690    // DateMonthWithThirtyOneDays ::: one of
691    //   01 03 05 07 08 10 12
692    //
693    // NOTE: Jiff doesn't have a "month-day" type, but we still have a parsing
694    // function for it so that we can detect ambiguous time strings.
695    #[inline(always)]
696    fn parse_month_day<'i>(
697        &self,
698        input: &'i [u8],
699    ) -> Result<Parsed<'i, ()>, Error> {
700        let original = escape::Bytes(input);
701
702        // Parse month component.
703        let Parsed { value: month, mut input } =
704            self.parse_month(input).with_context(|| {
705                err!("failed to parse month in month-day {original:?}")
706            })?;
707
708        // Skip over optional separator.
709        if input.starts_with(b"-") {
710            input = &input[1..];
711        }
712
713        // Parse day component.
714        let Parsed { value: day, input } =
715            self.parse_day(input).with_context(|| {
716                err!("failed to parse day in month-day {original:?}")
717            })?;
718
719        // Check that the month-day is valid. Since Temporal's month-day
720        // permits 02-29, we use a leap year. The error message here is
721        // probably confusing, but these errors should never be exposed to the
722        // user.
723        let year = t::Year::N::<2024>();
724        let _ = Date::new_ranged(year, month, day).with_context(|| {
725            err!("month-day parsed from {original:?} is not valid")
726        })?;
727
728        // We have a valid year-month. But we don't return it because we just
729        // need to check validity.
730        Ok(Parsed { value: (), input })
731    }
732
733    // DateSpecYearMonth :::
734    //   DateYear -[opt] DateMonth
735    //
736    // NOTE: Jiff doesn't have a "year-month" type, but we still have a parsing
737    // function for it so that we can detect ambiguous time strings.
738    #[inline(always)]
739    fn parse_year_month<'i>(
740        &self,
741        input: &'i [u8],
742    ) -> Result<Parsed<'i, ()>, Error> {
743        let original = escape::Bytes(input);
744
745        // Parse year component.
746        let Parsed { value: year, mut input } =
747            self.parse_year(input).with_context(|| {
748                err!("failed to parse year in date {original:?}")
749            })?;
750
751        // Skip over optional separator.
752        if input.starts_with(b"-") {
753            input = &input[1..];
754        }
755
756        // Parse month component.
757        let Parsed { value: month, input } =
758            self.parse_month(input).with_context(|| {
759                err!("failed to parse month in month-day {original:?}")
760            })?;
761
762        // Check that the year-month is valid. We just use a day of 1, since
763        // every month in every year must have a day 1.
764        let day = t::Day::N::<1>();
765        let _ = Date::new_ranged(year, month, day).with_context(|| {
766            err!("year-month parsed from {original:?} is not valid")
767        })?;
768
769        // We have a valid year-month. But we don't return it because we just
770        // need to check validity.
771        Ok(Parsed { value: (), input })
772    }
773
774    // DateYear :::
775    //   DecimalDigit DecimalDigit DecimalDigit DecimalDigit
776    //   TemporalSign DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit
777    //
778    // NOTE: I don't really like the fact that in order to write a negative
779    // year, you need to use the six digit variant. Like, why not allow
780    // `-0001`? I'm not sure why, so for Chesterton's fence reasons, I'm
781    // sticking with the Temporal spec. But I may loosen this in the future. We
782    // should be careful not to introduce any possible ambiguities, though, I
783    // don't think there are any?
784    #[inline(always)]
785    fn parse_year<'i>(
786        &self,
787        input: &'i [u8],
788    ) -> Result<Parsed<'i, t::Year>, Error> {
789        let Parsed { value: sign, input } = self.parse_year_sign(input);
790        if let Some(sign) = sign {
791            let (year, input) = parse::split(input, 6).ok_or_else(|| {
792                err!(
793                    "expected six digit year (because of a leading sign), \
794                     but found end of input",
795                )
796            })?;
797            let year = parse::i64(year).with_context(|| {
798                err!(
799                    "failed to parse {year:?} as year (a six digit integer)",
800                    year = escape::Bytes(year),
801                )
802            })?;
803            let year =
804                t::Year::try_new("year", year).context("year is not valid")?;
805            if year == 0 && sign < 0 {
806                return Err(err!(
807                    "year zero must be written without a sign or a \
808                     positive sign, but not a negative sign",
809                ));
810            }
811            Ok(Parsed { value: year * sign, input })
812        } else {
813            let (year, input) = parse::split(input, 4).ok_or_else(|| {
814                err!(
815                    "expected four digit year (or leading sign for \
816                     six digit year), but found end of input",
817                )
818            })?;
819            let year = parse::i64(year).with_context(|| {
820                err!(
821                    "failed to parse {year:?} as year (a four digit integer)",
822                    year = escape::Bytes(year),
823                )
824            })?;
825            let year =
826                t::Year::try_new("year", year).context("year is not valid")?;
827            Ok(Parsed { value: year, input })
828        }
829    }
830
831    // DateMonth :::
832    //   0 NonZeroDigit
833    //   10
834    //   11
835    //   12
836    #[inline(always)]
837    fn parse_month<'i>(
838        &self,
839        input: &'i [u8],
840    ) -> Result<Parsed<'i, t::Month>, Error> {
841        let (month, input) = parse::split(input, 2).ok_or_else(|| {
842            err!("expected two digit month, but found end of input")
843        })?;
844        let month = parse::i64(month).with_context(|| {
845            err!(
846                "failed to parse {month:?} as month (a two digit integer)",
847                month = escape::Bytes(month),
848            )
849        })?;
850        let month =
851            t::Month::try_new("month", month).context("month is not valid")?;
852        Ok(Parsed { value: month, input })
853    }
854
855    // DateDay :::
856    //   0 NonZeroDigit
857    //   1 DecimalDigit
858    //   2 DecimalDigit
859    //   30
860    //   31
861    #[inline(always)]
862    fn parse_day<'i>(
863        &self,
864        input: &'i [u8],
865    ) -> Result<Parsed<'i, t::Day>, Error> {
866        let (day, input) = parse::split(input, 2).ok_or_else(|| {
867            err!("expected two digit day, but found end of input")
868        })?;
869        let day = parse::i64(day).with_context(|| {
870            err!(
871                "failed to parse {day:?} as day (a two digit integer)",
872                day = escape::Bytes(day),
873            )
874        })?;
875        let day = t::Day::try_new("day", day).context("day is not valid")?;
876        Ok(Parsed { value: day, input })
877    }
878
879    // TimeHour :::
880    //   Hour
881    //
882    // Hour :::
883    //   0 DecimalDigit
884    //   1 DecimalDigit
885    //   20
886    //   21
887    //   22
888    //   23
889    #[inline(always)]
890    fn parse_hour<'i>(
891        &self,
892        input: &'i [u8],
893    ) -> Result<Parsed<'i, t::Hour>, Error> {
894        let (hour, input) = parse::split(input, 2).ok_or_else(|| {
895            err!("expected two digit hour, but found end of input")
896        })?;
897        let hour = parse::i64(hour).with_context(|| {
898            err!(
899                "failed to parse {hour:?} as hour (a two digit integer)",
900                hour = escape::Bytes(hour),
901            )
902        })?;
903        let hour =
904            t::Hour::try_new("hour", hour).context("hour is not valid")?;
905        Ok(Parsed { value: hour, input })
906    }
907
908    // TimeMinute :::
909    //   MinuteSecond
910    //
911    // MinuteSecond :::
912    //   0 DecimalDigit
913    //   1 DecimalDigit
914    //   2 DecimalDigit
915    //   3 DecimalDigit
916    //   4 DecimalDigit
917    //   5 DecimalDigit
918    #[inline(always)]
919    fn parse_minute<'i>(
920        &self,
921        input: &'i [u8],
922    ) -> Result<Parsed<'i, t::Minute>, Error> {
923        let (minute, input) = parse::split(input, 2).ok_or_else(|| {
924            err!("expected two digit minute, but found end of input")
925        })?;
926        let minute = parse::i64(minute).with_context(|| {
927            err!(
928                "failed to parse {minute:?} as minute (a two digit integer)",
929                minute = escape::Bytes(minute),
930            )
931        })?;
932        let minute = t::Minute::try_new("minute", minute)
933            .context("minute is not valid")?;
934        Ok(Parsed { value: minute, input })
935    }
936
937    // TimeSecond :::
938    //   MinuteSecond
939    //   60
940    //
941    // MinuteSecond :::
942    //   0 DecimalDigit
943    //   1 DecimalDigit
944    //   2 DecimalDigit
945    //   3 DecimalDigit
946    //   4 DecimalDigit
947    //   5 DecimalDigit
948    #[inline(always)]
949    fn parse_second<'i>(
950        &self,
951        input: &'i [u8],
952    ) -> Result<Parsed<'i, t::Second>, Error> {
953        let (second, input) = parse::split(input, 2).ok_or_else(|| {
954            err!("expected two digit second, but found end of input",)
955        })?;
956        let mut second = parse::i64(second).with_context(|| {
957            err!(
958                "failed to parse {second:?} as second (a two digit integer)",
959                second = escape::Bytes(second),
960            )
961        })?;
962        // NOTE: I believe Temporal allows one to make this configurable. That
963        // is, to reject it. But for now, we just always clamp a leap second.
964        if second == 60 {
965            second = 59;
966        }
967        let second = t::Second::try_new("second", second)
968            .context("second is not valid")?;
969        Ok(Parsed { value: second, input })
970    }
971
972    #[inline(always)]
973    fn parse_offset<'i>(
974        &self,
975        input: &'i [u8],
976    ) -> Result<Parsed<'i, Option<ParsedOffset>>, Error> {
977        const P: offset::Parser =
978            offset::Parser::new().zulu(true).subminute(true);
979        P.parse_optional(input)
980    }
981
982    #[inline(always)]
983    fn parse_annotations<'i>(
984        &self,
985        input: &'i [u8],
986    ) -> Result<Parsed<'i, ParsedAnnotations<'i>>, Error> {
987        const P: rfc9557::Parser = rfc9557::Parser::new();
988        if input.is_empty() || input[0] != b'[' {
989            let value = ParsedAnnotations::none();
990            return Ok(Parsed { input, value });
991        }
992        P.parse(input)
993    }
994
995    /// Parses the separator that is expected to appear between
996    /// date components.
997    ///
998    /// When in extended mode, a `-` is expected. When not in extended mode,
999    /// no input is consumed and this routine never fails.
1000    #[inline(always)]
1001    fn parse_date_separator<'i>(
1002        &self,
1003        mut input: &'i [u8],
1004        extended: bool,
1005    ) -> Result<Parsed<'i, ()>, Error> {
1006        if !extended {
1007            // If we see a '-' when not in extended mode, then we can report
1008            // a better error message than, e.g., "-3 isn't a valid day."
1009            if input.starts_with(b"-") {
1010                return Err(err!(
1011                    "expected no separator after month since none was \
1012                     found after the year, but found a '-' separator",
1013                ));
1014            }
1015            return Ok(Parsed { value: (), input });
1016        }
1017        if input.is_empty() {
1018            return Err(err!(
1019                "expected '-' separator, but found end of input"
1020            ));
1021        }
1022        if input[0] != b'-' {
1023            return Err(err!(
1024                "expected '-' separator, but found {found:?} instead",
1025                found = escape::Byte(input[0]),
1026            ));
1027        }
1028        input = &input[1..];
1029        Ok(Parsed { value: (), input })
1030    }
1031
1032    /// Parses the separator that is expected to appear between time
1033    /// components. When `true` is returned, we expect to parse the next
1034    /// component. When `false` is returned, then no separator was found and
1035    /// there is no expectation of finding another component.
1036    ///
1037    /// When in extended mode, true is returned if and only if a separator is
1038    /// found.
1039    ///
1040    /// When in basic mode (not extended), then a subsequent component is only
1041    /// expected when `input` begins with two ASCII digits.
1042    #[inline(always)]
1043    fn parse_time_separator<'i>(
1044        &self,
1045        mut input: &'i [u8],
1046        extended: bool,
1047    ) -> Parsed<'i, bool> {
1048        if !extended {
1049            let expected =
1050                input.len() >= 2 && input[..2].iter().all(u8::is_ascii_digit);
1051            return Parsed { value: expected, input };
1052        }
1053        let is_separator = input.get(0).map_or(false, |&b| b == b':');
1054        if is_separator {
1055            input = &input[1..];
1056        }
1057        Parsed { value: is_separator, input }
1058    }
1059
1060    // TemporalSign :::
1061    //   ASCIISign
1062    //   <MINUS>
1063    //
1064    // ASCIISign ::: one of
1065    //   + -
1066    //
1067    // NOTE: We specifically only support ASCII signs. I think Temporal needs
1068    // to support `<MINUS>` because of other things in ECMA script that
1069    // require it?[1]
1070    //
1071    // [1]: https://github.com/tc39/proposal-temporal/issues/2843
1072    #[inline(always)]
1073    fn parse_year_sign<'i>(
1074        &self,
1075        mut input: &'i [u8],
1076    ) -> Parsed<'i, Option<t::Sign>> {
1077        let Some(sign) = input.get(0).copied() else {
1078            return Parsed { value: None, input };
1079        };
1080        let sign = if sign == b'+' {
1081            t::Sign::N::<1>()
1082        } else if sign == b'-' {
1083            t::Sign::N::<-1>()
1084        } else {
1085            return Parsed { value: None, input };
1086        };
1087        input = &input[1..];
1088        Parsed { value: Some(sign), input }
1089    }
1090}
1091
1092/// A parser for Temporal spans.
1093///
1094/// Note that in Temporal, a "span" is called a "duration."
1095#[derive(Debug)]
1096pub(super) struct SpanParser {
1097    /// There are currently no configuration options for this parser.
1098    _priv: (),
1099}
1100
1101impl SpanParser {
1102    /// Create a new Temporal span parser with the default configuration.
1103    pub(super) const fn new() -> SpanParser {
1104        SpanParser { _priv: () }
1105    }
1106
1107    #[inline(always)]
1108    pub(super) fn parse_temporal_duration<'i>(
1109        &self,
1110        input: &'i [u8],
1111    ) -> Result<Parsed<'i, Span>, Error> {
1112        self.parse_span(input).context(
1113            "failed to parse ISO 8601 \
1114             duration string into `Span`",
1115        )
1116    }
1117
1118    #[inline(always)]
1119    pub(super) fn parse_signed_duration<'i>(
1120        &self,
1121        input: &'i [u8],
1122    ) -> Result<Parsed<'i, SignedDuration>, Error> {
1123        self.parse_duration(input).context(
1124            "failed to parse ISO 8601 \
1125             duration string into `SignedDuration`",
1126        )
1127    }
1128
1129    #[inline(always)]
1130    fn parse_span<'i>(
1131        &self,
1132        input: &'i [u8],
1133    ) -> Result<Parsed<'i, Span>, Error> {
1134        let original = escape::Bytes(input);
1135        let Parsed { value: sign, input } = self.parse_sign(input);
1136        let Parsed { input, .. } = self.parse_duration_designator(input)?;
1137        let Parsed { value: (mut span, parsed_any_date), input } =
1138            self.parse_date_units(input, Span::new())?;
1139        let Parsed { value: has_time, mut input } =
1140            self.parse_time_designator(input);
1141        if has_time {
1142            let parsed = self.parse_time_units(input, span)?;
1143            input = parsed.input;
1144
1145            let (time_span, parsed_any_time) = parsed.value;
1146            if !parsed_any_time {
1147                return Err(err!(
1148                    "found a time designator (T or t) in an ISO 8601 \
1149                     duration string in {original:?}, but did not find \
1150                     any time units",
1151                ));
1152            }
1153            span = time_span;
1154        } else if !parsed_any_date {
1155            return Err(err!(
1156                "found the start of a ISO 8601 duration string \
1157                 in {original:?}, but did not find any units",
1158            ));
1159        }
1160        if sign < 0 {
1161            span = span.negate();
1162        }
1163        Ok(Parsed { value: span, input })
1164    }
1165
1166    #[inline(always)]
1167    fn parse_duration<'i>(
1168        &self,
1169        input: &'i [u8],
1170    ) -> Result<Parsed<'i, SignedDuration>, Error> {
1171        let Parsed { value: sign, input } = self.parse_sign(input);
1172        let Parsed { input, .. } = self.parse_duration_designator(input)?;
1173        let Parsed { value: has_time, input } =
1174            self.parse_time_designator(input);
1175        if !has_time {
1176            return Err(err!(
1177                "parsing ISO 8601 duration into SignedDuration requires \
1178                 that the duration contain a time component and no \
1179                 components of days or greater",
1180            ));
1181        }
1182        let Parsed { value: dur, input } =
1183            self.parse_time_units_duration(input, sign == -1)?;
1184        Ok(Parsed { value: dur, input })
1185    }
1186
1187    /// Parses consecutive date units from an ISO 8601 duration string into the
1188    /// span given.
1189    ///
1190    /// If 1 or more units were found, then `true` is also returned. Otherwise,
1191    /// `false` indicates that no units were parsed. (Which the caller may want
1192    /// to treat as an error.)
1193    #[inline(always)]
1194    fn parse_date_units<'i>(
1195        &self,
1196        mut input: &'i [u8],
1197        mut span: Span,
1198    ) -> Result<Parsed<'i, (Span, bool)>, Error> {
1199        let mut parsed_any = false;
1200        let mut prev_unit: Option<Unit> = None;
1201        loop {
1202            let parsed = self.parse_unit_value(input)?;
1203            input = parsed.input;
1204            let Some(value) = parsed.value else { break };
1205
1206            let parsed = self.parse_unit_date_designator(input)?;
1207            input = parsed.input;
1208            let unit = parsed.value;
1209
1210            if let Some(prev_unit) = prev_unit {
1211                if prev_unit <= unit {
1212                    return Err(err!(
1213                        "found value {value:?} with unit {unit} \
1214                         after unit {prev_unit}, but units must be \
1215                         written from largest to smallest \
1216                         (and they can't be repeated)",
1217                        unit = unit.singular(),
1218                        prev_unit = prev_unit.singular(),
1219                    ));
1220                }
1221            }
1222            prev_unit = Some(unit);
1223            span = span.try_units_ranged(unit, value).with_context(|| {
1224                err!(
1225                    "failed to set value {value:?} as {unit} unit on span",
1226                    unit = Unit::from(unit).singular(),
1227                )
1228            })?;
1229            parsed_any = true;
1230        }
1231        Ok(Parsed { value: (span, parsed_any), input })
1232    }
1233
1234    /// Parses consecutive time units from an ISO 8601 duration string into the
1235    /// span given.
1236    ///
1237    /// If 1 or more units were found, then `true` is also returned. Otherwise,
1238    /// `false` indicates that no units were parsed. (Which the caller may want
1239    /// to treat as an error.)
1240    #[inline(always)]
1241    fn parse_time_units<'i>(
1242        &self,
1243        mut input: &'i [u8],
1244        mut span: Span,
1245    ) -> Result<Parsed<'i, (Span, bool)>, Error> {
1246        let mut parsed_any = false;
1247        let mut prev_unit: Option<Unit> = None;
1248        loop {
1249            let parsed = self.parse_unit_value(input)?;
1250            input = parsed.input;
1251            let Some(value) = parsed.value else { break };
1252
1253            let parsed = parse_temporal_fraction(input)?;
1254            input = parsed.input;
1255            let fraction = parsed.value;
1256
1257            let parsed = self.parse_unit_time_designator(input)?;
1258            input = parsed.input;
1259            let unit = parsed.value;
1260
1261            if let Some(prev_unit) = prev_unit {
1262                if prev_unit <= unit {
1263                    return Err(err!(
1264                        "found value {value:?} with unit {unit} \
1265                         after unit {prev_unit}, but units must be \
1266                         written from largest to smallest \
1267                         (and they can't be repeated)",
1268                        unit = unit.singular(),
1269                        prev_unit = prev_unit.singular(),
1270                    ));
1271                }
1272            }
1273            prev_unit = Some(unit);
1274            parsed_any = true;
1275
1276            if let Some(fraction) = fraction {
1277                span = fractional_time_to_span(unit, value, fraction, span)?;
1278                // Once we see a fraction, we are done. We don't permit parsing
1279                // any more units. That is, a fraction can only occur on the
1280                // lowest unit of time.
1281                break;
1282            } else {
1283                let result =
1284                    span.try_units_ranged(unit, value).with_context(|| {
1285                        err!(
1286                            "failed to set value {value:?} \
1287                             as {unit} unit on span",
1288                            unit = Unit::from(unit).singular(),
1289                        )
1290                    });
1291                // This is annoying, but because we can write out a larger
1292                // number of hours/minutes/seconds than what we actually
1293                // support, we need to be prepared to parse an unbalanced span
1294                // if our time units are too big here. This entire dance is
1295                // because ISO 8601 requires fractional seconds to represent
1296                // milli-, micro- and nano-seconds. This means that spans
1297                // cannot retain their full fidelity when roundtripping through
1298                // ISO 8601. However, it is guaranteed that their total elapsed
1299                // time represented will never change.
1300                span = match result {
1301                    Ok(span) => span,
1302                    Err(_) => fractional_time_to_span(
1303                        unit,
1304                        value,
1305                        t::SubsecNanosecond::N::<0>(),
1306                        span,
1307                    )?,
1308                };
1309            }
1310        }
1311        Ok(Parsed { value: (span, parsed_any), input })
1312    }
1313
1314    /// Parses consecutive time units from an ISO 8601 duration string into
1315    /// a Jiff signed duration.
1316    ///
1317    /// If no time units are found, then this returns an error.
1318    #[inline(always)]
1319    fn parse_time_units_duration<'i>(
1320        &self,
1321        mut input: &'i [u8],
1322        negative: bool,
1323    ) -> Result<Parsed<'i, SignedDuration>, Error> {
1324        let mut parsed_any = false;
1325        let mut prev_unit: Option<Unit> = None;
1326        let mut dur = SignedDuration::ZERO;
1327
1328        loop {
1329            let parsed = self.parse_unit_value(input)?;
1330            input = parsed.input;
1331            let Some(value) = parsed.value else { break };
1332
1333            let parsed = parse_temporal_fraction(input)?;
1334            input = parsed.input;
1335            let fraction = parsed.value;
1336
1337            let parsed = self.parse_unit_time_designator(input)?;
1338            input = parsed.input;
1339            let unit = parsed.value;
1340
1341            if let Some(prev_unit) = prev_unit {
1342                if prev_unit <= unit {
1343                    return Err(err!(
1344                        "found value {value:?} with unit {unit} \
1345                         after unit {prev_unit}, but units must be \
1346                         written from largest to smallest \
1347                         (and they can't be repeated)",
1348                        unit = unit.singular(),
1349                        prev_unit = prev_unit.singular(),
1350                    ));
1351                }
1352            }
1353            prev_unit = Some(unit);
1354            parsed_any = true;
1355
1356            // Convert our parsed unit into a number of seconds.
1357            let unit_secs = match unit {
1358                Unit::Second => value.get(),
1359                Unit::Minute => {
1360                    let mins = value.get();
1361                    mins.checked_mul(60).ok_or_else(|| {
1362                        err!(
1363                            "minute units {mins} overflowed i64 when \
1364                             converted to seconds"
1365                        )
1366                    })?
1367                }
1368                Unit::Hour => {
1369                    let hours = value.get();
1370                    hours.checked_mul(3_600).ok_or_else(|| {
1371                        err!(
1372                            "hour units {hours} overflowed i64 when \
1373                             converted to seconds"
1374                        )
1375                    })?
1376                }
1377                // Guaranteed not to be here since `parse_unit_time_designator`
1378                // always returns hours, minutes or seconds.
1379                _ => unreachable!(),
1380            };
1381            // Never panics since nanos==0.
1382            let unit_dur = SignedDuration::new(unit_secs, 0);
1383            // And now try to add it to our existing duration.
1384            let result = if negative {
1385                dur.checked_sub(unit_dur)
1386            } else {
1387                dur.checked_add(unit_dur)
1388            };
1389            dur = result.ok_or_else(|| {
1390                err!(
1391                    "adding value {value} from unit {unit} overflowed \
1392                     signed duration {dur:?}",
1393                    unit = unit.singular(),
1394                )
1395            })?;
1396
1397            if let Some(fraction) = fraction {
1398                let fraction_dur =
1399                    fractional_time_to_duration(unit, fraction)?;
1400                let result = if negative {
1401                    dur.checked_sub(fraction_dur)
1402                } else {
1403                    dur.checked_add(fraction_dur)
1404                };
1405                dur = result.ok_or_else(|| {
1406                    err!(
1407                        "adding fractional duration {fraction_dur:?} \
1408                         from unit {unit} to {dur:?} overflowed \
1409                         signed duration limits",
1410                        unit = unit.singular(),
1411                    )
1412                })?;
1413                // Once we see a fraction, we are done. We don't permit parsing
1414                // any more units. That is, a fraction can only occur on the
1415                // lowest unit of time.
1416                break;
1417            }
1418        }
1419        if !parsed_any {
1420            return Err(err!(
1421                "expected at least one unit of time (hours, minutes or \
1422                 seconds) in ISO 8601 duration when parsing into a \
1423                 `SignedDuration`",
1424            ));
1425        }
1426        Ok(Parsed { value: dur, input })
1427    }
1428
1429    #[inline(always)]
1430    fn parse_unit_value<'i>(
1431        &self,
1432        mut input: &'i [u8],
1433    ) -> Result<Parsed<'i, Option<t::NoUnits>>, Error> {
1434        // Discovered via `i64::MAX.to_string().len()`.
1435        const MAX_I64_DIGITS: usize = 19;
1436
1437        let mkdigits = parse::slicer(input);
1438        while mkdigits(input).len() <= MAX_I64_DIGITS
1439            && input.first().map_or(false, u8::is_ascii_digit)
1440        {
1441            input = &input[1..];
1442        }
1443        let digits = mkdigits(input);
1444        if digits.is_empty() {
1445            return Ok(Parsed { value: None, input });
1446        }
1447        let value = parse::i64(digits).with_context(|| {
1448            err!(
1449                "failed to parse {digits:?} as 64-bit signed integer",
1450                digits = escape::Bytes(digits),
1451            )
1452        })?;
1453        // OK because t::NoUnits permits all possible i64 values.
1454        let value = t::NoUnits::new(value).unwrap();
1455        Ok(Parsed { value: Some(value), input })
1456    }
1457
1458    #[inline(always)]
1459    fn parse_unit_date_designator<'i>(
1460        &self,
1461        input: &'i [u8],
1462    ) -> Result<Parsed<'i, Unit>, Error> {
1463        if input.is_empty() {
1464            return Err(err!(
1465                "expected to find date unit designator suffix \
1466                 (Y, M, W or D), but found end of input",
1467            ));
1468        }
1469        let unit = match input[0] {
1470            b'Y' | b'y' => Unit::Year,
1471            b'M' | b'm' => Unit::Month,
1472            b'W' | b'w' => Unit::Week,
1473            b'D' | b'd' => Unit::Day,
1474            unknown => {
1475                return Err(err!(
1476                    "expected to find date unit designator suffix \
1477                     (Y, M, W or D), but found {found:?} instead",
1478                    found = escape::Byte(unknown),
1479                ));
1480            }
1481        };
1482        Ok(Parsed { value: unit, input: &input[1..] })
1483    }
1484
1485    #[inline(always)]
1486    fn parse_unit_time_designator<'i>(
1487        &self,
1488        input: &'i [u8],
1489    ) -> Result<Parsed<'i, Unit>, Error> {
1490        if input.is_empty() {
1491            return Err(err!(
1492                "expected to find time unit designator suffix \
1493                 (H, M or S), but found end of input",
1494            ));
1495        }
1496        let unit = match input[0] {
1497            b'H' | b'h' => Unit::Hour,
1498            b'M' | b'm' => Unit::Minute,
1499            b'S' | b's' => Unit::Second,
1500            unknown => {
1501                return Err(err!(
1502                    "expected to find time unit designator suffix \
1503                     (H, M or S), but found {found:?} instead",
1504                    found = escape::Byte(unknown),
1505                ));
1506            }
1507        };
1508        Ok(Parsed { value: unit, input: &input[1..] })
1509    }
1510
1511    // DurationDesignator ::: one of
1512    //   P p
1513    #[inline(always)]
1514    fn parse_duration_designator<'i>(
1515        &self,
1516        input: &'i [u8],
1517    ) -> Result<Parsed<'i, ()>, Error> {
1518        if input.is_empty() {
1519            return Err(err!(
1520                "expected to find duration beginning with 'P' or 'p', \
1521                 but found end of input",
1522            ));
1523        }
1524        if !matches!(input[0], b'P' | b'p') {
1525            return Err(err!(
1526                "expected 'P' or 'p' prefix to begin duration, \
1527                 but found {found:?} instead",
1528                found = escape::Byte(input[0]),
1529            ));
1530        }
1531        Ok(Parsed { value: (), input: &input[1..] })
1532    }
1533
1534    // TimeDesignator ::: one of
1535    //   T t
1536    #[inline(always)]
1537    fn parse_time_designator<'i>(&self, input: &'i [u8]) -> Parsed<'i, bool> {
1538        if input.is_empty() || !matches!(input[0], b'T' | b't') {
1539            return Parsed { value: false, input };
1540        }
1541        Parsed { value: true, input: &input[1..] }
1542    }
1543
1544    // TemporalSign :::
1545    //   ASCIISign
1546    //   <MINUS>
1547    //
1548    // NOTE: Like with other things with signs, we don't support the Unicode
1549    // <MINUS> sign. Just ASCII.
1550    #[inline(always)]
1551    fn parse_sign<'i>(&self, input: &'i [u8]) -> Parsed<'i, t::Sign> {
1552        let Some(sign) = input.get(0).copied() else {
1553            return Parsed { value: t::Sign::N::<1>(), input };
1554        };
1555        let sign = if sign == b'+' {
1556            t::Sign::N::<1>()
1557        } else if sign == b'-' {
1558            t::Sign::N::<-1>()
1559        } else {
1560            return Parsed { value: t::Sign::N::<1>(), input };
1561        };
1562        Parsed { value: sign, input: &input[1..] }
1563    }
1564}
1565
1566#[cfg(test)]
1567mod tests {
1568    use super::*;
1569
1570    #[test]
1571    fn ok_signed_duration() {
1572        let p =
1573            |input| SpanParser::new().parse_signed_duration(input).unwrap();
1574
1575        insta::assert_debug_snapshot!(p(b"PT0s"), @r###"
1576        Parsed {
1577            value: 0s,
1578            input: "",
1579        }
1580        "###);
1581        insta::assert_debug_snapshot!(p(b"PT0.000000001s"), @r###"
1582        Parsed {
1583            value: 1ns,
1584            input: "",
1585        }
1586        "###);
1587        insta::assert_debug_snapshot!(p(b"PT1s"), @r###"
1588        Parsed {
1589            value: 1s,
1590            input: "",
1591        }
1592        "###);
1593        insta::assert_debug_snapshot!(p(b"PT59s"), @r###"
1594        Parsed {
1595            value: 59s,
1596            input: "",
1597        }
1598        "###);
1599        insta::assert_debug_snapshot!(p(b"PT60s"), @r###"
1600        Parsed {
1601            value: 1m,
1602            input: "",
1603        }
1604        "###);
1605        insta::assert_debug_snapshot!(p(b"PT1m"), @r###"
1606        Parsed {
1607            value: 1m,
1608            input: "",
1609        }
1610        "###);
1611        insta::assert_debug_snapshot!(p(b"PT1m0.000000001s"), @r###"
1612        Parsed {
1613            value: 1m 1ns,
1614            input: "",
1615        }
1616        "###);
1617        insta::assert_debug_snapshot!(p(b"PT1.25m"), @r###"
1618        Parsed {
1619            value: 1m 15s,
1620            input: "",
1621        }
1622        "###);
1623        insta::assert_debug_snapshot!(p(b"PT1h"), @r###"
1624        Parsed {
1625            value: 1h,
1626            input: "",
1627        }
1628        "###);
1629        insta::assert_debug_snapshot!(p(b"PT1h0.000000001s"), @r###"
1630        Parsed {
1631            value: 1h 1ns,
1632            input: "",
1633        }
1634        "###);
1635        insta::assert_debug_snapshot!(p(b"PT1.25h"), @r###"
1636        Parsed {
1637            value: 1h 15m,
1638            input: "",
1639        }
1640        "###);
1641
1642        insta::assert_debug_snapshot!(p(b"-PT2562047788015215h30m8.999999999s"), @r###"
1643        Parsed {
1644            value: 2562047788015215h 30m 8s 999ms 999µs 999ns ago,
1645            input: "",
1646        }
1647        "###);
1648        insta::assert_debug_snapshot!(p(b"PT2562047788015215h30m7.999999999s"), @r###"
1649        Parsed {
1650            value: 2562047788015215h 30m 7s 999ms 999µs 999ns,
1651            input: "",
1652        }
1653        "###);
1654    }
1655
1656    #[test]
1657    fn err_signed_duration() {
1658        let p = |input| {
1659            SpanParser::new().parse_signed_duration(input).unwrap_err()
1660        };
1661
1662        insta::assert_snapshot!(
1663            p(b"P0d"),
1664            @"failed to parse ISO 8601 duration string into `SignedDuration`: parsing ISO 8601 duration into SignedDuration requires that the duration contain a time component and no components of days or greater",
1665        );
1666        insta::assert_snapshot!(
1667            p(b"PT0d"),
1668            @r###"failed to parse ISO 8601 duration string into `SignedDuration`: expected to find time unit designator suffix (H, M or S), but found "d" instead"###,
1669        );
1670        insta::assert_snapshot!(
1671            p(b"P0dT1s"),
1672            @"failed to parse ISO 8601 duration string into `SignedDuration`: parsing ISO 8601 duration into SignedDuration requires that the duration contain a time component and no components of days or greater",
1673        );
1674
1675        insta::assert_snapshot!(
1676            p(b""),
1677            @"failed to parse ISO 8601 duration string into `SignedDuration`: expected to find duration beginning with 'P' or 'p', but found end of input",
1678        );
1679        insta::assert_snapshot!(
1680            p(b"P"),
1681            @"failed to parse ISO 8601 duration string into `SignedDuration`: parsing ISO 8601 duration into SignedDuration requires that the duration contain a time component and no components of days or greater",
1682        );
1683        insta::assert_snapshot!(
1684            p(b"PT"),
1685            @"failed to parse ISO 8601 duration string into `SignedDuration`: expected at least one unit of time (hours, minutes or seconds) in ISO 8601 duration when parsing into a `SignedDuration`",
1686        );
1687        insta::assert_snapshot!(
1688            p(b"PTs"),
1689            @"failed to parse ISO 8601 duration string into `SignedDuration`: expected at least one unit of time (hours, minutes or seconds) in ISO 8601 duration when parsing into a `SignedDuration`",
1690        );
1691
1692        insta::assert_snapshot!(
1693            p(b"PT1s1m"),
1694            @"failed to parse ISO 8601 duration string into `SignedDuration`: found value 1 with unit minute after unit second, but units must be written from largest to smallest (and they can't be repeated)",
1695        );
1696        insta::assert_snapshot!(
1697            p(b"PT1s1h"),
1698            @"failed to parse ISO 8601 duration string into `SignedDuration`: found value 1 with unit hour after unit second, but units must be written from largest to smallest (and they can't be repeated)",
1699        );
1700        insta::assert_snapshot!(
1701            p(b"PT1m1h"),
1702            @"failed to parse ISO 8601 duration string into `SignedDuration`: found value 1 with unit hour after unit minute, but units must be written from largest to smallest (and they can't be repeated)",
1703        );
1704
1705        insta::assert_snapshot!(
1706            p(b"-PT9223372036854775809s"),
1707            @r###"failed to parse ISO 8601 duration string into `SignedDuration`: failed to parse "9223372036854775809" as 64-bit signed integer: number '9223372036854775809' too big to parse into 64-bit integer"###,
1708        );
1709        insta::assert_snapshot!(
1710            p(b"PT9223372036854775808s"),
1711            @r###"failed to parse ISO 8601 duration string into `SignedDuration`: failed to parse "9223372036854775808" as 64-bit signed integer: number '9223372036854775808' too big to parse into 64-bit integer"###,
1712        );
1713
1714        insta::assert_snapshot!(
1715            p(b"PT1m9223372036854775807s"),
1716            @"failed to parse ISO 8601 duration string into `SignedDuration`: adding value 9223372036854775807 from unit second overflowed signed duration 1m",
1717        );
1718        insta::assert_snapshot!(
1719            p(b"PT2562047788015215.6h"),
1720            @"failed to parse ISO 8601 duration string into `SignedDuration`: adding fractional duration 36m from unit hour to 2562047788015215h overflowed signed duration limits",
1721        );
1722    }
1723
1724    #[test]
1725    fn ok_temporal_duration_basic() {
1726        let p =
1727            |input| SpanParser::new().parse_temporal_duration(input).unwrap();
1728
1729        insta::assert_debug_snapshot!(p(b"P5d"), @r###"
1730        Parsed {
1731            value: 5d,
1732            input: "",
1733        }
1734        "###);
1735        insta::assert_debug_snapshot!(p(b"-P5d"), @r###"
1736        Parsed {
1737            value: 5d ago,
1738            input: "",
1739        }
1740        "###);
1741        insta::assert_debug_snapshot!(p(b"+P5d"), @r###"
1742        Parsed {
1743            value: 5d,
1744            input: "",
1745        }
1746        "###);
1747        insta::assert_debug_snapshot!(p(b"P5DT1s"), @r###"
1748        Parsed {
1749            value: 5d 1s,
1750            input: "",
1751        }
1752        "###);
1753        insta::assert_debug_snapshot!(p(b"PT1S"), @r###"
1754        Parsed {
1755            value: 1s,
1756            input: "",
1757        }
1758        "###);
1759        insta::assert_debug_snapshot!(p(b"PT0S"), @r###"
1760        Parsed {
1761            value: 0s,
1762            input: "",
1763        }
1764        "###);
1765        insta::assert_debug_snapshot!(p(b"P0Y"), @r###"
1766        Parsed {
1767            value: 0s,
1768            input: "",
1769        }
1770        "###);
1771        insta::assert_debug_snapshot!(p(b"P1Y1M1W1DT1H1M1S"), @r###"
1772        Parsed {
1773            value: 1y 1mo 1w 1d 1h 1m 1s,
1774            input: "",
1775        }
1776        "###);
1777        insta::assert_debug_snapshot!(p(b"P1y1m1w1dT1h1m1s"), @r###"
1778        Parsed {
1779            value: 1y 1mo 1w 1d 1h 1m 1s,
1780            input: "",
1781        }
1782        "###);
1783    }
1784
1785    #[test]
1786    fn ok_temporal_duration_fractional() {
1787        let p =
1788            |input| SpanParser::new().parse_temporal_duration(input).unwrap();
1789
1790        insta::assert_debug_snapshot!(p(b"PT0.5h"), @r###"
1791        Parsed {
1792            value: 30m,
1793            input: "",
1794        }
1795        "###);
1796        insta::assert_debug_snapshot!(p(b"PT0.123456789h"), @r###"
1797        Parsed {
1798            value: 7m 24s 444ms 440µs 400ns,
1799            input: "",
1800        }
1801        "###);
1802        insta::assert_debug_snapshot!(p(b"PT1.123456789h"), @r###"
1803        Parsed {
1804            value: 1h 7m 24s 444ms 440µs 400ns,
1805            input: "",
1806        }
1807        "###);
1808
1809        insta::assert_debug_snapshot!(p(b"PT0.5m"), @r###"
1810        Parsed {
1811            value: 30s,
1812            input: "",
1813        }
1814        "###);
1815        insta::assert_debug_snapshot!(p(b"PT0.123456789m"), @r###"
1816        Parsed {
1817            value: 7s 407ms 407µs 340ns,
1818            input: "",
1819        }
1820        "###);
1821        insta::assert_debug_snapshot!(p(b"PT1.123456789m"), @r###"
1822        Parsed {
1823            value: 1m 7s 407ms 407µs 340ns,
1824            input: "",
1825        }
1826        "###);
1827
1828        insta::assert_debug_snapshot!(p(b"PT0.5s"), @r###"
1829        Parsed {
1830            value: 500ms,
1831            input: "",
1832        }
1833        "###);
1834        insta::assert_debug_snapshot!(p(b"PT0.123456789s"), @r###"
1835        Parsed {
1836            value: 123ms 456µs 789ns,
1837            input: "",
1838        }
1839        "###);
1840        insta::assert_debug_snapshot!(p(b"PT1.123456789s"), @r###"
1841        Parsed {
1842            value: 1s 123ms 456µs 789ns,
1843            input: "",
1844        }
1845        "###);
1846
1847        // The tests below all have a whole second value that exceeds the
1848        // maximum allowed seconds in a span. But they should still parse
1849        // correctly by spilling over into milliseconds, microseconds and
1850        // nanoseconds.
1851        insta::assert_debug_snapshot!(p(b"PT1902545624836.854775807s"), @r###"
1852        Parsed {
1853            value: 631107417600s 631107417600000ms 631107417600000000µs 9223372036854775807ns,
1854            input: "",
1855        }
1856        "###);
1857        insta::assert_debug_snapshot!(p(b"PT175307616h10518456960m640330789636.854775807s"), @r###"
1858        Parsed {
1859            value: 175307616h 10518456960m 631107417600s 9223372036854ms 775µs 807ns,
1860            input: "",
1861        }
1862        "###);
1863        insta::assert_debug_snapshot!(p(b"-PT1902545624836.854775807s"), @r###"
1864        Parsed {
1865            value: 631107417600s 631107417600000ms 631107417600000000µs 9223372036854775807ns ago,
1866            input: "",
1867        }
1868        "###);
1869        insta::assert_debug_snapshot!(p(b"-PT175307616h10518456960m640330789636.854775807s"), @r###"
1870        Parsed {
1871            value: 175307616h 10518456960m 631107417600s 9223372036854ms 775µs 807ns ago,
1872            input: "",
1873        }
1874        "###);
1875    }
1876
1877    #[test]
1878    fn ok_temporal_duration_unbalanced() {
1879        let p =
1880            |input| SpanParser::new().parse_temporal_duration(input).unwrap();
1881
1882        insta::assert_debug_snapshot!(
1883            p(b"PT175307616h10518456960m1774446656760s"), @r###"
1884        Parsed {
1885            value: 175307616h 10518456960m 631107417600s 631107417600000ms 512231821560000000µs,
1886            input: "",
1887        }
1888        "###);
1889        insta::assert_debug_snapshot!(
1890            p(b"Pt843517082H"), @r###"
1891        Parsed {
1892            value: 175307616h 10518456960m 631107417600s 631107417600000ms 512231824800000000µs,
1893            input: "",
1894        }
1895        "###);
1896        insta::assert_debug_snapshot!(
1897            p(b"Pt843517081H"), @r###"
1898        Parsed {
1899            value: 175307616h 10518456960m 631107417600s 631107417600000ms 512231821200000000µs,
1900            input: "",
1901        }
1902        "###);
1903    }
1904
1905    #[test]
1906    fn ok_temporal_datetime_basic() {
1907        let p = |input| {
1908            DateTimeParser::new().parse_temporal_datetime(input).unwrap()
1909        };
1910
1911        insta::assert_debug_snapshot!(p(b"2024-06-01"), @r###"
1912        Parsed {
1913            value: ParsedDateTime {
1914                input: "2024-06-01",
1915                date: ParsedDate {
1916                    input: "2024-06-01",
1917                    date: 2024-06-01,
1918                },
1919                time: None,
1920                offset: None,
1921                annotations: ParsedAnnotations {
1922                    input: "",
1923                    time_zone: None,
1924                },
1925            },
1926            input: "",
1927        }
1928        "###);
1929        insta::assert_debug_snapshot!(p(b"2024-06-01[America/New_York]"), @r###"
1930        Parsed {
1931            value: ParsedDateTime {
1932                input: "2024-06-01[America/New_York]",
1933                date: ParsedDate {
1934                    input: "2024-06-01",
1935                    date: 2024-06-01,
1936                },
1937                time: None,
1938                offset: None,
1939                annotations: ParsedAnnotations {
1940                    input: "[America/New_York]",
1941                    time_zone: Some(
1942                        Named {
1943                            critical: false,
1944                            name: "America/New_York",
1945                        },
1946                    ),
1947                },
1948            },
1949            input: "",
1950        }
1951        "###);
1952        insta::assert_debug_snapshot!(p(b"2024-06-01T01:02:03"), @r###"
1953        Parsed {
1954            value: ParsedDateTime {
1955                input: "2024-06-01T01:02:03",
1956                date: ParsedDate {
1957                    input: "2024-06-01",
1958                    date: 2024-06-01,
1959                },
1960                time: Some(
1961                    ParsedTime {
1962                        input: "01:02:03",
1963                        time: 01:02:03,
1964                        extended: true,
1965                    },
1966                ),
1967                offset: None,
1968                annotations: ParsedAnnotations {
1969                    input: "",
1970                    time_zone: None,
1971                },
1972            },
1973            input: "",
1974        }
1975        "###);
1976        insta::assert_debug_snapshot!(p(b"2024-06-01T01:02:03-05"), @r###"
1977        Parsed {
1978            value: ParsedDateTime {
1979                input: "2024-06-01T01:02:03-05",
1980                date: ParsedDate {
1981                    input: "2024-06-01",
1982                    date: 2024-06-01,
1983                },
1984                time: Some(
1985                    ParsedTime {
1986                        input: "01:02:03",
1987                        time: 01:02:03,
1988                        extended: true,
1989                    },
1990                ),
1991                offset: Some(
1992                    ParsedOffset {
1993                        kind: Numeric(
1994                            -05,
1995                        ),
1996                    },
1997                ),
1998                annotations: ParsedAnnotations {
1999                    input: "",
2000                    time_zone: None,
2001                },
2002            },
2003            input: "",
2004        }
2005        "###);
2006        insta::assert_debug_snapshot!(p(b"2024-06-01T01:02:03-05[America/New_York]"), @r###"
2007        Parsed {
2008            value: ParsedDateTime {
2009                input: "2024-06-01T01:02:03-05[America/New_York]",
2010                date: ParsedDate {
2011                    input: "2024-06-01",
2012                    date: 2024-06-01,
2013                },
2014                time: Some(
2015                    ParsedTime {
2016                        input: "01:02:03",
2017                        time: 01:02:03,
2018                        extended: true,
2019                    },
2020                ),
2021                offset: Some(
2022                    ParsedOffset {
2023                        kind: Numeric(
2024                            -05,
2025                        ),
2026                    },
2027                ),
2028                annotations: ParsedAnnotations {
2029                    input: "[America/New_York]",
2030                    time_zone: Some(
2031                        Named {
2032                            critical: false,
2033                            name: "America/New_York",
2034                        },
2035                    ),
2036                },
2037            },
2038            input: "",
2039        }
2040        "###);
2041        insta::assert_debug_snapshot!(p(b"2024-06-01T01:02:03Z[America/New_York]"), @r###"
2042        Parsed {
2043            value: ParsedDateTime {
2044                input: "2024-06-01T01:02:03Z[America/New_York]",
2045                date: ParsedDate {
2046                    input: "2024-06-01",
2047                    date: 2024-06-01,
2048                },
2049                time: Some(
2050                    ParsedTime {
2051                        input: "01:02:03",
2052                        time: 01:02:03,
2053                        extended: true,
2054                    },
2055                ),
2056                offset: Some(
2057                    ParsedOffset {
2058                        kind: Zulu,
2059                    },
2060                ),
2061                annotations: ParsedAnnotations {
2062                    input: "[America/New_York]",
2063                    time_zone: Some(
2064                        Named {
2065                            critical: false,
2066                            name: "America/New_York",
2067                        },
2068                    ),
2069                },
2070            },
2071            input: "",
2072        }
2073        "###);
2074        insta::assert_debug_snapshot!(p(b"2024-06-01T01:02:03-01[America/New_York]"), @r###"
2075        Parsed {
2076            value: ParsedDateTime {
2077                input: "2024-06-01T01:02:03-01[America/New_York]",
2078                date: ParsedDate {
2079                    input: "2024-06-01",
2080                    date: 2024-06-01,
2081                },
2082                time: Some(
2083                    ParsedTime {
2084                        input: "01:02:03",
2085                        time: 01:02:03,
2086                        extended: true,
2087                    },
2088                ),
2089                offset: Some(
2090                    ParsedOffset {
2091                        kind: Numeric(
2092                            -01,
2093                        ),
2094                    },
2095                ),
2096                annotations: ParsedAnnotations {
2097                    input: "[America/New_York]",
2098                    time_zone: Some(
2099                        Named {
2100                            critical: false,
2101                            name: "America/New_York",
2102                        },
2103                    ),
2104                },
2105            },
2106            input: "",
2107        }
2108        "###);
2109    }
2110
2111    #[test]
2112    fn ok_temporal_datetime_incomplete() {
2113        let p = |input| {
2114            DateTimeParser::new().parse_temporal_datetime(input).unwrap()
2115        };
2116
2117        insta::assert_debug_snapshot!(p(b"2024-06-01T01"), @r###"
2118        Parsed {
2119            value: ParsedDateTime {
2120                input: "2024-06-01T01",
2121                date: ParsedDate {
2122                    input: "2024-06-01",
2123                    date: 2024-06-01,
2124                },
2125                time: Some(
2126                    ParsedTime {
2127                        input: "01",
2128                        time: 01:00:00,
2129                        extended: false,
2130                    },
2131                ),
2132                offset: None,
2133                annotations: ParsedAnnotations {
2134                    input: "",
2135                    time_zone: None,
2136                },
2137            },
2138            input: "",
2139        }
2140        "###);
2141        insta::assert_debug_snapshot!(p(b"2024-06-01T0102"), @r###"
2142        Parsed {
2143            value: ParsedDateTime {
2144                input: "2024-06-01T0102",
2145                date: ParsedDate {
2146                    input: "2024-06-01",
2147                    date: 2024-06-01,
2148                },
2149                time: Some(
2150                    ParsedTime {
2151                        input: "0102",
2152                        time: 01:02:00,
2153                        extended: false,
2154                    },
2155                ),
2156                offset: None,
2157                annotations: ParsedAnnotations {
2158                    input: "",
2159                    time_zone: None,
2160                },
2161            },
2162            input: "",
2163        }
2164        "###);
2165        insta::assert_debug_snapshot!(p(b"2024-06-01T01:02"), @r###"
2166        Parsed {
2167            value: ParsedDateTime {
2168                input: "2024-06-01T01:02",
2169                date: ParsedDate {
2170                    input: "2024-06-01",
2171                    date: 2024-06-01,
2172                },
2173                time: Some(
2174                    ParsedTime {
2175                        input: "01:02",
2176                        time: 01:02:00,
2177                        extended: true,
2178                    },
2179                ),
2180                offset: None,
2181                annotations: ParsedAnnotations {
2182                    input: "",
2183                    time_zone: None,
2184                },
2185            },
2186            input: "",
2187        }
2188        "###);
2189    }
2190
2191    #[test]
2192    fn ok_temporal_datetime_separator() {
2193        let p = |input| {
2194            DateTimeParser::new().parse_temporal_datetime(input).unwrap()
2195        };
2196
2197        insta::assert_debug_snapshot!(p(b"2024-06-01t01:02:03"), @r###"
2198        Parsed {
2199            value: ParsedDateTime {
2200                input: "2024-06-01t01:02:03",
2201                date: ParsedDate {
2202                    input: "2024-06-01",
2203                    date: 2024-06-01,
2204                },
2205                time: Some(
2206                    ParsedTime {
2207                        input: "01:02:03",
2208                        time: 01:02:03,
2209                        extended: true,
2210                    },
2211                ),
2212                offset: None,
2213                annotations: ParsedAnnotations {
2214                    input: "",
2215                    time_zone: None,
2216                },
2217            },
2218            input: "",
2219        }
2220        "###);
2221        insta::assert_debug_snapshot!(p(b"2024-06-01 01:02:03"), @r###"
2222        Parsed {
2223            value: ParsedDateTime {
2224                input: "2024-06-01 01:02:03",
2225                date: ParsedDate {
2226                    input: "2024-06-01",
2227                    date: 2024-06-01,
2228                },
2229                time: Some(
2230                    ParsedTime {
2231                        input: "01:02:03",
2232                        time: 01:02:03,
2233                        extended: true,
2234                    },
2235                ),
2236                offset: None,
2237                annotations: ParsedAnnotations {
2238                    input: "",
2239                    time_zone: None,
2240                },
2241            },
2242            input: "",
2243        }
2244        "###);
2245    }
2246
2247    #[test]
2248    fn ok_temporal_time_basic() {
2249        let p =
2250            |input| DateTimeParser::new().parse_temporal_time(input).unwrap();
2251
2252        insta::assert_debug_snapshot!(p(b"01:02:03"), @r###"
2253        Parsed {
2254            value: ParsedTime {
2255                input: "01:02:03",
2256                time: 01:02:03,
2257                extended: true,
2258            },
2259            input: "",
2260        }
2261        "###);
2262        insta::assert_debug_snapshot!(p(b"130113"), @r###"
2263        Parsed {
2264            value: ParsedTime {
2265                input: "130113",
2266                time: 13:01:13,
2267                extended: false,
2268            },
2269            input: "",
2270        }
2271        "###);
2272        insta::assert_debug_snapshot!(p(b"T01:02:03"), @r###"
2273        Parsed {
2274            value: ParsedTime {
2275                input: "01:02:03",
2276                time: 01:02:03,
2277                extended: true,
2278            },
2279            input: "",
2280        }
2281        "###);
2282        insta::assert_debug_snapshot!(p(b"T010203"), @r###"
2283        Parsed {
2284            value: ParsedTime {
2285                input: "010203",
2286                time: 01:02:03,
2287                extended: false,
2288            },
2289            input: "",
2290        }
2291        "###);
2292    }
2293
2294    #[test]
2295    fn ok_temporal_time_from_full_datetime() {
2296        let p =
2297            |input| DateTimeParser::new().parse_temporal_time(input).unwrap();
2298
2299        insta::assert_debug_snapshot!(p(b"2024-06-01T01:02:03"), @r###"
2300        Parsed {
2301            value: ParsedTime {
2302                input: "01:02:03",
2303                time: 01:02:03,
2304                extended: true,
2305            },
2306            input: "",
2307        }
2308        "###);
2309        insta::assert_debug_snapshot!(p(b"2024-06-01T01:02:03.123"), @r###"
2310        Parsed {
2311            value: ParsedTime {
2312                input: "01:02:03.123",
2313                time: 01:02:03.123,
2314                extended: true,
2315            },
2316            input: "",
2317        }
2318        "###);
2319        insta::assert_debug_snapshot!(p(b"2024-06-01T01"), @r###"
2320        Parsed {
2321            value: ParsedTime {
2322                input: "01",
2323                time: 01:00:00,
2324                extended: false,
2325            },
2326            input: "",
2327        }
2328        "###);
2329        insta::assert_debug_snapshot!(p(b"2024-06-01T0102"), @r###"
2330        Parsed {
2331            value: ParsedTime {
2332                input: "0102",
2333                time: 01:02:00,
2334                extended: false,
2335            },
2336            input: "",
2337        }
2338        "###);
2339        insta::assert_debug_snapshot!(p(b"2024-06-01T010203"), @r###"
2340        Parsed {
2341            value: ParsedTime {
2342                input: "010203",
2343                time: 01:02:03,
2344                extended: false,
2345            },
2346            input: "",
2347        }
2348        "###);
2349        insta::assert_debug_snapshot!(p(b"2024-06-01T010203-05"), @r###"
2350        Parsed {
2351            value: ParsedTime {
2352                input: "010203",
2353                time: 01:02:03,
2354                extended: false,
2355            },
2356            input: "",
2357        }
2358        "###);
2359        insta::assert_debug_snapshot!(
2360            p(b"2024-06-01T010203-05[America/New_York]"), @r###"
2361        Parsed {
2362            value: ParsedTime {
2363                input: "010203",
2364                time: 01:02:03,
2365                extended: false,
2366            },
2367            input: "",
2368        }
2369        "###);
2370        insta::assert_debug_snapshot!(
2371            p(b"2024-06-01T010203[America/New_York]"), @r###"
2372        Parsed {
2373            value: ParsedTime {
2374                input: "010203",
2375                time: 01:02:03,
2376                extended: false,
2377            },
2378            input: "",
2379        }
2380        "###);
2381    }
2382
2383    #[test]
2384    fn err_temporal_time_ambiguous() {
2385        let p = |input| {
2386            DateTimeParser::new().parse_temporal_time(input).unwrap_err()
2387        };
2388
2389        insta::assert_snapshot!(
2390            p(b"010203"),
2391            @r###"parsed time from "010203" is ambiguous with a month-day date"###,
2392        );
2393        insta::assert_snapshot!(
2394            p(b"130112"),
2395            @r###"parsed time from "130112" is ambiguous with a year-month date"###,
2396        );
2397    }
2398
2399    #[test]
2400    fn err_temporal_time_missing_time() {
2401        let p = |input| {
2402            DateTimeParser::new().parse_temporal_time(input).unwrap_err()
2403        };
2404
2405        insta::assert_snapshot!(
2406            p(b"2024-06-01[America/New_York]"),
2407            @r###"successfully parsed date from "2024-06-01[America/New_York]", but no time component was found"###,
2408        );
2409        // 2099 is not a valid time, but 2099-12-01 is a valid date, so this
2410        // carves a path where a full datetime parse is OK, but a basic
2411        // time-only parse is not.
2412        insta::assert_snapshot!(
2413            p(b"2099-12-01[America/New_York]"),
2414            @r###"successfully parsed date from "2099-12-01[America/New_York]", but no time component was found"###,
2415        );
2416        // Like above, but this time we use an invalid date. As a result, we
2417        // get an error reported not on the invalid date, but on how it is an
2418        // invalid time. (Because we're asking for a time here.)
2419        insta::assert_snapshot!(
2420            p(b"2099-13-01[America/New_York]"),
2421            @r###"failed to parse minute in time "2099-13-01[America/New_York]": minute is not valid: parameter 'minute' with value 99 is not in the required range of 0..=59"###,
2422        );
2423    }
2424
2425    #[test]
2426    fn err_temporal_time_zulu() {
2427        let p = |input| {
2428            DateTimeParser::new().parse_temporal_time(input).unwrap_err()
2429        };
2430
2431        insta::assert_snapshot!(
2432            p(b"T00:00:00Z"),
2433            @"cannot parse civil time from string with a Zulu offset, parse as a `Timestamp` and convert to a civil time instead",
2434        );
2435        insta::assert_snapshot!(
2436            p(b"00:00:00Z"),
2437            @"cannot parse plain time from string with a Zulu offset, parse as a `Timestamp` and convert to a plain time instead",
2438        );
2439        insta::assert_snapshot!(
2440            p(b"000000Z"),
2441            @"cannot parse plain time from string with a Zulu offset, parse as a `Timestamp` and convert to a plain time instead",
2442        );
2443        insta::assert_snapshot!(
2444            p(b"2099-12-01T00:00:00Z"),
2445            @"cannot parse plain time from full datetime string with a Zulu offset, parse as a `Timestamp` and convert to a plain time instead",
2446        );
2447    }
2448
2449    #[test]
2450    fn ok_date_basic() {
2451        let p = |input| DateTimeParser::new().parse_date_spec(input).unwrap();
2452
2453        insta::assert_debug_snapshot!(p(b"2010-03-14"), @r###"
2454        Parsed {
2455            value: ParsedDate {
2456                input: "2010-03-14",
2457                date: 2010-03-14,
2458            },
2459            input: "",
2460        }
2461        "###);
2462        insta::assert_debug_snapshot!(p(b"20100314"), @r###"
2463        Parsed {
2464            value: ParsedDate {
2465                input: "20100314",
2466                date: 2010-03-14,
2467            },
2468            input: "",
2469        }
2470        "###);
2471        insta::assert_debug_snapshot!(p(b"2010-03-14T01:02:03"), @r###"
2472        Parsed {
2473            value: ParsedDate {
2474                input: "2010-03-14",
2475                date: 2010-03-14,
2476            },
2477            input: "T01:02:03",
2478        }
2479        "###);
2480        insta::assert_debug_snapshot!(p(b"-009999-03-14"), @r###"
2481        Parsed {
2482            value: ParsedDate {
2483                input: "-009999-03-14",
2484                date: -009999-03-14,
2485            },
2486            input: "",
2487        }
2488        "###);
2489        insta::assert_debug_snapshot!(p(b"+009999-03-14"), @r###"
2490        Parsed {
2491            value: ParsedDate {
2492                input: "+009999-03-14",
2493                date: 9999-03-14,
2494            },
2495            input: "",
2496        }
2497        "###);
2498    }
2499
2500    #[test]
2501    fn err_date_empty() {
2502        insta::assert_snapshot!(
2503            DateTimeParser::new().parse_date_spec(b"").unwrap_err(),
2504            @r###"failed to parse year in date "": expected four digit year (or leading sign for six digit year), but found end of input"###,
2505        );
2506    }
2507
2508    #[test]
2509    fn err_date_year() {
2510        insta::assert_snapshot!(
2511            DateTimeParser::new().parse_date_spec(b"123").unwrap_err(),
2512            @r###"failed to parse year in date "123": expected four digit year (or leading sign for six digit year), but found end of input"###,
2513        );
2514        insta::assert_snapshot!(
2515            DateTimeParser::new().parse_date_spec(b"123a").unwrap_err(),
2516            @r###"failed to parse year in date "123a": failed to parse "123a" as year (a four digit integer): invalid digit, expected 0-9 but got a"###,
2517        );
2518
2519        insta::assert_snapshot!(
2520            DateTimeParser::new().parse_date_spec(b"-9999").unwrap_err(),
2521            @r###"failed to parse year in date "-9999": expected six digit year (because of a leading sign), but found end of input"###,
2522        );
2523        insta::assert_snapshot!(
2524            DateTimeParser::new().parse_date_spec(b"+9999").unwrap_err(),
2525            @r###"failed to parse year in date "+9999": expected six digit year (because of a leading sign), but found end of input"###,
2526        );
2527        insta::assert_snapshot!(
2528            DateTimeParser::new().parse_date_spec(b"-99999").unwrap_err(),
2529            @r###"failed to parse year in date "-99999": expected six digit year (because of a leading sign), but found end of input"###,
2530        );
2531        insta::assert_snapshot!(
2532            DateTimeParser::new().parse_date_spec(b"+99999").unwrap_err(),
2533            @r###"failed to parse year in date "+99999": expected six digit year (because of a leading sign), but found end of input"###,
2534        );
2535        insta::assert_snapshot!(
2536            DateTimeParser::new().parse_date_spec(b"-99999a").unwrap_err(),
2537            @r###"failed to parse year in date "-99999a": failed to parse "99999a" as year (a six digit integer): invalid digit, expected 0-9 but got a"###,
2538        );
2539        insta::assert_snapshot!(
2540            DateTimeParser::new().parse_date_spec(b"+999999").unwrap_err(),
2541            @r###"failed to parse year in date "+999999": year is not valid: parameter 'year' with value 999999 is not in the required range of -9999..=9999"###,
2542        );
2543        insta::assert_snapshot!(
2544            DateTimeParser::new().parse_date_spec(b"-010000").unwrap_err(),
2545            @r###"failed to parse year in date "-010000": year is not valid: parameter 'year' with value 10000 is not in the required range of -9999..=9999"###,
2546        );
2547    }
2548
2549    #[test]
2550    fn err_date_month() {
2551        insta::assert_snapshot!(
2552            DateTimeParser::new().parse_date_spec(b"2024-").unwrap_err(),
2553            @r###"failed to parse month in date "2024-": expected two digit month, but found end of input"###,
2554        );
2555        insta::assert_snapshot!(
2556            DateTimeParser::new().parse_date_spec(b"2024").unwrap_err(),
2557            @r###"failed to parse month in date "2024": expected two digit month, but found end of input"###,
2558        );
2559        insta::assert_snapshot!(
2560            DateTimeParser::new().parse_date_spec(b"2024-13-01").unwrap_err(),
2561            @r###"failed to parse month in date "2024-13-01": month is not valid: parameter 'month' with value 13 is not in the required range of 1..=12"###,
2562        );
2563        insta::assert_snapshot!(
2564            DateTimeParser::new().parse_date_spec(b"20241301").unwrap_err(),
2565            @r###"failed to parse month in date "20241301": month is not valid: parameter 'month' with value 13 is not in the required range of 1..=12"###,
2566        );
2567    }
2568
2569    #[test]
2570    fn err_date_day() {
2571        insta::assert_snapshot!(
2572            DateTimeParser::new().parse_date_spec(b"2024-12-").unwrap_err(),
2573            @r###"failed to parse day in date "2024-12-": expected two digit day, but found end of input"###,
2574        );
2575        insta::assert_snapshot!(
2576            DateTimeParser::new().parse_date_spec(b"202412").unwrap_err(),
2577            @r###"failed to parse day in date "202412": expected two digit day, but found end of input"###,
2578        );
2579        insta::assert_snapshot!(
2580            DateTimeParser::new().parse_date_spec(b"2024-12-40").unwrap_err(),
2581            @r###"failed to parse day in date "2024-12-40": day is not valid: parameter 'day' with value 40 is not in the required range of 1..=31"###,
2582        );
2583        insta::assert_snapshot!(
2584            DateTimeParser::new().parse_date_spec(b"2024-11-31").unwrap_err(),
2585            @r###"date parsed from "2024-11-31" is not valid: parameter 'day' with value 31 is not in the required range of 1..=30"###,
2586        );
2587        insta::assert_snapshot!(
2588            DateTimeParser::new().parse_date_spec(b"2024-02-30").unwrap_err(),
2589            @r###"date parsed from "2024-02-30" is not valid: parameter 'day' with value 30 is not in the required range of 1..=29"###,
2590        );
2591        insta::assert_snapshot!(
2592            DateTimeParser::new().parse_date_spec(b"2023-02-29").unwrap_err(),
2593            @r###"date parsed from "2023-02-29" is not valid: parameter 'day' with value 29 is not in the required range of 1..=28"###,
2594        );
2595    }
2596
2597    #[test]
2598    fn err_date_separator() {
2599        insta::assert_snapshot!(
2600            DateTimeParser::new().parse_date_spec(b"2024-1231").unwrap_err(),
2601            @r###"failed to parse separator after month: expected '-' separator, but found "3" instead"###,
2602        );
2603        insta::assert_snapshot!(
2604            DateTimeParser::new().parse_date_spec(b"202412-31").unwrap_err(),
2605            @"failed to parse separator after month: expected no separator after month since none was found after the year, but found a '-' separator",
2606        );
2607    }
2608
2609    #[test]
2610    fn ok_time_basic() {
2611        let p = |input| DateTimeParser::new().parse_time_spec(input).unwrap();
2612
2613        insta::assert_debug_snapshot!(p(b"01:02:03"), @r###"
2614        Parsed {
2615            value: ParsedTime {
2616                input: "01:02:03",
2617                time: 01:02:03,
2618                extended: true,
2619            },
2620            input: "",
2621        }
2622        "###);
2623        insta::assert_debug_snapshot!(p(b"010203"), @r###"
2624        Parsed {
2625            value: ParsedTime {
2626                input: "010203",
2627                time: 01:02:03,
2628                extended: false,
2629            },
2630            input: "",
2631        }
2632        "###);
2633    }
2634
2635    #[test]
2636    fn ok_time_fractional() {
2637        let p = |input| DateTimeParser::new().parse_time_spec(input).unwrap();
2638
2639        insta::assert_debug_snapshot!(p(b"01:02:03.123456789"), @r###"
2640        Parsed {
2641            value: ParsedTime {
2642                input: "01:02:03.123456789",
2643                time: 01:02:03.123456789,
2644                extended: true,
2645            },
2646            input: "",
2647        }
2648        "###);
2649        insta::assert_debug_snapshot!(p(b"010203.123456789"), @r###"
2650        Parsed {
2651            value: ParsedTime {
2652                input: "010203.123456789",
2653                time: 01:02:03.123456789,
2654                extended: false,
2655            },
2656            input: "",
2657        }
2658        "###);
2659
2660        insta::assert_debug_snapshot!(p(b"01:02:03.9"), @r###"
2661        Parsed {
2662            value: ParsedTime {
2663                input: "01:02:03.9",
2664                time: 01:02:03.9,
2665                extended: true,
2666            },
2667            input: "",
2668        }
2669        "###);
2670    }
2671
2672    #[test]
2673    fn ok_time_no_fractional() {
2674        let p = |input| DateTimeParser::new().parse_time_spec(input).unwrap();
2675
2676        insta::assert_debug_snapshot!(p(b"01:02.123456789"), @r###"
2677        Parsed {
2678            value: ParsedTime {
2679                input: "01:02",
2680                time: 01:02:00,
2681                extended: true,
2682            },
2683            input: ".123456789",
2684        }
2685        "###);
2686    }
2687
2688    #[test]
2689    fn ok_time_leap() {
2690        let p = |input| DateTimeParser::new().parse_time_spec(input).unwrap();
2691
2692        insta::assert_debug_snapshot!(p(b"01:02:60"), @r###"
2693        Parsed {
2694            value: ParsedTime {
2695                input: "01:02:60",
2696                time: 01:02:59,
2697                extended: true,
2698            },
2699            input: "",
2700        }
2701        "###);
2702    }
2703
2704    #[test]
2705    fn ok_time_mixed_format() {
2706        let p = |input| DateTimeParser::new().parse_time_spec(input).unwrap();
2707
2708        insta::assert_debug_snapshot!(p(b"01:0203"), @r###"
2709        Parsed {
2710            value: ParsedTime {
2711                input: "01:02",
2712                time: 01:02:00,
2713                extended: true,
2714            },
2715            input: "03",
2716        }
2717        "###);
2718        insta::assert_debug_snapshot!(p(b"0102:03"), @r###"
2719        Parsed {
2720            value: ParsedTime {
2721                input: "0102",
2722                time: 01:02:00,
2723                extended: false,
2724            },
2725            input: ":03",
2726        }
2727        "###);
2728    }
2729
2730    #[test]
2731    fn err_time_empty() {
2732        insta::assert_snapshot!(
2733            DateTimeParser::new().parse_time_spec(b"").unwrap_err(),
2734            @r###"failed to parse hour in time "": expected two digit hour, but found end of input"###,
2735        );
2736    }
2737
2738    #[test]
2739    fn err_time_hour() {
2740        insta::assert_snapshot!(
2741            DateTimeParser::new().parse_time_spec(b"a").unwrap_err(),
2742            @r###"failed to parse hour in time "a": expected two digit hour, but found end of input"###,
2743        );
2744        insta::assert_snapshot!(
2745            DateTimeParser::new().parse_time_spec(b"1a").unwrap_err(),
2746            @r###"failed to parse hour in time "1a": failed to parse "1a" as hour (a two digit integer): invalid digit, expected 0-9 but got a"###,
2747        );
2748        insta::assert_snapshot!(
2749            DateTimeParser::new().parse_time_spec(b"24").unwrap_err(),
2750            @r###"failed to parse hour in time "24": hour is not valid: parameter 'hour' with value 24 is not in the required range of 0..=23"###,
2751        );
2752    }
2753
2754    #[test]
2755    fn err_time_minute() {
2756        insta::assert_snapshot!(
2757            DateTimeParser::new().parse_time_spec(b"01:").unwrap_err(),
2758            @r###"failed to parse minute in time "01:": expected two digit minute, but found end of input"###,
2759        );
2760        insta::assert_snapshot!(
2761            DateTimeParser::new().parse_time_spec(b"01:a").unwrap_err(),
2762            @r###"failed to parse minute in time "01:a": expected two digit minute, but found end of input"###,
2763        );
2764        insta::assert_snapshot!(
2765            DateTimeParser::new().parse_time_spec(b"01:1a").unwrap_err(),
2766            @r###"failed to parse minute in time "01:1a": failed to parse "1a" as minute (a two digit integer): invalid digit, expected 0-9 but got a"###,
2767        );
2768        insta::assert_snapshot!(
2769            DateTimeParser::new().parse_time_spec(b"01:60").unwrap_err(),
2770            @r###"failed to parse minute in time "01:60": minute is not valid: parameter 'minute' with value 60 is not in the required range of 0..=59"###,
2771        );
2772    }
2773
2774    #[test]
2775    fn err_time_second() {
2776        insta::assert_snapshot!(
2777            DateTimeParser::new().parse_time_spec(b"01:02:").unwrap_err(),
2778            @r###"failed to parse second in time "01:02:": expected two digit second, but found end of input"###,
2779        );
2780        insta::assert_snapshot!(
2781            DateTimeParser::new().parse_time_spec(b"01:02:a").unwrap_err(),
2782            @r###"failed to parse second in time "01:02:a": expected two digit second, but found end of input"###,
2783        );
2784        insta::assert_snapshot!(
2785            DateTimeParser::new().parse_time_spec(b"01:02:1a").unwrap_err(),
2786            @r###"failed to parse second in time "01:02:1a": failed to parse "1a" as second (a two digit integer): invalid digit, expected 0-9 but got a"###,
2787        );
2788        insta::assert_snapshot!(
2789            DateTimeParser::new().parse_time_spec(b"01:02:61").unwrap_err(),
2790            @r###"failed to parse second in time "01:02:61": second is not valid: parameter 'second' with value 61 is not in the required range of 0..=59"###,
2791        );
2792    }
2793
2794    #[test]
2795    fn err_time_fractional() {
2796        insta::assert_snapshot!(
2797            DateTimeParser::new().parse_time_spec(b"01:02:03.").unwrap_err(),
2798            @r###"failed to parse fractional nanoseconds in time "01:02:03.": found decimal after seconds component, but did not find any decimal digits after decimal"###,
2799        );
2800        insta::assert_snapshot!(
2801            DateTimeParser::new().parse_time_spec(b"01:02:03.a").unwrap_err(),
2802            @r###"failed to parse fractional nanoseconds in time "01:02:03.a": found decimal after seconds component, but did not find any decimal digits after decimal"###,
2803        );
2804    }
2805}