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#[derive(Debug)]
25pub(super) struct ParsedDateTime<'i> {
26 input: escape::Bytes<'i>,
28 date: ParsedDate<'i>,
30 time: Option<ParsedTime<'i>>,
32 offset: Option<ParsedOffset>,
34 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 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 let Some(ref parsed_offset) = self.offset else {
92 return Ok(tz.into_ambiguous_zoned(dt));
93 };
94 if parsed_offset.is_zulu() {
95 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 parsed == candidate {
115 return true;
116 }
117 if candidate.part_seconds_ranged() == 0 {
120 return parsed == candidate;
121 }
122 let Ok(candidate) = candidate.round(Unit::Minute) else {
123 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#[derive(Debug)]
199pub(super) struct ParsedDate<'i> {
200 input: escape::Bytes<'i>,
202 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#[derive(Debug)]
214pub(super) struct ParsedTime<'i> {
215 input: escape::Bytes<'i>,
217 time: Time,
219 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 input: escape::Bytes<'i>,
239 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#[derive(Debug)]
294pub(super) struct DateTimeParser {
295 _priv: (),
297}
298
299impl DateTimeParser {
300 pub(super) const fn new() -> DateTimeParser {
302 DateTimeParser { _priv: () }
303 }
304
305 #[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 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 #[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 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 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 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 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 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 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 Err(_) => mknamed(consumed, input),
544 }
545 }
546 }
547
548 #[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 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 let Parsed { input, .. } = self
568 .parse_date_separator(input, extended)
569 .context("failed to parse separator after year")?;
570
571 let Parsed { value: month, input } =
573 self.parse_month(input).with_context(|| {
574 err!("failed to parse month in date {original:?}")
575 })?;
576
577 let Parsed { input, .. } = self
579 .parse_date_separator(input, extended)
580 .context("failed to parse separator after month")?;
581
582 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 #[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 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 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 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 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 #[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 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 if input.starts_with(b"-") {
710 input = &input[1..];
711 }
712
713 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 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 Ok(Parsed { value: (), input })
731 }
732
733 #[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 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 if input.starts_with(b"-") {
753 input = &input[1..];
754 }
755
756 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 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 Ok(Parsed { value: (), input })
772 }
773
774 #[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 #[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 #[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 #[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 #[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 #[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 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 #[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 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 #[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 #[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#[derive(Debug)]
1096pub(super) struct SpanParser {
1097 _priv: (),
1099}
1100
1101impl SpanParser {
1102 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 #[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 #[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 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 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 #[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 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 _ => unreachable!(),
1380 };
1381 let unit_dur = SignedDuration::new(unit_secs, 0);
1383 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 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 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 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 #[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 #[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 #[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 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 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 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}