jiff/error/fmt/
strtime.rs

1use crate::{civil::Weekday, error, tz::Offset, util::escape};
2
3#[derive(Clone, Debug)]
4pub(crate) enum Error {
5    ColonCount {
6        directive: u8,
7    },
8    DirectiveFailure {
9        directive: u8,
10        colons: u8,
11    },
12    DirectiveFailureDot {
13        directive: u8,
14    },
15    ExpectedDirectiveAfterColons,
16    ExpectedDirectiveAfterFlag {
17        flag: u8,
18    },
19    ExpectedDirectiveAfterWidth,
20    FailedStrftime,
21    FailedStrptime,
22    FailedWidth,
23    InvalidDate,
24    InvalidISOWeekDate,
25    InvalidWeekdayMonday {
26        got: Weekday,
27    },
28    InvalidWeekdaySunday {
29        got: Weekday,
30    },
31    MismatchOffset {
32        parsed: Offset,
33        got: Offset,
34    },
35    MismatchWeekday {
36        parsed: Weekday,
37        got: Weekday,
38    },
39    MissingTimeHourForFractional,
40    MissingTimeHourForMinute,
41    MissingTimeHourForSecond,
42    MissingTimeMinuteForFractional,
43    MissingTimeMinuteForSecond,
44    MissingTimeSecondForFractional,
45    RangeTimestamp,
46    RangeWidth,
47    RequiredDateForDateTime,
48    RequiredDateTimeForTimestamp,
49    RequiredDateTimeForZoned,
50    RequiredOffsetForTimestamp,
51    RequiredSomeDayForDate,
52    RequiredTimeForDateTime,
53    RequiredYearForDate,
54    UnconsumedStrptime {
55        #[cfg(feature = "alloc")]
56        remaining: alloc::boxed::Box<[u8]>,
57    },
58    UnexpectedEndAfterDot,
59    UnexpectedEndAfterPercent,
60    UnknownDirectiveAfterDot {
61        directive: u8,
62    },
63    UnknownDirective {
64        directive: u8,
65    },
66    ZonedOffsetOrTz,
67}
68
69impl Error {
70    pub(crate) fn unconsumed(_remaining: &[u8]) -> Error {
71        Error::UnconsumedStrptime {
72            #[cfg(feature = "alloc")]
73            remaining: _remaining.into(),
74        }
75    }
76}
77
78impl error::IntoError for Error {
79    fn into_error(self) -> error::Error {
80        self.into()
81    }
82}
83
84impl From<Error> for error::Error {
85    #[cold]
86    #[inline(never)]
87    fn from(err: Error) -> error::Error {
88        error::ErrorKind::FmtStrtime(err).into()
89    }
90}
91
92impl core::fmt::Display for Error {
93    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
94        use self::Error::*;
95
96        match *self {
97            ColonCount { directive } => write!(
98                f,
99                "invalid number of `:` in `%{directive}` directive",
100                directive = escape::Byte(directive),
101            ),
102            DirectiveFailure { directive, colons } => write!(
103                f,
104                "%{colons}{directive} failed",
105                colons = escape::RepeatByte { byte: b':', count: colons },
106                directive = escape::Byte(directive),
107            ),
108            DirectiveFailureDot { directive } => write!(
109                f,
110                "%.{directive} failed",
111                directive = escape::Byte(directive),
112            ),
113            ExpectedDirectiveAfterColons => f.write_str(
114                "expected to find specifier directive after colons, \
115                 but found end of format string",
116            ),
117            ExpectedDirectiveAfterFlag { flag } => write!(
118                f,
119                "expected to find specifier directive after flag \
120                 `{flag}`, but found end of format string",
121                flag = escape::Byte(flag),
122            ),
123            ExpectedDirectiveAfterWidth => f.write_str(
124                "expected to find specifier directive after parsed width, \
125                 but found end of format string",
126            ),
127            FailedStrftime => f.write_str("strftime formatting failed"),
128            FailedStrptime => f.write_str("strptime parsing failed"),
129            FailedWidth => {
130                f.write_str("failed to parse conversion specifier width")
131            }
132            InvalidDate => f.write_str("invalid date"),
133            InvalidISOWeekDate => f.write_str("invalid ISO 8601 week date"),
134            InvalidWeekdayMonday { got } => write!(
135                f,
136                "weekday `{got:?}` is not valid for \
137                 Monday based week number",
138            ),
139            InvalidWeekdaySunday { got } => write!(
140                f,
141                "weekday `{got:?}` is not valid for \
142                 Sunday based week number",
143            ),
144            MissingTimeHourForFractional => f.write_str(
145                "parsing format did not include hour directive, \
146                 but did include fractional second directive (cannot have \
147                 smaller time units with bigger time units missing)",
148            ),
149            MissingTimeHourForMinute => f.write_str(
150                "parsing format did not include hour directive, \
151                 but did include minute directive (cannot have \
152                 smaller time units with bigger time units missing)",
153            ),
154            MissingTimeHourForSecond => f.write_str(
155                "parsing format did not include hour directive, \
156                 but did include second directive (cannot have \
157                 smaller time units with bigger time units missing)",
158            ),
159            MissingTimeMinuteForFractional => f.write_str(
160                "parsing format did not include minute directive, \
161                 but did include fractional second directive (cannot have \
162                 smaller time units with bigger time units missing)",
163            ),
164            MissingTimeMinuteForSecond => f.write_str(
165                "parsing format did not include minute directive, \
166                 but did include second directive (cannot have \
167                 smaller time units with bigger time units missing)",
168            ),
169            MissingTimeSecondForFractional => f.write_str(
170                "parsing format did not include second directive, \
171                 but did include fractional second directive (cannot have \
172                 smaller time units with bigger time units missing)",
173            ),
174            MismatchOffset { parsed, got } => write!(
175                f,
176                "parsed time zone offset `{parsed}`, but \
177                 offset from timestamp and time zone is `{got}`",
178            ),
179            MismatchWeekday { parsed, got } => write!(
180                f,
181                "parsed weekday `{parsed:?}` does not match \
182                 weekday `{got:?}` from parsed date",
183            ),
184            RangeTimestamp => f.write_str(
185                "parsed datetime and offset, \
186                 but combining them into a zoned datetime \
187                 is outside Jiff's supported timestamp range",
188            ),
189            RangeWidth => write!(
190                f,
191                "parsed width is too big, max is {max}",
192                max = u8::MAX
193            ),
194            RequiredDateForDateTime => {
195                f.write_str("date required to parse datetime")
196            }
197            RequiredDateTimeForTimestamp => {
198                f.write_str("datetime required to parse timestamp")
199            }
200            RequiredDateTimeForZoned => {
201                f.write_str("datetime required to parse zoned datetime")
202            }
203            RequiredOffsetForTimestamp => {
204                f.write_str("offset required to parse timestamp")
205            }
206            RequiredSomeDayForDate => f.write_str(
207                "a month/day, day-of-year or week date must be \
208                 present to create a date, but none were found",
209            ),
210            RequiredTimeForDateTime => {
211                f.write_str("time required to parse datetime")
212            }
213            RequiredYearForDate => f.write_str("year required to parse date"),
214            #[cfg(feature = "alloc")]
215            UnconsumedStrptime { ref remaining } => write!(
216                f,
217                "strptime expects to consume the entire input, but \
218                 `{remaining}` remains unparsed",
219                remaining = escape::Bytes(remaining),
220            ),
221            #[cfg(not(feature = "alloc"))]
222            UnconsumedStrptime {} => f.write_str(
223                "strptime expects to consume the entire input, but \
224                 there is unparsed input remaining",
225            ),
226            UnexpectedEndAfterDot => f.write_str(
227                "invalid format string, expected directive after `%.`",
228            ),
229            UnexpectedEndAfterPercent => f.write_str(
230                "invalid format string, expected byte after `%`, \
231                 but found end of format string",
232            ),
233            UnknownDirective { directive } => write!(
234                f,
235                "found unrecognized specifier directive `{directive}`",
236                directive = escape::Byte(directive),
237            ),
238            UnknownDirectiveAfterDot { directive } => write!(
239                f,
240                "found unrecognized specifier directive `{directive}` \
241                 following `%.`",
242                directive = escape::Byte(directive),
243            ),
244            ZonedOffsetOrTz => f.write_str(
245                "either offset (from `%z`) or IANA time zone identifier \
246                 (from `%Q`) is required for parsing zoned datetime",
247            ),
248        }
249    }
250}
251
252#[derive(Clone, Debug)]
253pub(crate) enum ParseError {
254    ExpectedAmPm,
255    ExpectedAmPmTooShort,
256    ExpectedIanaTz,
257    ExpectedIanaTzEndOfInput,
258    ExpectedMonthAbbreviation,
259    ExpectedMonthAbbreviationTooShort,
260    ExpectedWeekdayAbbreviation,
261    ExpectedWeekdayAbbreviationTooShort,
262    ExpectedChoice {
263        available: &'static [&'static [u8]],
264    },
265    ExpectedFractionalDigit,
266    ExpectedMatchLiteralByte {
267        expected: u8,
268        got: u8,
269    },
270    ExpectedMatchLiteralEndOfInput {
271        expected: u8,
272    },
273    ExpectedNonEmpty {
274        directive: u8,
275    },
276    #[cfg(not(feature = "alloc"))]
277    NotAllowedAlloc {
278        directive: u8,
279        colons: u8,
280    },
281    NotAllowedLocaleClockTime,
282    NotAllowedLocaleDate,
283    NotAllowedLocaleDateAndTime,
284    NotAllowedLocaleTwelveHourClockTime,
285    NotAllowedTimeZoneAbbreviation,
286    ParseDay,
287    ParseDayOfYear,
288    ParseCentury,
289    ParseFractionalSeconds,
290    ParseHour,
291    ParseIsoWeekNumber,
292    ParseIsoWeekYear,
293    ParseIsoWeekYearTwoDigit,
294    ParseMinute,
295    ParseMondayWeekNumber,
296    ParseMonth,
297    ParseSecond,
298    ParseSundayWeekNumber,
299    ParseTimestamp,
300    ParseWeekdayNumber,
301    ParseYear,
302    ParseYearTwoDigit,
303    UnknownMonthName,
304    UnknownWeekdayAbbreviation,
305}
306
307impl error::IntoError for ParseError {
308    fn into_error(self) -> error::Error {
309        self.into()
310    }
311}
312
313impl From<ParseError> for error::Error {
314    #[cold]
315    #[inline(never)]
316    fn from(err: ParseError) -> error::Error {
317        error::ErrorKind::FmtStrtimeParse(err).into()
318    }
319}
320
321impl core::fmt::Display for ParseError {
322    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
323        use self::ParseError::*;
324
325        match *self {
326            ExpectedAmPm => f.write_str("expected to find `AM` or `PM`"),
327            ExpectedAmPmTooShort => f.write_str(
328                "expected to find `AM` or `PM`, \
329                 but the remaining input is too short \
330                 to contain one",
331            ),
332            ExpectedIanaTz => f.write_str(
333                "expected to find the start of an IANA time zone \
334                 identifier name or component",
335            ),
336            ExpectedIanaTzEndOfInput => f.write_str(
337                "expected to find the start of an IANA time zone \
338                 identifier name or component, \
339                 but found end of input instead",
340            ),
341            ExpectedMonthAbbreviation => {
342                f.write_str("expected to find month name abbreviation")
343            }
344            ExpectedMonthAbbreviationTooShort => f.write_str(
345                "expected to find month name abbreviation, \
346                 but the remaining input is too short \
347                 to contain one",
348            ),
349            ExpectedWeekdayAbbreviation => {
350                f.write_str("expected to find weekday abbreviation")
351            }
352            ExpectedWeekdayAbbreviationTooShort => f.write_str(
353                "expected to find weekday abbreviation, \
354                 but the remaining input is too short \
355                 to contain one",
356            ),
357            ExpectedChoice { available } => {
358                f.write_str(
359                    "failed to find expected value, available choices are: ",
360                )?;
361                for (i, choice) in available.iter().enumerate() {
362                    if i > 0 {
363                        f.write_str(", ")?;
364                    }
365                    write!(f, "{}", escape::Bytes(choice))?;
366                }
367                Ok(())
368            }
369            ExpectedFractionalDigit => f.write_str(
370                "expected at least one fractional decimal digit, \
371                 but did not find any",
372            ),
373            ExpectedMatchLiteralByte { expected, got } => write!(
374                f,
375                "expected to match literal byte `{expected}` from \
376                 format string, but found byte `{got}` in input",
377                expected = escape::Byte(expected),
378                got = escape::Byte(got),
379            ),
380            ExpectedMatchLiteralEndOfInput { expected } => write!(
381                f,
382                "expected to match literal byte `{expected}` from \
383                 format string, but found end of input",
384                expected = escape::Byte(expected),
385            ),
386            ExpectedNonEmpty { directive } => write!(
387                f,
388                "expected non-empty input for directive `%{directive}`, \
389                 but found end of input",
390                directive = escape::Byte(directive),
391            ),
392            #[cfg(not(feature = "alloc"))]
393            NotAllowedAlloc { directive, colons } => write!(
394                f,
395                "cannot parse `%{colons}{directive}` \
396                 without Jiff's `alloc` feature enabled",
397                colons = escape::RepeatByte { byte: b':', count: colons },
398                directive = escape::Byte(directive),
399            ),
400            NotAllowedLocaleClockTime => {
401                f.write_str("parsing locale clock time is not allowed")
402            }
403            NotAllowedLocaleDate => {
404                f.write_str("parsing locale date is not allowed")
405            }
406            NotAllowedLocaleDateAndTime => {
407                f.write_str("parsing locale date and time is not allowed")
408            }
409            NotAllowedLocaleTwelveHourClockTime => {
410                f.write_str("parsing locale 12-hour clock time is not allowed")
411            }
412            NotAllowedTimeZoneAbbreviation => {
413                f.write_str("parsing time zone abbreviation is not allowed")
414            }
415            ParseCentury => {
416                f.write_str("failed to parse year number for century")
417            }
418            ParseDay => f.write_str("failed to parse day number"),
419            ParseDayOfYear => {
420                f.write_str("failed to parse day of year number")
421            }
422            ParseFractionalSeconds => f.write_str(
423                "failed to parse fractional second component \
424                 (up to 9 digits, nanosecond precision)",
425            ),
426            ParseHour => f.write_str("failed to parse hour number"),
427            ParseMinute => f.write_str("failed to parse minute number"),
428            ParseWeekdayNumber => {
429                f.write_str("failed to parse weekday number")
430            }
431            ParseIsoWeekNumber => {
432                f.write_str("failed to parse ISO 8601 week number")
433            }
434            ParseIsoWeekYear => {
435                f.write_str("failed to parse ISO 8601 week year")
436            }
437            ParseIsoWeekYearTwoDigit => {
438                f.write_str("failed to parse 2-digit ISO 8601 week year")
439            }
440            ParseMondayWeekNumber => {
441                f.write_str("failed to parse Monday-based week number")
442            }
443            ParseMonth => f.write_str("failed to parse month number"),
444            ParseSecond => f.write_str("failed to parse second number"),
445            ParseSundayWeekNumber => {
446                f.write_str("failed to parse Sunday-based week number")
447            }
448            ParseTimestamp => {
449                f.write_str("failed to parse Unix timestamp (in seconds)")
450            }
451            ParseYear => f.write_str("failed to parse year"),
452            ParseYearTwoDigit => f.write_str("failed to parse 2-digit year"),
453            UnknownMonthName => f.write_str("unrecognized month name"),
454            UnknownWeekdayAbbreviation => {
455                f.write_str("unrecognized weekday abbreviation")
456            }
457        }
458    }
459}
460
461#[derive(Clone, Debug)]
462pub(crate) enum FormatError {
463    RequiresDate,
464    RequiresInstant,
465    RequiresOffset,
466    RequiresTime,
467    RequiresTimeZone,
468    RequiresTimeZoneOrOffset,
469    InvalidUtf8,
470    ZeroPrecisionFloat,
471    ZeroPrecisionNano,
472}
473
474impl error::IntoError for FormatError {
475    fn into_error(self) -> error::Error {
476        self.into()
477    }
478}
479
480impl From<FormatError> for error::Error {
481    #[cold]
482    #[inline(never)]
483    fn from(err: FormatError) -> error::Error {
484        error::ErrorKind::FmtStrtimeFormat(err).into()
485    }
486}
487
488impl core::fmt::Display for FormatError {
489    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
490        use self::FormatError::*;
491
492        match *self {
493            RequiresDate => f.write_str("requires date to format"),
494            RequiresInstant => f.write_str(
495                "requires instant (a timestamp or a date, time and offset)",
496            ),
497            RequiresTime => f.write_str("requires time to format"),
498            RequiresOffset => f.write_str("requires time zone offset"),
499            RequiresTimeZone => {
500                f.write_str("requires IANA time zone identifier")
501            }
502            RequiresTimeZoneOrOffset => f.write_str(
503                "requires IANA time zone identifier or \
504                 time zone offset, but neither were present",
505            ),
506            InvalidUtf8 => {
507                f.write_str("invalid format string, it must be valid UTF-8")
508            }
509            ZeroPrecisionFloat => {
510                f.write_str("zero precision with %f is not allowed")
511            }
512            ZeroPrecisionNano => {
513                f.write_str("zero precision with %N is not allowed")
514            }
515        }
516    }
517}