jiff/fmt/strtime/
parse.rs

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