jiff/fmt/strtime/
parse.rs

1use core::fmt::Write;
2
3use crate::{
4    civil::Weekday,
5    error::{err, ErrorContext},
6    fmt::{
7        offset,
8        strtime::{BrokenDownTime, Extension, Flag, Meridiem},
9        Parsed,
10    },
11    tz::Offset,
12    util::{
13        escape, parse,
14        rangeint::{ri8, RFrom},
15        t::{self, C},
16    },
17    Error, Timestamp,
18};
19
20pub(super) struct Parser<'f, 'i, 't> {
21    pub(super) fmt: &'f [u8],
22    pub(super) inp: &'i [u8],
23    pub(super) tm: &'t mut BrokenDownTime,
24}
25
26impl<'f, 'i, 't> Parser<'f, 'i, 't> {
27    pub(super) fn parse(&mut self) -> Result<(), Error> {
28        while !self.fmt.is_empty() {
29            if self.f() != b'%' {
30                self.parse_literal()?;
31                continue;
32            }
33            if !self.bump_fmt() {
34                return Err(err!(
35                    "invalid format string, expected byte after '%', \
36                     but found end of format string",
37                ));
38            }
39            // We don't check this for `%.` since that currently always
40            // must lead to `%.f` which can actually parse the empty string!
41            if self.inp.is_empty() && self.f() != b'.' {
42                return Err(err!(
43                    "expected non-empty input for directive %{directive}, \
44                     but found end of input",
45                    directive = escape::Byte(self.f()),
46                ));
47            }
48            // Parse extensions like padding/case options and padding width.
49            let ext = self.parse_extension()?;
50            match self.f() {
51                b'%' => self.parse_percent().context("%% failed")?,
52                b'A' => self.parse_weekday_full().context("%A failed")?,
53                b'a' => self.parse_weekday_abbrev().context("%a failed")?,
54                b'B' => self.parse_month_name_full().context("%B failed")?,
55                b'b' => self.parse_month_name_abbrev().context("%b failed")?,
56                b'C' => self.parse_century(ext).context("%C failed")?,
57                b'D' => self.parse_american_date().context("%D failed")?,
58                b'd' => self.parse_day(ext).context("%d failed")?,
59                b'e' => self.parse_day(ext).context("%e failed")?,
60                b'F' => self.parse_iso_date().context("%F failed")?,
61                b'f' => self.parse_fractional(ext).context("%f failed")?,
62                b'G' => self.parse_iso_week_year(ext).context("%G failed")?,
63                b'g' => self.parse_iso_week_year2(ext).context("%g failed")?,
64                b'H' => self.parse_hour24(ext).context("%H failed")?,
65                b'h' => self.parse_month_name_abbrev().context("%h failed")?,
66                b'I' => self.parse_hour12(ext).context("%I failed")?,
67                b'j' => self.parse_day_of_year(ext).context("%j failed")?,
68                b'k' => self.parse_hour24(ext).context("%k failed")?,
69                b'l' => self.parse_hour12(ext).context("%l failed")?,
70                b'M' => self.parse_minute(ext).context("%M failed")?,
71                b'm' => self.parse_month(ext).context("%m failed")?,
72                b'N' => self.parse_fractional(ext).context("%N failed")?,
73                b'n' => self.parse_whitespace().context("%n failed")?,
74                b'P' => self.parse_ampm().context("%P failed")?,
75                b'p' => self.parse_ampm().context("%p failed")?,
76                b'Q' => match ext.colons {
77                    0 => self.parse_iana_nocolon().context("%Q failed")?,
78                    1 => self.parse_iana_colon().context("%:Q failed")?,
79                    _ => {
80                        return Err(err!(
81                            "invalid number of `:` in `%Q` directive"
82                        ))
83                    }
84                },
85                b'R' => self.parse_clock_nosecs().context("%R failed")?,
86                b'S' => self.parse_second(ext).context("%S failed")?,
87                b's' => self.parse_timestamp(ext).context("%s failed")?,
88                b'T' => self.parse_clock_secs().context("%T failed")?,
89                b't' => self.parse_whitespace().context("%t failed")?,
90                b'U' => self.parse_week_sun(ext).context("%U failed")?,
91                b'u' => self.parse_weekday_mon(ext).context("%u failed")?,
92                b'V' => self.parse_week_iso(ext).context("%V failed")?,
93                b'W' => self.parse_week_mon(ext).context("%W failed")?,
94                b'w' => self.parse_weekday_sun(ext).context("%w failed")?,
95                b'Y' => self.parse_year(ext).context("%Y failed")?,
96                b'y' => self.parse_year2(ext).context("%y failed")?,
97                b'z' => match ext.colons {
98                    0 => self.parse_offset_nocolon().context("%z failed")?,
99                    1 => self.parse_offset_colon().context("%:z failed")?,
100                    2 => self.parse_offset_colon2().context("%::z failed")?,
101                    3 => self.parse_offset_colon3().context("%:::z failed")?,
102                    _ => {
103                        return Err(err!(
104                            "invalid number of `:` in `%z` directive"
105                        ))
106                    }
107                },
108                b'c' => {
109                    return Err(err!("cannot parse locale date and time"));
110                }
111                b'r' => {
112                    return Err(err!(
113                        "cannot parse locale 12-hour clock time"
114                    ));
115                }
116                b'X' => {
117                    return Err(err!("cannot parse locale clock time"));
118                }
119                b'x' => {
120                    return Err(err!("cannot parse locale date"));
121                }
122                b'Z' => {
123                    return Err(err!("cannot parse time zone abbreviations"));
124                }
125                b'.' => {
126                    if !self.bump_fmt() {
127                        return Err(err!(
128                            "invalid format string, expected directive \
129                             after '%.'",
130                        ));
131                    }
132                    // Skip over any precision settings that might be here.
133                    // This is a specific special format supported by `%.f`.
134                    let (width, fmt) = Extension::parse_width(self.fmt)?;
135                    let ext = Extension { width, ..ext };
136                    self.fmt = fmt;
137                    match self.f() {
138                        b'f' => self
139                            .parse_dot_fractional(ext)
140                            .context("%.f failed")?,
141                        unk => {
142                            return Err(err!(
143                                "found unrecognized directive %{unk} \
144                                 following %.",
145                                unk = escape::Byte(unk),
146                            ));
147                        }
148                    }
149                }
150                unk => {
151                    return Err(err!(
152                        "found unrecognized directive %{unk}",
153                        unk = escape::Byte(unk),
154                    ));
155                }
156            }
157        }
158        Ok(())
159    }
160
161    /// Returns the byte at the current position of the format string.
162    ///
163    /// # Panics
164    ///
165    /// This panics when the entire format string has been consumed.
166    fn f(&self) -> u8 {
167        self.fmt[0]
168    }
169
170    /// Returns the byte at the current position of the input string.
171    ///
172    /// # Panics
173    ///
174    /// This panics when the entire input string has been consumed.
175    fn i(&self) -> u8 {
176        self.inp[0]
177    }
178
179    /// Bumps the position of the format string.
180    ///
181    /// This returns true in precisely the cases where `self.f()` will not
182    /// panic. i.e., When the end of the format string hasn't been reached yet.
183    fn bump_fmt(&mut self) -> bool {
184        self.fmt = &self.fmt[1..];
185        !self.fmt.is_empty()
186    }
187
188    /// Bumps the position of the input string.
189    ///
190    /// This returns true in precisely the cases where `self.i()` will not
191    /// panic. i.e., When the end of the input string hasn't been reached yet.
192    fn bump_input(&mut self) -> bool {
193        self.inp = &self.inp[1..];
194        !self.inp.is_empty()
195    }
196
197    /// Parses optional extensions before a specifier directive. That is, right
198    /// after the `%`. If any extensions are parsed, the parser is bumped
199    /// to the next byte. (If no next byte exists, then an error is returned.)
200    fn parse_extension(&mut self) -> Result<Extension, Error> {
201        let (flag, fmt) = Extension::parse_flag(self.fmt)?;
202        let (width, fmt) = Extension::parse_width(fmt)?;
203        let (colons, fmt) = Extension::parse_colons(fmt);
204        self.fmt = fmt;
205        Ok(Extension { flag, width, colons })
206    }
207
208    // We write out a parsing routine for each directive below. Each parsing
209    // routine assumes that the parser is positioned immediately after the
210    // `%` for the current directive, and that there is at least one unconsumed
211    // byte in the input.
212
213    /// Parses a literal from the input that matches the current byte in the
214    /// format string.
215    ///
216    /// This may consume multiple bytes from the input, for example, a single
217    /// whitespace byte in the format string can match zero or more whitespace
218    /// in the input.
219    fn parse_literal(&mut self) -> Result<(), Error> {
220        if self.f().is_ascii_whitespace() {
221            if !self.inp.is_empty() {
222                while self.i().is_ascii_whitespace() && self.bump_input() {}
223            }
224        } else if self.inp.is_empty() {
225            return Err(err!(
226                "expected to match literal byte {byte:?} from \
227                 format string, but found end of input",
228                byte = escape::Byte(self.fmt[0]),
229            ));
230        } else if self.f() != self.i() {
231            return Err(err!(
232                "expected to match literal byte {expect:?} from \
233                 format string, but found byte {found:?} in input",
234                expect = escape::Byte(self.f()),
235                found = escape::Byte(self.i()),
236            ));
237        } else {
238            self.bump_input();
239        }
240        self.bump_fmt();
241        Ok(())
242    }
243
244    /// Parses an arbitrary (zero or more) amount ASCII whitespace.
245    ///
246    /// This is for `%n` and `%t`.
247    fn parse_whitespace(&mut self) -> Result<(), Error> {
248        if !self.inp.is_empty() {
249            while self.i().is_ascii_whitespace() && self.bump_input() {}
250        }
251        self.bump_fmt();
252        Ok(())
253    }
254
255    /// Parses a literal '%' from the input.
256    fn parse_percent(&mut self) -> Result<(), Error> {
257        if self.i() != b'%' {
258            return Err(err!(
259                "expected '%' due to '%%' in format string, \
260                 but found {byte:?} in input",
261                byte = escape::Byte(self.inp[0]),
262            ));
263        }
264        self.bump_fmt();
265        self.bump_input();
266        Ok(())
267    }
268
269    /// Parses `%D`, which is equivalent to `%m/%d/%y`.
270    fn parse_american_date(&mut self) -> Result<(), Error> {
271        let mut p = Parser { fmt: b"%m/%d/%y", inp: self.inp, tm: self.tm };
272        p.parse()?;
273        self.inp = p.inp;
274        self.bump_fmt();
275        Ok(())
276    }
277
278    /// Parse `%p`, which indicates whether the time is AM or PM.
279    ///
280    /// This is generally only useful with `%I`. If, say, `%H` is used, then
281    /// the AM/PM moniker will be validated, but it doesn't actually influence
282    /// the clock time.
283    fn parse_ampm(&mut self) -> Result<(), Error> {
284        let (index, inp) = parse_ampm(self.inp)?;
285        self.inp = inp;
286
287        self.tm.meridiem = Some(match index {
288            0 => Meridiem::AM,
289            1 => Meridiem::PM,
290            // OK because 0 <= index <= 1.
291            index => unreachable!("unknown AM/PM index {index}"),
292        });
293        self.bump_fmt();
294        Ok(())
295    }
296
297    /// Parses `%T`, which is equivalent to `%H:%M:%S`.
298    fn parse_clock_secs(&mut self) -> Result<(), Error> {
299        let mut p = Parser { fmt: b"%H:%M:%S", inp: self.inp, tm: self.tm };
300        p.parse()?;
301        self.inp = p.inp;
302        self.bump_fmt();
303        Ok(())
304    }
305
306    /// Parses `%R`, which is equivalent to `%H:%M`.
307    fn parse_clock_nosecs(&mut self) -> Result<(), Error> {
308        let mut p = Parser { fmt: b"%H:%M", inp: self.inp, tm: self.tm };
309        p.parse()?;
310        self.inp = p.inp;
311        self.bump_fmt();
312        Ok(())
313    }
314
315    /// Parses `%d` and `%e`, which is equivalent to the day of the month.
316    ///
317    /// We merely require that it is in the range 1-31 here.
318    fn parse_day(&mut self, ext: Extension) -> Result<(), Error> {
319        let (day, inp) = ext
320            .parse_number(2, Flag::PadZero, self.inp)
321            .context("failed to parse day")?;
322        self.inp = inp;
323
324        let day =
325            t::Day::try_new("day", day).context("day number is invalid")?;
326        self.tm.day = Some(day);
327        self.bump_fmt();
328        Ok(())
329    }
330
331    /// Parses `%j`, which is equivalent to the day of the year.
332    ///
333    /// We merely require that it is in the range 1-366 here.
334    fn parse_day_of_year(&mut self, ext: Extension) -> Result<(), Error> {
335        let (day, inp) = ext
336            .parse_number(3, Flag::PadZero, self.inp)
337            .context("failed to parse day of year")?;
338        self.inp = inp;
339
340        let day = t::DayOfYear::try_new("day-of-year", day)
341            .context("day of year number is invalid")?;
342        self.tm.day_of_year = Some(day);
343        self.bump_fmt();
344        Ok(())
345    }
346
347    /// Parses `%H`, which is equivalent to the hour.
348    fn parse_hour24(&mut self, ext: Extension) -> Result<(), Error> {
349        let (hour, inp) = ext
350            .parse_number(2, Flag::PadZero, self.inp)
351            .context("failed to parse hour")?;
352        self.inp = inp;
353
354        let hour = t::Hour::try_new("hour", hour)
355            .context("hour number is invalid")?;
356        self.tm.hour = Some(hour);
357        self.bump_fmt();
358        Ok(())
359    }
360
361    /// Parses `%I`, which is equivalent to the hour on a 12-hour clock.
362    fn parse_hour12(&mut self, ext: Extension) -> Result<(), Error> {
363        type Hour12 = ri8<1, 12>;
364
365        let (hour, inp) = ext
366            .parse_number(2, Flag::PadZero, self.inp)
367            .context("failed to parse hour")?;
368        self.inp = inp;
369
370        let hour =
371            Hour12::try_new("hour", hour).context("hour number is invalid")?;
372        self.tm.hour = Some(t::Hour::rfrom(hour));
373        self.bump_fmt();
374        Ok(())
375    }
376
377    /// Parses `%F`, which is equivalent to `%Y-%m-%d`.
378    fn parse_iso_date(&mut self) -> Result<(), Error> {
379        let mut p = Parser { fmt: b"%Y-%m-%d", inp: self.inp, tm: self.tm };
380        p.parse()?;
381        self.inp = p.inp;
382        self.bump_fmt();
383        Ok(())
384    }
385
386    /// Parses `%M`, which is equivalent to the minute.
387    fn parse_minute(&mut self, ext: Extension) -> Result<(), Error> {
388        let (minute, inp) = ext
389            .parse_number(2, Flag::PadZero, self.inp)
390            .context("failed to parse minute")?;
391        self.inp = inp;
392
393        let minute = t::Minute::try_new("minute", minute)
394            .context("minute number is invalid")?;
395        self.tm.minute = Some(minute);
396        self.bump_fmt();
397        Ok(())
398    }
399
400    /// Parse `%Q`, which is the IANA time zone identifier or an offset without
401    /// colons.
402    fn parse_iana_nocolon(&mut self) -> Result<(), Error> {
403        #[cfg(not(feature = "alloc"))]
404        {
405            Err(err!(
406                "cannot parse `%Q` without Jiff's `alloc` feature enabled"
407            ))
408        }
409        #[cfg(feature = "alloc")]
410        {
411            use alloc::string::ToString;
412
413            if !self.inp.is_empty() && matches!(self.inp[0], b'+' | b'-') {
414                return self.parse_offset_nocolon();
415            }
416            let (iana, inp) = parse_iana(self.inp)?;
417            self.inp = inp;
418            self.tm.iana = Some(iana.to_string());
419            self.bump_fmt();
420            Ok(())
421        }
422    }
423
424    /// Parse `%:Q`, which is the IANA time zone identifier or an offset with
425    /// colons.
426    fn parse_iana_colon(&mut self) -> Result<(), Error> {
427        #[cfg(not(feature = "alloc"))]
428        {
429            Err(err!(
430                "cannot parse `%:Q` without Jiff's `alloc` feature enabled"
431            ))
432        }
433        #[cfg(feature = "alloc")]
434        {
435            use alloc::string::ToString;
436
437            if !self.inp.is_empty() && matches!(self.inp[0], b'+' | b'-') {
438                return self.parse_offset_colon();
439            }
440            let (iana, inp) = parse_iana(self.inp)?;
441            self.inp = inp;
442            self.tm.iana = Some(iana.to_string());
443            self.bump_fmt();
444            Ok(())
445        }
446    }
447
448    /// Parse `%z`, which is a time zone offset without colons that requires
449    /// a minutes component but will parse a second component if it's there.
450    fn parse_offset_nocolon(&mut self) -> Result<(), Error> {
451        static PARSER: offset::Parser = offset::Parser::new()
452            .zulu(false)
453            .require_minute(true)
454            .subminute(true)
455            .subsecond(false)
456            .colon(offset::Colon::Absent);
457
458        let Parsed { value, input } = PARSER.parse(self.inp)?;
459        self.tm.offset = Some(value.to_offset()?);
460        self.inp = input;
461        self.bump_fmt();
462
463        Ok(())
464    }
465
466    /// Parse `%:z`, which is a time zone offset with colons that requires
467    /// a minutes component but will parse a second component if it's there.
468    fn parse_offset_colon(&mut self) -> Result<(), Error> {
469        static PARSER: offset::Parser = offset::Parser::new()
470            .zulu(false)
471            .require_minute(true)
472            .subminute(true)
473            .subsecond(false)
474            .colon(offset::Colon::Required);
475
476        let Parsed { value, input } = PARSER.parse(self.inp)?;
477        self.tm.offset = Some(value.to_offset()?);
478        self.inp = input;
479        self.bump_fmt();
480
481        Ok(())
482    }
483
484    /// Parse `%::z`, which is a time zone offset with colons that requires
485    /// a seconds component.
486    fn parse_offset_colon2(&mut self) -> Result<(), Error> {
487        static PARSER: offset::Parser = offset::Parser::new()
488            .zulu(false)
489            .require_minute(true)
490            .require_second(true)
491            .subminute(true)
492            .subsecond(false)
493            .colon(offset::Colon::Required);
494
495        let Parsed { value, input } = PARSER.parse(self.inp)?;
496        self.tm.offset = Some(value.to_offset()?);
497        self.inp = input;
498        self.bump_fmt();
499
500        Ok(())
501    }
502
503    /// Parse `%:::z`, which is a time zone offset with colons that only
504    /// requires an hour component, but will parse minute/second components
505    /// if they are there.
506    fn parse_offset_colon3(&mut self) -> Result<(), Error> {
507        static PARSER: offset::Parser = offset::Parser::new()
508            .zulu(false)
509            .require_minute(false)
510            .require_second(false)
511            .subminute(true)
512            .subsecond(false)
513            .colon(offset::Colon::Required);
514
515        let Parsed { value, input } = PARSER.parse(self.inp)?;
516        self.tm.offset = Some(value.to_offset()?);
517        self.inp = input;
518        self.bump_fmt();
519
520        Ok(())
521    }
522
523    /// Parses `%S`, which is equivalent to the second.
524    fn parse_second(&mut self, ext: Extension) -> Result<(), Error> {
525        let (mut second, inp) = ext
526            .parse_number(2, Flag::PadZero, self.inp)
527            .context("failed to parse second")?;
528        self.inp = inp;
529
530        // As with other parses in Jiff, and like Temporal,
531        // we constrain `60` seconds to `59` because we don't
532        // support leap seconds.
533        if second == 60 {
534            second = 59;
535        }
536        let second = t::Second::try_new("second", second)
537            .context("second number is invalid")?;
538        self.tm.second = Some(second);
539        self.bump_fmt();
540        Ok(())
541    }
542
543    /// Parses `%s`, which is equivalent to a Unix timestamp.
544    fn parse_timestamp(&mut self, ext: Extension) -> Result<(), Error> {
545        let (sign, inp) = parse_optional_sign(self.inp);
546        let (timestamp, inp) = ext
547            // 19 comes from `i64::MAX.to_string().len()`.
548            .parse_number(19, Flag::PadSpace, inp)
549            .context("failed to parse Unix timestamp (in seconds)")?;
550        // I believe this error case is actually impossible. Since `timestamp`
551        // is guaranteed to be positive, and negating any positive `i64` will
552        // always result in a valid `i64`.
553        let timestamp = timestamp.checked_mul(sign).ok_or_else(|| {
554            err!(
555                "parsed Unix timestamp `{timestamp}` with a \
556                 leading `-` sign, which causes overflow",
557            )
558        })?;
559        let timestamp =
560            Timestamp::from_second(timestamp).with_context(|| {
561                err!(
562                    "parsed Unix timestamp `{timestamp}`, \
563                     but out of range of valid Jiff `Timestamp`",
564                )
565            })?;
566        self.inp = inp;
567
568        // This is basically just repeating the
569        // `From<Timestamp> for BrokenDownTime`
570        // trait implementation.
571        let dt = Offset::UTC.to_datetime(timestamp);
572        let (d, t) = (dt.date(), dt.time());
573        self.tm.offset = Some(Offset::UTC);
574        self.tm.year = Some(d.year_ranged());
575        self.tm.month = Some(d.month_ranged());
576        self.tm.day = Some(d.day_ranged());
577        self.tm.hour = Some(t.hour_ranged());
578        self.tm.minute = Some(t.minute_ranged());
579        self.tm.second = Some(t.second_ranged());
580        self.tm.subsec = Some(t.subsec_nanosecond_ranged());
581        self.tm.meridiem = Some(Meridiem::from(t));
582
583        self.bump_fmt();
584        Ok(())
585    }
586
587    /// Parses `%f` (or `%N`, which is an alias for `%9f`), which is equivalent
588    /// to a fractional second up to nanosecond precision. This must always
589    /// parse at least one decimal digit and does not parse any leading dot.
590    ///
591    /// At present, we don't use any flags/width/precision settings to
592    /// influence parsing. That is, `%3f` will parse the fractional component
593    /// in `0.123456789`.
594    fn parse_fractional(&mut self, _ext: Extension) -> Result<(), Error> {
595        let mkdigits = parse::slicer(self.inp);
596        while mkdigits(self.inp).len() < 9
597            && self.inp.first().map_or(false, u8::is_ascii_digit)
598        {
599            self.inp = &self.inp[1..];
600        }
601        let digits = mkdigits(self.inp);
602        if digits.is_empty() {
603            return Err(err!(
604                "expected at least one fractional decimal digit, \
605                 but did not find any",
606            ));
607        }
608        // I believe this error can never happen, since we know we have no more
609        // than 9 ASCII digits. Any sequence of 9 ASCII digits can be parsed
610        // into an `i64`.
611        let nanoseconds = parse::fraction(digits, 9).map_err(|err| {
612            err!(
613                "failed to parse {digits:?} as fractional second component \
614                 (up to 9 digits, nanosecond precision): {err}",
615                digits = escape::Bytes(digits),
616            )
617        })?;
618        // I believe this is also impossible to fail, since the maximal
619        // fractional nanosecond is 999_999_999, and which also corresponds
620        // to the maximal expressible number with 9 ASCII digits. So every
621        // possible expressible value here is in range.
622        let nanoseconds =
623            t::SubsecNanosecond::try_new("nanoseconds", nanoseconds).map_err(
624                |err| err!("fractional nanoseconds are not valid: {err}"),
625            )?;
626        self.tm.subsec = Some(nanoseconds);
627        self.bump_fmt();
628        Ok(())
629    }
630
631    /// Parses `%f`, which is equivalent to a dot followed by a fractional
632    /// second up to nanosecond precision. Note that if there is no leading
633    /// dot, then this successfully parses the empty string.
634    fn parse_dot_fractional(&mut self, ext: Extension) -> Result<(), Error> {
635        if !self.inp.starts_with(b".") {
636            self.bump_fmt();
637            return Ok(());
638        }
639        self.inp = &self.inp[1..];
640        self.parse_fractional(ext)
641    }
642
643    /// Parses `%m`, which is equivalent to the month.
644    fn parse_month(&mut self, ext: Extension) -> Result<(), Error> {
645        let (month, inp) = ext
646            .parse_number(2, Flag::PadZero, self.inp)
647            .context("failed to parse month")?;
648        self.inp = inp;
649
650        let month = t::Month::try_new("month", month)
651            .context("month number is invalid")?;
652        self.tm.month = Some(month);
653        self.bump_fmt();
654        Ok(())
655    }
656
657    /// Parse `%b` or `%h`, which is an abbreviated month name.
658    fn parse_month_name_abbrev(&mut self) -> Result<(), Error> {
659        let (index, inp) = parse_month_name_abbrev(self.inp)?;
660        self.inp = inp;
661
662        // Both are OK because 0 <= index <= 11.
663        let index = i8::try_from(index).unwrap();
664        self.tm.month = Some(t::Month::new(index + 1).unwrap());
665        self.bump_fmt();
666        Ok(())
667    }
668
669    /// Parse `%B`, which is a full month name.
670    fn parse_month_name_full(&mut self) -> Result<(), Error> {
671        static CHOICES: &'static [&'static [u8]] = &[
672            b"January",
673            b"February",
674            b"March",
675            b"April",
676            b"May",
677            b"June",
678            b"July",
679            b"August",
680            b"September",
681            b"October",
682            b"November",
683            b"December",
684        ];
685
686        let (index, inp) = parse_choice(self.inp, CHOICES)
687            .context("unrecognized month name")?;
688        self.inp = inp;
689
690        // Both are OK because 0 <= index <= 11.
691        let index = i8::try_from(index).unwrap();
692        self.tm.month = Some(t::Month::new(index + 1).unwrap());
693        self.bump_fmt();
694        Ok(())
695    }
696
697    /// Parse `%a`, which is an abbreviated weekday.
698    fn parse_weekday_abbrev(&mut self) -> Result<(), Error> {
699        let (index, inp) = parse_weekday_abbrev(self.inp)?;
700        self.inp = inp;
701
702        // Both are OK because 0 <= index <= 6.
703        let index = i8::try_from(index).unwrap();
704        self.tm.weekday =
705            Some(Weekday::from_sunday_zero_offset(index).unwrap());
706        self.bump_fmt();
707        Ok(())
708    }
709
710    /// Parse `%A`, which is a full weekday name.
711    fn parse_weekday_full(&mut self) -> Result<(), Error> {
712        static CHOICES: &'static [&'static [u8]] = &[
713            b"Sunday",
714            b"Monday",
715            b"Tuesday",
716            b"Wednesday",
717            b"Thursday",
718            b"Friday",
719            b"Saturday",
720        ];
721
722        let (index, inp) = parse_choice(self.inp, CHOICES)
723            .context("unrecognized weekday abbreviation")?;
724        self.inp = inp;
725
726        // Both are OK because 0 <= index <= 6.
727        let index = i8::try_from(index).unwrap();
728        self.tm.weekday =
729            Some(Weekday::from_sunday_zero_offset(index).unwrap());
730        self.bump_fmt();
731        Ok(())
732    }
733
734    /// Parse `%u`, which is a weekday number with Monday being `1` and
735    /// Sunday being `7`.
736    fn parse_weekday_mon(&mut self, ext: Extension) -> Result<(), Error> {
737        let (weekday, inp) = ext
738            .parse_number(1, Flag::NoPad, self.inp)
739            .context("failed to parse weekday number")?;
740        self.inp = inp;
741
742        let weekday = i8::try_from(weekday).map_err(|_| {
743            err!("parsed weekday number `{weekday}` is invalid")
744        })?;
745        let weekday = Weekday::from_monday_one_offset(weekday)
746            .context("weekday number is invalid")?;
747        self.tm.weekday = Some(weekday);
748        self.bump_fmt();
749        Ok(())
750    }
751
752    /// Parse `%w`, which is a weekday number with Sunday being `0`.
753    fn parse_weekday_sun(&mut self, ext: Extension) -> Result<(), Error> {
754        let (weekday, inp) = ext
755            .parse_number(1, Flag::NoPad, self.inp)
756            .context("failed to parse weekday number")?;
757        self.inp = inp;
758
759        let weekday = i8::try_from(weekday).map_err(|_| {
760            err!("parsed weekday number `{weekday}` is invalid")
761        })?;
762        let weekday = Weekday::from_sunday_zero_offset(weekday)
763            .context("weekday number is invalid")?;
764        self.tm.weekday = Some(weekday);
765        self.bump_fmt();
766        Ok(())
767    }
768
769    /// Parse `%U`, which is a week number with Sunday being the first day
770    /// in the first week numbered `01`.
771    fn parse_week_sun(&mut self, ext: Extension) -> Result<(), Error> {
772        let (week, inp) = ext
773            .parse_number(2, Flag::PadZero, self.inp)
774            .context("failed to parse Sunday-based week number")?;
775        self.inp = inp;
776
777        let week = t::WeekNum::try_new("week", week)
778            .context("Sunday-based week number is invalid")?;
779        self.tm.week_sun = Some(week);
780        self.bump_fmt();
781        Ok(())
782    }
783
784    /// Parse `%V`, which is an ISO 8601 week number.
785    fn parse_week_iso(&mut self, ext: Extension) -> Result<(), Error> {
786        let (week, inp) = ext
787            .parse_number(2, Flag::PadZero, self.inp)
788            .context("failed to parse ISO 8601 week number")?;
789        self.inp = inp;
790
791        let week = t::ISOWeek::try_new("week", week)
792            .context("ISO 8601 week number is invalid")?;
793        self.tm.iso_week = Some(week);
794        self.bump_fmt();
795        Ok(())
796    }
797
798    /// Parse `%W`, which is a week number with Monday being the first day
799    /// in the first week numbered `01`.
800    fn parse_week_mon(&mut self, ext: Extension) -> Result<(), Error> {
801        let (week, inp) = ext
802            .parse_number(2, Flag::PadZero, self.inp)
803            .context("failed to parse Monday-based week number")?;
804        self.inp = inp;
805
806        let week = t::WeekNum::try_new("week", week)
807            .context("Monday-based week number is invalid")?;
808        self.tm.week_mon = Some(week);
809        self.bump_fmt();
810        Ok(())
811    }
812
813    /// Parses `%Y`, which we permit to be any year, including a negative year.
814    fn parse_year(&mut self, ext: Extension) -> Result<(), Error> {
815        let (sign, inp) = parse_optional_sign(self.inp);
816        let (year, inp) = ext
817            .parse_number(4, Flag::PadZero, inp)
818            .context("failed to parse year")?;
819        self.inp = inp;
820
821        // OK because sign=={1,-1} and year can't be bigger than 4 digits
822        // so overflow isn't possible.
823        let year = sign.checked_mul(year).unwrap();
824        let year = t::Year::try_new("year", year)
825            .context("year number is invalid")?;
826        self.tm.year = Some(year);
827        self.bump_fmt();
828        Ok(())
829    }
830
831    /// Parses `%y`, which is equivalent to a 2-digit year.
832    ///
833    /// The numbers 69-99 refer to 1969-1999, while 00-68 refer to 2000-2068.
834    fn parse_year2(&mut self, ext: Extension) -> Result<(), Error> {
835        type Year2Digit = ri8<0, 99>;
836
837        let (year, inp) = ext
838            .parse_number(2, Flag::PadZero, self.inp)
839            .context("failed to parse 2-digit year")?;
840        self.inp = inp;
841
842        let year = Year2Digit::try_new("year (2 digits)", year)
843            .context("year number is invalid")?;
844        let mut year = t::Year::rfrom(year);
845        if year <= C(68) {
846            year += C(2000);
847        } else {
848            year += C(1900);
849        }
850        self.tm.year = Some(year);
851        self.bump_fmt();
852        Ok(())
853    }
854
855    /// Parses `%C`, which we permit to just be a century, including a negative
856    /// century.
857    fn parse_century(&mut self, ext: Extension) -> Result<(), Error> {
858        let (sign, inp) = parse_optional_sign(self.inp);
859        let (century, inp) = ext
860            .parse_number(2, Flag::NoPad, inp)
861            .context("failed to parse century")?;
862        self.inp = inp;
863
864        // OK because sign=={1,-1} and century can't be bigger than 2 digits
865        // so overflow isn't possible.
866        let century = sign.checked_mul(century).unwrap();
867        // Similarly, we have 64-bit integers here. Two digits multiplied by
868        // 100 will never overflow.
869        let year = century.checked_mul(100).unwrap();
870        // I believe the error condition here is impossible.
871        let year = t::Year::try_new("year", year)
872            .context("year number (from century) is invalid")?;
873        self.tm.year = Some(year);
874        self.bump_fmt();
875        Ok(())
876    }
877
878    /// Parses `%G`, which we permit to be any year, including a negative year.
879    fn parse_iso_week_year(&mut self, ext: Extension) -> Result<(), Error> {
880        let (sign, inp) = parse_optional_sign(self.inp);
881        let (year, inp) = ext
882            .parse_number(4, Flag::PadZero, inp)
883            .context("failed to parse ISO 8601 week-based year")?;
884        self.inp = inp;
885
886        // OK because sign=={1,-1} and year can't be bigger than 4 digits
887        // so overflow isn't possible.
888        let year = sign.checked_mul(year).unwrap();
889        let year = t::ISOYear::try_new("year", year)
890            .context("ISO 8601 week-based year number is invalid")?;
891        self.tm.iso_week_year = Some(year);
892        self.bump_fmt();
893        Ok(())
894    }
895
896    /// Parses `%g`, which is equivalent to a 2-digit ISO 8601 week-based year.
897    ///
898    /// The numbers 69-99 refer to 1969-1999, while 00-68 refer to 2000-2068.
899    fn parse_iso_week_year2(&mut self, ext: Extension) -> Result<(), Error> {
900        type Year2Digit = ri8<0, 99>;
901
902        let (year, inp) = ext
903            .parse_number(2, Flag::PadZero, self.inp)
904            .context("failed to parse 2-digit ISO 8601 week-based year")?;
905        self.inp = inp;
906
907        let year = Year2Digit::try_new("year (2 digits)", year)
908            .context("ISO 8601 week-based year number is invalid")?;
909        let mut year = t::ISOYear::rfrom(year);
910        if year <= C(68) {
911            year += C(2000);
912        } else {
913            year += C(1900);
914        }
915        self.tm.iso_week_year = Some(year);
916        self.bump_fmt();
917        Ok(())
918    }
919}
920
921impl Extension {
922    /// Parse an integer with the given default padding and flag settings.
923    ///
924    /// The default padding is usually 2 (4 for %Y) and the default flag is
925    /// usually Flag::PadZero (there are no cases where the default flag is
926    /// different at time of writing). But both the padding and the flag can be
927    /// overridden by the settings on this extension.
928    ///
929    /// Generally speaking, parsing ignores everything in an extension except
930    /// for padding. When padding is set, then parsing will limit itself to a
931    /// number of digits equal to the greater of the default padding size or
932    /// the configured padding size. This permits `%Y%m%d` to parse `20240730`
933    /// successfully, for example.
934    ///
935    /// The remaining input is returned. This returns an error if the given
936    /// input is empty.
937    #[cfg_attr(feature = "perf-inline", inline(always))]
938    fn parse_number<'i>(
939        &self,
940        default_pad_width: usize,
941        default_flag: Flag,
942        mut inp: &'i [u8],
943    ) -> Result<(i64, &'i [u8]), Error> {
944        let flag = self.flag.unwrap_or(default_flag);
945        let zero_pad_width = match flag {
946            Flag::PadSpace | Flag::NoPad => 0,
947            _ => self.width.map(usize::from).unwrap_or(default_pad_width),
948        };
949        let max_digits = default_pad_width.max(zero_pad_width);
950
951        // Strip and ignore any whitespace we might see here.
952        while inp.get(0).map_or(false, |b| b.is_ascii_whitespace()) {
953            inp = &inp[1..];
954        }
955        let mut digits = 0;
956        while digits < inp.len()
957            && digits < zero_pad_width
958            && inp[digits] == b'0'
959        {
960            digits += 1;
961        }
962        let mut n: i64 = 0;
963        while digits < inp.len()
964            && digits < max_digits
965            && inp[digits].is_ascii_digit()
966        {
967            let byte = inp[digits];
968            digits += 1;
969            // This is manually inlined from `crate::util::parse::i64` to avoid
970            // repeating this loop, and with some error cases removed since we
971            // know that `byte` is an ASCII digit.
972            let digit = i64::from(byte - b'0');
973            n = n
974                .checked_mul(10)
975                .and_then(|n| n.checked_add(digit))
976                .ok_or_else(|| {
977                    err!(
978                        "number '{}' too big to parse into 64-bit integer",
979                        escape::Bytes(&inp[..digits]),
980                    )
981                })?;
982        }
983        if digits == 0 {
984            return Err(err!("invalid number, no digits found"));
985        }
986        Ok((n, &inp[digits..]))
987    }
988}
989
990/// Parses an optional sign from the beginning of the input. If one isn't
991/// found, then the sign returned is positive.
992///
993/// This also returns the remaining unparsed input.
994#[cfg_attr(feature = "perf-inline", inline(always))]
995fn parse_optional_sign<'i>(input: &'i [u8]) -> (i64, &'i [u8]) {
996    if input.is_empty() {
997        (1, input)
998    } else if input[0] == b'-' {
999        (-1, &input[1..])
1000    } else if input[0] == b'+' {
1001        (1, &input[1..])
1002    } else {
1003        (1, input)
1004    }
1005}
1006
1007/// Parses the input such that, on success, the index of the first matching
1008/// choice (via ASCII case insensitive comparisons) is returned, along with
1009/// any remaining unparsed input.
1010///
1011/// If no choice given is a prefix of the input, then an error is returned.
1012/// The error includes the possible allowed choices.
1013fn parse_choice<'i>(
1014    input: &'i [u8],
1015    choices: &[&'static [u8]],
1016) -> Result<(usize, &'i [u8]), Error> {
1017    for (i, choice) in choices.into_iter().enumerate() {
1018        if input.len() < choice.len() {
1019            continue;
1020        }
1021        let (candidate, input) = input.split_at(choice.len());
1022        if candidate.eq_ignore_ascii_case(choice) {
1023            return Ok((i, input));
1024        }
1025    }
1026    #[cfg(feature = "alloc")]
1027    {
1028        let mut err = alloc::format!(
1029            "failed to find expected choice at beginning of {input:?}, \
1030             available choices are: ",
1031            input = escape::Bytes(input),
1032        );
1033        for (i, choice) in choices.iter().enumerate() {
1034            if i > 0 {
1035                write!(err, ", ").unwrap();
1036            }
1037            write!(err, "{}", escape::Bytes(choice)).unwrap();
1038        }
1039        Err(Error::adhoc(err))
1040    }
1041    #[cfg(not(feature = "alloc"))]
1042    {
1043        Err(err!(
1044            "failed to find expected value from a set of allowed choices"
1045        ))
1046    }
1047}
1048
1049/// Like `parse_choice`, but specialized for AM/PM.
1050///
1051/// This exists because AM/PM is common and we can take advantage of the fact
1052/// that they are both exactly two bytes.
1053#[cfg_attr(feature = "perf-inline", inline(always))]
1054fn parse_ampm<'i>(input: &'i [u8]) -> Result<(usize, &'i [u8]), Error> {
1055    if input.len() < 2 {
1056        return Err(err!(
1057            "expected to find AM or PM, \
1058             but the remaining input, {input:?}, is too short \
1059             to contain one",
1060            input = escape::Bytes(input),
1061        ));
1062    }
1063    let (x, input) = input.split_at(2);
1064    let candidate = &[x[0].to_ascii_lowercase(), x[1].to_ascii_lowercase()];
1065    let index = match candidate {
1066        b"am" => 0,
1067        b"pm" => 1,
1068        _ => {
1069            return Err(err!(
1070                "expected to find AM or PM, but found \
1071                {candidate:?} instead",
1072                candidate = escape::Bytes(x),
1073            ))
1074        }
1075    };
1076    Ok((index, input))
1077}
1078
1079/// Like `parse_choice`, but specialized for weekday abbreviation.
1080///
1081/// This exists because weekday abbreviations are common and we can take
1082/// advantage of the fact that they are all exactly three bytes.
1083#[cfg_attr(feature = "perf-inline", inline(always))]
1084fn parse_weekday_abbrev<'i>(
1085    input: &'i [u8],
1086) -> Result<(usize, &'i [u8]), Error> {
1087    if input.len() < 3 {
1088        return Err(err!(
1089            "expected to find a weekday abbreviation, \
1090             but the remaining input, {input:?}, is too short \
1091             to contain one",
1092            input = escape::Bytes(input),
1093        ));
1094    }
1095    let (x, input) = input.split_at(3);
1096    let candidate = &[
1097        x[0].to_ascii_lowercase(),
1098        x[1].to_ascii_lowercase(),
1099        x[2].to_ascii_lowercase(),
1100    ];
1101    let index = match candidate {
1102        b"sun" => 0,
1103        b"mon" => 1,
1104        b"tue" => 2,
1105        b"wed" => 3,
1106        b"thu" => 4,
1107        b"fri" => 5,
1108        b"sat" => 6,
1109        _ => {
1110            return Err(err!(
1111                "expected to find weekday abbreviation, but found \
1112                {candidate:?} instead",
1113                candidate = escape::Bytes(x),
1114            ))
1115        }
1116    };
1117    Ok((index, input))
1118}
1119
1120/// Like `parse_choice`, but specialized for month name abbreviation.
1121///
1122/// This exists because month name abbreviations are common and we can take
1123/// advantage of the fact that they are all exactly three bytes.
1124#[cfg_attr(feature = "perf-inline", inline(always))]
1125fn parse_month_name_abbrev<'i>(
1126    input: &'i [u8],
1127) -> Result<(usize, &'i [u8]), Error> {
1128    if input.len() < 3 {
1129        return Err(err!(
1130            "expected to find a month name abbreviation, \
1131             but the remaining input, {input:?}, is too short \
1132             to contain one",
1133            input = escape::Bytes(input),
1134        ));
1135    }
1136    let (x, input) = input.split_at(3);
1137    let candidate = &[
1138        x[0].to_ascii_lowercase(),
1139        x[1].to_ascii_lowercase(),
1140        x[2].to_ascii_lowercase(),
1141    ];
1142    let index = match candidate {
1143        b"jan" => 0,
1144        b"feb" => 1,
1145        b"mar" => 2,
1146        b"apr" => 3,
1147        b"may" => 4,
1148        b"jun" => 5,
1149        b"jul" => 6,
1150        b"aug" => 7,
1151        b"sep" => 8,
1152        b"oct" => 9,
1153        b"nov" => 10,
1154        b"dec" => 11,
1155        _ => {
1156            return Err(err!(
1157                "expected to find month name abbreviation, but found \
1158                 {candidate:?} instead",
1159                candidate = escape::Bytes(x),
1160            ))
1161        }
1162    };
1163    Ok((index, input))
1164}
1165
1166#[cfg_attr(feature = "perf-inline", inline(always))]
1167fn parse_iana<'i>(input: &'i [u8]) -> Result<(&'i str, &'i [u8]), Error> {
1168    let mkiana = parse::slicer(input);
1169    let (_, mut input) = parse_iana_component(input)?;
1170    while input.starts_with(b"/") {
1171        input = &input[1..];
1172        let (_, unconsumed) = parse_iana_component(input)?;
1173        input = unconsumed;
1174    }
1175    // This is OK because all bytes in a IANA TZ annotation are guaranteed
1176    // to be ASCII, or else we wouldn't be here. If this turns out to be
1177    // a perf issue, we can do an unchecked conversion here. But I figured
1178    // it would be better to start conservative.
1179    let iana = core::str::from_utf8(mkiana(input)).expect("ASCII");
1180    Ok((iana, input))
1181}
1182
1183/// Parses a single IANA name component. That is, the thing that leads all IANA
1184/// time zone identifiers and the thing that must always come after a `/`. This
1185/// returns an error if no component could be found.
1186#[cfg_attr(feature = "perf-inline", inline(always))]
1187fn parse_iana_component<'i>(
1188    mut input: &'i [u8],
1189) -> Result<(&'i [u8], &'i [u8]), Error> {
1190    let mkname = parse::slicer(input);
1191    if input.is_empty() {
1192        return Err(err!(
1193            "expected the start of an IANA time zone identifier \
1194             name or component, but found end of input instead",
1195        ));
1196    }
1197    if !matches!(input[0], b'_' | b'.' | b'A'..=b'Z' | b'a'..=b'z') {
1198        return Err(err!(
1199            "expected the start of an IANA time zone identifier \
1200             name or component, but found {:?} instead",
1201            escape::Byte(input[0]),
1202        ));
1203    }
1204    input = &input[1..];
1205
1206    let is_iana_char = |byte| {
1207        matches!(
1208            byte,
1209            b'_' | b'.' | b'+' | b'-' | b'0'..=b'9' | b'A'..=b'Z' | b'a'..=b'z',
1210        )
1211    };
1212    while !input.is_empty() && is_iana_char(input[0]) {
1213        input = &input[1..];
1214    }
1215    Ok((mkname(input), input))
1216}
1217
1218#[cfg(feature = "alloc")]
1219#[cfg(test)]
1220mod tests {
1221    use alloc::string::ToString;
1222
1223    use super::*;
1224
1225    #[test]
1226    fn ok_parse_zoned() {
1227        if crate::tz::db().is_definitively_empty() {
1228            return;
1229        }
1230
1231        let p = |fmt: &str, input: &str| {
1232            BrokenDownTime::parse_mono(fmt.as_bytes(), input.as_bytes())
1233                .unwrap()
1234                .to_zoned()
1235                .unwrap()
1236        };
1237
1238        insta::assert_debug_snapshot!(
1239            p("%h %d, %Y %H:%M:%S %z", "Apr 1, 2022 20:46:15 -0400"),
1240            @"2022-04-01T20:46:15-04:00[-04:00]",
1241        );
1242        insta::assert_debug_snapshot!(
1243            p("%h %d, %Y %H:%M:%S %Q", "Apr 1, 2022 20:46:15 -0400"),
1244            @"2022-04-01T20:46:15-04:00[-04:00]",
1245        );
1246        insta::assert_debug_snapshot!(
1247            p("%h %d, %Y %H:%M:%S [%Q]", "Apr 1, 2022 20:46:15 [America/New_York]"),
1248            @"2022-04-01T20:46:15-04:00[America/New_York]",
1249        );
1250        insta::assert_debug_snapshot!(
1251            p("%h %d, %Y %H:%M:%S %Q", "Apr 1, 2022 20:46:15 America/New_York"),
1252            @"2022-04-01T20:46:15-04:00[America/New_York]",
1253        );
1254        insta::assert_debug_snapshot!(
1255            p("%h %d, %Y %H:%M:%S %:z %:Q", "Apr 1, 2022 20:46:15 -08:00 -04:00"),
1256            @"2022-04-01T20:46:15-04:00[-04:00]",
1257        );
1258    }
1259
1260    #[test]
1261    fn ok_parse_timestamp() {
1262        let p = |fmt: &str, input: &str| {
1263            BrokenDownTime::parse_mono(fmt.as_bytes(), input.as_bytes())
1264                .unwrap()
1265                .to_timestamp()
1266                .unwrap()
1267        };
1268
1269        insta::assert_debug_snapshot!(
1270            p("%h %d, %Y %H:%M:%S %z", "Apr 1, 2022 20:46:15 -0400"),
1271            @"2022-04-02T00:46:15Z",
1272        );
1273        insta::assert_debug_snapshot!(
1274            p("%h %d, %Y %H:%M:%S %z", "Apr 1, 2022 20:46:15 +0400"),
1275            @"2022-04-01T16:46:15Z",
1276        );
1277        insta::assert_debug_snapshot!(
1278            p("%h %d, %Y %H:%M:%S %z", "Apr 1, 2022 20:46:15 -040059"),
1279            @"2022-04-02T00:47:14Z",
1280        );
1281
1282        insta::assert_debug_snapshot!(
1283            p("%h %d, %Y %H:%M:%S %:z", "Apr 1, 2022 20:46:15 -04:00"),
1284            @"2022-04-02T00:46:15Z",
1285        );
1286        insta::assert_debug_snapshot!(
1287            p("%h %d, %Y %H:%M:%S %:z", "Apr 1, 2022 20:46:15 +04:00"),
1288            @"2022-04-01T16:46:15Z",
1289        );
1290        insta::assert_debug_snapshot!(
1291            p("%h %d, %Y %H:%M:%S %:z", "Apr 1, 2022 20:46:15 -04:00:59"),
1292            @"2022-04-02T00:47:14Z",
1293        );
1294
1295        insta::assert_debug_snapshot!(
1296            p("%s", "0"),
1297            @"1970-01-01T00:00:00Z",
1298        );
1299        insta::assert_debug_snapshot!(
1300            p("%s", "-0"),
1301            @"1970-01-01T00:00:00Z",
1302        );
1303        insta::assert_debug_snapshot!(
1304            p("%s", "-1"),
1305            @"1969-12-31T23:59:59Z",
1306        );
1307        insta::assert_debug_snapshot!(
1308            p("%s", "1"),
1309            @"1970-01-01T00:00:01Z",
1310        );
1311        insta::assert_debug_snapshot!(
1312            p("%s", "+1"),
1313            @"1970-01-01T00:00:01Z",
1314        );
1315        insta::assert_debug_snapshot!(
1316            p("%s", "1737396540"),
1317            @"2025-01-20T18:09:00Z",
1318        );
1319        insta::assert_debug_snapshot!(
1320            p("%s", "-377705023201"),
1321            @"-009999-01-02T01:59:59Z",
1322        );
1323        insta::assert_debug_snapshot!(
1324            p("%s", "253402207200"),
1325            @"9999-12-30T22:00:00Z",
1326        );
1327    }
1328
1329    #[test]
1330    fn ok_parse_datetime() {
1331        let p = |fmt: &str, input: &str| {
1332            BrokenDownTime::parse_mono(fmt.as_bytes(), input.as_bytes())
1333                .unwrap()
1334                .to_datetime()
1335                .unwrap()
1336        };
1337
1338        insta::assert_debug_snapshot!(
1339            p("%h %d, %Y %H:%M:%S", "Apr 1, 2022 20:46:15"),
1340            @"2022-04-01T20:46:15",
1341        );
1342        insta::assert_debug_snapshot!(
1343            p("%h %05d, %Y %H:%M:%S", "Apr 1, 2022 20:46:15"),
1344            @"2022-04-01T20:46:15",
1345        );
1346    }
1347
1348    #[test]
1349    fn ok_parse_date() {
1350        let p = |fmt: &str, input: &str| {
1351            BrokenDownTime::parse_mono(fmt.as_bytes(), input.as_bytes())
1352                .unwrap()
1353                .to_date()
1354                .unwrap()
1355        };
1356
1357        insta::assert_debug_snapshot!(
1358            p("%m/%d/%y", "1/1/99"),
1359            @"1999-01-01",
1360        );
1361        insta::assert_debug_snapshot!(
1362            p("%m/%d/%04y", "1/1/0099"),
1363            @"1999-01-01",
1364        );
1365        insta::assert_debug_snapshot!(
1366            p("%D", "1/1/99"),
1367            @"1999-01-01",
1368        );
1369        insta::assert_debug_snapshot!(
1370            p("%m/%d/%Y", "1/1/0099"),
1371            @"0099-01-01",
1372        );
1373        insta::assert_debug_snapshot!(
1374            p("%m/%d/%Y", "1/1/1999"),
1375            @"1999-01-01",
1376        );
1377        insta::assert_debug_snapshot!(
1378            p("%m/%d/%Y", "12/31/9999"),
1379            @"9999-12-31",
1380        );
1381        insta::assert_debug_snapshot!(
1382            p("%m/%d/%Y", "01/01/-9999"),
1383            @"-009999-01-01",
1384        );
1385        insta::assert_snapshot!(
1386            p("%a %m/%d/%Y", "sun 7/14/2024"),
1387            @"2024-07-14",
1388        );
1389        insta::assert_snapshot!(
1390            p("%A %m/%d/%Y", "sUnDaY 7/14/2024"),
1391            @"2024-07-14",
1392        );
1393        insta::assert_snapshot!(
1394            p("%b %d %Y", "Jul 14 2024"),
1395            @"2024-07-14",
1396        );
1397        insta::assert_snapshot!(
1398            p("%B %d, %Y", "July 14, 2024"),
1399            @"2024-07-14",
1400        );
1401        insta::assert_snapshot!(
1402            p("%A, %B %d, %Y", "Wednesday, dEcEmBeR 25, 2024"),
1403            @"2024-12-25",
1404        );
1405
1406        insta::assert_debug_snapshot!(
1407            p("%Y%m%d", "20240730"),
1408            @"2024-07-30",
1409        );
1410        insta::assert_debug_snapshot!(
1411            p("%Y%m%d", "09990730"),
1412            @"0999-07-30",
1413        );
1414        insta::assert_debug_snapshot!(
1415            p("%Y%m%d", "9990111"),
1416            @"9990-11-01",
1417        );
1418        insta::assert_debug_snapshot!(
1419            p("%3Y%m%d", "09990111"),
1420            @"0999-01-11",
1421        );
1422        insta::assert_debug_snapshot!(
1423            p("%5Y%m%d", "09990111"),
1424            @"9990-11-01",
1425        );
1426        insta::assert_debug_snapshot!(
1427            p("%5Y%m%d", "009990111"),
1428            @"0999-01-11",
1429        );
1430
1431        insta::assert_debug_snapshot!(
1432            p("%C-%m-%d", "20-07-01"),
1433            @"2000-07-01",
1434        );
1435        insta::assert_debug_snapshot!(
1436            p("%C-%m-%d", "-20-07-01"),
1437            @"-002000-07-01",
1438        );
1439        insta::assert_debug_snapshot!(
1440            p("%C-%m-%d", "9-07-01"),
1441            @"0900-07-01",
1442        );
1443        insta::assert_debug_snapshot!(
1444            p("%C-%m-%d", "-9-07-01"),
1445            @"-000900-07-01",
1446        );
1447        insta::assert_debug_snapshot!(
1448            p("%C-%m-%d", "09-07-01"),
1449            @"0900-07-01",
1450        );
1451        insta::assert_debug_snapshot!(
1452            p("%C-%m-%d", "-09-07-01"),
1453            @"-000900-07-01",
1454        );
1455        insta::assert_debug_snapshot!(
1456            p("%C-%m-%d", "0-07-01"),
1457            @"0000-07-01",
1458        );
1459        insta::assert_debug_snapshot!(
1460            p("%C-%m-%d", "-0-07-01"),
1461            @"0000-07-01",
1462        );
1463
1464        insta::assert_snapshot!(
1465            p("%u %m/%d/%Y", "7 7/14/2024"),
1466            @"2024-07-14",
1467        );
1468        insta::assert_snapshot!(
1469            p("%w %m/%d/%Y", "0 7/14/2024"),
1470            @"2024-07-14",
1471        );
1472
1473        insta::assert_snapshot!(
1474            p("%Y-%U-%u", "2025-00-6"),
1475            @"2025-01-04",
1476        );
1477        insta::assert_snapshot!(
1478            p("%Y-%U-%u", "2025-01-7"),
1479            @"2025-01-05",
1480        );
1481        insta::assert_snapshot!(
1482            p("%Y-%U-%u", "2025-01-1"),
1483            @"2025-01-06",
1484        );
1485
1486        insta::assert_snapshot!(
1487            p("%Y-%W-%u", "2025-00-6"),
1488            @"2025-01-04",
1489        );
1490        insta::assert_snapshot!(
1491            p("%Y-%W-%u", "2025-00-7"),
1492            @"2025-01-05",
1493        );
1494        insta::assert_snapshot!(
1495            p("%Y-%W-%u", "2025-01-1"),
1496            @"2025-01-06",
1497        );
1498        insta::assert_snapshot!(
1499            p("%Y-%W-%u", "2025-01-2"),
1500            @"2025-01-07",
1501        );
1502    }
1503
1504    #[test]
1505    fn ok_parse_time() {
1506        let p = |fmt: &str, input: &str| {
1507            BrokenDownTime::parse_mono(fmt.as_bytes(), input.as_bytes())
1508                .unwrap()
1509                .to_time()
1510                .unwrap()
1511        };
1512
1513        insta::assert_debug_snapshot!(
1514            p("%H:%M", "15:48"),
1515            @"15:48:00",
1516        );
1517        insta::assert_debug_snapshot!(
1518            p("%H:%M:%S", "15:48:59"),
1519            @"15:48:59",
1520        );
1521        insta::assert_debug_snapshot!(
1522            p("%H:%M:%S", "15:48:60"),
1523            @"15:48:59",
1524        );
1525        insta::assert_debug_snapshot!(
1526            p("%T", "15:48:59"),
1527            @"15:48:59",
1528        );
1529        insta::assert_debug_snapshot!(
1530            p("%R", "15:48"),
1531            @"15:48:00",
1532        );
1533
1534        insta::assert_debug_snapshot!(
1535            p("%H %p", "5 am"),
1536            @"05:00:00",
1537        );
1538        insta::assert_debug_snapshot!(
1539            p("%H%p", "5am"),
1540            @"05:00:00",
1541        );
1542        insta::assert_debug_snapshot!(
1543            p("%H%p", "11pm"),
1544            @"23:00:00",
1545        );
1546        insta::assert_debug_snapshot!(
1547            p("%I%p", "11pm"),
1548            @"23:00:00",
1549        );
1550        insta::assert_debug_snapshot!(
1551            p("%I%p", "12am"),
1552            @"00:00:00",
1553        );
1554        insta::assert_debug_snapshot!(
1555            p("%H%p", "23pm"),
1556            @"23:00:00",
1557        );
1558        insta::assert_debug_snapshot!(
1559            p("%H%p", "23am"),
1560            @"11:00:00",
1561        );
1562
1563        insta::assert_debug_snapshot!(
1564            p("%H:%M:%S%.f", "15:48:01.1"),
1565            @"15:48:01.1",
1566        );
1567        insta::assert_debug_snapshot!(
1568            p("%H:%M:%S%.255f", "15:48:01.1"),
1569            @"15:48:01.1",
1570        );
1571        insta::assert_debug_snapshot!(
1572            p("%H:%M:%S%255.255f", "15:48:01.1"),
1573            @"15:48:01.1",
1574        );
1575        insta::assert_debug_snapshot!(
1576            p("%H:%M:%S%.f", "15:48:01"),
1577            @"15:48:01",
1578        );
1579        insta::assert_debug_snapshot!(
1580            p("%H:%M:%S%.fa", "15:48:01a"),
1581            @"15:48:01",
1582        );
1583        insta::assert_debug_snapshot!(
1584            p("%H:%M:%S%.f", "15:48:01.123456789"),
1585            @"15:48:01.123456789",
1586        );
1587        insta::assert_debug_snapshot!(
1588            p("%H:%M:%S%.f", "15:48:01.000000001"),
1589            @"15:48:01.000000001",
1590        );
1591
1592        insta::assert_debug_snapshot!(
1593            p("%H:%M:%S.%f", "15:48:01.1"),
1594            @"15:48:01.1",
1595        );
1596        insta::assert_debug_snapshot!(
1597            p("%H:%M:%S.%3f", "15:48:01.123"),
1598            @"15:48:01.123",
1599        );
1600        insta::assert_debug_snapshot!(
1601            p("%H:%M:%S.%3f", "15:48:01.123456"),
1602            @"15:48:01.123456",
1603        );
1604
1605        insta::assert_debug_snapshot!(
1606            p("%H:%M:%S.%N", "15:48:01.1"),
1607            @"15:48:01.1",
1608        );
1609        insta::assert_debug_snapshot!(
1610            p("%H:%M:%S.%3N", "15:48:01.123"),
1611            @"15:48:01.123",
1612        );
1613        insta::assert_debug_snapshot!(
1614            p("%H:%M:%S.%3N", "15:48:01.123456"),
1615            @"15:48:01.123456",
1616        );
1617
1618        insta::assert_debug_snapshot!(
1619            p("%H", "09"),
1620            @"09:00:00",
1621        );
1622        insta::assert_debug_snapshot!(
1623            p("%H", " 9"),
1624            @"09:00:00",
1625        );
1626        insta::assert_debug_snapshot!(
1627            p("%H", "15"),
1628            @"15:00:00",
1629        );
1630        insta::assert_debug_snapshot!(
1631            p("%k", "09"),
1632            @"09:00:00",
1633        );
1634        insta::assert_debug_snapshot!(
1635            p("%k", " 9"),
1636            @"09:00:00",
1637        );
1638        insta::assert_debug_snapshot!(
1639            p("%k", "15"),
1640            @"15:00:00",
1641        );
1642
1643        insta::assert_debug_snapshot!(
1644            p("%I", "09"),
1645            @"09:00:00",
1646        );
1647        insta::assert_debug_snapshot!(
1648            p("%I", " 9"),
1649            @"09:00:00",
1650        );
1651        insta::assert_debug_snapshot!(
1652            p("%l", "09"),
1653            @"09:00:00",
1654        );
1655        insta::assert_debug_snapshot!(
1656            p("%l", " 9"),
1657            @"09:00:00",
1658        );
1659    }
1660
1661    #[test]
1662    fn ok_parse_whitespace() {
1663        let p = |fmt: &str, input: &str| {
1664            BrokenDownTime::parse_mono(fmt.as_bytes(), input.as_bytes())
1665                .unwrap()
1666                .to_time()
1667                .unwrap()
1668        };
1669
1670        insta::assert_debug_snapshot!(
1671            p("%H%M", "1548"),
1672            @"15:48:00",
1673        );
1674        insta::assert_debug_snapshot!(
1675            p("%H%M", "15\n48"),
1676            @"15:48:00",
1677        );
1678        insta::assert_debug_snapshot!(
1679            p("%H%M", "15\t48"),
1680            @"15:48:00",
1681        );
1682        insta::assert_debug_snapshot!(
1683            p("%H%n%M", "1548"),
1684            @"15:48:00",
1685        );
1686        insta::assert_debug_snapshot!(
1687            p("%H%n%M", "15\n48"),
1688            @"15:48:00",
1689        );
1690        insta::assert_debug_snapshot!(
1691            p("%H%n%M", "15\t48"),
1692            @"15:48:00",
1693        );
1694        insta::assert_debug_snapshot!(
1695            p("%H%t%M", "1548"),
1696            @"15:48:00",
1697        );
1698        insta::assert_debug_snapshot!(
1699            p("%H%t%M", "15\n48"),
1700            @"15:48:00",
1701        );
1702        insta::assert_debug_snapshot!(
1703            p("%H%t%M", "15\t48"),
1704            @"15:48:00",
1705        );
1706    }
1707
1708    #[test]
1709    fn ok_parse_offset() {
1710        let p = |fmt: &str, input: &str| {
1711            BrokenDownTime::parse_mono(fmt.as_bytes(), input.as_bytes())
1712                .unwrap()
1713                .to_offset()
1714                .unwrap()
1715        };
1716
1717        insta::assert_debug_snapshot!(
1718            p("%z", "+0530"),
1719            @"05:30:00",
1720        );
1721        insta::assert_debug_snapshot!(
1722            p("%z", "-0530"),
1723            @"-05:30:00",
1724        );
1725        insta::assert_debug_snapshot!(
1726            p("%z", "-0500"),
1727            @"-05:00:00",
1728        );
1729        insta::assert_debug_snapshot!(
1730            p("%z", "+053015"),
1731            @"05:30:15",
1732        );
1733        insta::assert_debug_snapshot!(
1734            p("%z", "+050015"),
1735            @"05:00:15",
1736        );
1737
1738        insta::assert_debug_snapshot!(
1739            p("%:z", "+05:30"),
1740            @"05:30:00",
1741        );
1742        insta::assert_debug_snapshot!(
1743            p("%:z", "-05:30"),
1744            @"-05:30:00",
1745        );
1746        insta::assert_debug_snapshot!(
1747            p("%:z", "-05:00"),
1748            @"-05:00:00",
1749        );
1750        insta::assert_debug_snapshot!(
1751            p("%:z", "+05:30:15"),
1752            @"05:30:15",
1753        );
1754        insta::assert_debug_snapshot!(
1755            p("%:z", "-05:00:15"),
1756            @"-05:00:15",
1757        );
1758
1759        insta::assert_debug_snapshot!(
1760            p("%::z", "+05:30:15"),
1761            @"05:30:15",
1762        );
1763        insta::assert_debug_snapshot!(
1764            p("%::z", "-05:30:15"),
1765            @"-05:30:15",
1766        );
1767        insta::assert_debug_snapshot!(
1768            p("%::z", "-05:00:00"),
1769            @"-05:00:00",
1770        );
1771        insta::assert_debug_snapshot!(
1772            p("%::z", "-05:00:15"),
1773            @"-05:00:15",
1774        );
1775
1776        insta::assert_debug_snapshot!(
1777            p("%:::z", "+05"),
1778            @"05:00:00",
1779        );
1780        insta::assert_debug_snapshot!(
1781            p("%:::z", "-05"),
1782            @"-05:00:00",
1783        );
1784        insta::assert_debug_snapshot!(
1785            p("%:::z", "+00"),
1786            @"00:00:00",
1787        );
1788        insta::assert_debug_snapshot!(
1789            p("%:::z", "-00"),
1790            @"00:00:00",
1791        );
1792        insta::assert_debug_snapshot!(
1793            p("%:::z", "+05:30"),
1794            @"05:30:00",
1795        );
1796        insta::assert_debug_snapshot!(
1797            p("%:::z", "-05:30"),
1798            @"-05:30:00",
1799        );
1800        insta::assert_debug_snapshot!(
1801            p("%:::z", "+05:30:15"),
1802            @"05:30:15",
1803        );
1804        insta::assert_debug_snapshot!(
1805            p("%:::z", "-05:30:15"),
1806            @"-05:30:15",
1807        );
1808        insta::assert_debug_snapshot!(
1809            p("%:::z", "-05:00:00"),
1810            @"-05:00:00",
1811        );
1812        insta::assert_debug_snapshot!(
1813            p("%:::z", "-05:00:15"),
1814            @"-05:00:15",
1815        );
1816    }
1817
1818    #[test]
1819    fn err_parse() {
1820        let p = |fmt: &str, input: &str| {
1821            BrokenDownTime::parse_mono(fmt.as_bytes(), input.as_bytes())
1822                .unwrap_err()
1823                .to_string()
1824        };
1825
1826        insta::assert_snapshot!(
1827            p("%M", ""),
1828            @"strptime parsing failed: expected non-empty input for directive %M, but found end of input",
1829        );
1830        insta::assert_snapshot!(
1831            p("%M", "a"),
1832            @"strptime parsing failed: %M failed: failed to parse minute: invalid number, no digits found",
1833        );
1834        insta::assert_snapshot!(
1835            p("%M%S", "15"),
1836            @"strptime parsing failed: expected non-empty input for directive %S, but found end of input",
1837        );
1838        insta::assert_snapshot!(
1839            p("%M%a", "Sun"),
1840            @"strptime parsing failed: %M failed: failed to parse minute: invalid number, no digits found",
1841        );
1842
1843        insta::assert_snapshot!(
1844            p("%y", "999"),
1845            @r###"strptime expects to consume the entire input, but "9" remains unparsed"###,
1846        );
1847        insta::assert_snapshot!(
1848            p("%Y", "-10000"),
1849            @r###"strptime expects to consume the entire input, but "0" remains unparsed"###,
1850        );
1851        insta::assert_snapshot!(
1852            p("%Y", "10000"),
1853            @r###"strptime expects to consume the entire input, but "0" remains unparsed"###,
1854        );
1855        insta::assert_snapshot!(
1856            p("%A %m/%d/%y", "Mon 7/14/24"),
1857            @r#"strptime parsing failed: %A failed: unrecognized weekday abbreviation: failed to find expected choice at beginning of "Mon 7/14/24", available choices are: Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday"#,
1858        );
1859        insta::assert_snapshot!(
1860            p("%b", "Bad"),
1861            @r###"strptime parsing failed: %b failed: expected to find month name abbreviation, but found "Bad" instead"###,
1862        );
1863        insta::assert_snapshot!(
1864            p("%h", "July"),
1865            @r###"strptime expects to consume the entire input, but "y" remains unparsed"###,
1866        );
1867        insta::assert_snapshot!(
1868            p("%B", "Jul"),
1869            @r###"strptime parsing failed: %B failed: unrecognized month name: failed to find expected choice at beginning of "Jul", available choices are: January, February, March, April, May, June, July, August, September, October, November, December"###,
1870        );
1871        insta::assert_snapshot!(
1872            p("%H", "24"),
1873            @"strptime parsing failed: %H failed: hour number is invalid: parameter 'hour' with value 24 is not in the required range of 0..=23",
1874        );
1875        insta::assert_snapshot!(
1876            p("%M", "60"),
1877            @"strptime parsing failed: %M failed: minute number is invalid: parameter 'minute' with value 60 is not in the required range of 0..=59",
1878        );
1879        insta::assert_snapshot!(
1880            p("%S", "61"),
1881            @"strptime parsing failed: %S failed: second number is invalid: parameter 'second' with value 61 is not in the required range of 0..=59",
1882        );
1883        insta::assert_snapshot!(
1884            p("%I", "0"),
1885            @"strptime parsing failed: %I failed: hour number is invalid: parameter 'hour' with value 0 is not in the required range of 1..=12",
1886        );
1887        insta::assert_snapshot!(
1888            p("%I", "13"),
1889            @"strptime parsing failed: %I failed: hour number is invalid: parameter 'hour' with value 13 is not in the required range of 1..=12",
1890        );
1891        insta::assert_snapshot!(
1892            p("%p", "aa"),
1893            @r###"strptime parsing failed: %p failed: expected to find AM or PM, but found "aa" instead"###,
1894        );
1895
1896        insta::assert_snapshot!(
1897            p("%_", " "),
1898            @r###"strptime parsing failed: expected to find specifier directive after flag "_", but found end of format string"###,
1899        );
1900        insta::assert_snapshot!(
1901            p("%-", " "),
1902            @r###"strptime parsing failed: expected to find specifier directive after flag "-", but found end of format string"###,
1903        );
1904        insta::assert_snapshot!(
1905            p("%0", " "),
1906            @r###"strptime parsing failed: expected to find specifier directive after flag "0", but found end of format string"###,
1907        );
1908        insta::assert_snapshot!(
1909            p("%^", " "),
1910            @r###"strptime parsing failed: expected to find specifier directive after flag "^", but found end of format string"###,
1911        );
1912        insta::assert_snapshot!(
1913            p("%#", " "),
1914            @r###"strptime parsing failed: expected to find specifier directive after flag "#", but found end of format string"###,
1915        );
1916        insta::assert_snapshot!(
1917            p("%_1", " "),
1918            @"strptime parsing failed: expected to find specifier directive after width 1, but found end of format string",
1919        );
1920        insta::assert_snapshot!(
1921            p("%_23", " "),
1922            @"strptime parsing failed: expected to find specifier directive after width 23, but found end of format string",
1923        );
1924
1925        insta::assert_snapshot!(
1926            p("%H:%M:%S%.f", "15:59:01."),
1927            @"strptime parsing failed: %.f failed: expected at least one fractional decimal digit, but did not find any",
1928        );
1929        insta::assert_snapshot!(
1930            p("%H:%M:%S%.f", "15:59:01.a"),
1931            @"strptime parsing failed: %.f failed: expected at least one fractional decimal digit, but did not find any",
1932        );
1933        insta::assert_snapshot!(
1934            p("%H:%M:%S%.f", "15:59:01.1234567891"),
1935            @r###"strptime expects to consume the entire input, but "1" remains unparsed"###,
1936        );
1937        insta::assert_snapshot!(
1938            p("%H:%M:%S.%f", "15:59:01."),
1939            @"strptime parsing failed: expected non-empty input for directive %f, but found end of input",
1940        );
1941        insta::assert_snapshot!(
1942            p("%H:%M:%S.%f", "15:59:01"),
1943            @r###"strptime parsing failed: expected to match literal byte "." from format string, but found end of input"###,
1944        );
1945        insta::assert_snapshot!(
1946            p("%H:%M:%S.%f", "15:59:01.a"),
1947            @"strptime parsing failed: %f failed: expected at least one fractional decimal digit, but did not find any",
1948        );
1949        insta::assert_snapshot!(
1950            p("%H:%M:%S.%N", "15:59:01."),
1951            @"strptime parsing failed: expected non-empty input for directive %N, but found end of input",
1952        );
1953        insta::assert_snapshot!(
1954            p("%H:%M:%S.%N", "15:59:01"),
1955            @r###"strptime parsing failed: expected to match literal byte "." from format string, but found end of input"###,
1956        );
1957        insta::assert_snapshot!(
1958            p("%H:%M:%S.%N", "15:59:01.a"),
1959            @"strptime parsing failed: %N failed: expected at least one fractional decimal digit, but did not find any",
1960        );
1961
1962        insta::assert_snapshot!(
1963            p("%Q", "+America/New_York"),
1964            @r#"strptime parsing failed: %Q failed: failed to parse hours in UTC numeric offset "+America/New_York": failed to parse "Am" as hours (a two digit integer): invalid digit, expected 0-9 but got A"#,
1965        );
1966        insta::assert_snapshot!(
1967            p("%Q", "-America/New_York"),
1968            @r#"strptime parsing failed: %Q failed: failed to parse hours in UTC numeric offset "-America/New_York": failed to parse "Am" as hours (a two digit integer): invalid digit, expected 0-9 but got A"#,
1969        );
1970        insta::assert_snapshot!(
1971            p("%:Q", "+0400"),
1972            @r#"strptime parsing failed: %:Q failed: parsed hour component of time zone offset from "+0400", but could not find required colon separator"#,
1973        );
1974        insta::assert_snapshot!(
1975            p("%Q", "+04:00"),
1976            @r#"strptime parsing failed: %Q failed: parsed hour component of time zone offset from "+04:00", but found colon after hours which is not allowed"#,
1977        );
1978        insta::assert_snapshot!(
1979            p("%Q", "America/"),
1980            @"strptime parsing failed: %Q failed: expected the start of an IANA time zone identifier name or component, but found end of input instead",
1981        );
1982        insta::assert_snapshot!(
1983            p("%Q", "America/+"),
1984            @r###"strptime parsing failed: %Q failed: expected the start of an IANA time zone identifier name or component, but found "+" instead"###,
1985        );
1986
1987        insta::assert_snapshot!(
1988            p("%s", "-377705023202"),
1989            @"strptime parsing failed: %s failed: parsed Unix timestamp `-377705023202`, but out of range of valid Jiff `Timestamp`: parameter 'second' with value -377705023202 is not in the required range of -377705023201..=253402207200",
1990        );
1991        insta::assert_snapshot!(
1992            p("%s", "253402207201"),
1993            @"strptime parsing failed: %s failed: parsed Unix timestamp `253402207201`, but out of range of valid Jiff `Timestamp`: parameter 'second' with value 253402207201 is not in the required range of -377705023201..=253402207200",
1994        );
1995        insta::assert_snapshot!(
1996            p("%s", "-9999999999999999999"),
1997            @"strptime parsing failed: %s failed: failed to parse Unix timestamp (in seconds): number '9999999999999999999' too big to parse into 64-bit integer",
1998        );
1999        insta::assert_snapshot!(
2000            p("%s", "9999999999999999999"),
2001            @"strptime parsing failed: %s failed: failed to parse Unix timestamp (in seconds): number '9999999999999999999' too big to parse into 64-bit integer",
2002        );
2003
2004        insta::assert_snapshot!(
2005            p("%u", "0"),
2006            @"strptime parsing failed: %u failed: weekday number is invalid: parameter 'weekday' with value 0 is not in the required range of 1..=7",
2007        );
2008        insta::assert_snapshot!(
2009            p("%w", "7"),
2010            @"strptime parsing failed: %w failed: weekday number is invalid: parameter 'weekday' with value 7 is not in the required range of 0..=6",
2011        );
2012        insta::assert_snapshot!(
2013            p("%u", "128"),
2014            @r###"strptime expects to consume the entire input, but "28" remains unparsed"###,
2015        );
2016        insta::assert_snapshot!(
2017            p("%w", "128"),
2018            @r###"strptime expects to consume the entire input, but "28" remains unparsed"###,
2019        );
2020    }
2021
2022    #[test]
2023    fn err_parse_date() {
2024        let p = |fmt: &str, input: &str| {
2025            BrokenDownTime::parse_mono(fmt.as_bytes(), input.as_bytes())
2026                .unwrap()
2027                .to_date()
2028                .unwrap_err()
2029                .to_string()
2030        };
2031
2032        insta::assert_snapshot!(
2033            p("%Y", "2024"),
2034            @"a month/day, day-of-year or week date must be present to create a date, but none were found",
2035        );
2036        insta::assert_snapshot!(
2037            p("%m", "7"),
2038            @"missing year, date cannot be created",
2039        );
2040        insta::assert_snapshot!(
2041            p("%d", "25"),
2042            @"missing year, date cannot be created",
2043        );
2044        insta::assert_snapshot!(
2045            p("%Y-%m", "2024-7"),
2046            @"a month/day, day-of-year or week date must be present to create a date, but none were found",
2047        );
2048        insta::assert_snapshot!(
2049            p("%Y-%d", "2024-25"),
2050            @"a month/day, day-of-year or week date must be present to create a date, but none were found",
2051        );
2052        insta::assert_snapshot!(
2053            p("%m-%d", "7-25"),
2054            @"missing year, date cannot be created",
2055        );
2056
2057        insta::assert_snapshot!(
2058            p("%m/%d/%y", "6/31/24"),
2059            @"invalid date: parameter 'day' with value 31 is not in the required range of 1..=30",
2060        );
2061        insta::assert_snapshot!(
2062            p("%m/%d/%y", "2/29/23"),
2063            @"invalid date: parameter 'day' with value 29 is not in the required range of 1..=28",
2064        );
2065        insta::assert_snapshot!(
2066            p("%a %m/%d/%y", "Mon 7/14/24"),
2067            @"parsed weekday Monday does not match weekday Sunday from parsed date 2024-07-14",
2068        );
2069        insta::assert_snapshot!(
2070            p("%A %m/%d/%y", "Monday 7/14/24"),
2071            @"parsed weekday Monday does not match weekday Sunday from parsed date 2024-07-14",
2072        );
2073
2074        insta::assert_snapshot!(
2075            p("%Y-%U-%u", "2025-00-2"),
2076            @"weekday `Tuesday` is not valid for Sunday based week number `0` in year `2025`",
2077        );
2078        insta::assert_snapshot!(
2079            p("%Y-%W-%u", "2025-00-2"),
2080            @"weekday `Tuesday` is not valid for Monday based week number `0` in year `2025`",
2081        );
2082    }
2083
2084    #[test]
2085    fn err_parse_time() {
2086        let p = |fmt: &str, input: &str| {
2087            BrokenDownTime::parse_mono(fmt.as_bytes(), input.as_bytes())
2088                .unwrap()
2089                .to_time()
2090                .unwrap_err()
2091                .to_string()
2092        };
2093
2094        insta::assert_snapshot!(
2095            p("%M", "59"),
2096            @"parsing format did not include hour directive, but did include minute directive (cannot have smaller time units with bigger time units missing)",
2097        );
2098        insta::assert_snapshot!(
2099            p("%S", "59"),
2100            @"parsing format did not include hour directive, but did include second directive (cannot have smaller time units with bigger time units missing)",
2101        );
2102        insta::assert_snapshot!(
2103            p("%M:%S", "59:59"),
2104            @"parsing format did not include hour directive, but did include minute directive (cannot have smaller time units with bigger time units missing)",
2105        );
2106        insta::assert_snapshot!(
2107            p("%H:%S", "15:59"),
2108            @"parsing format did not include minute directive, but did include second directive (cannot have smaller time units with bigger time units missing)",
2109        );
2110    }
2111
2112    #[test]
2113    fn err_parse_offset() {
2114        let p = |fmt: &str, input: &str| {
2115            BrokenDownTime::parse_mono(fmt.as_bytes(), input.as_bytes())
2116                .unwrap_err()
2117                .to_string()
2118        };
2119
2120        insta::assert_snapshot!(
2121            p("%z", "+05:30"),
2122            @r#"strptime parsing failed: %z failed: parsed hour component of time zone offset from "+05:30", but found colon after hours which is not allowed"#,
2123        );
2124        insta::assert_snapshot!(
2125            p("%:z", "+0530"),
2126            @r#"strptime parsing failed: %:z failed: parsed hour component of time zone offset from "+0530", but could not find required colon separator"#,
2127        );
2128        insta::assert_snapshot!(
2129            p("%::z", "+0530"),
2130            @r#"strptime parsing failed: %::z failed: parsed hour component of time zone offset from "+0530", but could not find required colon separator"#,
2131        );
2132        insta::assert_snapshot!(
2133            p("%:::z", "+0530"),
2134            @r#"strptime parsing failed: %:::z failed: parsed hour component of time zone offset from "+0530", but could not find required colon separator"#,
2135        );
2136
2137        insta::assert_snapshot!(
2138            p("%z", "+05"),
2139            @r#"strptime parsing failed: %z failed: parsed hour component of time zone offset from "+05", but could not find required minute component"#,
2140        );
2141        insta::assert_snapshot!(
2142            p("%:z", "+05"),
2143            @r#"strptime parsing failed: %:z failed: parsed hour component of time zone offset from "+05", but could not find required minute component"#,
2144        );
2145        insta::assert_snapshot!(
2146            p("%::z", "+05"),
2147            @r#"strptime parsing failed: %::z failed: parsed hour component of time zone offset from "+05", but could not find required minute component"#,
2148        );
2149        insta::assert_snapshot!(
2150            p("%::z", "+05:30"),
2151            @r#"strptime parsing failed: %::z failed: parsed hour and minute components of time zone offset from "+05:30", but could not find required second component"#,
2152        );
2153        insta::assert_snapshot!(
2154            p("%:::z", "+5"),
2155            @r#"strptime parsing failed: %:::z failed: failed to parse hours in UTC numeric offset "+5": expected two digit hour after sign, but found end of input"#,
2156        );
2157
2158        insta::assert_snapshot!(
2159            p("%z", "+0530:15"),
2160            @r#"strptime expects to consume the entire input, but ":15" remains unparsed"#,
2161        );
2162        insta::assert_snapshot!(
2163            p("%:z", "+05:3015"),
2164            @r#"strptime expects to consume the entire input, but "15" remains unparsed"#,
2165        );
2166        insta::assert_snapshot!(
2167            p("%::z", "+05:3015"),
2168            @r#"strptime parsing failed: %::z failed: parsed hour and minute components of time zone offset from "+05:3015", but could not find required second component"#,
2169        );
2170        insta::assert_snapshot!(
2171            p("%:::z", "+05:3015"),
2172            @r#"strptime expects to consume the entire input, but "15" remains unparsed"#,
2173        );
2174    }
2175}