jiff/fmt/strtime/
parse.rs

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            // We don't check this for `%.` since that currently always
38            // must lead to `%.f` which can actually parse the empty string!
39            if self.inp.is_empty() && self.f() != b'.' {
40                return Err(Error::from(PE::ExpectedNonEmpty {
41                    directive: self.f(),
42                }));
43            }
44            // Parse extensions like padding/case options and padding width.
45            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                    // Skip over any precision settings that might be here.
118                    // This is a specific special format supported by `%.f`.
119                    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    /// Returns the byte at the current position of the format string.
144    ///
145    /// # Panics
146    ///
147    /// This panics when the entire format string has been consumed.
148    fn f(&self) -> u8 {
149        self.fmt[0]
150    }
151
152    /// Returns the byte at the current position of the input string.
153    ///
154    /// # Panics
155    ///
156    /// This panics when the entire input string has been consumed.
157    fn i(&self) -> u8 {
158        self.inp[0]
159    }
160
161    /// Bumps the position of the format string.
162    ///
163    /// This returns true in precisely the cases where `self.f()` will not
164    /// panic. i.e., When the end of the format string hasn't been reached yet.
165    fn bump_fmt(&mut self) -> bool {
166        self.fmt = &self.fmt[1..];
167        !self.fmt.is_empty()
168    }
169
170    /// Bumps the position of the input string.
171    ///
172    /// This returns true in precisely the cases where `self.i()` will not
173    /// panic. i.e., When the end of the input string hasn't been reached yet.
174    fn bump_input(&mut self) -> bool {
175        self.inp = &self.inp[1..];
176        !self.inp.is_empty()
177    }
178
179    /// Parses optional extensions before a specifier directive. That is, right
180    /// after the `%`. If any extensions are parsed, the parser is bumped
181    /// to the next byte. (If no next byte exists, then an error is returned.)
182    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    // We write out a parsing routine for each directive below. Each parsing
191    // routine assumes that the parser is positioned immediately after the
192    // `%` for the current directive, and that there is at least one unconsumed
193    // byte in the input.
194
195    /// Parses a literal from the input that matches the current byte in the
196    /// format string.
197    ///
198    /// This may consume multiple bytes from the input, for example, a single
199    /// whitespace byte in the format string can match zero or more whitespace
200    /// in the input.
201    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    /// Parses an arbitrary (zero or more) amount ASCII whitespace.
223    ///
224    /// This is for `%n` and `%t`.
225    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    /// Parses a literal '%' from the input.
234    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    /// Parses `%D`, which is equivalent to `%m/%d/%y`.
247    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    /// Parse `%p`, which indicates whether the time is AM or PM.
256    ///
257    /// This is generally only useful with `%I`. If, say, `%H` is used, then
258    /// the AM/PM moniker will be validated, but it doesn't actually influence
259    /// the clock time.
260    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            // OK because 0 <= index <= 1.
268            _ => unreachable!("unknown AM/PM index"),
269        }));
270        self.bump_fmt();
271        Ok(())
272    }
273
274    /// Parses `%T`, which is equivalent to `%H:%M:%S`.
275    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    /// Parses `%R`, which is equivalent to `%H:%M`.
284    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    /// Parses `%d` and `%e`, which is equivalent to the day of the month.
293    ///
294    /// We merely require that it is in the range 1-31 here.
295    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    /// Parses `%j`, which is equivalent to the day of the year.
307    ///
308    /// We merely require that it is in the range 1-366 here.
309    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    /// Parses `%H`, which is equivalent to the hour.
322    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        // OK because our hour is in bounds.
330        self.tm.set_hour(Some(hour)).unwrap();
331        self.bump_fmt();
332        Ok(())
333    }
334
335    /// Parses `%I`, which is equivalent to the hour on a 12-hour clock.
336    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        // OK because our hour is in bounds.
344        self.tm.set_hour(Some(hour)).unwrap();
345        self.bump_fmt();
346        Ok(())
347    }
348
349    /// Parses `%F`, which is equivalent to `%Y-%m-%d`.
350    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    /// Parses `%M`, which is equivalent to the minute.
359    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    /// Parse `%Q`, which is the IANA time zone identifier or an offset without
372    /// colons.
373    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    /// Parse `%:Q`, which is the IANA time zone identifier or an offset with
397    /// colons.
398    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    /// Parse `%z`, which is a time zone offset without colons that requires
422    /// a minutes component but will parse a second component if it's there.
423    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    /// Parse `%:z`, which is a time zone offset with colons that requires
440    /// a minutes component but will parse a second component if it's there.
441    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    /// Parse `%::z`, which is a time zone offset with colons that requires
458    /// a seconds component.
459    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    /// Parse `%:::z`, which is a time zone offset with colons that only
477    /// requires an hour component, but will parse minute/second components
478    /// if they are there.
479    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    /// Parses `%S`, which is equivalent to the second.
497    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        // As with other parses in Jiff, and like Temporal,
504        // we constrain `60` seconds to `59` because we don't
505        // support leap seconds.
506        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    /// Parses `%s`, which is equivalent to a Unix timestamp.
516    fn parse_timestamp(&mut self, ext: Extension) -> Result<(), Error> {
517        let (sign, inp) = parse_optional_sign(self.inp);
518        let (timestamp, inp) = ext
519            // 19 comes from `i64::MAX.to_string().len()`.
520            .parse_number(19, Flag::PadSpace, inp)
521            .context(PE::ParseTimestamp)?;
522        // I believe this error case is actually impossible. Since `timestamp`
523        // is guaranteed to be positive, and negating any positive `i64` will
524        // always result in a valid `i64`.
525        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    /// Parses `%f` (or `%N`, which is an alias for `%9f`), which is equivalent
537    /// to a fractional second up to nanosecond precision. This must always
538    /// parse at least one decimal digit and does not parse any leading dot.
539    ///
540    /// At present, we don't use any flags/width/precision settings to
541    /// influence parsing. That is, `%3f` will parse the fractional component
542    /// in `0.123456789`.
543    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        // I believe this error can never happen, since we know we have no more
555        // than 9 ASCII digits. Any sequence of 9 ASCII digits can be parsed
556        // into an `i64`.
557        let nanoseconds =
558            parse::fraction(digits).context(PE::ParseFractionalSeconds)?;
559        // I believe this is also impossible to fail, since the maximal
560        // fractional nanosecond is 999_999_999, and which also corresponds
561        // to the maximal expressible number with 9 ASCII digits. So every
562        // possible expressible value here is in range.
563        self.tm.subsec = Some(
564            b::SubsecNanosecond::check(nanoseconds)
565                .context(PE::ParseFractionalSeconds)?,
566        );
567        self.bump_fmt();
568        Ok(())
569    }
570
571    /// Parses `%f`, which is equivalent to a dot followed by a fractional
572    /// second up to nanosecond precision. Note that if there is no leading
573    /// dot, then this successfully parses the empty string.
574    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    /// Parses `%m`, which is equivalent to the month.
584    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    /// Parse `%b` or `%h`, which is an abbreviated month name.
596    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        // Both are OK because 0 <= index <= 11.
601        self.tm.month = Some(index + 1);
602        self.bump_fmt();
603        Ok(())
604    }
605
606    /// Parse `%B`, which is a full month name.
607    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        // Both are OK because 0 <= index <= 11.
628        self.tm.month = Some(index + 1);
629        self.bump_fmt();
630        Ok(())
631    }
632
633    /// Parse `%a`, which is an abbreviated weekday.
634    fn parse_weekday_abbrev(&mut self) -> Result<(), Error> {
635        let (index, inp) = parse_weekday_abbrev(self.inp)?;
636        self.inp = inp;
637
638        // Both are OK because 0 <= index <= 6.
639        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    /// Parse `%A`, which is a full weekday name.
647    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        // Both are OK because 0 <= index <= 6.
663        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    /// Parse `%u`, which is a weekday number with Monday being `1` and
671    /// Sunday being `7`.
672    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    /// Parse `%w`, which is a weekday number with Sunday being `0`.
688    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    /// Parse `%U`, which is a week number with Sunday being the first day
704    /// in the first week numbered `01`.
705    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    /// Parse `%V`, which is an ISO 8601 week number.
718    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    /// Parse `%W`, which is a week number with Monday being the first day
731    /// in the first week numbered `01`.
732    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    /// Parses `%Y`, which we permit to be any year, including a negative year.
745    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        // OK because sign=={1,-1} and year can't be bigger than 4 digits
752        // so overflow isn't possible.
753        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    /// Parses `%y`, which is equivalent to a 2-digit year.
760    ///
761    /// The numbers 69-99 refer to 1969-1999, while 00-68 refer to 2000-2068.
762    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    /// Parses `%C`, which we permit to just be a century, including a negative
781    /// century.
782    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        // OK because sign=={1,-1} and century can't be bigger than 2 digits
791        // so overflow isn't possible.
792        let century = sign.checked_mul(century).unwrap();
793        // Similarly, we have 64-bit integers here. Two digits multiplied by
794        // 100 will never overflow.
795        let year = century.checked_mul(100).unwrap();
796        // I believe the error condition here is impossible.
797        self.tm.year = Some(b::Year::check(year).context(PE::ParseCentury)?);
798        self.bump_fmt();
799        Ok(())
800    }
801
802    /// Parses `%G`, which we permit to be any year, including a negative year.
803    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        // OK because sign=={1,-1} and year can't be bigger than 4 digits
811        // so overflow isn't possible.
812        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    /// Parses `%g`, which is equivalent to a 2-digit ISO 8601 week-based year.
820    ///
821    /// The numbers 69-99 refer to 1969-1999, while 00-68 refer to 2000-2068.
822    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    /// Parse an integer with the given default padding and flag settings.
843    ///
844    /// The default padding is usually 2 (4 for %Y) and the default flag is
845    /// usually Flag::PadZero (there are no cases where the default flag is
846    /// different at time of writing). But both the padding and the flag can be
847    /// overridden by the settings on this extension.
848    ///
849    /// Generally speaking, parsing ignores everything in an extension except
850    /// for padding. When padding is set, then parsing will limit itself to a
851    /// number of digits equal to the greater of the default padding size or
852    /// the configured padding size. This permits `%Y%m%d` to parse `20240730`
853    /// successfully, for example.
854    ///
855    /// The remaining input is returned. This returns an error if the given
856    /// input is empty.
857    #[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        // Strip and ignore any whitespace we might see here.
872        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            // This is manually inlined from `crate::util::parse::i64` to avoid
890            // repeating this loop, and with some error cases removed since we
891            // know that `byte` is an ASCII digit.
892            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/// Parses an optional sign from the beginning of the input. If one isn't
906/// found, then the sign returned is positive.
907///
908/// This also returns the remaining unparsed input.
909#[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
922/// Parses the input such that, on success, the index of the first matching
923/// choice (via ASCII case insensitive comparisons) is returned, along with
924/// any remaining unparsed input.
925///
926/// If no choice given is a prefix of the input, then an error is returned.
927/// The error includes the possible allowed choices.
928///
929/// # Panics
930///
931/// When `choices.len()` exceeds `i8::MAX`.
932fn 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/// Like `parse_choice`, but specialized for AM/PM.
950///
951/// This exists because AM/PM is common and we can take advantage of the fact
952/// that they are both exactly two bytes.
953#[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/// Like `parse_choice`, but specialized for weekday abbreviation.
969///
970/// This exists because weekday abbreviations are common and we can take
971/// advantage of the fact that they are all exactly three bytes.
972#[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/// Like `parse_choice`, but specialized for month name abbreviation.
999///
1000/// This exists because month name abbreviations are common and we can take
1001/// advantage of the fact that they are all exactly three bytes.
1002#[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    // This is OK because all bytes in a IANA TZ annotation are guaranteed
1043    // to be ASCII, or else we wouldn't be here. If this turns out to be
1044    // a perf issue, we can do an unchecked conversion here. But I figured
1045    // it would be better to start conservative.
1046    let iana = core::str::from_utf8(mkiana(input)).expect("ASCII");
1047    Ok((iana, input))
1048}
1049
1050/// Parses a single IANA name component. That is, the thing that leads all IANA
1051/// time zone identifiers and the thing that must always come after a `/`. This
1052/// returns an error if no component could be found.
1053#[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    /// Regression test for checked arithmetic panicking.
2056    ///
2057    /// Ref https://github.com/BurntSushi/jiff/issues/426
2058    #[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}