Skip to main content

deep_time/
parser.rs

1use crate::error::{DtErr, DtErrKind};
2use crate::{Dt, Meridiem, Offset, TimeParts, Weekday, an_err};
3use core::result::Result;
4use core::str;
5
6#[cfg(feature = "alloc")]
7use crate::Scale;
8
9/// A pre-validated, reusable date/time format string.
10///
11/// - Format is validated **once** at construction (`new` returns `Result`).
12/// - Format bytes are copied into an owned fixed-size buffer.
13/// - Only ASCII formats are accepted.
14///
15/// ## See also
16///
17/// - [`StrPTimeFmt::new`]
18/// - [`StrPTimeFmt::to_dt`]
19/// - [`StrPTimeFmt::to_str`]
20#[derive(Debug, Clone, Copy)]
21pub struct StrPTimeFmt {
22    fmt: [u8; Self::MAX_FORMAT_LEN],
23    len: usize,
24}
25
26impl StrPTimeFmt {
27    pub const MAX_FORMAT_LEN: usize = 256;
28
29    /// Creates a new validated format.
30    ///
31    /// - Validates syntax and supported directives.
32    /// - Requires the format to be valid ASCII and ≤ 256 bytes.
33    /// - Returns a `DtErr` on any failure.
34    ///
35    /// ## Examples
36    ///
37    /// ```
38    /// # #[cfg(feature = "parse")]
39    /// # {
40    /// use deep_time::{Dt, StrPTimeFmt};
41    ///
42    /// let fmt = Dt::parse_fmt("%F %T").unwrap();
43    ///
44    /// // parse a datetime
45    /// let dt = fmt.to_dt("2025-05-23 14:30:00", false, false, false).unwrap();
46    ///
47    /// // change a datetimes format
48    /// let s = fmt.to_str("2000-01-01 12:00:00", "%d %m %Y %H:%M:%S", false, false, false).unwrap();
49    /// # }
50    /// ```
51    pub fn new(fmt: &str) -> Result<Self, DtErr> {
52        if fmt.len() > Self::MAX_FORMAT_LEN {
53            return Err(an_err!(
54                DtErrKind::UnexpectedEnd,
55                "format string too long (max {} bytes)",
56                Self::MAX_FORMAT_LEN
57            ));
58        }
59        let fmt = fmt.as_bytes();
60        if !fmt.is_ascii() {
61            return Err(an_err!(
62                DtErrKind::UnexpectedEnd,
63                "format string must be ASCII"
64            ));
65        }
66
67        Self::validate_format(fmt)?;
68
69        let mut buffer = [0u8; Self::MAX_FORMAT_LEN];
70        buffer[..fmt.len()].copy_from_slice(fmt);
71
72        Ok(Self {
73            fmt: buffer,
74            len: fmt.len(),
75        })
76    }
77
78    /// Parses a date/time string using this pre-validated format.
79    ///
80    /// The four boolean flags control lenient parsing behavior — see
81    /// [`Dt::from_str`](../struct.Dt.html#method.from_str) for full documentation.
82    ///
83    /// ## Parameters
84    ///
85    /// - `s`: The input string to parse.
86    /// - `inp_can_end_before_fmt`: Allow input to end before format is fully consumed.
87    /// - `fmt_can_end_before_inp`: Allow format to end before input is fully consumed.
88    /// - `allow_partial_date`: Default missing month/day to `1` instead of erroring.
89    ///
90    /// ## Errors
91    ///
92    /// Returns [`DtErr`] for parse failures, incomplete data, trailing characters, etc.
93    ///
94    /// ## Example
95    ///
96    /// ```
97    /// use deep_time::{Dt, StrPTimeFmt};
98    ///
99    /// let fmt = Dt::parse_fmt("%F %T").unwrap();
100    /// let dt = fmt.to_dt("2025-05-23 14:30:00", false, false, false).unwrap();
101    /// ```
102    pub fn to_dt(
103        &self,
104        s: &str,
105        inp_can_end_before_fmt: bool,
106        fmt_can_end_before_inp: bool,
107        allow_partial_date: bool,
108    ) -> Result<Dt, DtErr> {
109        TimeParts::from_str(
110            self.as_str()?,
111            s,
112            inp_can_end_before_fmt,
113            fmt_can_end_before_inp,
114            allow_partial_date,
115        )
116        .and_then(|p| p.to_dt())
117    }
118
119    /// Formats a [`Dt`] into a string using this pre-validated format and a given
120    /// output format.
121    ///
122    /// Effectively parses a [`str`] with the contained format, then outputs a
123    /// [`String`](`alloc::string::String`) to a new given format.
124    ///
125    /// Requires the `alloc` feature.
126    ///
127    /// ## Parameters
128    ///
129    /// - `s`: datetime input [`str`].
130    /// - `output_fmt`: The new format to output the datetime as.
131    /// - The remaining three flags are passed through to the internal `to_dt` call.
132    ///
133    /// ## Example
134    ///
135    /// ```
136    /// use deep_time::{Dt, StrPTimeFmt};
137    ///
138    /// let fmt = Dt::parse_fmt("%Y-%m-%dT%H:%M:%S").unwrap();
139    /// let s = fmt.to_str("2000-01-01T12:00:00", "%d %m %Y %H:%M:%S", false, false, false).unwrap();
140    /// ```
141    #[cfg(feature = "alloc")]
142    pub fn to_str(
143        &self,
144        s: &str,
145        output_fmt: &str,
146        inp_can_end_before_fmt: bool,
147        fmt_can_end_before_inp: bool,
148        allow_partial_date: bool,
149    ) -> Result<alloc::string::String, DtErr> {
150        self.to_dt(
151            s,
152            inp_can_end_before_fmt,
153            fmt_can_end_before_inp,
154            allow_partial_date,
155        )?
156        .to_str(Scale::TAI, output_fmt)
157    }
158
159    fn validate_format(mut fmt: &[u8]) -> Result<(), DtErr> {
160        while !fmt.is_empty() {
161            if fmt[0] != b'%' {
162                // literal character (including whitespace) — always valid
163                fmt = &fmt[1..];
164                continue;
165            }
166
167            // lone % at end of format
168            if fmt.len() == 1 {
169                return Err(an_err!(DtErrKind::UnexpectedEnd, "after %"));
170            }
171            fmt = &fmt[1..]; // eat %
172
173            // reuse existing helper for flags/width/colons
174            let (_, _, _, new_fmt) = Parser::parse_format_extensions(fmt, 0);
175            fmt = new_fmt;
176
177            if fmt.is_empty() {
178                return Err(an_err!(DtErrKind::UnexpectedEnd, "expected directive"));
179            }
180
181            let directive = fmt[0];
182
183            match directive {
184            // all currently supported directives
185            b'%' | b'A' | b'a' | b'B' | b'b' | b'h' | b'C' | b'd' | b'e' |
186            b'f' | b'N' | b'G' | b'g' | b'H' | b'k' | b'I' | b'l' | b'j' |
187            b'M' | b'm' | b'n' | b't' | b'P' | b'p' | b'Q' | b'S' | b's' |
188            b'U' | b'u' | b'V' | b'W' | b'w' | b'Y' | b'y' | b'z' |
189            // shortcuts
190            b'F' | b'D' | b'T' | b'R' |
191            // library directives
192            b'*' => {
193                fmt = &fmt[1..];
194            }
195
196            b'.' => {
197                // special case for %.f / %.3N etc.
198                fmt = &fmt[1..]; // eat the .
199
200                // optional width digits
201                while !fmt.is_empty() && fmt[0].is_ascii_digit() {
202                    fmt = &fmt[1..];
203                }
204
205                let next = fmt.first().copied().unwrap_or(0);
206                if !matches!(next, b'f' | b'N') {
207                    return Err(an_err!(DtErrKind::BadFractional, "{}", char::from(next)));
208                }
209                fmt = &fmt[1..];
210            }
211
212            // explicitly unsupported (same as Parser)
213            b'c' | b'r' | b'X' | b'x' | b'Z' => {
214                return Err(an_err!(
215                    DtErrKind::UnsupportedDirective,
216                    "{}",
217                    char::from(directive)
218                ));
219            }
220
221            _ => {
222                return Err(an_err!(DtErrKind::UnknownDirective));
223            }
224        }
225        }
226
227        Ok(())
228    }
229
230    #[inline]
231    fn as_bytes(&self) -> &[u8] {
232        &self.fmt[..self.len]
233    }
234
235    #[inline]
236    fn as_str(&self) -> Result<&str, DtErr> {
237        match core::str::from_utf8(self.as_bytes()) {
238            Ok(f) => Ok(f),
239            Err(e) => Err(an_err!(DtErrKind::InvalidBytes, "{}", e)),
240        }
241    }
242}
243
244pub(crate) struct Parser<'f, 'i, 't> {
245    pub(crate) fmt: &'f [u8], // remaining format string
246    pub(crate) inp: &'i [u8], // remaining input string
247    tm: &'t mut TimeParts,
248    inp_can_end_before_fmt: bool,
249}
250
251impl<'f, 'i, 't> Parser<'f, 'i, 't> {
252    pub(crate) fn new(
253        fmt: &'f [u8],
254        inp: &'i [u8],
255        tm: &'t mut TimeParts,
256        inp_can_end_before_fmt: bool,
257    ) -> Self {
258        Self {
259            fmt,
260            inp,
261            tm,
262            inp_can_end_before_fmt,
263        }
264    }
265
266    #[inline]
267    fn current_format_byte(&self) -> u8 {
268        self.fmt[0]
269    }
270
271    #[inline]
272    fn current_input_byte(&self) -> u8 {
273        self.inp[0]
274    }
275
276    #[inline]
277    fn advance_format(&mut self) -> bool {
278        self.fmt = &self.fmt[1..];
279        !self.fmt.is_empty()
280    }
281
282    #[inline]
283    fn advance_input(&mut self) -> bool {
284        self.inp = &self.inp[1..];
285        !self.inp.is_empty()
286    }
287
288    pub(crate) fn parse(&mut self) -> Result<(), DtErr> {
289        while !self.fmt.is_empty() {
290            if self.current_format_byte() != b'%' {
291                self.parse_literal_character()?;
292                continue;
293            }
294            if !self.advance_format() {
295                return Err(an_err!(DtErrKind::UnexpectedEnd, "after %"));
296            }
297
298            let (flag, width, colons, new_fmt) = Self::parse_format_extensions(self.fmt, 0);
299            self.fmt = new_fmt;
300
301            let directive = self.fmt.first().copied().unwrap_or(0);
302
303            if self.inp.is_empty() {
304                if self.inp_can_end_before_fmt {
305                    if !matches!(directive, b'.' | b'f' | b'N') {
306                        return Err(an_err!(DtErrKind::UnexpectedEnd, "input exhausted"));
307                    }
308                } else {
309                    return Ok(());
310                }
311            }
312
313            match directive {
314                b'%' => self.parse_percent_sign()?,
315                b'A' => self.parse_weekday_full()?,
316                b'a' => self.parse_weekday_abbrev()?,
317                b'B' => self.parse_month_name_full()?,
318                b'b' | b'h' => self.parse_month_name_abbrev()?,
319                b'C' => self.parse_century(flag, width, colons)?,
320                b'd' | b'e' => self.parse_day_of_month(flag, width, colons, true)?,
321                b'f' | b'N' => {
322                    self.parse_fractional_seconds(flag, width, colons)?;
323                    self.advance_format();
324                }
325                b'G' => self.parse_iso_week_year(flag, width, colons)?,
326                b'g' => self.parse_two_digit_iso_week_year(flag, width, colons)?,
327                b'H' | b'k' => self.parse_hour24(flag, width, colons, true)?,
328                b'I' | b'l' => self.parse_hour12(flag, width, colons)?,
329                b'j' => self.parse_day_of_year(flag, width, colons)?,
330                b'M' => self.parse_minute(flag, width, colons, true)?,
331                b'm' => self.parse_month_number(flag, width, colons, true)?,
332                b'n' | b't' => self.skip_whitespace()?,
333                b'P' | b'p' => self.parse_ampm()?,
334                b'Q' => self.parse_iana_or_offset(flag, width, colons)?,
335                b'S' => self.parse_second(flag, width, colons, true)?,
336                b's' => self.parse_unix_timestamp(flag, width, colons)?,
337                b'U' => self.parse_week_number_sunday_based(flag, width, colons)?,
338                b'u' => self.parse_weekday_number_monday_based(flag, width, colons)?,
339                b'V' => self.parse_week_iso(flag, width, colons)?,
340                b'W' => self.parse_week_number_monday_based(flag, width, colons)?,
341                b'w' => self.parse_weekday_number_sunday_based(flag, width, colons)?,
342                b'Y' => self.parse_full_year(flag, width, colons, true)?,
343                b'y' => self.parse_two_digit_year(flag, width, colons, true)?,
344                b'z' => self.parse_timezone_offset(flag, width, colons)?,
345                b'.' => {
346                    if !self.advance_format() {
347                        return Err(an_err!(DtErrKind::UnexpectedEnd, "after ."));
348                    }
349
350                    let width = if !self.fmt.is_empty()
351                        && self.current_format_byte().is_ascii_digit()
352                    {
353                        let start = self.fmt;
354                        while !self.fmt.is_empty() && self.current_format_byte().is_ascii_digit() {
355                            self.advance_format();
356                        }
357                        core::str::from_utf8(&start[..start.len() - self.fmt.len()])
358                            .ok()
359                            .and_then(|s| s.parse::<u8>().ok())
360                    } else {
361                        None
362                    };
363
364                    let next: u8 = self.fmt.first().copied().unwrap_or(0);
365                    if !matches!(next, b'f' | b'N') {
366                        return Err(an_err!(DtErrKind::BadFractional, "{}", char::from(next)));
367                    }
368                    self.advance_format();
369
370                    self.parse_optional_dot_fractional(flag, width, colons)?;
371                }
372                // shortcuts
373                b'F' => self.parse_iso_date()?,
374                b'D' => self.parse_us_date_shortcut()?,
375                b'T' => self.parse_time_with_seconds_shortcut()?,
376                b'R' => self.parse_time_without_seconds_shortcut()?,
377                // Library directives
378                b'*' => self.parse_unbounded_year()?,
379                // b'L' => self.parse_scale()?,
380                b'c' | b'r' | b'X' | b'x' | b'Z' => {
381                    return Err(an_err!(
382                        DtErrKind::UnsupportedDirective,
383                        "{}",
384                        char::from(directive)
385                    ));
386                }
387                _ => {
388                    return Err(an_err!(DtErrKind::UnknownDirective));
389                }
390            }
391        }
392        Ok(())
393    }
394
395    fn parse_literal_character(&mut self) -> Result<(), DtErr> {
396        let c = self.current_format_byte();
397        if c.is_ascii_whitespace() {
398            while !self.inp.is_empty() && self.current_input_byte().is_ascii_whitespace() {
399                self.advance_input();
400            }
401        } else if self.inp.is_empty() || self.current_input_byte() != c {
402            return Err(an_err!(DtErrKind::MismatchedLiteral, "literal"));
403        } else {
404            self.advance_input();
405        }
406        self.advance_format();
407        Ok(())
408    }
409
410    #[inline]
411    fn skip_whitespace(&mut self) -> Result<(), DtErr> {
412        while !self.inp.is_empty() && self.current_input_byte().is_ascii_whitespace() {
413            self.advance_input();
414        }
415        self.advance_format();
416        Ok(())
417    }
418
419    #[inline]
420    fn parse_percent_sign(&mut self) -> Result<(), DtErr> {
421        if self.inp.is_empty() || self.current_input_byte() != b'%' {
422            return Err(an_err!(
423                DtErrKind::MismatchedLiteral,
424                "% got: {}",
425                char::from(self.current_input_byte())
426            ));
427        }
428        self.advance_input();
429        self.advance_format();
430        Ok(())
431    }
432
433    #[inline]
434    fn parse_optional_dot_fractional(
435        &mut self,
436        flag: Option<u8>,
437        width: Option<u8>,
438        colons: u8,
439    ) -> Result<(), DtErr> {
440        // dot is optional in the input for %.f
441        // (also supports explicit literal dot before %.f, e.g. %S.%.f)
442        if !self.inp.is_empty() && self.current_input_byte() == b'.' {
443            self.advance_input();
444        }
445        self.parse_fractional_seconds(flag, width, colons)?;
446        Ok(())
447    }
448
449    #[inline]
450    fn parse_full_year(
451        &mut self,
452        flag: Option<u8>,
453        width: Option<u8>,
454        _colons: u8,
455        advance: bool,
456    ) -> Result<(), DtErr> {
457        let (y, remaining) = match Self::parse_padded_i64(self.inp, flag, width, 4, b'0') {
458            Ok(v) => v,
459            Err(_) => return Err(an_err!(DtErrKind::ExpectedValue, "year")),
460        };
461        self.tm.yr = Some(y);
462        self.inp = remaining;
463        if advance {
464            self.advance_format();
465        }
466        Ok(())
467    }
468
469    #[inline]
470    fn parse_unbounded_year(&mut self) -> Result<(), DtErr> {
471        let (y, remaining) = match Self::parse_arbitrary_i64(self.inp) {
472            Ok(v) => v,
473            Err(_) => return Err(an_err!(DtErrKind::ExpectedValue, "year")),
474        };
475        self.tm.yr = Some(y);
476        self.inp = remaining;
477        self.advance_format();
478        Ok(())
479    }
480
481    #[inline]
482    fn parse_two_digit_year(
483        &mut self,
484        flag: Option<u8>,
485        width: Option<u8>,
486        _colons: u8,
487        advance: bool,
488    ) -> Result<(), DtErr> {
489        let (y, remaining) = match Self::parse_u8_padded(self.inp, flag, width, 2, b'0') {
490            Ok(v) => v,
491            Err(_) => return Err(an_err!(DtErrKind::ExpectedValue, "year")),
492        };
493        self.inp = remaining;
494        let year = if y <= 68 {
495            2000i64 + (y as i64)
496        } else {
497            1900i64 + (y as i64)
498        };
499        self.tm.yr = Some(year);
500        if advance {
501            self.advance_format();
502        }
503        Ok(())
504    }
505
506    #[inline]
507    fn parse_century(
508        &mut self,
509        flag: Option<u8>,
510        width: Option<u8>,
511        _colons: u8,
512    ) -> Result<(), DtErr> {
513        let (sign, after_sign) = Self::parse_optional_sign(self.inp);
514        let (c, remaining) = match Self::parse_padded_i64(after_sign, flag, width, 2, b'_') {
515            Ok(v) => v,
516            Err(_) => return Err(an_err!(DtErrKind::ExpectedValue, "century")),
517        };
518        self.inp = remaining;
519        let year = if sign < 0 { -c * 100 } else { c * 100 };
520        self.tm.yr = Some(year);
521        self.advance_format();
522        Ok(())
523    }
524
525    #[inline]
526    fn parse_iso_week_year(
527        &mut self,
528        flag: Option<u8>,
529        width: Option<u8>,
530        _colons: u8,
531    ) -> Result<(), DtErr> {
532        let (y, remaining) = match Self::parse_padded_i64(self.inp, flag, width, 4, b'0') {
533            Ok(v) => v,
534            Err(_) => return Err(an_err!(DtErrKind::ExpectedValue, "iso week year")),
535        };
536        self.tm.iso_wk_yr = Some(y);
537        self.inp = remaining;
538        self.advance_format();
539        Ok(())
540    }
541
542    #[inline]
543    fn parse_two_digit_iso_week_year(
544        &mut self,
545        flag: Option<u8>,
546        width: Option<u8>,
547        _colons: u8,
548    ) -> Result<(), DtErr> {
549        let (y, remaining) = match Self::parse_u8_padded(self.inp, flag, width, 2, b'0') {
550            Ok(v) => v,
551            Err(_) => {
552                return Err(an_err!(DtErrKind::ExpectedValue, "iso week year"));
553            }
554        };
555        self.inp = remaining;
556        let year = if y <= 68 {
557            2000i64 + (y as i64)
558        } else {
559            1900i64 + (y as i64)
560        };
561        self.tm.iso_wk_yr = Some(year);
562        self.advance_format();
563        Ok(())
564    }
565
566    #[inline]
567    fn parse_month_number(
568        &mut self,
569        flag: Option<u8>,
570        width: Option<u8>,
571        _colons: u8,
572        advance: bool,
573    ) -> Result<(), DtErr> {
574        let (m, remaining) = match Self::parse_u8_padded(self.inp, flag, width, 2, b'0') {
575            Ok(v) => v,
576            Err(_) => return Err(an_err!(DtErrKind::ExpectedValue, "digit month")),
577        };
578        if !(1..=12).contains(&m) {
579            return Err(an_err!(DtErrKind::OutOfRange, "month (1..=12): {}", m));
580        }
581        self.tm.mo = Some(m);
582        self.inp = remaining;
583        if advance {
584            self.advance_format();
585        }
586        Ok(())
587    }
588
589    #[inline]
590    fn parse_day_of_month(
591        &mut self,
592        flag: Option<u8>,
593        width: Option<u8>,
594        _colons: u8,
595        advance: bool,
596    ) -> Result<(), DtErr> {
597        let (d, remaining) = match Self::parse_u8_padded(self.inp, flag, width, 2, b'0') {
598            Ok(v) => v,
599            Err(_) => return Err(an_err!(DtErrKind::ExpectedValue, "day")),
600        };
601        if !(1..=31).contains(&d) {
602            return Err(an_err!(DtErrKind::OutOfRange, "day (1..=31): {}", d));
603        }
604        self.tm.day = Some(d);
605        self.inp = remaining;
606        if advance {
607            self.advance_format();
608        }
609        Ok(())
610    }
611
612    #[inline]
613    fn parse_day_of_year(
614        &mut self,
615        flag: Option<u8>,
616        width: Option<u8>,
617        _colons: u8,
618    ) -> Result<(), DtErr> {
619        let (n, remaining) = match Self::parse_padded_number(self.inp, flag, width, 3, b'0') {
620            Ok(v) => v,
621            Err(_) => return Err(an_err!(DtErrKind::ExpectedValue, "day of year")),
622        };
623        let day = n as u16;
624        if !(1..=366).contains(&day) {
625            return Err(an_err!(
626                DtErrKind::OutOfRange,
627                "day of year (1..=366): {}",
628                day
629            ));
630        }
631        self.tm.day_of_yr = Some(day);
632        self.inp = remaining;
633        self.advance_format();
634        Ok(())
635    }
636
637    #[inline]
638    fn parse_hour24(
639        &mut self,
640        flag: Option<u8>,
641        width: Option<u8>,
642        _colons: u8,
643        advance: bool,
644    ) -> Result<(), DtErr> {
645        let (h, remaining) = match Self::parse_u8_padded(self.inp, flag, width, 2, b'0') {
646            Ok(v) => v,
647            Err(_) => return Err(an_err!(DtErrKind::ExpectedValue, "hour")),
648        };
649        if h > 23 {
650            return Err(an_err!(DtErrKind::OutOfRange, "hour (0..=23): {}", h));
651        }
652        self.tm.hr = Some(h);
653        self.inp = remaining;
654        if advance {
655            self.advance_format();
656        }
657        Ok(())
658    }
659
660    #[inline]
661    fn parse_hour12(
662        &mut self,
663        flag: Option<u8>,
664        width: Option<u8>,
665        _colons: u8,
666    ) -> Result<(), DtErr> {
667        let (h, remaining) = match Self::parse_u8_padded(self.inp, flag, width, 2, b'0') {
668            Ok(v) => v,
669            Err(_) => return Err(an_err!(DtErrKind::ExpectedValue, "hour")),
670        };
671        if !(1..=12).contains(&h) {
672            return Err(an_err!(DtErrKind::OutOfRange, "hour (1..=12): {}", h));
673        }
674        self.tm.hr = Some(h);
675        self.inp = remaining;
676        self.advance_format();
677        Ok(())
678    }
679
680    #[inline]
681    fn parse_minute(
682        &mut self,
683        flag: Option<u8>,
684        width: Option<u8>,
685        _colons: u8,
686        advance: bool,
687    ) -> Result<(), DtErr> {
688        let (m, remaining) = match Self::parse_u8_padded(self.inp, flag, width, 2, b'0') {
689            Ok(v) => v,
690            Err(_) => return Err(an_err!(DtErrKind::ExpectedValue, "minute")),
691        };
692        if m > 59 {
693            return Err(an_err!(DtErrKind::OutOfRange, "minute (0..=59): {}", m));
694        }
695        self.tm.min = Some(m);
696        self.inp = remaining;
697        if advance {
698            self.advance_format();
699        }
700        Ok(())
701    }
702
703    #[inline]
704    fn parse_second(
705        &mut self,
706        flag: Option<u8>,
707        width: Option<u8>,
708        _colons: u8,
709        advance: bool,
710    ) -> Result<(), DtErr> {
711        let (s, remaining) = match Self::parse_u8_padded(self.inp, flag, width, 2, b'0') {
712            Ok(v) => v,
713            Err(_) => return Err(an_err!(DtErrKind::ExpectedValue, "seconds")),
714        };
715        if s > 60 {
716            return Err(an_err!(DtErrKind::OutOfRange, "seconds (0..=60): {}", s));
717        }
718        self.tm.sec = Some(s);
719        self.tm.is_leap_sec = s == 60;
720        self.inp = remaining;
721        if advance {
722            self.advance_format();
723        }
724        Ok(())
725    }
726
727    #[inline]
728    fn parse_fractional_seconds(
729        &mut self,
730        _flag: Option<u8>,
731        width: Option<u8>,
732        _colons: u8,
733    ) -> Result<(), DtErr> {
734        // Make %f, %N, %3N, %6N, etc. also accept an optional leading '.'
735        // (symmetric with the %.f case handled in parse_optional_dot_fractional)
736        if !self.inp.is_empty() && self.current_input_byte() == b'.' {
737            self.advance_input();
738        }
739        let max_digits = width.map(|w| w as usize).unwrap_or(usize::MAX);
740        const TARGET_DIGITS: usize = 18; // attoseconds
741        let mut frac: u64 = 0;
742        let mut digits_read = 0usize;
743        while !self.inp.is_empty()
744            && self.current_input_byte().is_ascii_digit()
745            && digits_read < max_digits
746        {
747            if digits_read < TARGET_DIGITS {
748                let d = (self.current_input_byte() - b'0') as u64;
749                frac = frac * 10 + d;
750            }
751            self.advance_input();
752            digits_read += 1;
753        }
754        if digits_read == 0 {
755            return Err(an_err!(DtErrKind::ExpectedValue, "frac seconds"));
756        }
757        let attos = if digits_read >= TARGET_DIGITS {
758            frac
759        } else {
760            let multiplier = 10u64.pow((TARGET_DIGITS - digits_read) as u32);
761            frac * multiplier
762        };
763        self.tm.attos = Some(attos);
764        Ok(())
765    }
766
767    #[inline]
768    fn parse_unix_timestamp(
769        &mut self,
770        flag: Option<u8>,
771        width: Option<u8>,
772        _colons: u8,
773    ) -> Result<(), DtErr> {
774        let (sign, after_sign) = Self::parse_optional_sign(self.inp);
775        let (n, remaining) = match Self::parse_padded_number(after_sign, flag, width, 19, b' ') {
776            Ok(v) => v,
777            Err(_) => return Err(an_err!(DtErrKind::ExpectedValue, "timestamp")),
778        };
779        let timestamp = if sign < 0 {
780            match n.checked_neg() {
781                Some(ts) => ts,
782                None => return Err(an_err!(DtErrKind::OutOfRange, "timestamp")),
783            }
784        } else {
785            n
786        };
787        self.tm.unix_timestamp_seconds = Some(timestamp);
788        self.inp = remaining;
789        self.advance_format();
790        Ok(())
791    }
792
793    #[inline]
794    fn parse_month_name_abbrev(&mut self) -> Result<(), DtErr> {
795        if self.inp.len() < 3 {
796            return Err(an_err!(DtErrKind::InvalidName, "abbrev. month name"));
797        }
798        let x = &self.inp[..3];
799        let candidate = [
800            x[0].to_ascii_lowercase(),
801            x[1].to_ascii_lowercase(),
802            x[2].to_ascii_lowercase(),
803        ];
804        let index = match &candidate {
805            b"jan" => 0,
806            b"feb" => 1,
807            b"mar" => 2,
808            b"apr" => 3,
809            b"may" => 4,
810            b"jun" => 5,
811            b"jul" => 6,
812            b"aug" => 7,
813            b"sep" => 8,
814            b"oct" => 9,
815            b"nov" => 10,
816            b"dec" => 11,
817            _ => {
818                return Err(an_err!(DtErrKind::InvalidName, "abbrev. month name"));
819            }
820        };
821        self.inp = &self.inp[3..];
822        self.tm.mo = Some(index + 1);
823        self.advance_format();
824        Ok(())
825    }
826
827    #[inline]
828    fn parse_month_name_full(&mut self) -> Result<(), DtErr> {
829        static CHOICES: &[&[u8]] = &[
830            b"January",
831            b"February",
832            b"March",
833            b"April",
834            b"May",
835            b"June",
836            b"July",
837            b"August",
838            b"September",
839            b"October",
840            b"November",
841            b"December",
842        ];
843        let (index, remaining) = match Self::match_from_choice_list(self.inp, CHOICES) {
844            Ok(v) => v,
845            Err(_) => return Err(an_err!(DtErrKind::InvalidName, "month name")),
846        };
847        self.inp = remaining;
848        self.tm.mo = Some(index + 1);
849        self.advance_format();
850        Ok(())
851    }
852
853    #[inline]
854    fn parse_weekday_abbrev(&mut self) -> Result<(), DtErr> {
855        if self.inp.len() < 3 {
856            return Err(an_err!(DtErrKind::InvalidName, "abbrev. weekday"));
857        }
858        let x = &self.inp[..3];
859        let candidate = [
860            x[0].to_ascii_lowercase(),
861            x[1].to_ascii_lowercase(),
862            x[2].to_ascii_lowercase(),
863        ];
864        let index = match &candidate {
865            b"sun" => 0,
866            b"mon" => 1,
867            b"tue" => 2,
868            b"wed" => 3,
869            b"thu" => 4,
870            b"fri" => 5,
871            b"sat" => 6,
872            _ => {
873                return Err(an_err!(DtErrKind::InvalidName, "abbrev. weekday"));
874            }
875        };
876        self.inp = &self.inp[3..];
877        self.tm.wkday = Some(
878            Weekday::from_sunday_zero_offset(index)
879                .ok_or_else(|| an_err!(DtErrKind::InvalidName, "abbrev. weekday"))?,
880        );
881        self.advance_format();
882        Ok(())
883    }
884
885    #[inline]
886    fn parse_weekday_full(&mut self) -> Result<(), DtErr> {
887        static CHOICES: &[&[u8]] = &[
888            b"Sunday",
889            b"Monday",
890            b"Tuesday",
891            b"Wednesday",
892            b"Thursday",
893            b"Friday",
894            b"Saturday",
895        ];
896        let (index, remaining) = match Self::match_from_choice_list(self.inp, CHOICES) {
897            Ok(v) => v,
898            Err(_) => return Err(an_err!(DtErrKind::InvalidName, "weekday")),
899        };
900        self.inp = remaining;
901        self.tm.wkday = Some(
902            Weekday::from_sunday_zero_offset(index)
903                .ok_or_else(|| an_err!(DtErrKind::InvalidName, "weekday"))?,
904        );
905        self.advance_format();
906        Ok(())
907    }
908
909    #[inline]
910    fn parse_weekday_number_monday_based(
911        &mut self,
912        flag: Option<u8>,
913        width: Option<u8>,
914        _colons: u8,
915    ) -> Result<(), DtErr> {
916        let (w, remaining) = match Self::parse_u8_padded(self.inp, flag, width, 1, b'_') {
917            Ok(v) => v,
918            Err(_) => {
919                return Err(an_err!(
920                    DtErrKind::ExpectedValue,
921                    "monday based weekday number"
922                ));
923            }
924        };
925        let wd = Weekday::from_monday_one_offset(w)
926            .ok_or_else(|| an_err!(DtErrKind::OutOfRange, "monday based weekday number"))?;
927        self.tm.wkday = Some(wd);
928        self.inp = remaining;
929        self.advance_format();
930        Ok(())
931    }
932
933    #[inline]
934    fn parse_weekday_number_sunday_based(
935        &mut self,
936        flag: Option<u8>,
937        width: Option<u8>,
938        _colons: u8,
939    ) -> Result<(), DtErr> {
940        let (w, remaining) = match Self::parse_u8_padded(self.inp, flag, width, 1, b'_') {
941            Ok(v) => v,
942            Err(_) => {
943                return Err(an_err!(
944                    DtErrKind::ExpectedValue,
945                    "sunday based weekday number"
946                ));
947            }
948        };
949        let wd = Weekday::from_sunday_zero_offset(w)
950            .ok_or_else(|| an_err!(DtErrKind::OutOfRange, "sunday based weekday number"))?;
951        self.tm.wkday = Some(wd);
952        self.inp = remaining;
953        self.advance_format();
954        Ok(())
955    }
956
957    #[inline]
958    fn parse_ampm(&mut self) -> Result<(), DtErr> {
959        if self.inp.len() < 2 {
960            return Err(an_err!(DtErrKind::InvalidName, "am/pm"));
961        }
962        let slice = &self.inp[..2];
963        self.tm.meridiem = Some(if slice.eq_ignore_ascii_case(b"am") {
964            Meridiem::AM
965        } else if slice.eq_ignore_ascii_case(b"pm") {
966            Meridiem::PM
967        } else {
968            return Err(an_err!(DtErrKind::InvalidName, "am/pm"));
969        });
970        self.inp = &self.inp[2..];
971        self.advance_format();
972        Ok(())
973    }
974
975    #[inline]
976    fn parse_week_number_sunday_based(
977        &mut self,
978        flag: Option<u8>,
979        width: Option<u8>,
980        _colons: u8,
981    ) -> Result<(), DtErr> {
982        let (w, remaining) = match Self::parse_u8_padded(self.inp, flag, width, 2, b'0') {
983            Ok(v) => v,
984            Err(_) => {
985                return Err(an_err!(
986                    DtErrKind::ExpectedValue,
987                    "week number sunday based"
988                ));
989            }
990        };
991        self.tm.wk_sun = Some(w);
992        self.inp = remaining;
993        self.advance_format();
994        Ok(())
995    }
996
997    #[inline]
998    fn parse_week_number_monday_based(
999        &mut self,
1000        flag: Option<u8>,
1001        width: Option<u8>,
1002        _colons: u8,
1003    ) -> Result<(), DtErr> {
1004        let (w, remaining) = match Self::parse_u8_padded(self.inp, flag, width, 2, b'0') {
1005            Ok(v) => v,
1006            Err(_) => {
1007                return Err(an_err!(
1008                    DtErrKind::ExpectedValue,
1009                    "week number monday based"
1010                ));
1011            }
1012        };
1013        self.tm.wk_mon = Some(w);
1014        self.inp = remaining;
1015        self.advance_format();
1016        Ok(())
1017    }
1018
1019    #[inline]
1020    fn parse_week_iso(
1021        &mut self,
1022        flag: Option<u8>,
1023        width: Option<u8>,
1024        _colons: u8,
1025    ) -> Result<(), DtErr> {
1026        let (w, remaining) = match Self::parse_u8_padded(self.inp, flag, width, 2, b'0') {
1027            Ok(v) => v,
1028            Err(_) => return Err(an_err!(DtErrKind::ExpectedValue, "iso week")),
1029        };
1030        if !(1..=53).contains(&w) {
1031            return Err(an_err!(DtErrKind::OutOfRange, "iso week (1..=53): {}", w));
1032        }
1033        self.tm.iso_wk = Some(w);
1034        self.inp = remaining;
1035        self.advance_format();
1036        Ok(())
1037    }
1038
1039    #[inline]
1040    fn parse_timezone_offset(
1041        &mut self,
1042        _flag: Option<u8>,
1043        _width: Option<u8>,
1044        colons: u8,
1045    ) -> Result<(), DtErr> {
1046        let sign = match self.inp.first() {
1047            Some(b'+') => 1i32,
1048            Some(b'-') => -1i32,
1049            _ => {
1050                return Err(an_err!(
1051                    DtErrKind::InvalidTimezoneOffset,
1052                    "must start with + or -"
1053                ));
1054            }
1055        };
1056        self.advance_input();
1057
1058        let mut total_seconds = self.parse_offset_hours()? * 3600;
1059
1060        match colons {
1061            0 => {
1062                let minutes = match self.parse_offset_mm_ss() {
1063                    Ok(m) => m,
1064                    Err(_) => {
1065                        return Err(an_err!(DtErrKind::InvalidTimezoneOffset, "minutes"));
1066                    }
1067                };
1068                total_seconds += minutes * 60;
1069                if self.inp.len() >= 2
1070                    && let Ok(seconds) = self.parse_offset_mm_ss()
1071                {
1072                    total_seconds += seconds;
1073                }
1074            }
1075            1..=3 => {
1076                let minutes_required = colons != 3;
1077                if self.inp.first() == Some(&b':') {
1078                    self.advance_input();
1079                    let minutes = match self.parse_offset_mm_ss() {
1080                        Ok(m) => m,
1081                        Err(_) => {
1082                            return Err(an_err!(DtErrKind::InvalidTimezoneOffset, "minutes"));
1083                        }
1084                    };
1085                    total_seconds += minutes * 60;
1086                    if self.inp.first() == Some(&b':') {
1087                        self.advance_input();
1088                        let seconds = match self.parse_offset_mm_ss() {
1089                            Ok(s) => s,
1090                            Err(_) => {
1091                                return Err(an_err!(DtErrKind::InvalidTimezoneOffset, "seconds",));
1092                            }
1093                        };
1094                        total_seconds += seconds;
1095                    } else if colons == 2 {
1096                        return Err(an_err!(DtErrKind::InvalidTimezoneOffset, "num colons"));
1097                    }
1098                } else if minutes_required {
1099                    return Err(an_err!(DtErrKind::InvalidTimezoneOffset, "num colons"));
1100                }
1101            }
1102            _ => {
1103                return Err(an_err!(DtErrKind::InvalidTimezoneOffset, "num colons"));
1104            }
1105        }
1106
1107        // Store the fixed offset (in seconds) in our core TimeZone type.
1108        self.tm.offset = Some(Offset::Fixed(sign * total_seconds));
1109        self.advance_format();
1110        Ok(())
1111    }
1112
1113    #[inline]
1114    fn parse_offset_hours(&mut self) -> Result<i32, DtErr> {
1115        let mut n = 0i32;
1116        let mut digits = 0;
1117        while digits < 2 && !self.inp.is_empty() && self.current_input_byte().is_ascii_digit() {
1118            n = n * 10 + (self.current_input_byte() - b'0') as i32;
1119            self.advance_input();
1120            digits += 1;
1121        }
1122        if digits == 0 {
1123            return Err(an_err!(DtErrKind::InvalidTimezoneOffset, "hour"));
1124        }
1125        if n > 23 {
1126            return Err(an_err!(
1127                DtErrKind::InvalidTimezoneOffset,
1128                "hour (0..=23): {}",
1129                n
1130            ));
1131        }
1132        Ok(n)
1133    }
1134
1135    #[inline]
1136    fn parse_offset_mm_ss(&mut self) -> Result<i32, DtErr> {
1137        if self.inp.len() < 2 {
1138            return Err(an_err!(
1139                DtErrKind::InvalidTimezoneOffset,
1140                "minutes or seconds"
1141            ));
1142        }
1143        let slice = &self.inp[..2];
1144        let n = match core::str::from_utf8(slice)
1145            .ok()
1146            .and_then(|s| s.parse::<i32>().ok())
1147            .filter(|&n| (0..=59).contains(&n))
1148        {
1149            Some(n) => n,
1150            None => {
1151                return Err(an_err!(
1152                    DtErrKind::InvalidTimezoneOffset,
1153                    "minutes or seconds"
1154                ));
1155            }
1156        };
1157        self.inp = &self.inp[2..];
1158        Ok(n)
1159    }
1160
1161    #[inline]
1162    fn parse_iana_or_offset(
1163        &mut self,
1164        _flag: Option<u8>,
1165        _width: Option<u8>,
1166        colons: u8,
1167    ) -> Result<(), DtErr> {
1168        if !self.inp.is_empty() && matches!(self.current_input_byte(), b'+' | b'-') {
1169            return self.parse_timezone_offset(_flag, _width, colons);
1170        }
1171        let (iana_str, remaining) = match Self::parse_iana(self.inp) {
1172            Ok(v) => v,
1173            Err(_) => {
1174                return Err(an_err!(
1175                    DtErrKind::InvalidTimezoneOffset,
1176                    "expected iana or offset"
1177                ));
1178            }
1179        };
1180        let name_to_use = if iana_str.len() > 50 {
1181            &iana_str[0..50]
1182        } else {
1183            iana_str
1184        };
1185        self.tm.set_iana_name(Some(name_to_use));
1186        self.tm.offset = Some(Offset::None);
1187        self.inp = remaining;
1188        self.advance_format();
1189        Ok(())
1190    }
1191
1192    // fn parse_scale(&mut self) -> Result<(), DtErr> {
1193    //     if self.inp.is_empty() || !self.inp[0].is_ascii_alphabetic() {
1194    //         return Err(an_err!(DtErrKind::InvalidItem, "invalid clocktype"));
1195    //     }
1196    //     let start = self.inp;
1197    //     let mut pos = 0usize;
1198    //     // Use `start[pos]`, not `self.inp[pos]`
1199    //     while pos < start.len() && pos < 8 && start[pos].is_ascii_alphanumeric() {
1200    //         pos += 1;
1201    //     }
1202    //     let abbrev = core::str::from_utf8(&start[..pos])
1203    //         .map_err(|_| an_err!(DtErrKind::InvalidItem, "invalid clocktype"))?;
1204    //     self.inp = &start[pos..];
1205    //     self.advance_format();
1206    //     if let Some(ct) = Scale::from_abbrev(abbrev) {
1207    //         self.tm.scale = ct;
1208    //         Ok(())
1209    //     } else {
1210    //         Err(an_err!(DtErrKind::InvalidItem, "invalid clocktype"))
1211    //     }
1212    // }
1213
1214    #[inline]
1215    fn parse_iso_date(&mut self) -> Result<(), DtErr> {
1216        self.parse_full_year(None, None, 0, false)?;
1217        self.parse_literal_character_byte(b'-')?;
1218        self.parse_month_number(None, None, 0, false)?;
1219        self.parse_literal_character_byte(b'-')?;
1220        self.parse_day_of_month(None, None, 0, false)?;
1221        self.advance_format(); // eat %F
1222        Ok(())
1223    }
1224
1225    #[inline]
1226    fn parse_us_date_shortcut(&mut self) -> Result<(), DtErr> {
1227        self.parse_month_number(None, None, 0, false)?;
1228        self.parse_literal_character_byte(b'/')?;
1229        self.parse_day_of_month(None, None, 0, false)?;
1230        self.parse_literal_character_byte(b'/')?;
1231        self.parse_two_digit_year(None, None, 0, false)?;
1232        self.advance_format(); // eat %D
1233        Ok(())
1234    }
1235
1236    #[inline]
1237    fn parse_time_with_seconds_shortcut(&mut self) -> Result<(), DtErr> {
1238        self.parse_hour24(None, None, 0, false)?;
1239        self.parse_literal_character_byte(b':')?;
1240        self.parse_minute(None, None, 0, false)?;
1241        self.parse_literal_character_byte(b':')?;
1242        self.parse_second(None, None, 0, false)?;
1243        self.advance_format(); // eat %T
1244        Ok(())
1245    }
1246
1247    #[inline]
1248    fn parse_time_without_seconds_shortcut(&mut self) -> Result<(), DtErr> {
1249        self.parse_hour24(None, None, 0, false)?;
1250        self.parse_literal_character_byte(b':')?;
1251        self.parse_minute(None, None, 0, false)?;
1252        self.advance_format(); // eat %R
1253        Ok(())
1254    }
1255
1256    #[inline]
1257    fn parse_literal_character_byte(&mut self, expected: u8) -> Result<(), DtErr> {
1258        if self.inp.is_empty() || self.current_input_byte() != expected {
1259            return Err(an_err!(
1260                DtErrKind::MismatchedLiteral,
1261                "Expected literal char"
1262            ));
1263        }
1264        self.advance_input();
1265        Ok(())
1266    }
1267
1268    #[inline]
1269    pub(crate) fn parse_format_extensions(
1270        fmt: &[u8],
1271        mut pos: usize,
1272    ) -> (Option<u8>, Option<u8>, u8, &[u8]) {
1273        let mut flag = None;
1274        let mut width = None;
1275        let mut colons = 0u8;
1276        if matches!(fmt.get(pos), Some(b'-' | b'_' | b'0' | b'^' | b'#')) {
1277            flag = Some(fmt[pos]);
1278            pos += 1;
1279        }
1280        // Width (e.g. %4Y, %02d, %-3j, %^10A – width after flag)
1281        if matches!(fmt.get(pos), Some(c) if c.is_ascii_digit()) {
1282            let mut w = 0u16;
1283            while pos < fmt.len() && fmt[pos].is_ascii_digit() {
1284                w = w * 10 + u16::from(fmt[pos] - b'0');
1285                pos += 1;
1286            }
1287            if w <= u8::MAX as u16 {
1288                width = Some(w as u8);
1289            }
1290        }
1291        // Colons (for %:z, %::z, %:::z, %:Q, etc.)
1292        while matches!(fmt.get(pos), Some(b':')) {
1293            colons += 1;
1294            pos += 1;
1295        }
1296        (flag, width, colons, &fmt[pos..])
1297    }
1298
1299    fn parse_optional_sign(inp: &[u8]) -> (i32, &[u8]) {
1300        if let Some(b'-') = inp.first() {
1301            (-1, &inp[1..])
1302        } else if let Some(b'+') = inp.first() {
1303            (1, &inp[1..])
1304        } else {
1305            (1, inp)
1306        }
1307    }
1308
1309    #[inline]
1310    fn parse_digits(inp: &[u8]) -> (&[u8], &[u8]) {
1311        let mut pos = 0;
1312        while pos < inp.len() && inp[pos].is_ascii_digit() {
1313            pos += 1;
1314        }
1315        (&inp[..pos], &inp[pos..])
1316    }
1317
1318    #[inline]
1319    fn parse_padded_number(
1320        inp: &[u8],
1321        flag: Option<u8>,
1322        width: Option<u8>,
1323        default_pad_width: usize,
1324        default_flag: u8,
1325    ) -> Result<(i64, &[u8]), ()> {
1326        let mut pos = 0;
1327        // Skip leading whitespace
1328        while pos < inp.len() && inp[pos].is_ascii_whitespace() {
1329            pos += 1;
1330        }
1331        if pos >= inp.len() {
1332            return Err(());
1333        }
1334        // Resolve effective padding flag (ignore ^ and # for numeric parsing – they are no-ops)
1335        let effective_flag = match flag {
1336            Some(b'^') | Some(b'#') => default_flag,
1337            Some(f) => f,
1338            None => default_flag,
1339        };
1340        let zero_pad_width = match effective_flag {
1341            b'_' | b' ' | b'-' => 0, // PadSpace or NoPad
1342            _ => width.map(usize::from).unwrap_or(default_pad_width),
1343        };
1344        let max_digits = default_pad_width.max(zero_pad_width);
1345        let mut n: i64 = 0;
1346        let mut digits = 0usize;
1347        while digits < zero_pad_width && pos + digits < inp.len() && inp[pos + digits] == b'0' {
1348            digits += 1;
1349        }
1350        // Then parse the rest of the digits up to max_digits
1351        while digits < max_digits && pos + digits < inp.len() && inp[pos + digits].is_ascii_digit()
1352        {
1353            let digit = i64::from(inp[pos + digits] - b'0');
1354            n = n
1355                .checked_mul(10)
1356                .and_then(|x| x.checked_add(digit))
1357                .ok_or(())?;
1358            digits += 1;
1359        }
1360        if digits == 0 {
1361            return Err(());
1362        }
1363        Ok((n, &inp[pos + digits..]))
1364    }
1365
1366    #[inline]
1367    fn parse_u8_padded(
1368        inp: &[u8],
1369        flag: Option<u8>,
1370        width: Option<u8>,
1371        default_pad_width: usize,
1372        default_flag: u8,
1373    ) -> Result<(u8, &[u8]), ()> {
1374        let (n, remaining) =
1375            Self::parse_padded_number(inp, flag, width, default_pad_width, default_flag)?;
1376        if !(0..=255).contains(&n) {
1377            return Err(());
1378        }
1379        Ok((n as u8, remaining))
1380    }
1381
1382    #[inline]
1383    fn match_from_choice_list<'a>(inp: &'a [u8], choices: &[&[u8]]) -> Result<(u8, &'a [u8]), ()> {
1384        for (i, choice) in choices.iter().enumerate() {
1385            if inp.len() < choice.len() {
1386                continue;
1387            }
1388            let candidate = &inp[..choice.len()];
1389            if candidate.eq_ignore_ascii_case(choice) {
1390                return Ok((i as u8, &inp[choice.len()..]));
1391            }
1392        }
1393        Err(())
1394    }
1395
1396    #[inline]
1397    fn parse_iana(inp: &[u8]) -> Result<(&str, &[u8]), ()> {
1398        let start = inp;
1399        let mut pos = 0;
1400
1401        if pos >= inp.len() || !matches!(inp[pos], b'_' | b'.' | b'A'..=b'Z' | b'a'..=b'z') {
1402            return Err(());
1403        }
1404        pos += 1;
1405
1406        while pos < inp.len() {
1407            if matches!(
1408                inp[pos],
1409                b'_' | b'.' | b'+' | b'-' | b'0'..=b'9' | b'A'..=b'Z' | b'a'..=b'z'
1410            ) {
1411                pos += 1;
1412            } else if inp[pos] == b'/' {
1413                pos += 1;
1414                if pos >= inp.len() || !matches!(inp[pos], b'_' | b'.' | b'A'..=b'Z' | b'a'..=b'z')
1415                {
1416                    return Err(());
1417                }
1418                pos += 1;
1419            } else {
1420                break;
1421            }
1422        }
1423        let iana = core::str::from_utf8(&start[..pos]).map_err(|_| ())?;
1424        Ok((iana, &start[pos..]))
1425    }
1426
1427    #[inline]
1428    fn parse_padded_i64(
1429        inp: &[u8],
1430        flag: Option<u8>,
1431        width: Option<u8>,
1432        default_pad_width: usize,
1433        default_flag: u8,
1434    ) -> Result<(i64, &[u8]), ()> {
1435        let (sign, after_sign) = Self::parse_optional_sign(inp);
1436        let (n, remaining) =
1437            Self::parse_padded_number(after_sign, flag, width, default_pad_width, default_flag)?;
1438        let mut y = n;
1439        if sign < 0 {
1440            y = -y;
1441        }
1442        Ok((y, remaining))
1443    }
1444
1445    #[inline]
1446    fn parse_arbitrary_i64(inp: &[u8]) -> Result<(i64, &[u8]), ()> {
1447        let (sign, after_sign) = Self::parse_optional_sign(inp);
1448        let (digits, remaining) = Self::parse_digits(after_sign);
1449        if digits.is_empty() {
1450            return Err(());
1451        }
1452        let mut y: i64 = 0;
1453        for &byte in digits {
1454            let d = (byte - b'0') as i64;
1455            y = y.checked_mul(10).and_then(|x| x.checked_add(d)).ok_or(())?;
1456        }
1457        if sign < 0 {
1458            y = y.checked_neg().ok_or(())?;
1459        }
1460        Ok((y, remaining))
1461    }
1462}