1use crate::{
2 civil::Weekday,
3 error::{
4 fmt::strtime::{Error as E, ParseError as PE},
5 util::ParseIntError,
6 ErrorContext,
7 },
8 fmt::{
9 offset,
10 strtime::{BrokenDownTime, Extension, Flag, Meridiem},
11 Parsed,
12 },
13 util::{b, parse},
14 Error, Timestamp,
15};
16
17pub(super) struct Parser<'f, 'i, 't> {
18 pub(super) fmt: &'f [u8],
19 pub(super) inp: &'i [u8],
20 pub(super) tm: &'t mut BrokenDownTime,
21}
22
23impl<'f, 'i, 't> Parser<'f, 'i, 't> {
24 pub(super) fn parse(&mut self) -> Result<(), Error> {
25 let failc =
26 |directive, colons| E::DirectiveFailure { directive, colons };
27 let fail = |directive| failc(directive, 0);
28
29 while !self.fmt.is_empty() {
30 if self.f() != b'%' {
31 self.parse_literal()?;
32 continue;
33 }
34 if !self.bump_fmt() {
35 return Err(Error::from(E::UnexpectedEndAfterPercent));
36 }
37 if self.inp.is_empty() && self.f() != b'.' {
40 return Err(Error::from(PE::ExpectedNonEmpty {
41 directive: self.f(),
42 }));
43 }
44 let ext = self.parse_extension()?;
46 match self.f() {
47 b'%' => self.parse_percent().context(fail(b'%'))?,
48 b'A' => self.parse_weekday_full().context(fail(b'A'))?,
49 b'a' => self.parse_weekday_abbrev().context(fail(b'a'))?,
50 b'B' => self.parse_month_name_full().context(fail(b'B'))?,
51 b'b' => self.parse_month_name_abbrev().context(fail(b'b'))?,
52 b'C' => self.parse_century(ext).context(fail(b'C'))?,
53 b'D' => self.parse_american_date().context(fail(b'D'))?,
54 b'd' => self.parse_day(ext).context(fail(b'd'))?,
55 b'e' => self.parse_day(ext).context(fail(b'e'))?,
56 b'F' => self.parse_iso_date().context(fail(b'F'))?,
57 b'f' => self.parse_fractional(ext).context(fail(b'f'))?,
58 b'G' => self.parse_iso_week_year(ext).context(fail(b'G'))?,
59 b'g' => self.parse_iso_week_year2(ext).context(fail(b'g'))?,
60 b'H' => self.parse_hour24(ext).context(fail(b'H'))?,
61 b'h' => self.parse_month_name_abbrev().context(fail(b'h'))?,
62 b'I' => self.parse_hour12(ext).context(fail(b'I'))?,
63 b'j' => self.parse_day_of_year(ext).context(fail(b'j'))?,
64 b'k' => self.parse_hour24(ext).context(fail(b'k'))?,
65 b'l' => self.parse_hour12(ext).context(fail(b'l'))?,
66 b'M' => self.parse_minute(ext).context(fail(b'M'))?,
67 b'm' => self.parse_month(ext).context(fail(b'm'))?,
68 b'N' => self.parse_fractional(ext).context(fail(b'N'))?,
69 b'n' => self.parse_whitespace().context(fail(b'n'))?,
70 b'P' => self.parse_ampm().context(fail(b'P'))?,
71 b'p' => self.parse_ampm().context(fail(b'p'))?,
72 b'Q' => match ext.colons {
73 0 => self.parse_iana_nocolon().context(fail(b'Q'))?,
74 1 => self.parse_iana_colon().context(failc(b'Q', 1))?,
75 _ => return Err(E::ColonCount { directive: b'Q' }.into()),
76 },
77 b'R' => self.parse_clock_nosecs().context(fail(b'R'))?,
78 b'S' => self.parse_second(ext).context(fail(b'S'))?,
79 b's' => self.parse_timestamp(ext).context(fail(b's'))?,
80 b'T' => self.parse_clock_secs().context(fail(b'T'))?,
81 b't' => self.parse_whitespace().context(fail(b't'))?,
82 b'U' => self.parse_week_sun(ext).context(fail(b'U'))?,
83 b'u' => self.parse_weekday_mon(ext).context(fail(b'u'))?,
84 b'V' => self.parse_week_iso(ext).context(fail(b'V'))?,
85 b'W' => self.parse_week_mon(ext).context(fail(b'W'))?,
86 b'w' => self.parse_weekday_sun(ext).context(fail(b'w'))?,
87 b'Y' => self.parse_year(ext).context(fail(b'Y'))?,
88 b'y' => self.parse_year2(ext).context(fail(b'y'))?,
89 b'z' => match ext.colons {
90 0 => self.parse_offset_nocolon().context(fail(b'z'))?,
91 1 => self.parse_offset_colon().context(failc(b'z', 1))?,
92 2 => self.parse_offset_colon2().context(failc(b'z', 2))?,
93 3 => self.parse_offset_colon3().context(failc(b'z', 3))?,
94 _ => return Err(E::ColonCount { directive: b'z' }.into()),
95 },
96 b'c' => {
97 return Err(Error::from(PE::NotAllowedLocaleDateAndTime))
98 }
99 b'r' => {
100 return Err(Error::from(
101 PE::NotAllowedLocaleTwelveHourClockTime,
102 ))
103 }
104 b'X' => {
105 return Err(Error::from(PE::NotAllowedLocaleClockTime))
106 }
107 b'x' => return Err(Error::from(PE::NotAllowedLocaleDate)),
108 b'Z' => {
109 return Err(Error::from(
110 PE::NotAllowedTimeZoneAbbreviation,
111 ))
112 }
113 b'.' => {
114 if !self.bump_fmt() {
115 return Err(E::UnexpectedEndAfterDot.into());
116 }
117 let (width, fmt) = Extension::parse_width(self.fmt)?;
120 let ext = Extension { width, ..ext };
121 self.fmt = fmt;
122 match self.f() {
123 b'f' => self.parse_dot_fractional(ext).context(
124 E::DirectiveFailureDot { directive: b'f' },
125 )?,
126 unk => {
127 return Err(Error::from(
128 E::UnknownDirectiveAfterDot { directive: unk },
129 ));
130 }
131 }
132 }
133 unk => {
134 return Err(Error::from(E::UnknownDirective {
135 directive: unk,
136 }));
137 }
138 }
139 }
140 Ok(())
141 }
142
143 fn f(&self) -> u8 {
149 self.fmt[0]
150 }
151
152 fn i(&self) -> u8 {
158 self.inp[0]
159 }
160
161 fn bump_fmt(&mut self) -> bool {
166 self.fmt = &self.fmt[1..];
167 !self.fmt.is_empty()
168 }
169
170 fn bump_input(&mut self) -> bool {
175 self.inp = &self.inp[1..];
176 !self.inp.is_empty()
177 }
178
179 fn parse_extension(&mut self) -> Result<Extension, Error> {
183 let (flag, fmt) = Extension::parse_flag(self.fmt)?;
184 let (width, fmt) = Extension::parse_width(fmt)?;
185 let (colons, fmt) = Extension::parse_colons(fmt)?;
186 self.fmt = fmt;
187 Ok(Extension { flag, width, colons })
188 }
189
190 fn parse_literal(&mut self) -> Result<(), Error> {
202 if self.f().is_ascii_whitespace() {
203 if !self.inp.is_empty() {
204 while self.i().is_ascii_whitespace() && self.bump_input() {}
205 }
206 } else if self.inp.is_empty() {
207 return Err(Error::from(PE::ExpectedMatchLiteralEndOfInput {
208 expected: self.f(),
209 }));
210 } else if self.f() != self.i() {
211 return Err(Error::from(PE::ExpectedMatchLiteralByte {
212 expected: self.fmt[0],
213 got: self.i(),
214 }));
215 } else {
216 self.bump_input();
217 }
218 self.bump_fmt();
219 Ok(())
220 }
221
222 fn parse_whitespace(&mut self) -> Result<(), Error> {
226 if !self.inp.is_empty() {
227 while self.i().is_ascii_whitespace() && self.bump_input() {}
228 }
229 self.bump_fmt();
230 Ok(())
231 }
232
233 fn parse_percent(&mut self) -> Result<(), Error> {
235 if self.i() != b'%' {
236 return Err(Error::from(PE::ExpectedMatchLiteralByte {
237 expected: b'%',
238 got: self.i(),
239 }));
240 }
241 self.bump_fmt();
242 self.bump_input();
243 Ok(())
244 }
245
246 fn parse_american_date(&mut self) -> Result<(), Error> {
248 let mut p = Parser { fmt: b"%m/%d/%y", inp: self.inp, tm: self.tm };
249 p.parse()?;
250 self.inp = p.inp;
251 self.bump_fmt();
252 Ok(())
253 }
254
255 fn parse_ampm(&mut self) -> Result<(), Error> {
261 let (index, inp) = parse_ampm(self.inp)?;
262 self.inp = inp;
263
264 self.tm.set_meridiem(Some(match index {
265 0 => Meridiem::AM,
266 1 => Meridiem::PM,
267 _ => unreachable!("unknown AM/PM index"),
269 }));
270 self.bump_fmt();
271 Ok(())
272 }
273
274 fn parse_clock_secs(&mut self) -> Result<(), Error> {
276 let mut p = Parser { fmt: b"%H:%M:%S", inp: self.inp, tm: self.tm };
277 p.parse()?;
278 self.inp = p.inp;
279 self.bump_fmt();
280 Ok(())
281 }
282
283 fn parse_clock_nosecs(&mut self) -> Result<(), Error> {
285 let mut p = Parser { fmt: b"%H:%M", inp: self.inp, tm: self.tm };
286 p.parse()?;
287 self.inp = p.inp;
288 self.bump_fmt();
289 Ok(())
290 }
291
292 fn parse_day(&mut self, ext: Extension) -> Result<(), Error> {
296 let (day, inp) = ext
297 .parse_number(2, Flag::PadZero, self.inp)
298 .context(PE::ParseDay)?;
299 self.inp = inp;
300
301 self.tm.day = Some(b::Day::check(day).context(PE::ParseDay)?);
302 self.bump_fmt();
303 Ok(())
304 }
305
306 fn parse_day_of_year(&mut self, ext: Extension) -> Result<(), Error> {
310 let (day, inp) = ext
311 .parse_number(3, Flag::PadZero, self.inp)
312 .context(PE::ParseDayOfYear)?;
313 self.inp = inp;
314
315 self.tm.day_of_year =
316 Some(b::DayOfYear::check(day).context(PE::ParseDayOfYear)?);
317 self.bump_fmt();
318 Ok(())
319 }
320
321 fn parse_hour24(&mut self, ext: Extension) -> Result<(), Error> {
323 let (hour, inp) = ext
324 .parse_number(2, Flag::PadZero, self.inp)
325 .context(PE::ParseHour)?;
326 self.inp = inp;
327
328 let hour = b::Hour::check(hour).context(PE::ParseHour)?;
329 self.tm.set_hour(Some(hour)).unwrap();
331 self.bump_fmt();
332 Ok(())
333 }
334
335 fn parse_hour12(&mut self, ext: Extension) -> Result<(), Error> {
337 let (hour, inp) = ext
338 .parse_number(2, Flag::PadZero, self.inp)
339 .context(PE::ParseHour)?;
340 self.inp = inp;
341
342 let hour = b::Hour12::check(hour).context(PE::ParseHour)?;
343 self.tm.set_hour(Some(hour)).unwrap();
345 self.bump_fmt();
346 Ok(())
347 }
348
349 fn parse_iso_date(&mut self) -> Result<(), Error> {
351 let mut p = Parser { fmt: b"%Y-%m-%d", inp: self.inp, tm: self.tm };
352 p.parse()?;
353 self.inp = p.inp;
354 self.bump_fmt();
355 Ok(())
356 }
357
358 fn parse_minute(&mut self, ext: Extension) -> Result<(), Error> {
360 let (minute, inp) = ext
361 .parse_number(2, Flag::PadZero, self.inp)
362 .context(PE::ParseMinute)?;
363 self.inp = inp;
364
365 self.tm.minute =
366 Some(b::Minute::check(minute).context(PE::ParseMinute)?);
367 self.bump_fmt();
368 Ok(())
369 }
370
371 fn parse_iana_nocolon(&mut self) -> Result<(), Error> {
374 #[cfg(not(feature = "alloc"))]
375 {
376 Err(Error::from(PE::NotAllowedAlloc {
377 directive: b'Q',
378 colons: 0,
379 }))
380 }
381 #[cfg(feature = "alloc")]
382 {
383 use alloc::string::ToString;
384
385 if !self.inp.is_empty() && matches!(self.inp[0], b'+' | b'-') {
386 return self.parse_offset_nocolon();
387 }
388 let (iana, inp) = parse_iana(self.inp)?;
389 self.inp = inp;
390 self.tm.iana = Some(iana.to_string());
391 self.bump_fmt();
392 Ok(())
393 }
394 }
395
396 fn parse_iana_colon(&mut self) -> Result<(), Error> {
399 #[cfg(not(feature = "alloc"))]
400 {
401 Err(Error::from(PE::NotAllowedAlloc {
402 directive: b'Q',
403 colons: 1,
404 }))
405 }
406 #[cfg(feature = "alloc")]
407 {
408 use alloc::string::ToString;
409
410 if !self.inp.is_empty() && matches!(self.inp[0], b'+' | b'-') {
411 return self.parse_offset_colon();
412 }
413 let (iana, inp) = parse_iana(self.inp)?;
414 self.inp = inp;
415 self.tm.iana = Some(iana.to_string());
416 self.bump_fmt();
417 Ok(())
418 }
419 }
420
421 fn parse_offset_nocolon(&mut self) -> Result<(), Error> {
424 static PARSER: offset::Parser = offset::Parser::new()
425 .zulu(false)
426 .require_minute(true)
427 .subminute(true)
428 .subsecond(false)
429 .colon(offset::Colon::Absent);
430
431 let Parsed { value, input } = PARSER.parse(self.inp)?;
432 self.tm.offset = Some(value.to_offset()?);
433 self.inp = input;
434 self.bump_fmt();
435
436 Ok(())
437 }
438
439 fn parse_offset_colon(&mut self) -> Result<(), Error> {
442 static PARSER: offset::Parser = offset::Parser::new()
443 .zulu(false)
444 .require_minute(true)
445 .subminute(true)
446 .subsecond(false)
447 .colon(offset::Colon::Required);
448
449 let Parsed { value, input } = PARSER.parse(self.inp)?;
450 self.tm.offset = Some(value.to_offset()?);
451 self.inp = input;
452 self.bump_fmt();
453
454 Ok(())
455 }
456
457 fn parse_offset_colon2(&mut self) -> Result<(), Error> {
460 static PARSER: offset::Parser = offset::Parser::new()
461 .zulu(false)
462 .require_minute(true)
463 .require_second(true)
464 .subminute(true)
465 .subsecond(false)
466 .colon(offset::Colon::Required);
467
468 let Parsed { value, input } = PARSER.parse(self.inp)?;
469 self.tm.offset = Some(value.to_offset()?);
470 self.inp = input;
471 self.bump_fmt();
472
473 Ok(())
474 }
475
476 fn parse_offset_colon3(&mut self) -> Result<(), Error> {
480 static PARSER: offset::Parser = offset::Parser::new()
481 .zulu(false)
482 .require_minute(false)
483 .require_second(false)
484 .subminute(true)
485 .subsecond(false)
486 .colon(offset::Colon::Required);
487
488 let Parsed { value, input } = PARSER.parse(self.inp)?;
489 self.tm.offset = Some(value.to_offset()?);
490 self.inp = input;
491 self.bump_fmt();
492
493 Ok(())
494 }
495
496 fn parse_second(&mut self, ext: Extension) -> Result<(), Error> {
498 let (mut second, inp) = ext
499 .parse_number(2, Flag::PadZero, self.inp)
500 .context(PE::ParseSecond)?;
501 self.inp = inp;
502
503 if second == 60 {
507 second = 59;
508 }
509 self.tm.second =
510 Some(b::Second::check(second).context(PE::ParseSecond)?);
511 self.bump_fmt();
512 Ok(())
513 }
514
515 fn parse_timestamp(&mut self, ext: Extension) -> Result<(), Error> {
517 let (sign, inp) = parse_optional_sign(self.inp);
518 let (timestamp, inp) = ext
519 .parse_number(19, Flag::PadSpace, inp)
521 .context(PE::ParseTimestamp)?;
522 let timestamp =
526 timestamp.checked_mul(sign).ok_or(PE::ParseTimestamp)?;
527 let timestamp =
528 Timestamp::from_second(timestamp).context(PE::ParseTimestamp)?;
529 self.inp = inp;
530 self.tm.timestamp = Some(timestamp);
531
532 self.bump_fmt();
533 Ok(())
534 }
535
536 fn parse_fractional(&mut self, _ext: Extension) -> Result<(), Error> {
544 let mkdigits = parse::slicer(self.inp);
545 while mkdigits(self.inp).len() < 9
546 && self.inp.first().map_or(false, u8::is_ascii_digit)
547 {
548 self.inp = &self.inp[1..];
549 }
550 let digits = mkdigits(self.inp);
551 if digits.is_empty() {
552 return Err(Error::from(PE::ExpectedFractionalDigit));
553 }
554 let nanoseconds =
558 parse::fraction(digits).context(PE::ParseFractionalSeconds)?;
559 self.tm.subsec = Some(
564 b::SubsecNanosecond::check(nanoseconds)
565 .context(PE::ParseFractionalSeconds)?,
566 );
567 self.bump_fmt();
568 Ok(())
569 }
570
571 fn parse_dot_fractional(&mut self, ext: Extension) -> Result<(), Error> {
575 if !self.inp.starts_with(b".") {
576 self.bump_fmt();
577 return Ok(());
578 }
579 self.inp = &self.inp[1..];
580 self.parse_fractional(ext)
581 }
582
583 fn parse_month(&mut self, ext: Extension) -> Result<(), Error> {
585 let (month, inp) = ext
586 .parse_number(2, Flag::PadZero, self.inp)
587 .context(PE::ParseMonth)?;
588 self.inp = inp;
589
590 self.tm.month = Some(b::Month::check(month).context(PE::ParseMonth)?);
591 self.bump_fmt();
592 Ok(())
593 }
594
595 fn parse_month_name_abbrev(&mut self) -> Result<(), Error> {
597 let (index, inp) = parse_month_name_abbrev(self.inp)?;
598 self.inp = inp;
599
600 self.tm.month = Some(index + 1);
602 self.bump_fmt();
603 Ok(())
604 }
605
606 fn parse_month_name_full(&mut self) -> Result<(), Error> {
608 static CHOICES: &'static [&'static [u8]] = &[
609 b"January",
610 b"February",
611 b"March",
612 b"April",
613 b"May",
614 b"June",
615 b"July",
616 b"August",
617 b"September",
618 b"October",
619 b"November",
620 b"December",
621 ];
622
623 let (index, inp) =
624 parse_choice(self.inp, CHOICES).context(PE::UnknownMonthName)?;
625 self.inp = inp;
626
627 self.tm.month = Some(index + 1);
629 self.bump_fmt();
630 Ok(())
631 }
632
633 fn parse_weekday_abbrev(&mut self) -> Result<(), Error> {
635 let (index, inp) = parse_weekday_abbrev(self.inp)?;
636 self.inp = inp;
637
638 let index = i8::try_from(index).unwrap();
640 self.tm.weekday =
641 Some(Weekday::from_sunday_zero_offset(index).unwrap());
642 self.bump_fmt();
643 Ok(())
644 }
645
646 fn parse_weekday_full(&mut self) -> Result<(), Error> {
648 static CHOICES: &'static [&'static [u8]] = &[
649 b"Sunday",
650 b"Monday",
651 b"Tuesday",
652 b"Wednesday",
653 b"Thursday",
654 b"Friday",
655 b"Saturday",
656 ];
657
658 let (index, inp) = parse_choice(self.inp, CHOICES)
659 .context(PE::UnknownWeekdayAbbreviation)?;
660 self.inp = inp;
661
662 let index = i8::try_from(index).unwrap();
664 self.tm.weekday =
665 Some(Weekday::from_sunday_zero_offset(index).unwrap());
666 self.bump_fmt();
667 Ok(())
668 }
669
670 fn parse_weekday_mon(&mut self, ext: Extension) -> Result<(), Error> {
673 let (weekday, inp) = ext
674 .parse_number(1, Flag::NoPad, self.inp)
675 .context(PE::ParseWeekdayNumber)?;
676 self.inp = inp;
677
678 let weekday =
679 i8::try_from(weekday).map_err(|_| PE::ParseWeekdayNumber)?;
680 let weekday = Weekday::from_monday_one_offset(weekday)
681 .context(PE::ParseWeekdayNumber)?;
682 self.tm.weekday = Some(weekday);
683 self.bump_fmt();
684 Ok(())
685 }
686
687 fn parse_weekday_sun(&mut self, ext: Extension) -> Result<(), Error> {
689 let (weekday, inp) = ext
690 .parse_number(1, Flag::NoPad, self.inp)
691 .context(PE::ParseWeekdayNumber)?;
692 self.inp = inp;
693
694 let weekday =
695 i8::try_from(weekday).map_err(|_| PE::ParseWeekdayNumber)?;
696 let weekday = Weekday::from_sunday_zero_offset(weekday)
697 .context(PE::ParseWeekdayNumber)?;
698 self.tm.weekday = Some(weekday);
699 self.bump_fmt();
700 Ok(())
701 }
702
703 fn parse_week_sun(&mut self, ext: Extension) -> Result<(), Error> {
706 let (week, inp) = ext
707 .parse_number(2, Flag::PadZero, self.inp)
708 .context(PE::ParseSundayWeekNumber)?;
709 self.inp = inp;
710
711 self.tm.week_sun =
712 Some(b::WeekNum::check(week).context(PE::ParseSundayWeekNumber)?);
713 self.bump_fmt();
714 Ok(())
715 }
716
717 fn parse_week_iso(&mut self, ext: Extension) -> Result<(), Error> {
719 let (week, inp) = ext
720 .parse_number(2, Flag::PadZero, self.inp)
721 .context(PE::ParseIsoWeekNumber)?;
722 self.inp = inp;
723
724 self.tm.iso_week =
725 Some(b::ISOWeek::check(week).context(PE::ParseIsoWeekNumber)?);
726 self.bump_fmt();
727 Ok(())
728 }
729
730 fn parse_week_mon(&mut self, ext: Extension) -> Result<(), Error> {
733 let (week, inp) = ext
734 .parse_number(2, Flag::PadZero, self.inp)
735 .context(PE::ParseMondayWeekNumber)?;
736 self.inp = inp;
737
738 self.tm.week_mon =
739 Some(b::WeekNum::check(week).context(PE::ParseMondayWeekNumber)?);
740 self.bump_fmt();
741 Ok(())
742 }
743
744 fn parse_year(&mut self, ext: Extension) -> Result<(), Error> {
746 let (sign, inp) = parse_optional_sign(self.inp);
747 let (year, inp) =
748 ext.parse_number(4, Flag::PadZero, inp).context(PE::ParseYear)?;
749 self.inp = inp;
750
751 let year = sign.checked_mul(year).unwrap();
754 self.tm.year = Some(b::Year::check(year).context(PE::ParseYear)?);
755 self.bump_fmt();
756 Ok(())
757 }
758
759 fn parse_year2(&mut self, ext: Extension) -> Result<(), Error> {
763 let (year, inp) = ext
764 .parse_number(2, Flag::PadZero, self.inp)
765 .context(PE::ParseYearTwoDigit)?;
766 self.inp = inp;
767
768 let mut year =
769 b::YearTwoDigit::check(year).context(PE::ParseYearTwoDigit)?;
770 if year <= 68 {
771 year += 2000;
772 } else {
773 year += 1900;
774 }
775 self.tm.year = Some(year);
776 self.bump_fmt();
777 Ok(())
778 }
779
780 fn parse_century(&mut self, ext: Extension) -> Result<(), Error> {
783 let (sign, inp) = parse_optional_sign(self.inp);
784 let (century, inp) =
785 ext.parse_number(2, Flag::NoPad, inp).context(PE::ParseCentury)?;
786 self.inp = inp;
787
788 let century =
789 i64::from(b::Century::check(century).context(PE::ParseCentury)?);
790 let century = sign.checked_mul(century).unwrap();
793 let year = century.checked_mul(100).unwrap();
796 self.tm.year = Some(b::Year::check(year).context(PE::ParseCentury)?);
798 self.bump_fmt();
799 Ok(())
800 }
801
802 fn parse_iso_week_year(&mut self, ext: Extension) -> Result<(), Error> {
804 let (sign, inp) = parse_optional_sign(self.inp);
805 let (year, inp) = ext
806 .parse_number(4, Flag::PadZero, inp)
807 .context(PE::ParseIsoWeekYear)?;
808 self.inp = inp;
809
810 let year = sign.checked_mul(year).unwrap();
813 self.tm.iso_week_year =
814 Some(b::ISOYear::check(year).context(PE::ParseIsoWeekYear)?);
815 self.bump_fmt();
816 Ok(())
817 }
818
819 fn parse_iso_week_year2(&mut self, ext: Extension) -> Result<(), Error> {
823 let (year, inp) = ext
824 .parse_number(2, Flag::PadZero, self.inp)
825 .context(PE::ParseIsoWeekYearTwoDigit)?;
826 self.inp = inp;
827
828 let mut year = b::YearTwoDigit::check(year)
829 .context(PE::ParseIsoWeekYearTwoDigit)?;
830 if year <= 68 {
831 year += 2000;
832 } else {
833 year += 1900;
834 }
835 self.tm.iso_week_year = Some(year);
836 self.bump_fmt();
837 Ok(())
838 }
839}
840
841impl Extension {
842 #[cfg_attr(feature = "perf-inline", inline(always))]
858 fn parse_number<'i>(
859 &self,
860 default_pad_width: usize,
861 default_flag: Flag,
862 mut inp: &'i [u8],
863 ) -> Result<(i64, &'i [u8]), Error> {
864 let flag = self.flag.unwrap_or(default_flag);
865 let zero_pad_width = match flag {
866 Flag::PadSpace | Flag::NoPad => 0,
867 _ => self.width.map(usize::from).unwrap_or(default_pad_width),
868 };
869 let max_digits = default_pad_width.max(zero_pad_width);
870
871 while inp.get(0).map_or(false, |b| b.is_ascii_whitespace()) {
873 inp = &inp[1..];
874 }
875 let mut digits = 0;
876 while digits < inp.len()
877 && digits < zero_pad_width
878 && inp[digits] == b'0'
879 {
880 digits += 1;
881 }
882 let mut n: i64 = 0;
883 while digits < inp.len()
884 && digits < max_digits
885 && inp[digits].is_ascii_digit()
886 {
887 let byte = inp[digits];
888 digits += 1;
889 let digit = i64::from(byte - b'0');
893 n = n
894 .checked_mul(10)
895 .and_then(|n| n.checked_add(digit))
896 .ok_or(ParseIntError::TooBig)?;
897 }
898 if digits == 0 {
899 return Err(Error::from(ParseIntError::NoDigitsFound));
900 }
901 Ok((n, &inp[digits..]))
902 }
903}
904
905#[cfg_attr(feature = "perf-inline", inline(always))]
910fn parse_optional_sign<'i>(input: &'i [u8]) -> (i64, &'i [u8]) {
911 if input.is_empty() {
912 (1, input)
913 } else if input[0] == b'-' {
914 (-1, &input[1..])
915 } else if input[0] == b'+' {
916 (1, &input[1..])
917 } else {
918 (1, input)
919 }
920}
921
922fn parse_choice<'i>(
933 input: &'i [u8],
934 choices: &'static [&'static [u8]],
935) -> Result<(i8, &'i [u8]), Error> {
936 debug_assert!(choices.len() < usize::from(i8::MAX.unsigned_abs()));
937 for (i, choice) in choices.into_iter().enumerate() {
938 if input.len() < choice.len() {
939 continue;
940 }
941 let (candidate, input) = input.split_at(choice.len());
942 if candidate.eq_ignore_ascii_case(choice) {
943 return Ok((i8::try_from(i).unwrap(), input));
944 }
945 }
946 Err(Error::from(PE::ExpectedChoice { available: choices }))
947}
948
949#[cfg_attr(feature = "perf-inline", inline(always))]
954fn parse_ampm<'i>(input: &'i [u8]) -> Result<(usize, &'i [u8]), Error> {
955 if input.len() < 2 {
956 return Err(Error::from(PE::ExpectedAmPmTooShort));
957 }
958 let (x, input) = input.split_at(2);
959 let candidate = &[x[0].to_ascii_lowercase(), x[1].to_ascii_lowercase()];
960 let index = match candidate {
961 b"am" => 0,
962 b"pm" => 1,
963 _ => return Err(Error::from(PE::ExpectedAmPm)),
964 };
965 Ok((index, input))
966}
967
968#[cfg_attr(feature = "perf-inline", inline(always))]
973fn parse_weekday_abbrev<'i>(
974 input: &'i [u8],
975) -> Result<(usize, &'i [u8]), Error> {
976 if input.len() < 3 {
977 return Err(Error::from(PE::ExpectedWeekdayAbbreviationTooShort));
978 }
979 let (x, input) = input.split_at(3);
980 let candidate = &[
981 x[0].to_ascii_lowercase(),
982 x[1].to_ascii_lowercase(),
983 x[2].to_ascii_lowercase(),
984 ];
985 let index = match candidate {
986 b"sun" => 0,
987 b"mon" => 1,
988 b"tue" => 2,
989 b"wed" => 3,
990 b"thu" => 4,
991 b"fri" => 5,
992 b"sat" => 6,
993 _ => return Err(Error::from(PE::ExpectedWeekdayAbbreviation)),
994 };
995 Ok((index, input))
996}
997
998#[cfg_attr(feature = "perf-inline", inline(always))]
1003fn parse_month_name_abbrev<'i>(
1004 input: &'i [u8],
1005) -> Result<(i8, &'i [u8]), Error> {
1006 if input.len() < 3 {
1007 return Err(Error::from(PE::ExpectedMonthAbbreviationTooShort));
1008 }
1009 let (x, input) = input.split_at(3);
1010 let candidate = &[
1011 x[0].to_ascii_lowercase(),
1012 x[1].to_ascii_lowercase(),
1013 x[2].to_ascii_lowercase(),
1014 ];
1015 let index = match candidate {
1016 b"jan" => 0,
1017 b"feb" => 1,
1018 b"mar" => 2,
1019 b"apr" => 3,
1020 b"may" => 4,
1021 b"jun" => 5,
1022 b"jul" => 6,
1023 b"aug" => 7,
1024 b"sep" => 8,
1025 b"oct" => 9,
1026 b"nov" => 10,
1027 b"dec" => 11,
1028 _ => return Err(Error::from(PE::ExpectedMonthAbbreviation)),
1029 };
1030 Ok((index, input))
1031}
1032
1033#[cfg_attr(feature = "perf-inline", inline(always))]
1034fn parse_iana<'i>(input: &'i [u8]) -> Result<(&'i str, &'i [u8]), Error> {
1035 let mkiana = parse::slicer(input);
1036 let (_, mut input) = parse_iana_component(input)?;
1037 while let Some(tail) = input.strip_prefix(b"/") {
1038 input = tail;
1039 let (_, unconsumed) = parse_iana_component(input)?;
1040 input = unconsumed;
1041 }
1042 let iana = core::str::from_utf8(mkiana(input)).expect("ASCII");
1047 Ok((iana, input))
1048}
1049
1050#[cfg_attr(feature = "perf-inline", inline(always))]
1054fn parse_iana_component<'i>(
1055 mut input: &'i [u8],
1056) -> Result<(&'i [u8], &'i [u8]), Error> {
1057 let mkname = parse::slicer(input);
1058 if input.is_empty() {
1059 return Err(Error::from(PE::ExpectedIanaTzEndOfInput));
1060 }
1061 if !matches!(input[0], b'_' | b'.' | b'A'..=b'Z' | b'a'..=b'z') {
1062 return Err(Error::from(PE::ExpectedIanaTz));
1063 }
1064 input = &input[1..];
1065
1066 let is_iana_char = |byte| {
1067 matches!(
1068 byte,
1069 b'_' | b'.' | b'+' | b'-' | b'0'..=b'9' | b'A'..=b'Z' | b'a'..=b'z',
1070 )
1071 };
1072 loop {
1073 let Some((&first, tail)) = input.split_first() else { break };
1074 if !is_iana_char(first) {
1075 break;
1076 }
1077 input = tail;
1078 }
1079 Ok((mkname(input), input))
1080}
1081
1082#[cfg(feature = "alloc")]
1083#[cfg(test)]
1084mod tests {
1085 use alloc::string::ToString;
1086
1087 use super::*;
1088
1089 #[test]
1090 fn ok_parse_zoned() {
1091 if crate::tz::db().is_definitively_empty() {
1092 return;
1093 }
1094
1095 let p = |fmt: &str, input: &str| {
1096 BrokenDownTime::parse_mono(fmt.as_bytes(), input.as_bytes())
1097 .unwrap()
1098 .to_zoned()
1099 .unwrap()
1100 };
1101
1102 insta::assert_debug_snapshot!(
1103 p("%h %d, %Y %H:%M:%S %z", "Apr 1, 2022 20:46:15 -0400"),
1104 @"2022-04-01T20:46:15-04:00[-04:00]",
1105 );
1106 insta::assert_debug_snapshot!(
1107 p("%h %d, %Y %H:%M:%S %Q", "Apr 1, 2022 20:46:15 -0400"),
1108 @"2022-04-01T20:46:15-04:00[-04:00]",
1109 );
1110 insta::assert_debug_snapshot!(
1111 p("%h %d, %Y %H:%M:%S [%Q]", "Apr 1, 2022 20:46:15 [America/New_York]"),
1112 @"2022-04-01T20:46:15-04:00[America/New_York]",
1113 );
1114 insta::assert_debug_snapshot!(
1115 p("%h %d, %Y %H:%M:%S %Q", "Apr 1, 2022 20:46:15 America/New_York"),
1116 @"2022-04-01T20:46:15-04:00[America/New_York]",
1117 );
1118 insta::assert_debug_snapshot!(
1119 p("%h %d, %Y %H:%M:%S %:z %:Q", "Apr 1, 2022 20:46:15 -08:00 -04:00"),
1120 @"2022-04-01T20:46:15-04:00[-04:00]",
1121 );
1122 }
1123
1124 #[test]
1125 fn ok_parse_timestamp() {
1126 let p = |fmt: &str, input: &str| {
1127 BrokenDownTime::parse_mono(fmt.as_bytes(), input.as_bytes())
1128 .unwrap()
1129 .to_timestamp()
1130 .unwrap()
1131 };
1132
1133 insta::assert_debug_snapshot!(
1134 p("%h %d, %Y %H:%M:%S %z", "Apr 1, 2022 20:46:15 -0400"),
1135 @"2022-04-02T00:46:15Z",
1136 );
1137 insta::assert_debug_snapshot!(
1138 p("%h %d, %Y %H:%M:%S %z", "Apr 1, 2022 20:46:15 +0400"),
1139 @"2022-04-01T16:46:15Z",
1140 );
1141 insta::assert_debug_snapshot!(
1142 p("%h %d, %Y %H:%M:%S %z", "Apr 1, 2022 20:46:15 -040059"),
1143 @"2022-04-02T00:47:14Z",
1144 );
1145
1146 insta::assert_debug_snapshot!(
1147 p("%h %d, %Y %H:%M:%S %:z", "Apr 1, 2022 20:46:15 -04:00"),
1148 @"2022-04-02T00:46:15Z",
1149 );
1150 insta::assert_debug_snapshot!(
1151 p("%h %d, %Y %H:%M:%S %:z", "Apr 1, 2022 20:46:15 +04:00"),
1152 @"2022-04-01T16:46:15Z",
1153 );
1154 insta::assert_debug_snapshot!(
1155 p("%h %d, %Y %H:%M:%S %:z", "Apr 1, 2022 20:46:15 -04:00:59"),
1156 @"2022-04-02T00:47:14Z",
1157 );
1158
1159 insta::assert_debug_snapshot!(
1160 p("%s", "0"),
1161 @"1970-01-01T00:00:00Z",
1162 );
1163 insta::assert_debug_snapshot!(
1164 p("%s", "-0"),
1165 @"1970-01-01T00:00:00Z",
1166 );
1167 insta::assert_debug_snapshot!(
1168 p("%s", "-1"),
1169 @"1969-12-31T23:59:59Z",
1170 );
1171 insta::assert_debug_snapshot!(
1172 p("%s", "1"),
1173 @"1970-01-01T00:00:01Z",
1174 );
1175 insta::assert_debug_snapshot!(
1176 p("%s", "+1"),
1177 @"1970-01-01T00:00:01Z",
1178 );
1179 insta::assert_debug_snapshot!(
1180 p("%s", "1737396540"),
1181 @"2025-01-20T18:09:00Z",
1182 );
1183 insta::assert_debug_snapshot!(
1184 p("%s", "-377705023201"),
1185 @"-009999-01-02T01:59:59Z",
1186 );
1187 insta::assert_debug_snapshot!(
1188 p("%s", "253402207200"),
1189 @"9999-12-30T22:00:00Z",
1190 );
1191 }
1192
1193 #[test]
1194 fn ok_parse_datetime() {
1195 let p = |fmt: &str, input: &str| {
1196 BrokenDownTime::parse_mono(fmt.as_bytes(), input.as_bytes())
1197 .unwrap()
1198 .to_datetime()
1199 .unwrap()
1200 };
1201
1202 insta::assert_debug_snapshot!(
1203 p("%h %d, %Y %H:%M:%S", "Apr 1, 2022 20:46:15"),
1204 @"2022-04-01T20:46:15",
1205 );
1206 insta::assert_debug_snapshot!(
1207 p("%h %05d, %Y %H:%M:%S", "Apr 1, 2022 20:46:15"),
1208 @"2022-04-01T20:46:15",
1209 );
1210 }
1211
1212 #[test]
1213 fn ok_parse_date() {
1214 let p = |fmt: &str, input: &str| {
1215 BrokenDownTime::parse_mono(fmt.as_bytes(), input.as_bytes())
1216 .unwrap()
1217 .to_date()
1218 .unwrap()
1219 };
1220
1221 insta::assert_debug_snapshot!(
1222 p("%m/%d/%y", "1/1/99"),
1223 @"1999-01-01",
1224 );
1225 insta::assert_debug_snapshot!(
1226 p("%m/%d/%04y", "1/1/0099"),
1227 @"1999-01-01",
1228 );
1229 insta::assert_debug_snapshot!(
1230 p("%D", "1/1/99"),
1231 @"1999-01-01",
1232 );
1233 insta::assert_debug_snapshot!(
1234 p("%m/%d/%Y", "1/1/0099"),
1235 @"0099-01-01",
1236 );
1237 insta::assert_debug_snapshot!(
1238 p("%m/%d/%Y", "1/1/1999"),
1239 @"1999-01-01",
1240 );
1241 insta::assert_debug_snapshot!(
1242 p("%m/%d/%Y", "12/31/9999"),
1243 @"9999-12-31",
1244 );
1245 insta::assert_debug_snapshot!(
1246 p("%m/%d/%Y", "01/01/-9999"),
1247 @"-009999-01-01",
1248 );
1249 insta::assert_snapshot!(
1250 p("%a %m/%d/%Y", "sun 7/14/2024"),
1251 @"2024-07-14",
1252 );
1253 insta::assert_snapshot!(
1254 p("%A %m/%d/%Y", "sUnDaY 7/14/2024"),
1255 @"2024-07-14",
1256 );
1257 insta::assert_snapshot!(
1258 p("%b %d %Y", "Jul 14 2024"),
1259 @"2024-07-14",
1260 );
1261 insta::assert_snapshot!(
1262 p("%B %d, %Y", "July 14, 2024"),
1263 @"2024-07-14",
1264 );
1265 insta::assert_snapshot!(
1266 p("%A, %B %d, %Y", "Wednesday, dEcEmBeR 25, 2024"),
1267 @"2024-12-25",
1268 );
1269
1270 insta::assert_debug_snapshot!(
1271 p("%Y%m%d", "20240730"),
1272 @"2024-07-30",
1273 );
1274 insta::assert_debug_snapshot!(
1275 p("%Y%m%d", "09990730"),
1276 @"0999-07-30",
1277 );
1278 insta::assert_debug_snapshot!(
1279 p("%Y%m%d", "9990111"),
1280 @"9990-11-01",
1281 );
1282 insta::assert_debug_snapshot!(
1283 p("%3Y%m%d", "09990111"),
1284 @"0999-01-11",
1285 );
1286 insta::assert_debug_snapshot!(
1287 p("%5Y%m%d", "09990111"),
1288 @"9990-11-01",
1289 );
1290 insta::assert_debug_snapshot!(
1291 p("%5Y%m%d", "009990111"),
1292 @"0999-01-11",
1293 );
1294
1295 insta::assert_debug_snapshot!(
1296 p("%C-%m-%d", "20-07-01"),
1297 @"2000-07-01",
1298 );
1299 insta::assert_debug_snapshot!(
1300 p("%C-%m-%d", "-20-07-01"),
1301 @"-002000-07-01",
1302 );
1303 insta::assert_debug_snapshot!(
1304 p("%C-%m-%d", "9-07-01"),
1305 @"0900-07-01",
1306 );
1307 insta::assert_debug_snapshot!(
1308 p("%C-%m-%d", "-9-07-01"),
1309 @"-000900-07-01",
1310 );
1311 insta::assert_debug_snapshot!(
1312 p("%C-%m-%d", "09-07-01"),
1313 @"0900-07-01",
1314 );
1315 insta::assert_debug_snapshot!(
1316 p("%C-%m-%d", "-09-07-01"),
1317 @"-000900-07-01",
1318 );
1319 insta::assert_debug_snapshot!(
1320 p("%C-%m-%d", "0-07-01"),
1321 @"0000-07-01",
1322 );
1323 insta::assert_debug_snapshot!(
1324 p("%C-%m-%d", "-0-07-01"),
1325 @"0000-07-01",
1326 );
1327
1328 insta::assert_snapshot!(
1329 p("%u %m/%d/%Y", "7 7/14/2024"),
1330 @"2024-07-14",
1331 );
1332 insta::assert_snapshot!(
1333 p("%w %m/%d/%Y", "0 7/14/2024"),
1334 @"2024-07-14",
1335 );
1336
1337 insta::assert_snapshot!(
1338 p("%Y-%U-%u", "2025-00-6"),
1339 @"2025-01-04",
1340 );
1341 insta::assert_snapshot!(
1342 p("%Y-%U-%u", "2025-01-7"),
1343 @"2025-01-05",
1344 );
1345 insta::assert_snapshot!(
1346 p("%Y-%U-%u", "2025-01-1"),
1347 @"2025-01-06",
1348 );
1349
1350 insta::assert_snapshot!(
1351 p("%Y-%W-%u", "2025-00-6"),
1352 @"2025-01-04",
1353 );
1354 insta::assert_snapshot!(
1355 p("%Y-%W-%u", "2025-00-7"),
1356 @"2025-01-05",
1357 );
1358 insta::assert_snapshot!(
1359 p("%Y-%W-%u", "2025-01-1"),
1360 @"2025-01-06",
1361 );
1362 insta::assert_snapshot!(
1363 p("%Y-%W-%u", "2025-01-2"),
1364 @"2025-01-07",
1365 );
1366 }
1367
1368 #[test]
1369 fn ok_parse_time() {
1370 let p = |fmt: &str, input: &str| {
1371 BrokenDownTime::parse_mono(fmt.as_bytes(), input.as_bytes())
1372 .unwrap()
1373 .to_time()
1374 .unwrap()
1375 };
1376
1377 insta::assert_debug_snapshot!(
1378 p("%H:%M", "15:48"),
1379 @"15:48:00",
1380 );
1381 insta::assert_debug_snapshot!(
1382 p("%H:%M:%S", "15:48:59"),
1383 @"15:48:59",
1384 );
1385 insta::assert_debug_snapshot!(
1386 p("%H:%M:%S", "15:48:60"),
1387 @"15:48:59",
1388 );
1389 insta::assert_debug_snapshot!(
1390 p("%T", "15:48:59"),
1391 @"15:48:59",
1392 );
1393 insta::assert_debug_snapshot!(
1394 p("%R", "15:48"),
1395 @"15:48:00",
1396 );
1397
1398 insta::assert_debug_snapshot!(
1399 p("%H %p", "5 am"),
1400 @"05:00:00",
1401 );
1402 insta::assert_debug_snapshot!(
1403 p("%H%p", "5am"),
1404 @"05:00:00",
1405 );
1406 insta::assert_debug_snapshot!(
1407 p("%H%p", "11pm"),
1408 @"23:00:00",
1409 );
1410 insta::assert_debug_snapshot!(
1411 p("%I%p", "11pm"),
1412 @"23:00:00",
1413 );
1414 insta::assert_debug_snapshot!(
1415 p("%I%p", "12am"),
1416 @"00:00:00",
1417 );
1418 insta::assert_debug_snapshot!(
1419 p("%H%p", "23pm"),
1420 @"23:00:00",
1421 );
1422 insta::assert_debug_snapshot!(
1423 p("%H%p", "23am"),
1424 @"11:00:00",
1425 );
1426
1427 insta::assert_debug_snapshot!(
1428 p("%H:%M:%S%.f", "15:48:01.1"),
1429 @"15:48:01.1",
1430 );
1431 insta::assert_debug_snapshot!(
1432 p("%H:%M:%S%.255f", "15:48:01.1"),
1433 @"15:48:01.1",
1434 );
1435 insta::assert_debug_snapshot!(
1436 p("%H:%M:%S%255.255f", "15:48:01.1"),
1437 @"15:48:01.1",
1438 );
1439 insta::assert_debug_snapshot!(
1440 p("%H:%M:%S%.f", "15:48:01"),
1441 @"15:48:01",
1442 );
1443 insta::assert_debug_snapshot!(
1444 p("%H:%M:%S%.fa", "15:48:01a"),
1445 @"15:48:01",
1446 );
1447 insta::assert_debug_snapshot!(
1448 p("%H:%M:%S%.f", "15:48:01.123456789"),
1449 @"15:48:01.123456789",
1450 );
1451 insta::assert_debug_snapshot!(
1452 p("%H:%M:%S%.f", "15:48:01.000000001"),
1453 @"15:48:01.000000001",
1454 );
1455
1456 insta::assert_debug_snapshot!(
1457 p("%H:%M:%S.%f", "15:48:01.1"),
1458 @"15:48:01.1",
1459 );
1460 insta::assert_debug_snapshot!(
1461 p("%H:%M:%S.%3f", "15:48:01.123"),
1462 @"15:48:01.123",
1463 );
1464 insta::assert_debug_snapshot!(
1465 p("%H:%M:%S.%3f", "15:48:01.123456"),
1466 @"15:48:01.123456",
1467 );
1468
1469 insta::assert_debug_snapshot!(
1470 p("%H:%M:%S.%N", "15:48:01.1"),
1471 @"15:48:01.1",
1472 );
1473 insta::assert_debug_snapshot!(
1474 p("%H:%M:%S.%3N", "15:48:01.123"),
1475 @"15:48:01.123",
1476 );
1477 insta::assert_debug_snapshot!(
1478 p("%H:%M:%S.%3N", "15:48:01.123456"),
1479 @"15:48:01.123456",
1480 );
1481
1482 insta::assert_debug_snapshot!(
1483 p("%H", "09"),
1484 @"09:00:00",
1485 );
1486 insta::assert_debug_snapshot!(
1487 p("%H", " 9"),
1488 @"09:00:00",
1489 );
1490 insta::assert_debug_snapshot!(
1491 p("%H", "15"),
1492 @"15:00:00",
1493 );
1494 insta::assert_debug_snapshot!(
1495 p("%k", "09"),
1496 @"09:00:00",
1497 );
1498 insta::assert_debug_snapshot!(
1499 p("%k", " 9"),
1500 @"09:00:00",
1501 );
1502 insta::assert_debug_snapshot!(
1503 p("%k", "15"),
1504 @"15:00:00",
1505 );
1506
1507 insta::assert_debug_snapshot!(
1508 p("%I", "09"),
1509 @"09:00:00",
1510 );
1511 insta::assert_debug_snapshot!(
1512 p("%I", " 9"),
1513 @"09:00:00",
1514 );
1515 insta::assert_debug_snapshot!(
1516 p("%l", "09"),
1517 @"09:00:00",
1518 );
1519 insta::assert_debug_snapshot!(
1520 p("%l", " 9"),
1521 @"09:00:00",
1522 );
1523 }
1524
1525 #[test]
1526 fn ok_parse_whitespace() {
1527 let p = |fmt: &str, input: &str| {
1528 BrokenDownTime::parse_mono(fmt.as_bytes(), input.as_bytes())
1529 .unwrap()
1530 .to_time()
1531 .unwrap()
1532 };
1533
1534 insta::assert_debug_snapshot!(
1535 p("%H%M", "1548"),
1536 @"15:48:00",
1537 );
1538 insta::assert_debug_snapshot!(
1539 p("%H%M", "15\n48"),
1540 @"15:48:00",
1541 );
1542 insta::assert_debug_snapshot!(
1543 p("%H%M", "15\t48"),
1544 @"15:48:00",
1545 );
1546 insta::assert_debug_snapshot!(
1547 p("%H%n%M", "1548"),
1548 @"15:48:00",
1549 );
1550 insta::assert_debug_snapshot!(
1551 p("%H%n%M", "15\n48"),
1552 @"15:48:00",
1553 );
1554 insta::assert_debug_snapshot!(
1555 p("%H%n%M", "15\t48"),
1556 @"15:48:00",
1557 );
1558 insta::assert_debug_snapshot!(
1559 p("%H%t%M", "1548"),
1560 @"15:48:00",
1561 );
1562 insta::assert_debug_snapshot!(
1563 p("%H%t%M", "15\n48"),
1564 @"15:48:00",
1565 );
1566 insta::assert_debug_snapshot!(
1567 p("%H%t%M", "15\t48"),
1568 @"15:48:00",
1569 );
1570 }
1571
1572 #[test]
1573 fn ok_parse_offset() {
1574 let p = |fmt: &str, input: &str| {
1575 BrokenDownTime::parse_mono(fmt.as_bytes(), input.as_bytes())
1576 .unwrap()
1577 .offset
1578 .unwrap()
1579 };
1580
1581 insta::assert_debug_snapshot!(
1582 p("%z", "+0530"),
1583 @"05:30:00",
1584 );
1585 insta::assert_debug_snapshot!(
1586 p("%z", "-0530"),
1587 @"-05:30:00",
1588 );
1589 insta::assert_debug_snapshot!(
1590 p("%z", "-0500"),
1591 @"-05:00:00",
1592 );
1593 insta::assert_debug_snapshot!(
1594 p("%z", "+053015"),
1595 @"05:30:15",
1596 );
1597 insta::assert_debug_snapshot!(
1598 p("%z", "+050015"),
1599 @"05:00:15",
1600 );
1601
1602 insta::assert_debug_snapshot!(
1603 p("%:z", "+05:30"),
1604 @"05:30:00",
1605 );
1606 insta::assert_debug_snapshot!(
1607 p("%:z", "-05:30"),
1608 @"-05:30:00",
1609 );
1610 insta::assert_debug_snapshot!(
1611 p("%:z", "-05:00"),
1612 @"-05:00:00",
1613 );
1614 insta::assert_debug_snapshot!(
1615 p("%:z", "+05:30:15"),
1616 @"05:30:15",
1617 );
1618 insta::assert_debug_snapshot!(
1619 p("%:z", "-05:00:15"),
1620 @"-05:00:15",
1621 );
1622
1623 insta::assert_debug_snapshot!(
1624 p("%::z", "+05:30:15"),
1625 @"05:30:15",
1626 );
1627 insta::assert_debug_snapshot!(
1628 p("%::z", "-05:30:15"),
1629 @"-05:30:15",
1630 );
1631 insta::assert_debug_snapshot!(
1632 p("%::z", "-05:00:00"),
1633 @"-05:00:00",
1634 );
1635 insta::assert_debug_snapshot!(
1636 p("%::z", "-05:00:15"),
1637 @"-05:00:15",
1638 );
1639
1640 insta::assert_debug_snapshot!(
1641 p("%:::z", "+05"),
1642 @"05:00:00",
1643 );
1644 insta::assert_debug_snapshot!(
1645 p("%:::z", "-05"),
1646 @"-05:00:00",
1647 );
1648 insta::assert_debug_snapshot!(
1649 p("%:::z", "+00"),
1650 @"00:00:00",
1651 );
1652 insta::assert_debug_snapshot!(
1653 p("%:::z", "-00"),
1654 @"00:00:00",
1655 );
1656 insta::assert_debug_snapshot!(
1657 p("%:::z", "+05:30"),
1658 @"05:30:00",
1659 );
1660 insta::assert_debug_snapshot!(
1661 p("%:::z", "-05:30"),
1662 @"-05:30:00",
1663 );
1664 insta::assert_debug_snapshot!(
1665 p("%:::z", "+05:30:15"),
1666 @"05:30:15",
1667 );
1668 insta::assert_debug_snapshot!(
1669 p("%:::z", "-05:30:15"),
1670 @"-05:30:15",
1671 );
1672 insta::assert_debug_snapshot!(
1673 p("%:::z", "-05:00:00"),
1674 @"-05:00:00",
1675 );
1676 insta::assert_debug_snapshot!(
1677 p("%:::z", "-05:00:15"),
1678 @"-05:00:15",
1679 );
1680 }
1681
1682 #[test]
1683 fn err_parse() {
1684 let p = |fmt: &str, input: &str| {
1685 BrokenDownTime::parse_mono(fmt.as_bytes(), input.as_bytes())
1686 .unwrap_err()
1687 .to_string()
1688 };
1689
1690 insta::assert_snapshot!(
1691 p("%M", ""),
1692 @"strptime parsing failed: expected non-empty input for directive `%M`, but found end of input",
1693 );
1694 insta::assert_snapshot!(
1695 p("%M", "a"),
1696 @"strptime parsing failed: %M failed: failed to parse minute number: invalid number, no digits found",
1697 );
1698 insta::assert_snapshot!(
1699 p("%M%S", "15"),
1700 @"strptime parsing failed: expected non-empty input for directive `%S`, but found end of input",
1701 );
1702 insta::assert_snapshot!(
1703 p("%M%a", "Sun"),
1704 @"strptime parsing failed: %M failed: failed to parse minute number: invalid number, no digits found",
1705 );
1706
1707 insta::assert_snapshot!(
1708 p("%y", "999"),
1709 @"strptime expects to consume the entire input, but `9` remains unparsed",
1710 );
1711 insta::assert_snapshot!(
1712 p("%Y", "-10000"),
1713 @"strptime expects to consume the entire input, but `0` remains unparsed",
1714 );
1715 insta::assert_snapshot!(
1716 p("%Y", "10000"),
1717 @"strptime expects to consume the entire input, but `0` remains unparsed",
1718 );
1719 insta::assert_snapshot!(
1720 p("%A %m/%d/%y", "Mon 7/14/24"),
1721 @"strptime parsing failed: %A failed: unrecognized weekday abbreviation: failed to find expected value, available choices are: Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday",
1722 );
1723 insta::assert_snapshot!(
1724 p("%b", "Bad"),
1725 @"strptime parsing failed: %b failed: expected to find month name abbreviation",
1726 );
1727 insta::assert_snapshot!(
1728 p("%h", "July"),
1729 @"strptime expects to consume the entire input, but `y` remains unparsed",
1730 );
1731 insta::assert_snapshot!(
1732 p("%B", "Jul"),
1733 @"strptime parsing failed: %B failed: unrecognized month name: failed to find expected value, available choices are: January, February, March, April, May, June, July, August, September, October, November, December",
1734 );
1735 insta::assert_snapshot!(
1736 p("%H", "24"),
1737 @"strptime parsing failed: %H failed: failed to parse hour number: parameter 'hour' is not in the required range of 0..=23",
1738 );
1739 insta::assert_snapshot!(
1740 p("%M", "60"),
1741 @"strptime parsing failed: %M failed: failed to parse minute number: parameter 'minute' is not in the required range of 0..=59",
1742 );
1743 insta::assert_snapshot!(
1744 p("%S", "61"),
1745 @"strptime parsing failed: %S failed: failed to parse second number: parameter 'second' is not in the required range of 0..=59",
1746 );
1747 insta::assert_snapshot!(
1748 p("%I", "0"),
1749 @"strptime parsing failed: %I failed: failed to parse hour number: parameter 'hour (12 hour clock)' is not in the required range of 1..=12",
1750 );
1751 insta::assert_snapshot!(
1752 p("%I", "13"),
1753 @"strptime parsing failed: %I failed: failed to parse hour number: parameter 'hour (12 hour clock)' is not in the required range of 1..=12",
1754 );
1755 insta::assert_snapshot!(
1756 p("%p", "aa"),
1757 @"strptime parsing failed: %p failed: expected to find `AM` or `PM`",
1758 );
1759
1760 insta::assert_snapshot!(
1761 p("%_", " "),
1762 @"strptime parsing failed: expected to find specifier directive after flag `_`, but found end of format string",
1763 );
1764 insta::assert_snapshot!(
1765 p("%-", " "),
1766 @"strptime parsing failed: expected to find specifier directive after flag `-`, but found end of format string",
1767 );
1768 insta::assert_snapshot!(
1769 p("%0", " "),
1770 @"strptime parsing failed: expected to find specifier directive after flag `0`, but found end of format string",
1771 );
1772 insta::assert_snapshot!(
1773 p("%^", " "),
1774 @"strptime parsing failed: expected to find specifier directive after flag `^`, but found end of format string",
1775 );
1776 insta::assert_snapshot!(
1777 p("%#", " "),
1778 @"strptime parsing failed: expected to find specifier directive after flag `#`, but found end of format string",
1779 );
1780 insta::assert_snapshot!(
1781 p("%_1", " "),
1782 @"strptime parsing failed: expected to find specifier directive after parsed width, but found end of format string",
1783 );
1784 insta::assert_snapshot!(
1785 p("%_23", " "),
1786 @"strptime parsing failed: expected to find specifier directive after parsed width, but found end of format string",
1787 );
1788
1789 insta::assert_snapshot!(
1790 p("%:", " "),
1791 @"strptime parsing failed: expected to find specifier directive after colons, but found end of format string",
1792 );
1793
1794 insta::assert_snapshot!(
1795 p("%::", " "),
1796 @"strptime parsing failed: expected to find specifier directive after colons, but found end of format string",
1797 );
1798
1799 insta::assert_snapshot!(
1800 p("%:::", " "),
1801 @"strptime parsing failed: expected to find specifier directive after colons, but found end of format string",
1802 );
1803
1804 insta::assert_snapshot!(
1805 p("%H:%M:%S%.f", "15:59:01."),
1806 @"strptime parsing failed: %.f failed: expected at least one fractional decimal digit, but did not find any",
1807 );
1808 insta::assert_snapshot!(
1809 p("%H:%M:%S%.f", "15:59:01.a"),
1810 @"strptime parsing failed: %.f failed: expected at least one fractional decimal digit, but did not find any",
1811 );
1812 insta::assert_snapshot!(
1813 p("%H:%M:%S%.f", "15:59:01.1234567891"),
1814 @"strptime expects to consume the entire input, but `1` remains unparsed",
1815 );
1816 insta::assert_snapshot!(
1817 p("%H:%M:%S.%f", "15:59:01."),
1818 @"strptime parsing failed: expected non-empty input for directive `%f`, but found end of input",
1819 );
1820 insta::assert_snapshot!(
1821 p("%H:%M:%S.%f", "15:59:01"),
1822 @"strptime parsing failed: expected to match literal byte `.` from format string, but found end of input",
1823 );
1824 insta::assert_snapshot!(
1825 p("%H:%M:%S.%f", "15:59:01.a"),
1826 @"strptime parsing failed: %f failed: expected at least one fractional decimal digit, but did not find any",
1827 );
1828 insta::assert_snapshot!(
1829 p("%H:%M:%S.%N", "15:59:01."),
1830 @"strptime parsing failed: expected non-empty input for directive `%N`, but found end of input",
1831 );
1832 insta::assert_snapshot!(
1833 p("%H:%M:%S.%N", "15:59:01"),
1834 @"strptime parsing failed: expected to match literal byte `.` from format string, but found end of input",
1835 );
1836 insta::assert_snapshot!(
1837 p("%H:%M:%S.%N", "15:59:01.a"),
1838 @"strptime parsing failed: %N failed: expected at least one fractional decimal digit, but did not find any",
1839 );
1840
1841 insta::assert_snapshot!(
1842 p("%Q", "+America/New_York"),
1843 @"strptime parsing failed: %Q failed: failed to parse hours in UTC numeric offset: failed to parse hours (requires a two digit integer): invalid digit, expected 0-9 but got A",
1844 );
1845 insta::assert_snapshot!(
1846 p("%Q", "-America/New_York"),
1847 @"strptime parsing failed: %Q failed: failed to parse hours in UTC numeric offset: failed to parse hours (requires a two digit integer): invalid digit, expected 0-9 but got A",
1848 );
1849 insta::assert_snapshot!(
1850 p("%:Q", "+0400"),
1851 @"strptime parsing failed: %:Q failed: parsed hour component of time zone offset, but could not find required colon separator",
1852 );
1853 insta::assert_snapshot!(
1854 p("%Q", "+04:00"),
1855 @"strptime parsing failed: %Q failed: parsed hour component of time zone offset, but found colon after hours which is not allowed",
1856 );
1857 insta::assert_snapshot!(
1858 p("%Q", "America/"),
1859 @"strptime parsing failed: %Q failed: expected to find the start of an IANA time zone identifier name or component, but found end of input instead",
1860 );
1861 insta::assert_snapshot!(
1862 p("%Q", "America/+"),
1863 @"strptime parsing failed: %Q failed: expected to find the start of an IANA time zone identifier name or component",
1864 );
1865
1866 insta::assert_snapshot!(
1867 p("%s", "-377705023202"),
1868 @"strptime parsing failed: %s failed: failed to parse Unix timestamp (in seconds): parameter 'Unix timestamp seconds' is not in the required range of -377705023201..=253402207200",
1869 );
1870 insta::assert_snapshot!(
1871 p("%s", "253402207201"),
1872 @"strptime parsing failed: %s failed: failed to parse Unix timestamp (in seconds): parameter 'Unix timestamp seconds' is not in the required range of -377705023201..=253402207200",
1873 );
1874 insta::assert_snapshot!(
1875 p("%s", "-9999999999999999999"),
1876 @"strptime parsing failed: %s failed: failed to parse Unix timestamp (in seconds): number too big to parse into 64-bit integer",
1877 );
1878 insta::assert_snapshot!(
1879 p("%s", "9999999999999999999"),
1880 @"strptime parsing failed: %s failed: failed to parse Unix timestamp (in seconds): number too big to parse into 64-bit integer",
1881 );
1882
1883 insta::assert_snapshot!(
1884 p("%u", "0"),
1885 @"strptime parsing failed: %u failed: failed to parse weekday number: parameter 'weekday (Monday 1-indexed)' is not in the required range of 1..=7",
1886 );
1887 insta::assert_snapshot!(
1888 p("%w", "7"),
1889 @"strptime parsing failed: %w failed: failed to parse weekday number: parameter 'weekday (Sunday 0-indexed)' is not in the required range of 0..=6",
1890 );
1891 insta::assert_snapshot!(
1892 p("%u", "128"),
1893 @"strptime expects to consume the entire input, but `28` remains unparsed",
1894 );
1895 insta::assert_snapshot!(
1896 p("%w", "128"),
1897 @"strptime expects to consume the entire input, but `28` remains unparsed",
1898 );
1899 }
1900
1901 #[test]
1902 fn err_parse_date() {
1903 let p = |fmt: &str, input: &str| {
1904 BrokenDownTime::parse_mono(fmt.as_bytes(), input.as_bytes())
1905 .unwrap()
1906 .to_date()
1907 .unwrap_err()
1908 .to_string()
1909 };
1910
1911 insta::assert_snapshot!(
1912 p("%Y", "2024"),
1913 @"a month/day, day-of-year or week date must be present to create a date, but none were found",
1914 );
1915 insta::assert_snapshot!(
1916 p("%m", "7"),
1917 @"year required to parse date",
1918 );
1919 insta::assert_snapshot!(
1920 p("%d", "25"),
1921 @"year required to parse date",
1922 );
1923 insta::assert_snapshot!(
1924 p("%Y-%m", "2024-7"),
1925 @"a month/day, day-of-year or week date must be present to create a date, but none were found",
1926 );
1927 insta::assert_snapshot!(
1928 p("%Y-%d", "2024-25"),
1929 @"a month/day, day-of-year or week date must be present to create a date, but none were found",
1930 );
1931 insta::assert_snapshot!(
1932 p("%m-%d", "7-25"),
1933 @"year required to parse date",
1934 );
1935
1936 insta::assert_snapshot!(
1937 p("%m/%d/%y", "6/31/24"),
1938 @"invalid date: parameter 'day' for `2024-06` is invalid, must be in range `1..=30`",
1939 );
1940 insta::assert_snapshot!(
1941 p("%m/%d/%y", "2/29/23"),
1942 @"invalid date: parameter 'day' for `2023-02` is invalid, must be in range `1..=28`",
1943 );
1944 insta::assert_snapshot!(
1945 p("%a %m/%d/%y", "Mon 7/14/24"),
1946 @"parsed weekday `Monday` does not match weekday `Sunday` from parsed date",
1947 );
1948 insta::assert_snapshot!(
1949 p("%A %m/%d/%y", "Monday 7/14/24"),
1950 @"parsed weekday `Monday` does not match weekday `Sunday` from parsed date",
1951 );
1952
1953 insta::assert_snapshot!(
1954 p("%Y-%U-%u", "2025-00-2"),
1955 @"weekday `Tuesday` is not valid for Sunday based week number",
1956 );
1957 insta::assert_snapshot!(
1958 p("%Y-%W-%u", "2025-00-2"),
1959 @"weekday `Tuesday` is not valid for Monday based week number",
1960 );
1961 }
1962
1963 #[test]
1964 fn err_parse_time() {
1965 let p = |fmt: &str, input: &str| {
1966 BrokenDownTime::parse_mono(fmt.as_bytes(), input.as_bytes())
1967 .unwrap()
1968 .to_time()
1969 .unwrap_err()
1970 .to_string()
1971 };
1972
1973 insta::assert_snapshot!(
1974 p("%M", "59"),
1975 @"parsing format did not include hour directive, but did include minute directive (cannot have smaller time units with bigger time units missing)",
1976 );
1977 insta::assert_snapshot!(
1978 p("%S", "59"),
1979 @"parsing format did not include hour directive, but did include second directive (cannot have smaller time units with bigger time units missing)",
1980 );
1981 insta::assert_snapshot!(
1982 p("%M:%S", "59:59"),
1983 @"parsing format did not include hour directive, but did include minute directive (cannot have smaller time units with bigger time units missing)",
1984 );
1985 insta::assert_snapshot!(
1986 p("%H:%S", "15:59"),
1987 @"parsing format did not include minute directive, but did include second directive (cannot have smaller time units with bigger time units missing)",
1988 );
1989 }
1990
1991 #[test]
1992 fn err_parse_offset() {
1993 let p = |fmt: &str, input: &str| {
1994 BrokenDownTime::parse_mono(fmt.as_bytes(), input.as_bytes())
1995 .unwrap_err()
1996 .to_string()
1997 };
1998
1999 insta::assert_snapshot!(
2000 p("%z", "+05:30"),
2001 @"strptime parsing failed: %z failed: parsed hour component of time zone offset, but found colon after hours which is not allowed",
2002 );
2003 insta::assert_snapshot!(
2004 p("%:z", "+0530"),
2005 @"strptime parsing failed: %:z failed: parsed hour component of time zone offset, but could not find required colon separator",
2006 );
2007 insta::assert_snapshot!(
2008 p("%::z", "+0530"),
2009 @"strptime parsing failed: %::z failed: parsed hour component of time zone offset, but could not find required colon separator",
2010 );
2011 insta::assert_snapshot!(
2012 p("%:::z", "+0530"),
2013 @"strptime parsing failed: %:::z failed: parsed hour component of time zone offset, but could not find required colon separator",
2014 );
2015
2016 insta::assert_snapshot!(
2017 p("%z", "+05"),
2018 @"strptime parsing failed: %z failed: parsed hour component of time zone offset, but could not find required minute component",
2019 );
2020 insta::assert_snapshot!(
2021 p("%:z", "+05"),
2022 @"strptime parsing failed: %:z failed: parsed hour component of time zone offset, but could not find required minute component",
2023 );
2024 insta::assert_snapshot!(
2025 p("%::z", "+05"),
2026 @"strptime parsing failed: %::z failed: parsed hour component of time zone offset, but could not find required minute component",
2027 );
2028 insta::assert_snapshot!(
2029 p("%::z", "+05:30"),
2030 @"strptime parsing failed: %::z failed: parsed hour and minute components of time zone offset, but could not find required second component",
2031 );
2032 insta::assert_snapshot!(
2033 p("%:::z", "+5"),
2034 @"strptime parsing failed: %:::z failed: failed to parse hours in UTC numeric offset: expected two digit hour after sign, but found end of input",
2035 );
2036
2037 insta::assert_snapshot!(
2038 p("%z", "+0530:15"),
2039 @"strptime expects to consume the entire input, but `:15` remains unparsed",
2040 );
2041 insta::assert_snapshot!(
2042 p("%:z", "+05:3015"),
2043 @"strptime expects to consume the entire input, but `15` remains unparsed",
2044 );
2045 insta::assert_snapshot!(
2046 p("%::z", "+05:3015"),
2047 @"strptime parsing failed: %::z failed: parsed hour and minute components of time zone offset, but could not find required second component",
2048 );
2049 insta::assert_snapshot!(
2050 p("%:::z", "+05:3015"),
2051 @"strptime expects to consume the entire input, but `15` remains unparsed",
2052 );
2053 }
2054
2055 #[test]
2059 fn err_parse_large_century() {
2060 let p = |fmt: &str, input: &str| {
2061 BrokenDownTime::parse_mono(fmt.as_bytes(), input.as_bytes())
2062 .unwrap_err()
2063 .to_string()
2064 };
2065
2066 insta::assert_snapshot!(
2067 p("%^50C%", "2000000000000000000#0077)()"),
2068 @"strptime parsing failed: %C failed: failed to parse year number for century: parameter 'century' is not in the required range of 0..=99",
2069 );
2070 }
2071}