Skip to main content

jiff/fmt/strtime/
mod.rs

1/*!
2Support for "printf"-style parsing and formatting.
3
4While the routines exposed in this module very closely resemble the
5corresponding [`strptime`] and [`strftime`] POSIX functions, it is not a goal
6for the formatting machinery to precisely match POSIX semantics.
7
8If there is a conversion specifier you need that Jiff doesn't support, please
9[create a new issue][create-issue].
10
11The formatting and parsing in this module does not currently support any
12form of localization. Please see [this issue][locale] about the topic of
13localization in Jiff.
14
15[create-issue]: https://github.com/BurntSushi/jiff/issues/new
16[locale]: https://github.com/BurntSushi/jiff/issues/4
17
18# Example
19
20This shows how to parse a civil date and its weekday:
21
22```
23use jiff::civil::Date;
24
25let date = Date::strptime("%Y-%m-%d is a %A", "2024-07-15 is a Monday")?;
26assert_eq!(date.to_string(), "2024-07-15");
27// Leading zeros are optional for numbers in all cases:
28let date = Date::strptime("%Y-%m-%d is a %A", "2024-07-15 is a Monday")?;
29assert_eq!(date.to_string(), "2024-07-15");
30// Parsing does error checking! 2024-07-15 was not a Tuesday.
31assert!(Date::strptime("%Y-%m-%d is a %A", "2024-07-15 is a Tuesday").is_err());
32
33# Ok::<(), Box<dyn std::error::Error>>(())
34```
35
36And this shows how to format a zoned datetime with a time zone abbreviation:
37
38```
39use jiff::civil::date;
40
41let zdt = date(2024, 7, 15).at(17, 30, 59, 0).in_tz("Australia/Tasmania")?;
42// %-I instead of %I means no padding.
43let string = zdt.strftime("%A, %B %d, %Y at %-I:%M%P %Z").to_string();
44assert_eq!(string, "Monday, July 15, 2024 at 5:30pm AEST");
45
46# Ok::<(), Box<dyn std::error::Error>>(())
47```
48
49Or parse a zoned datetime with an IANA time zone identifier:
50
51```
52use jiff::{civil::date, Zoned};
53
54let zdt = Zoned::strptime(
55    "%A, %B %d, %Y at %-I:%M%P %:Q",
56    "Monday, July 15, 2024 at 5:30pm Australia/Tasmania",
57)?;
58assert_eq!(
59    zdt,
60    date(2024, 7, 15).at(17, 30, 0, 0).in_tz("Australia/Tasmania")?,
61);
62
63# Ok::<(), Box<dyn std::error::Error>>(())
64```
65
66# Usage
67
68For most cases, you can use the `strptime` and `strftime` methods on the
69corresponding datetime type. For example, [`Zoned::strptime`] and
70[`Zoned::strftime`]. However, the [`BrokenDownTime`] type in this module
71provides a little more control.
72
73For example, assuming `t` is a `civil::Time`, then
74`t.strftime("%Y").to_string()` will actually panic because a `civil::Time` does
75not have a year. While the underlying formatting machinery actually returns
76an error, this error gets turned into a panic by virtue of going through the
77`std::fmt::Display` and `std::string::ToString` APIs.
78
79In contrast, [`BrokenDownTime::format`] (or just [`format`](format())) can
80report the error to you without any panicking:
81
82```
83use jiff::{civil::time, fmt::strtime};
84
85let t = time(23, 59, 59, 0);
86assert_eq!(
87    strtime::format("%Y", t).unwrap_err().to_string(),
88    "strftime formatting failed: %Y failed: requires date to format",
89);
90```
91
92# Advice
93
94The formatting machinery supported by this module is not especially expressive.
95The pattern language is a simple sequence of conversion specifiers interspersed
96by literals and arbitrary whitespace. This means that you sometimes need
97delimiters or spaces between components. For example, this is fine:
98
99```
100use jiff::fmt::strtime;
101
102let date = strtime::parse("%Y%m%d", "20240715")?.to_date()?;
103assert_eq!(date.to_string(), "2024-07-15");
104# Ok::<(), Box<dyn std::error::Error>>(())
105```
106
107But this is ambiguous (is the year `999` or `9990`?):
108
109```
110use jiff::fmt::strtime;
111
112assert!(strtime::parse("%Y%m%d", "9990715").is_err());
113```
114
115In this case, since years greedily consume up to 4 digits by default, `9990`
116is parsed as the year. And since months greedily consume up to 2 digits by
117default, `71` is parsed as the month, which results in an invalid day. If you
118expect your datetimes to always use 4 digits for the year, then it might be
119okay to skip on the delimiters. For example, the year `999` could be written
120with a leading zero:
121
122```
123use jiff::fmt::strtime;
124
125let date = strtime::parse("%Y%m%d", "09990715")?.to_date()?;
126assert_eq!(date.to_string(), "0999-07-15");
127// Indeed, the leading zero is written by default when
128// formatting, since years are padded out to 4 digits
129// by default:
130assert_eq!(date.strftime("%Y%m%d").to_string(), "09990715");
131
132# Ok::<(), Box<dyn std::error::Error>>(())
133```
134
135The main advice here is that these APIs can come in handy for ad hoc tasks that
136would otherwise be annoying to deal with. For example, I once wrote a tool to
137extract data from an XML dump of my SMS messages, and one of the date formats
138used was `Apr 1, 2022 20:46:15`. That doesn't correspond to any standard, and
139while parsing it with a regex isn't that difficult, it's pretty annoying,
140especially because of the English abbreviated month name. That's exactly the
141kind of use case where this module shines.
142
143If the formatting machinery in this module isn't flexible enough for your use
144case and you don't control the format, it is recommended to write a bespoke
145parser (possibly with regex). It is unlikely that the expressiveness of this
146formatting machinery will be improved much. (Although it is plausible to add
147new conversion specifiers.)
148
149# Conversion specifications
150
151This table lists the complete set of conversion specifiers supported in the
152format. While most conversion specifiers are supported as is in both parsing
153and formatting, there are some differences. Where differences occur, they are
154noted in the table below.
155
156When parsing, and whenever a conversion specifier matches an enumeration of
157strings, the strings are matched without regard to ASCII case.
158
159| Specifier | Example | Description |
160| --------- | ------- | ----------- |
161| `%%` | `%%` | A literal `%`. |
162| `%A`, `%a` | `Sunday`, `Sun` | The full and abbreviated weekday, respectively. |
163| `%B`, `%b`, `%h` | `June`, `Jun`, `Jun` | The full and abbreviated month name, respectively. |
164| `%C` | `20` | The century of the year. No padding. |
165| `%c` | `2024 M07 14, Sun 17:31:59` | The date and clock time via [`Custom`]. Supported when formatting only. |
166| `%D` | `7/14/24` | Equivalent to `%m/%d/%y`. |
167| `%d`, `%e` | `25`, ` 5` | The day of the month. `%d` is zero-padded, `%e` is space padded. |
168| `%F` | `2024-07-14` | Equivalent to `%Y-%m-%d`. |
169| `%f` | `000456` | Fractional seconds, up to nanosecond precision. |
170| `%.f` | `.000456` | Optional fractional seconds, with dot, up to nanosecond precision. |
171| `%G` | `2024` | An [ISO 8601 week-based] year. Zero padded to 4 digits. |
172| `%g` | `24` | A two-digit [ISO 8601 week-based] year. Represents only 1969-2068. Zero padded. |
173| `%H` | `23` | The hour in a 24 hour clock. Zero padded. |
174| `%I` | `11` | The hour in a 12 hour clock. Zero padded. |
175| `%j` | `060` | The day of the year. Range is `1..=366`. Zero padded to 3 digits. |
176| `%k` | `15` | The hour in a 24 hour clock. Space padded. |
177| `%l` | ` 3` | The hour in a 12 hour clock. Space padded. |
178| `%M` | `04` | The minute. Zero padded. |
179| `%m` | `01` | The month. Zero padded. |
180| `%N` | `123456000` | Fractional seconds, up to nanosecond precision. Alias for `%9f`. |
181| `%n` | `\n` | Formats as a newline character. Parses arbitrary whitespace. |
182| `%P` | `am` | Whether the time is in the AM or PM, lowercase. |
183| `%p` | `PM` | Whether the time is in the AM or PM, uppercase. |
184| `%Q` | `America/New_York`, `+0530` | An IANA time zone identifier, or `%z` if one doesn't exist. |
185| `%:Q` | `America/New_York`, `+05:30` | An IANA time zone identifier, or `%:z` if one doesn't exist. |
186| `%q` | `4` | The quarter of the year. Supported when formatting only. |
187| `%R` | `23:30` | Equivalent to `%H:%M`. |
188| `%r` | `8:30:00 AM` | The 12-hour clock time via [`Custom`]. Supported when formatting only. |
189| `%S` | `59` | The second. Zero padded. |
190| `%s` | `1737396540` | A Unix timestamp, in seconds. |
191| `%T` | `23:30:59` | Equivalent to `%H:%M:%S`. |
192| `%t` | `\t` | Formats as a tab character. Parses arbitrary whitespace. |
193| `%U` | `03` | Week number. Week 1 is the first week starting with a Sunday. Zero padded. |
194| `%u` | `7` | The day of the week beginning with Monday at `1`. |
195| `%V` | `05` | Week number in the [ISO 8601 week-based] calendar. Zero padded. |
196| `%W` | `03` | Week number. Week 1 is the first week starting with a Monday. Zero padded. |
197| `%w` | `0` | The day of the week beginning with Sunday at `0`. |
198| `%X` | `17:31:59` | The clock time via [`Custom`]. Supported when formatting only. |
199| `%x` | `2024 M07 14` | The date via [`Custom`]. Supported when formatting only. |
200| `%Y` | `2024` | A full year, including century. Zero padded to 4 digits. |
201| `%y` | `24` | A two-digit year. Represents only 1969-2068. Zero padded. |
202| `%Z` | `EDT` | A time zone abbreviation. Supported when formatting only. |
203| `%z` | `+0530` | A time zone offset in the format `[+-]HHMM[SS]`. |
204| `%:z` | `+05:30` | A time zone offset in the format `[+-]HH:MM[:SS]`. |
205| `%::z` | `+05:30:00` | A time zone offset in the format `[+-]HH:MM:SS`. |
206| `%:::z` | `-04`, `+05:30` | A time zone offset in the format `[+-]HH:[MM[:SS]]`. |
207
208When formatting, the following flags can be inserted immediately after the `%`
209and before the directive:
210
211* `_` - Pad a numeric result to the left with spaces.
212* `-` - Do not pad a numeric result.
213* `0` - Pad a numeric result to the left with zeros.
214* `^` - Use alphabetic uppercase for all relevant strings.
215* `#` - Swap the case of the result string. This is typically only useful with
216`%p` or `%Z`, since they are the only conversion specifiers that emit strings
217entirely in uppercase by default.
218
219The above flags override the "default" settings of a specifier. For example,
220`%_d` pads with spaces instead of zeros, and `%0e` pads with zeros instead of
221spaces. The exceptions are the locale (`%c`, `%r`, `%X`, `%x`), and time zone
222(`%z`, `%:z`) specifiers. They are unaffected by any flags.
223
224Moreover, any number of decimal digits can be inserted after the (possibly
225absent) flag and before the directive, so long as the parsed number is less
226than 256. The number formed by these digits will correspond to the minimum
227amount of padding (to the left). Note that padding is clamped to a maximum of
228`20`.
229
230The flags and padding amount above may be used when parsing as well. Most
231settings are ignored during parsing except for padding. For example, if one
232wanted to parse `003` as the day `3`, then one should use `%03d`. Otherwise, by
233default, `%d` will only try to consume at most 2 digits.
234
235The `%f` and `%.f` flags also support specifying the precision, up to
236nanoseconds. For example, `%3f` and `%.3f` will both always print a fractional
237second component to exactly 3 decimal places. When no precision is specified,
238then `%f` will always emit at least one digit, even if it's zero. But `%.f`
239will emit the empty string when the fractional component is zero. Otherwise, it
240will include the leading `.`. For parsing, `%f` does not include the leading
241dot, but `%.f` does. Note that all of the options above are still parsed for
242`%f` and `%.f`, but they are all no-ops (except for the padding for `%f`, which
243is instead interpreted as a precision setting). When using a precision setting,
244truncation is used. If you need a different rounding mode, you should use
245higher level APIs like [`Timestamp::round`] or [`Zoned::round`].
246
247# Conditionally unsupported
248
249Jiff does not support `%Q` or `%:Q` (IANA time zone identifier) when the
250`alloc` crate feature is not enabled. This is because a time zone identifier
251is variable width data. If you have a use case for this, please
252[detail it in a new issue](https://github.com/BurntSushi/jiff/issues/new).
253
254# Unsupported
255
256The following things are currently unsupported:
257
258* Parsing or formatting fractional seconds in the time time zone offset.
259* The `%+` conversion specifier is not supported since there doesn't seem to
260  be any consistent definition for it.
261* With only Jiff, the `%c`, `%r`, `%X` and `%x` locale oriented specifiers
262  use a default "unknown" locale via the [`DefaultCustom`] implementation
263  of the [`Custom`] trait. An example of the default locale format for `%c`
264  is `2024 M07 14, Sun 17:31:59`. One can either switch the POSIX locale
265  via [`PosixCustom`] (e.g., `Sun Jul 14 17:31:59 2024`), or write your own
266  implementation of [`Custom`] powered by [`icu`] and glued together with Jiff
267  via [`jiff-icu`].
268* The `E` and `O` locale modifiers are not supported.
269
270[`strftime`]: https://pubs.opengroup.org/onlinepubs/009695399/functions/strftime.html
271[`strptime`]: https://pubs.opengroup.org/onlinepubs/009695399/functions/strptime.html
272[ISO 8601 week-based]: https://en.wikipedia.org/wiki/ISO_week_date
273[`icu`]: https://docs.rs/icu
274[`jiff-icu`]: https://docs.rs/jiff-icu
275*/
276
277use crate::{
278    civil::{Date, DateTime, ISOWeekDate, Time, Weekday},
279    error::{fmt::strtime::Error as E, ErrorContext},
280    fmt::{
281        buffer::{ArrayBuffer, BorrowedWriter},
282        strtime::{format::Formatter, parse::Parser},
283        Write,
284    },
285    tz::{Offset, OffsetConflict, TimeZone, TimeZoneDatabase},
286    util::{
287        self, escape,
288        rangeint::RInto,
289        t::{self, C},
290    },
291    Error, Timestamp, Zoned,
292};
293
294mod format;
295mod parse;
296
297/// Parse the given `input` according to the given `format` string.
298///
299/// See the [module documentation](self) for details on what's supported.
300///
301/// This routine is the same as [`BrokenDownTime::parse`], but may be more
302/// convenient to call.
303///
304/// # Errors
305///
306/// This returns an error when parsing failed. This might happen because
307/// the format string itself was invalid, or because the input didn't match
308/// the format string.
309///
310/// # Example
311///
312/// This example shows how to parse something resembling a RFC 2822 datetime:
313///
314/// ```
315/// use jiff::{civil::date, fmt::strtime, tz};
316///
317/// let zdt = strtime::parse(
318///     "%a, %d %b %Y %T %z",
319///     "Mon, 15 Jul 2024 16:24:59 -0400",
320/// )?.to_zoned()?;
321///
322/// let tz = tz::offset(-4).to_time_zone();
323/// assert_eq!(zdt, date(2024, 7, 15).at(16, 24, 59, 0).to_zoned(tz)?);
324///
325/// # Ok::<(), Box<dyn std::error::Error>>(())
326/// ```
327///
328/// Of course, one should prefer using the [`fmt::rfc2822`](super::rfc2822)
329/// module, which contains a dedicated RFC 2822 parser. For example, the above
330/// format string does not part all valid RFC 2822 datetimes, since, e.g.,
331/// the leading weekday is optional and so are the seconds in the time, but
332/// `strptime`-like APIs have no way of expressing such requirements.
333///
334/// [RFC 2822]: https://datatracker.ietf.org/doc/html/rfc2822
335///
336/// # Example: parse RFC 3339 timestamp with fractional seconds
337///
338/// ```
339/// use jiff::{civil::date, fmt::strtime};
340///
341/// let zdt = strtime::parse(
342///     "%Y-%m-%dT%H:%M:%S%.f%:z",
343///     "2024-07-15T16:24:59.123456789-04:00",
344/// )?.to_zoned()?;
345/// assert_eq!(
346///     zdt,
347///     date(2024, 7, 15).at(16, 24, 59, 123_456_789).in_tz("America/New_York")?,
348/// );
349///
350/// # Ok::<(), Box<dyn std::error::Error>>(())
351/// ```
352#[inline]
353pub fn parse(
354    format: impl AsRef<[u8]>,
355    input: impl AsRef<[u8]>,
356) -> Result<BrokenDownTime, Error> {
357    BrokenDownTime::parse(format, input)
358}
359
360/// Format the given broken down time using the format string given.
361///
362/// See the [module documentation](self) for details on what's supported.
363///
364/// This routine is like [`BrokenDownTime::format`], but may be more
365/// convenient to call. Also, it returns a `String` instead of accepting a
366/// [`fmt::Write`](super::Write) trait implementation to write to.
367///
368/// Note that `broken_down_time` can be _anything_ that can be converted into
369/// it. This includes, for example, [`Zoned`], [`Timestamp`], [`DateTime`],
370/// [`Date`] and [`Time`].
371///
372/// # Errors
373///
374/// This returns an error when formatting failed. Formatting can fail either
375/// because of an invalid format string, or if formatting requires a field in
376/// `BrokenDownTime` to be set that isn't. For example, trying to format a
377/// [`DateTime`] with the `%z` specifier will fail because a `DateTime` has no
378/// time zone or offset information associated with it.
379///
380/// # Example
381///
382/// This example shows how to format a `Zoned` into something resembling a RFC
383/// 2822 datetime:
384///
385/// ```
386/// use jiff::{civil::date, fmt::strtime};
387///
388/// let zdt = date(2024, 7, 15).at(16, 24, 59, 0).in_tz("America/New_York")?;
389/// let string = strtime::format("%a, %-d %b %Y %T %z", &zdt)?;
390/// assert_eq!(string, "Mon, 15 Jul 2024 16:24:59 -0400");
391///
392/// # Ok::<(), Box<dyn std::error::Error>>(())
393/// ```
394///
395/// Of course, one should prefer using the [`fmt::rfc2822`](super::rfc2822)
396/// module, which contains a dedicated RFC 2822 printer.
397///
398/// [RFC 2822]: https://datatracker.ietf.org/doc/html/rfc2822
399///
400/// # Example: `date`-like output
401///
402/// While the output of the Unix `date` command is likely locale specific,
403/// this is what it looks like on my system:
404///
405/// ```
406/// use jiff::{civil::date, fmt::strtime};
407///
408/// let zdt = date(2024, 7, 15).at(16, 24, 59, 0).in_tz("America/New_York")?;
409/// let string = strtime::format("%a %b %e %I:%M:%S %p %Z %Y", &zdt)?;
410/// assert_eq!(string, "Mon Jul 15 04:24:59 PM EDT 2024");
411///
412/// # Ok::<(), Box<dyn std::error::Error>>(())
413/// ```
414///
415/// # Example: RFC 3339 compatible output with fractional seconds
416///
417/// ```
418/// use jiff::{civil::date, fmt::strtime};
419///
420/// let zdt = date(2024, 7, 15)
421///     .at(16, 24, 59, 123_456_789)
422///     .in_tz("America/New_York")?;
423/// let string = strtime::format("%Y-%m-%dT%H:%M:%S%.f%:z", &zdt)?;
424/// assert_eq!(string, "2024-07-15T16:24:59.123456789-04:00");
425///
426/// # Ok::<(), Box<dyn std::error::Error>>(())
427/// ```
428#[cfg(any(test, feature = "alloc"))]
429#[inline]
430pub fn format(
431    format: impl AsRef<[u8]>,
432    broken_down_time: impl Into<BrokenDownTime>,
433) -> Result<alloc::string::String, Error> {
434    let broken_down_time: BrokenDownTime = broken_down_time.into();
435
436    let format = format.as_ref();
437    let mut buf = alloc::string::String::with_capacity(format.len());
438    broken_down_time.format(format, &mut buf)?;
439    Ok(buf)
440}
441
442/// Configuration for customizing the behavior of formatting or parsing.
443///
444/// One important use case enabled by this type is the ability to set a
445/// [`Custom`] trait implementation to use when calling
446/// [`BrokenDownTime::format_with_config`]
447/// or [`BrokenDownTime::to_string_with_config`].
448///
449/// It is generally expected that most callers should not need to use this.
450/// At present, the only reasons to use this are:
451///
452/// * If you specifically need to provide locale aware formatting within
453/// the context of `strtime`-style APIs. Unless you specifically need this,
454/// you should prefer using the [`icu`] crate via [`jiff-icu`] to do type
455/// conversions. More specifically, follow the examples in the `icu::datetime`
456/// module for a modern approach to datetime localization that leverages
457/// Unicode.
458/// * If you specifically need to opt into "lenient" parsing such that most
459/// errors when formatting are silently ignored.
460///
461/// # Example
462///
463/// This example shows how to use [`PosixCustom`] via `strtime` formatting:
464///
465/// ```
466/// use jiff::{civil, fmt::strtime::{BrokenDownTime, PosixCustom, Config}};
467///
468/// let config = Config::new().custom(PosixCustom::new());
469/// let dt = civil::date(2025, 7, 1).at(17, 30, 0, 0);
470/// let tm = BrokenDownTime::from(dt);
471/// assert_eq!(
472///     tm.to_string_with_config(&config, "%c")?,
473///     "Tue Jul  1 17:30:00 2025",
474/// );
475///
476/// # Ok::<(), Box<dyn std::error::Error>>(())
477/// ```
478///
479/// [`icu`]: https://docs.rs/icu
480/// [`jiff-icu`]: https://docs.rs/jiff-icu
481#[derive(Clone, Debug)]
482pub struct Config<C> {
483    custom: C,
484    lenient: bool,
485}
486
487impl Config<DefaultCustom> {
488    /// Create a new default `Config` that uses [`DefaultCustom`].
489    #[inline]
490    pub const fn new() -> Config<DefaultCustom> {
491        Config { custom: DefaultCustom::new(), lenient: false }
492    }
493}
494
495impl<C> Config<C> {
496    /// Set the implementation of [`Custom`] to use in `strtime`-style APIs
497    /// that use this configuration.
498    #[inline]
499    pub fn custom<U: Custom>(self, custom: U) -> Config<U> {
500        Config { custom, lenient: self.lenient }
501    }
502
503    /// Enable lenient formatting.
504    ///
505    /// When this is enabled, most errors that occur during formatting are
506    /// silently ignored. For example, if you try to format `%z` with a
507    /// [`BrokenDownTime`] that lacks a time zone offset, this would normally
508    /// result in an error. In contrast, when lenient mode is enabled, this
509    /// would just result in `%z` being written literally. Similarly, using
510    /// invalid UTF-8 in the format string would normally result in an error.
511    /// In lenient mode, invalid UTF-8 is automatically turned into the Unicode
512    /// replacement codepoint `U+FFFD` (which looks like this: `�`).
513    ///
514    /// Generally speaking, when this is enabled, the only error that can
515    /// occur when formatting is if a write to the underlying writer fails.
516    /// When using a writer that never errors (like `String`, unless allocation
517    /// fails), it follows that enabling lenient parsing will result in a
518    /// formatting operation that never fails (unless allocation fails).
519    ///
520    /// This currently has no effect on parsing, although this may change in
521    /// the future.
522    ///
523    /// Lenient formatting is disabled by default. It is strongly recommended
524    /// to keep it disabled in order to avoid mysterious failure modes for end
525    /// users. You should only enable this if you have strict requirements to
526    /// conform to legacy software behavior.
527    ///
528    /// # API stability
529    ///
530    /// An artifact of lenient parsing is that most error behaviors are
531    /// squashed in favor of writing the errant conversion specifier literally.
532    /// This means that if you use something like `%+`, which is currently
533    /// unrecognized, then that will result in a literal `%+` in the string
534    /// returned. But Jiff may one day add support for `%+` in a semver
535    /// compatible release.
536    ///
537    /// Stated differently, the set of unknown or error conditions is not
538    /// fixed and may decrease with time. This in turn means that the precise
539    /// conditions under which a conversion specifier gets written literally
540    /// to the resulting string may change over time in semver compatible
541    /// releases of Jiff.
542    ///
543    /// The alternative would be that Jiff could never add any new conversion
544    /// specifiers without making a semver incompatible release. The intent
545    /// of this policy is to avoid that scenario and permit reasonable
546    /// evolution of Jiff's `strtime` support.
547    ///
548    /// # Example
549    ///
550    /// This example shows how `%z` will be written literally if it would
551    /// otherwise fail:
552    ///
553    /// ```
554    /// use jiff::{civil, fmt::strtime::{BrokenDownTime, Config}};
555    ///
556    /// let tm = BrokenDownTime::from(civil::date(2025, 4, 30));
557    /// assert_eq!(
558    ///     tm.to_string("%F %z").unwrap_err().to_string(),
559    ///     "strftime formatting failed: %z failed: \
560    ///      requires time zone offset",
561    /// );
562    ///
563    /// // Now enable lenient mode:
564    /// let config = Config::new().lenient(true);
565    /// assert_eq!(
566    ///     tm.to_string_with_config(&config, "%F %z").unwrap(),
567    ///     "2025-04-30 %z",
568    /// );
569    ///
570    /// // Lenient mode also applies when using an unsupported
571    /// // or unrecognized conversion specifier. This would
572    /// // normally return an error for example:
573    /// assert_eq!(
574    ///     tm.to_string_with_config(&config, "%+ %0").unwrap(),
575    ///     "%+ %0",
576    /// );
577    /// ```
578    #[inline]
579    pub fn lenient(self, yes: bool) -> Config<C> {
580        Config { lenient: yes, ..self }
581    }
582}
583
584/// An interface for customizing `strtime`-style parsing and formatting.
585///
586/// Each method on this trait comes with a default implementation corresponding
587/// to the behavior of [`DefaultCustom`]. More methods on this trait may be
588/// added in the future.
589///
590/// Implementers of this trait can be attached to a [`Config`] which can then
591/// be passed to [`BrokenDownTime::format_with_config`] or
592/// [`BrokenDownTime::to_string_with_config`].
593///
594/// New methods with default implementations may be added to this trait in
595/// semver compatible releases of Jiff.
596///
597/// # Motivation
598///
599/// While Jiff's API is generally locale-agnostic, this trait is meant to
600/// provide a best effort "hook" for tailoring the behavior of `strtime`
601/// routines. More specifically, for conversion specifiers in `strtime`-style
602/// APIs that are influenced by locale settings.
603///
604/// In general, a `strtime`-style API is not optimal for localization.
605/// It's both too flexible and not expressive enough. As a result, mixing
606/// localization with `strtime`-style APIs is likely not a good idea. However,
607/// this is sometimes required for legacy or convenience reasons, and that's
608/// why Jiff provides this hook.
609///
610/// If you do need to localize datetimes but don't have a requirement to
611/// have it integrate with `strtime`-style APIs, then you should use the
612/// [`icu`] crate via [`jiff-icu`] for type conversions. And then follow the
613/// examples in the `icu::datetime` API for formatting datetimes.
614///
615/// # Supported conversion specifiers
616///
617/// Currently, only formatting for the following specifiers is supported:
618///
619/// * `%c` - Formatting the date and time.
620/// * `%r` - Formatting the 12-hour clock time.
621/// * `%X` - Formatting the clock time.
622/// * `%x` - Formatting the date.
623///
624/// # Unsupported behavior
625///
626/// This trait currently does not support parsing based on locale in any way.
627///
628/// This trait also does not support locale specific behavior for `%a`/`%A`
629/// (day of the week), `%b`/`%B` (name of the month) or `%p`/`%P` (AM or PM).
630/// Supporting these is problematic with modern localization APIs, since
631/// modern APIs do not expose options to localize these things independent of
632/// anything else. Instead, they are subsumed most holistically into, e.g.,
633/// "print the long form of a date in the current locale."
634///
635/// Since the motivation for this trait is not really to provide the best way
636/// to localize datetimes, but rather, to facilitate convenience and
637/// inter-operation with legacy systems, it is plausible that the behaviors
638/// listed above could be supported by Jiff. If you need the above behaviors,
639/// please [open a new issue](https://github.com/BurntSushi/jiff/issues/new)
640/// with a proposal.
641///
642/// # Example
643///
644/// This example shows the difference between the default locale and the
645/// POSIX locale:
646///
647/// ```
648/// use jiff::{civil, fmt::strtime::{BrokenDownTime, PosixCustom, Config}};
649///
650/// let dt = civil::date(2025, 7, 1).at(17, 30, 0, 0);
651/// let tm = BrokenDownTime::from(dt);
652/// assert_eq!(
653///     tm.to_string("%c")?,
654///     "2025 M07 1, Tue 17:30:00",
655/// );
656///
657/// let config = Config::new().custom(PosixCustom::new());
658/// assert_eq!(
659///     tm.to_string_with_config(&config, "%c")?,
660///     "Tue Jul  1 17:30:00 2025",
661/// );
662///
663/// # Ok::<(), Box<dyn std::error::Error>>(())
664/// ```
665///
666/// [`icu`]: https://docs.rs/icu
667/// [`jiff-icu`]: https://docs.rs/jiff-icu
668pub trait Custom: Sized {
669    /// Called when formatting a datetime with the `%c` flag.
670    ///
671    /// This defaults to the implementation for [`DefaultCustom`].
672    fn format_datetime<W: Write>(
673        &self,
674        config: &Config<Self>,
675        _ext: &Extension,
676        tm: &BrokenDownTime,
677        wtr: &mut W,
678    ) -> Result<(), Error> {
679        tm.format_with_config(config, "%Y M%m %-d, %a %H:%M:%S", wtr)
680    }
681
682    /// Called when formatting a datetime with the `%x` flag.
683    ///
684    /// This defaults to the implementation for [`DefaultCustom`].
685    fn format_date<W: Write>(
686        &self,
687        config: &Config<Self>,
688        _ext: &Extension,
689        tm: &BrokenDownTime,
690        wtr: &mut W,
691    ) -> Result<(), Error> {
692        // 2025 M04 27
693        tm.format_with_config(config, "%Y M%m %-d", wtr)
694    }
695
696    /// Called when formatting a datetime with the `%X` flag.
697    ///
698    /// This defaults to the implementation for [`DefaultCustom`].
699    fn format_time<W: Write>(
700        &self,
701        config: &Config<Self>,
702        _ext: &Extension,
703        tm: &BrokenDownTime,
704        wtr: &mut W,
705    ) -> Result<(), Error> {
706        tm.format_with_config(config, "%H:%M:%S", wtr)
707    }
708
709    /// Called when formatting a datetime with the `%r` flag.
710    ///
711    /// This defaults to the implementation for [`DefaultCustom`].
712    fn format_12hour_time<W: Write>(
713        &self,
714        config: &Config<Self>,
715        _ext: &Extension,
716        tm: &BrokenDownTime,
717        wtr: &mut W,
718    ) -> Result<(), Error> {
719        tm.format_with_config(config, "%-I:%M:%S %p", wtr)
720    }
721}
722
723/// The default trait implementation of [`Custom`].
724///
725/// Whenever one uses the formatting or parsing routines in this module
726/// without providing a configuration, then this customization is the one
727/// that gets used.
728///
729/// The behavior of the locale formatting of this type is meant to match that
730/// of Unicode's `und` locale.
731///
732/// # Example
733///
734/// This example shows how to explicitly use [`DefaultCustom`] via `strtime`
735/// formatting:
736///
737/// ```
738/// use jiff::{civil, fmt::strtime::{BrokenDownTime, DefaultCustom, Config}};
739///
740/// let config = Config::new().custom(DefaultCustom::new());
741/// let dt = civil::date(2025, 7, 1).at(17, 30, 0, 0);
742/// let tm = BrokenDownTime::from(dt);
743/// assert_eq!(
744///     tm.to_string_with_config(&config, "%c")?,
745///     "2025 M07 1, Tue 17:30:00",
746/// );
747///
748/// # Ok::<(), Box<dyn std::error::Error>>(())
749/// ```
750#[derive(Clone, Debug, Default)]
751pub struct DefaultCustom(());
752
753impl DefaultCustom {
754    /// Create a new instance of this default customization.
755    pub const fn new() -> DefaultCustom {
756        DefaultCustom(())
757    }
758}
759
760impl Custom for DefaultCustom {}
761
762/// A POSIX locale implementation of [`Custom`].
763///
764/// The behavior of the locale formatting of this type is meant to match that
765/// of POSIX's `C` locale.
766///
767/// # Example
768///
769/// This example shows how to use [`PosixCustom`] via `strtime` formatting:
770///
771/// ```
772/// use jiff::{civil, fmt::strtime::{BrokenDownTime, PosixCustom, Config}};
773///
774/// let config = Config::new().custom(PosixCustom::new());
775/// let dt = civil::date(2025, 7, 1).at(17, 30, 0, 0);
776/// let tm = BrokenDownTime::from(dt);
777/// assert_eq!(
778///     tm.to_string_with_config(&config, "%c")?,
779///     "Tue Jul  1 17:30:00 2025",
780/// );
781///
782/// # Ok::<(), Box<dyn std::error::Error>>(())
783/// ```
784#[derive(Clone, Debug, Default)]
785pub struct PosixCustom(());
786
787impl PosixCustom {
788    /// Create a new instance of this POSIX customization.
789    pub const fn new() -> PosixCustom {
790        PosixCustom(())
791    }
792}
793
794impl Custom for PosixCustom {
795    fn format_datetime<W: Write>(
796        &self,
797        config: &Config<Self>,
798        _ext: &Extension,
799        tm: &BrokenDownTime,
800        wtr: &mut W,
801    ) -> Result<(), Error> {
802        tm.format_with_config(config, "%a %b %e %H:%M:%S %Y", wtr)
803    }
804
805    fn format_date<W: Write>(
806        &self,
807        config: &Config<Self>,
808        _ext: &Extension,
809        tm: &BrokenDownTime,
810        wtr: &mut W,
811    ) -> Result<(), Error> {
812        tm.format_with_config(config, "%m/%d/%y", wtr)
813    }
814
815    fn format_time<W: Write>(
816        &self,
817        config: &Config<Self>,
818        _ext: &Extension,
819        tm: &BrokenDownTime,
820        wtr: &mut W,
821    ) -> Result<(), Error> {
822        tm.format_with_config(config, "%H:%M:%S", wtr)
823    }
824
825    fn format_12hour_time<W: Write>(
826        &self,
827        config: &Config<Self>,
828        _ext: &Extension,
829        tm: &BrokenDownTime,
830        wtr: &mut W,
831    ) -> Result<(), Error> {
832        tm.format_with_config(config, "%I:%M:%S %p", wtr)
833    }
834}
835
836/// The "broken down time" used by parsing and formatting.
837///
838/// This is a lower level aspect of the `strptime` and `strftime` APIs that you
839/// probably won't need to use directly. The main use case is if you want to
840/// observe formatting errors or if you want to format a datetime to something
841/// other than a `String` via the [`fmt::Write`](super::Write) trait.
842///
843/// Otherwise, typical use of this module happens indirectly via APIs like
844/// [`Zoned::strptime`] and [`Zoned::strftime`].
845///
846/// # Design
847///
848/// This is the type that parsing writes to and formatting reads from. That
849/// is, parsing proceeds by writing individual parsed fields to this type, and
850/// then converting the fields to datetime types like [`Zoned`] only after
851/// parsing is complete. Similarly, formatting always begins by converting
852/// datetime types like `Zoned` into a `BrokenDownTime`, and then formatting
853/// the individual fields from there.
854// Design:
855//
856// This is meant to be very similar to libc's `struct tm` in that it
857// represents civil time, although may have an offset attached to it, in which
858// case it represents an absolute time. The main difference is that each field
859// is explicitly optional, where as in C, there's no way to tell whether a
860// field is "set" or not. In C, this isn't so much a problem, because the
861// caller needs to explicitly pass in a pointer to a `struct tm`, and so the
862// API makes it clear that it's going to mutate the time.
863//
864// But in Rust, we really just want to accept a format string, an input and
865// return a fresh datetime. (Nevermind the fact that we don't provide a way
866// to mutate datetimes in place.) We could just use "default" units like you
867// might in C, but it would be very surprising if `%m-%d` just decided to fill
868// in the year for you with some default value. So we track which pieces have
869// been set individually and return errors when requesting, e.g., a `Date`
870// when no `year` has been parsed.
871//
872// We do permit time units to be filled in by default, as-is consistent with
873// the rest of Jiff's API. e.g., If a `DateTime` is requested but the format
874// string has no directives for time, we'll happy default to midnight. The
875// only catch is that you can't omit time units bigger than any present time
876// unit. For example, only `%M` doesn't fly. If you want to parse minutes, you
877// also have to parse hours.
878#[derive(Debug, Default)]
879pub struct BrokenDownTime {
880    year: Option<t::Year>,
881    month: Option<t::Month>,
882    day: Option<t::Day>,
883    day_of_year: Option<t::DayOfYear>,
884    iso_week_year: Option<t::ISOYear>,
885    iso_week: Option<t::ISOWeek>,
886    week_sun: Option<t::WeekNum>,
887    week_mon: Option<t::WeekNum>,
888    hour: Option<t::Hour>,
889    minute: Option<t::Minute>,
890    second: Option<t::Second>,
891    subsec: Option<t::SubsecNanosecond>,
892    offset: Option<Offset>,
893    // Used to confirm that it is consistent
894    // with the date given. It usually isn't
895    // used to pick a date on its own, but can
896    // be for week dates.
897    weekday: Option<Weekday>,
898    // Only generally useful with %I. But can still
899    // be used with, say, %H. In that case, AM will
900    // turn 13 o'clock to 1 o'clock.
901    meridiem: Option<Meridiem>,
902    // A timestamp. Set when converting from
903    // a `Zoned` or `Timestamp`, or when parsing `%s`.
904    timestamp: Option<Timestamp>,
905    // The time zone. Currently used only when
906    // formatting a `Zoned`.
907    tz: Option<TimeZone>,
908    // The IANA time zone identifier. Used only when
909    // formatting a `Zoned`.
910    #[cfg(feature = "alloc")]
911    iana: Option<alloc::string::String>,
912}
913
914impl BrokenDownTime {
915    /// Parse the given `input` according to the given `format` string.
916    ///
917    /// See the [module documentation](self) for details on what's supported.
918    ///
919    /// This routine is the same as the module level free function
920    /// [`strtime::parse`](parse()).
921    ///
922    /// # Errors
923    ///
924    /// This returns an error when parsing failed. This might happen because
925    /// the format string itself was invalid, or because the input didn't match
926    /// the format string.
927    ///
928    /// # Example
929    ///
930    /// ```
931    /// use jiff::{civil, fmt::strtime::BrokenDownTime};
932    ///
933    /// let tm = BrokenDownTime::parse("%m/%d/%y", "7/14/24")?;
934    /// let date = tm.to_date()?;
935    /// assert_eq!(date, civil::date(2024, 7, 14));
936    ///
937    /// # Ok::<(), Box<dyn std::error::Error>>(())
938    /// ```
939    #[inline]
940    pub fn parse(
941        format: impl AsRef<[u8]>,
942        input: impl AsRef<[u8]>,
943    ) -> Result<BrokenDownTime, Error> {
944        BrokenDownTime::parse_mono(format.as_ref(), input.as_ref())
945    }
946
947    #[inline]
948    fn parse_mono(fmt: &[u8], inp: &[u8]) -> Result<BrokenDownTime, Error> {
949        let mut pieces = BrokenDownTime::default();
950        let mut p = Parser { fmt, inp, tm: &mut pieces };
951        p.parse().context(E::FailedStrptime)?;
952        if !p.inp.is_empty() {
953            return Err(Error::from(E::unconsumed(p.inp)));
954        }
955        Ok(pieces)
956    }
957
958    /// Parse a prefix of the given `input` according to the given `format`
959    /// string. The offset returned corresponds to the number of bytes parsed.
960    /// That is, the length of the prefix (which may be the length of the
961    /// entire input if there are no unparsed bytes remaining).
962    ///
963    /// See the [module documentation](self) for details on what's supported.
964    ///
965    /// This is like [`BrokenDownTime::parse`], but it won't return an error
966    /// if there is input remaining after parsing the format directives.
967    ///
968    /// # Errors
969    ///
970    /// This returns an error when parsing failed. This might happen because
971    /// the format string itself was invalid, or because the input didn't match
972    /// the format string.
973    ///
974    /// # Example
975    ///
976    /// ```
977    /// use jiff::{civil, fmt::strtime::BrokenDownTime};
978    ///
979    /// // %y only parses two-digit years, so the 99 following
980    /// // 24 is unparsed!
981    /// let input = "7/14/2499";
982    /// let (tm, offset) = BrokenDownTime::parse_prefix("%m/%d/%y", input)?;
983    /// let date = tm.to_date()?;
984    /// assert_eq!(date, civil::date(2024, 7, 14));
985    /// assert_eq!(offset, 7);
986    /// assert_eq!(&input[offset..], "99");
987    ///
988    /// # Ok::<(), Box<dyn std::error::Error>>(())
989    /// ```
990    ///
991    /// If the entire input is parsed, then the offset is the length of the
992    /// input:
993    ///
994    /// ```
995    /// use jiff::{civil, fmt::strtime::BrokenDownTime};
996    ///
997    /// let (tm, offset) = BrokenDownTime::parse_prefix(
998    ///     "%m/%d/%y", "7/14/24",
999    /// )?;
1000    /// let date = tm.to_date()?;
1001    /// assert_eq!(date, civil::date(2024, 7, 14));
1002    /// assert_eq!(offset, 7);
1003    ///
1004    /// # Ok::<(), Box<dyn std::error::Error>>(())
1005    /// ```
1006    ///
1007    /// # Example: how to parse only a part of a timestamp
1008    ///
1009    /// If you only need, for example, the date from a timestamp, then you
1010    /// can parse it as a prefix:
1011    ///
1012    /// ```
1013    /// use jiff::{civil, fmt::strtime::BrokenDownTime};
1014    ///
1015    /// let input = "2024-01-20T17:55Z";
1016    /// let (tm, offset) = BrokenDownTime::parse_prefix("%Y-%m-%d", input)?;
1017    /// let date = tm.to_date()?;
1018    /// assert_eq!(date, civil::date(2024, 1, 20));
1019    /// assert_eq!(offset, 10);
1020    /// assert_eq!(&input[offset..], "T17:55Z");
1021    ///
1022    /// # Ok::<(), Box<dyn std::error::Error>>(())
1023    /// ```
1024    ///
1025    /// Note though that Jiff's default parsing functions are already quite
1026    /// flexible, and one can just parse a civil date directly from a timestamp
1027    /// automatically:
1028    ///
1029    /// ```
1030    /// use jiff::civil;
1031    ///
1032    /// let input = "2024-01-20T17:55-05";
1033    /// let date: civil::Date = input.parse()?;
1034    /// assert_eq!(date, civil::date(2024, 1, 20));
1035    ///
1036    /// # Ok::<(), Box<dyn std::error::Error>>(())
1037    /// ```
1038    ///
1039    /// Although in this case, you don't get the length of the prefix parsed.
1040    #[inline]
1041    pub fn parse_prefix(
1042        format: impl AsRef<[u8]>,
1043        input: impl AsRef<[u8]>,
1044    ) -> Result<(BrokenDownTime, usize), Error> {
1045        BrokenDownTime::parse_prefix_mono(format.as_ref(), input.as_ref())
1046    }
1047
1048    #[inline]
1049    fn parse_prefix_mono(
1050        fmt: &[u8],
1051        inp: &[u8],
1052    ) -> Result<(BrokenDownTime, usize), Error> {
1053        let mkoffset = util::parse::offseter(inp);
1054        let mut pieces = BrokenDownTime::default();
1055        let mut p = Parser { fmt, inp, tm: &mut pieces };
1056        p.parse().context(E::FailedStrptime)?;
1057        let remainder = mkoffset(p.inp);
1058        Ok((pieces, remainder))
1059    }
1060
1061    /// Format this broken down time using the format string given.
1062    ///
1063    /// See the [module documentation](self) for details on what's supported.
1064    ///
1065    /// This routine is like the module level free function
1066    /// [`strtime::format`](parse()), except it takes a
1067    /// [`fmt::Write`](super::Write) trait implementations instead of assuming
1068    /// you want a `String`.
1069    ///
1070    /// # Errors
1071    ///
1072    /// This returns an error when formatting failed. Formatting can fail
1073    /// either because of an invalid format string, or if formatting requires
1074    /// a field in `BrokenDownTime` to be set that isn't. For example, trying
1075    /// to format a [`DateTime`] with the `%z` specifier will fail because a
1076    /// `DateTime` has no time zone or offset information associated with it.
1077    ///
1078    /// Formatting also fails if writing to the given writer fails.
1079    ///
1080    /// # Example
1081    ///
1082    /// This example shows a formatting option, `%Z`, that isn't available
1083    /// during parsing. Namely, `%Z` inserts a time zone abbreviation. This
1084    /// is generally only intended for display purposes, since it can be
1085    /// ambiguous when parsing.
1086    ///
1087    /// ```
1088    /// use jiff::{civil::date, fmt::strtime::BrokenDownTime};
1089    ///
1090    /// let zdt = date(2024, 7, 9).at(16, 24, 0, 0).in_tz("America/New_York")?;
1091    /// let tm = BrokenDownTime::from(&zdt);
1092    ///
1093    /// let mut buf = String::new();
1094    /// tm.format("%a %b %e %I:%M:%S %p %Z %Y", &mut buf)?;
1095    ///
1096    /// assert_eq!(buf, "Tue Jul  9 04:24:00 PM EDT 2024");
1097    ///
1098    /// # Ok::<(), Box<dyn std::error::Error>>(())
1099    /// ```
1100    #[inline]
1101    pub fn format<W: Write>(
1102        &self,
1103        format: impl AsRef<[u8]>,
1104        mut wtr: W,
1105    ) -> Result<(), Error> {
1106        self.format_with_config(&Config::new(), format, &mut wtr)
1107    }
1108
1109    /// Format this broken down time with a specific configuration using the
1110    /// format string given.
1111    ///
1112    /// See the [module documentation](self) for details on what's supported.
1113    ///
1114    /// This routine is like [`BrokenDownTime::format`], except that it
1115    /// permits callers to provide their own configuration instead of using
1116    /// the default. This routine also accepts a `&mut W` instead of a `W`,
1117    /// which may be more flexible in some situations.
1118    ///
1119    /// # Errors
1120    ///
1121    /// This returns an error when formatting failed. Formatting can fail
1122    /// either because of an invalid format string, or if formatting requires
1123    /// a field in `BrokenDownTime` to be set that isn't. For example, trying
1124    /// to format a [`DateTime`] with the `%z` specifier will fail because a
1125    /// `DateTime` has no time zone or offset information associated with it.
1126    ///
1127    /// Formatting also fails if writing to the given writer fails.
1128    ///
1129    /// # Example
1130    ///
1131    /// This example shows how to use [`PosixCustom`] to get formatting
1132    /// for conversion specifiers like `%c` in the POSIX locale:
1133    ///
1134    /// ```
1135    /// use jiff::{civil, fmt::strtime::{BrokenDownTime, PosixCustom, Config}};
1136    ///
1137    /// let mut buf = String::new();
1138    /// let dt = civil::date(2025, 7, 1).at(17, 30, 0, 0);
1139    /// let tm = BrokenDownTime::from(dt);
1140    /// tm.format("%c", &mut buf)?;
1141    /// assert_eq!(buf, "2025 M07 1, Tue 17:30:00");
1142    ///
1143    /// let config = Config::new().custom(PosixCustom::new());
1144    /// buf.clear();
1145    /// tm.format_with_config(&config, "%c", &mut buf)?;
1146    /// assert_eq!(buf, "Tue Jul  1 17:30:00 2025");
1147    ///
1148    /// # Ok::<(), Box<dyn std::error::Error>>(())
1149    /// ```
1150    #[inline]
1151    pub fn format_with_config<W: Write, L: Custom>(
1152        &self,
1153        config: &Config<L>,
1154        format: impl AsRef<[u8]>,
1155        wtr: &mut W,
1156    ) -> Result<(), Error> {
1157        let fmt = format.as_ref();
1158        let mut buf = ArrayBuffer::<100>::default();
1159        let mut bbuf = buf.as_borrowed();
1160        let mut wtr = BorrowedWriter::new(&mut bbuf, wtr);
1161        let mut formatter = Formatter { config, fmt, tm: self, wtr: &mut wtr };
1162        formatter.format().context(E::FailedStrftime)?;
1163        wtr.finish()
1164    }
1165
1166    /// Format this broken down time using the format string given into a new
1167    /// `String`.
1168    ///
1169    /// See the [module documentation](self) for details on what's supported.
1170    ///
1171    /// This is like [`BrokenDownTime::format`], but always uses a `String` to
1172    /// format the time into. If you need to reuse allocations or write a
1173    /// formatted time into a different type, then you should use
1174    /// [`BrokenDownTime::format`] instead.
1175    ///
1176    /// # Errors
1177    ///
1178    /// This returns an error when formatting failed. Formatting can fail
1179    /// either because of an invalid format string, or if formatting requires
1180    /// a field in `BrokenDownTime` to be set that isn't. For example, trying
1181    /// to format a [`DateTime`] with the `%z` specifier will fail because a
1182    /// `DateTime` has no time zone or offset information associated with it.
1183    ///
1184    /// # Example
1185    ///
1186    /// This example shows a formatting option, `%Z`, that isn't available
1187    /// during parsing. Namely, `%Z` inserts a time zone abbreviation. This
1188    /// is generally only intended for display purposes, since it can be
1189    /// ambiguous when parsing.
1190    ///
1191    /// ```
1192    /// use jiff::{civil::date, fmt::strtime::BrokenDownTime};
1193    ///
1194    /// let zdt = date(2024, 7, 9).at(16, 24, 0, 0).in_tz("America/New_York")?;
1195    /// let tm = BrokenDownTime::from(&zdt);
1196    /// let string = tm.to_string("%a %b %e %I:%M:%S %p %Z %Y")?;
1197    /// assert_eq!(string, "Tue Jul  9 04:24:00 PM EDT 2024");
1198    ///
1199    /// # Ok::<(), Box<dyn std::error::Error>>(())
1200    /// ```
1201    #[cfg(feature = "alloc")]
1202    #[inline]
1203    pub fn to_string(
1204        &self,
1205        format: impl AsRef<[u8]>,
1206    ) -> Result<alloc::string::String, Error> {
1207        let format = format.as_ref();
1208        let mut buf = alloc::string::String::with_capacity(format.len());
1209        self.format(format, &mut buf)?;
1210        Ok(buf)
1211    }
1212
1213    /// Format this broken down time with a specific configuration using the
1214    /// format string given into a new `String`.
1215    ///
1216    /// See the [module documentation](self) for details on what's supported.
1217    ///
1218    /// This routine is like [`BrokenDownTime::to_string`], except that it
1219    /// permits callers to provide their own configuration instead of using
1220    /// the default.
1221    ///
1222    /// # Errors
1223    ///
1224    /// This returns an error when formatting failed. Formatting can fail
1225    /// either because of an invalid format string, or if formatting requires
1226    /// a field in `BrokenDownTime` to be set that isn't. For example, trying
1227    /// to format a [`DateTime`] with the `%z` specifier will fail because a
1228    /// `DateTime` has no time zone or offset information associated with it.
1229    ///
1230    /// # Example
1231    ///
1232    /// This example shows how to use [`PosixCustom`] to get formatting
1233    /// for conversion specifiers like `%c` in the POSIX locale:
1234    ///
1235    /// ```
1236    /// use jiff::{civil, fmt::strtime::{BrokenDownTime, PosixCustom, Config}};
1237    ///
1238    /// let dt = civil::date(2025, 7, 1).at(17, 30, 0, 0);
1239    /// let tm = BrokenDownTime::from(dt);
1240    /// assert_eq!(
1241    ///     tm.to_string("%c")?,
1242    ///     "2025 M07 1, Tue 17:30:00",
1243    /// );
1244    ///
1245    /// let config = Config::new().custom(PosixCustom::new());
1246    /// assert_eq!(
1247    ///     tm.to_string_with_config(&config, "%c")?,
1248    ///     "Tue Jul  1 17:30:00 2025",
1249    /// );
1250    ///
1251    /// # Ok::<(), Box<dyn std::error::Error>>(())
1252    /// ```
1253    #[cfg(feature = "alloc")]
1254    #[inline]
1255    pub fn to_string_with_config<L: Custom>(
1256        &self,
1257        config: &Config<L>,
1258        format: impl AsRef<[u8]>,
1259    ) -> Result<alloc::string::String, Error> {
1260        let format = format.as_ref();
1261        let mut buf = alloc::string::String::with_capacity(format.len());
1262        self.format_with_config(config, format, &mut buf)?;
1263        Ok(buf)
1264    }
1265
1266    /// Extracts a zoned datetime from this broken down time.
1267    ///
1268    /// When an IANA time zone identifier is
1269    /// present but an offset is not, then the
1270    /// [`Disambiguation::Compatible`](crate::tz::Disambiguation::Compatible)
1271    /// strategy is used if the parsed datetime is ambiguous in the time zone.
1272    ///
1273    /// If you need to use a custom time zone database for doing IANA time
1274    /// zone identifier lookups (via the `%Q` directive), then use
1275    /// [`BrokenDownTime::to_zoned_with`].
1276    ///
1277    /// This always prefers an explicitly set timestamp over other components
1278    /// of this `BrokenDownTime`. An explicit timestamp is set via
1279    /// [`BrokenDownTime::set_timestamp`]. This most commonly occurs by parsing
1280    /// a `%s` conversion specifier. When an explicit timestamp is not present,
1281    /// then the instant is derived from a civil datetime with a UTC offset
1282    /// and/or a time zone.
1283    ///
1284    /// # Warning
1285    ///
1286    /// The `strtime` module APIs do not require an IANA time zone identifier
1287    /// to parse a `Zoned`. If one is not used, then if you format a zoned
1288    /// datetime in a time zone like `America/New_York` and then parse it back
1289    /// again, the zoned datetime you get back will be a "fixed offset" zoned
1290    /// datetime. This in turn means it will not perform daylight saving time
1291    /// safe arithmetic.
1292    ///
1293    /// However, the `%Q` directive may be used to both format and parse an
1294    /// IANA time zone identifier. It is strongly recommended to use this
1295    /// directive whenever one is formatting or parsing `Zoned` values since
1296    /// it permits correctly round-tripping `Zoned` values.
1297    ///
1298    /// # Errors
1299    ///
1300    /// This returns an error if there weren't enough components to construct
1301    /// an instant with a time zone. This requires an IANA time zone identifier
1302    /// or a UTC offset, as well as either an explicitly set timestamp (via
1303    /// [`BrokenDownTime::set_timestamp`]) or enough data set to form a civil
1304    /// datetime.
1305    ///
1306    /// When both a UTC offset and an IANA time zone identifier are found, then
1307    /// an error is returned if they are inconsistent with one another for the
1308    /// parsed timestamp.
1309    ///
1310    /// # Example
1311    ///
1312    /// This example shows how to parse a zoned datetime:
1313    ///
1314    /// ```
1315    /// use jiff::fmt::strtime;
1316    ///
1317    /// let zdt = strtime::parse(
1318    ///     "%F %H:%M %:z %:Q",
1319    ///     "2024-07-14 21:14 -04:00 US/Eastern",
1320    /// )?.to_zoned()?;
1321    /// assert_eq!(zdt.to_string(), "2024-07-14T21:14:00-04:00[US/Eastern]");
1322    ///
1323    /// # Ok::<(), Box<dyn std::error::Error>>(())
1324    /// ```
1325    ///
1326    /// # Example: time zone inconsistent with offset
1327    ///
1328    /// This shows that an error is returned when the offset is inconsistent
1329    /// with the time zone. For example, `US/Eastern` is in daylight saving
1330    /// time in July 2024:
1331    ///
1332    /// ```
1333    /// use jiff::fmt::strtime;
1334    ///
1335    /// let result = strtime::parse(
1336    ///     "%F %H:%M %:z %:Q",
1337    ///     "2024-07-14 21:14 -05:00 US/Eastern",
1338    /// )?.to_zoned();
1339    /// assert_eq!(
1340    ///     result.unwrap_err().to_string(),
1341    ///     "datetime could not resolve to a timestamp since `reject` \
1342    ///      conflict resolution was chosen, and because \
1343    ///      datetime has offset `-05`, \
1344    ///      but the time zone `US/Eastern` for the given datetime \
1345    ///      unambiguously has offset `-04`",
1346    /// );
1347    ///
1348    /// # Ok::<(), Box<dyn std::error::Error>>(())
1349    /// ```
1350    ///
1351    /// # Example: timestamp without offset
1352    ///
1353    /// If a timestamp has been parsed but there is no offset or IANA time
1354    /// zone identifier, then the zoned datetime will be in UTC via the
1355    /// `Etc/Unknown` time zone:
1356    ///
1357    /// ```
1358    /// use jiff::fmt::strtime;
1359    ///
1360    /// let zdt = strtime::parse("%s", "1760813400")?.to_zoned()?;
1361    /// assert_eq!(zdt.to_string(), "2025-10-18T18:50:00Z[Etc/Unknown]");
1362    ///
1363    /// # Ok::<(), Box<dyn std::error::Error>>(())
1364    /// ```
1365    #[inline]
1366    pub fn to_zoned(&self) -> Result<Zoned, Error> {
1367        self.to_zoned_with(crate::tz::db())
1368    }
1369
1370    /// Extracts a zoned datetime from this broken down time and uses the time
1371    /// zone database given for any IANA time zone identifier lookups.
1372    ///
1373    /// An IANA time zone identifier lookup is only performed when this
1374    /// `BrokenDownTime` contains an IANA time zone identifier. An IANA time
1375    /// zone identifier can be parsed with the `%Q` directive.
1376    ///
1377    /// When an IANA time zone identifier is
1378    /// present but an offset is not, then the
1379    /// [`Disambiguation::Compatible`](crate::tz::Disambiguation::Compatible)
1380    /// strategy is used if the parsed datetime is ambiguous in the time zone.
1381    ///
1382    /// This always prefers an explicitly set timestamp over other components
1383    /// of this `BrokenDownTime`. An explicit timestamp is set via
1384    /// [`BrokenDownTime::set_timestamp`]. This most commonly occurs by parsing
1385    /// a `%s` conversion specifier. When an explicit timestamp is not present,
1386    /// then the instant is derived from a civil datetime with a UTC offset
1387    /// and/or a time zone.
1388    ///
1389    /// # Warning
1390    ///
1391    /// The `strtime` module APIs do not require an IANA time zone identifier
1392    /// to parse a `Zoned`. If one is not used, then if you format a zoned
1393    /// datetime in a time zone like `America/New_York` and then parse it back
1394    /// again, the zoned datetime you get back will be a "fixed offset" zoned
1395    /// datetime. This in turn means it will not perform daylight saving time
1396    /// safe arithmetic.
1397    ///
1398    /// However, the `%Q` directive may be used to both format and parse an
1399    /// IANA time zone identifier. It is strongly recommended to use this
1400    /// directive whenever one is formatting or parsing `Zoned` values since
1401    /// it permits correctly round-tripping `Zoned` values.
1402    ///
1403    /// # Errors
1404    ///
1405    /// This returns an error if there weren't enough components to construct
1406    /// an instant with a time zone. This requires an IANA time zone identifier
1407    /// or a UTC offset, as well as either an explicitly set timestamp (via
1408    /// [`BrokenDownTime::set_timestamp`]) or enough data set to form a civil
1409    /// datetime.
1410    ///
1411    /// When both a UTC offset and an IANA time zone identifier are found, then
1412    /// an error is returned if they are inconsistent with one another for the
1413    /// parsed timestamp.
1414    ///
1415    /// # Example
1416    ///
1417    /// This example shows how to parse a zoned datetime:
1418    ///
1419    /// ```
1420    /// use jiff::fmt::strtime;
1421    ///
1422    /// let zdt = strtime::parse(
1423    ///     "%F %H:%M %:z %:Q",
1424    ///     "2024-07-14 21:14 -04:00 US/Eastern",
1425    /// )?.to_zoned_with(jiff::tz::db())?;
1426    /// assert_eq!(zdt.to_string(), "2024-07-14T21:14:00-04:00[US/Eastern]");
1427    ///
1428    /// # Ok::<(), Box<dyn std::error::Error>>(())
1429    /// ```
1430    #[inline]
1431    pub fn to_zoned_with(
1432        &self,
1433        db: &TimeZoneDatabase,
1434    ) -> Result<Zoned, Error> {
1435        match (self.offset, self.iana_time_zone()) {
1436            (None, None) => {
1437                if let Some(ts) = self.timestamp {
1438                    return Ok(ts.to_zoned(TimeZone::unknown()));
1439                }
1440                Err(Error::from(E::ZonedOffsetOrTz))
1441            }
1442            (Some(offset), None) => {
1443                let ts = match self.timestamp {
1444                    Some(ts) => ts,
1445                    None => {
1446                        let dt = self
1447                            .to_datetime()
1448                            .context(E::RequiredDateTimeForZoned)?;
1449                        let ts = offset
1450                            .to_timestamp(dt)
1451                            .context(E::RangeTimestamp)?;
1452                        ts
1453                    }
1454                };
1455                Ok(ts.to_zoned(TimeZone::fixed(offset)))
1456            }
1457            (None, Some(iana)) => {
1458                let tz = db.get(iana)?;
1459                match self.timestamp {
1460                    Some(ts) => Ok(ts.to_zoned(tz)),
1461                    None => {
1462                        let dt = self
1463                            .to_datetime()
1464                            .context(E::RequiredDateTimeForZoned)?;
1465                        Ok(tz.to_zoned(dt)?)
1466                    }
1467                }
1468            }
1469            (Some(offset), Some(iana)) => {
1470                let tz = db.get(iana)?;
1471                match self.timestamp {
1472                    Some(ts) => {
1473                        let zdt = ts.to_zoned(tz);
1474                        if zdt.offset() != offset {
1475                            return Err(Error::from(E::MismatchOffset {
1476                                parsed: offset,
1477                                got: zdt.offset(),
1478                            }));
1479                        }
1480                        Ok(zdt)
1481                    }
1482                    None => {
1483                        let dt = self
1484                            .to_datetime()
1485                            .context(E::RequiredDateTimeForZoned)?;
1486                        let azdt =
1487                            OffsetConflict::Reject.resolve(dt, offset, tz)?;
1488                        // Guaranteed that if OffsetConflict::Reject doesn't
1489                        // reject, then we get back an unambiguous zoned
1490                        // datetime.
1491                        let zdt = azdt.unambiguous().unwrap();
1492                        Ok(zdt)
1493                    }
1494                }
1495            }
1496        }
1497    }
1498
1499    /// Extracts a timestamp from this broken down time.
1500    ///
1501    /// This always prefers an explicitly set timestamp over other components
1502    /// of this `BrokenDownTime`. An explicit timestamp is set via
1503    /// [`BrokenDownTime::set_timestamp`]. This most commonly occurs by parsing
1504    /// a `%s` conversion specifier. When an explicit timestamp is not present,
1505    /// then the instant is derived from a civil datetime with a UTC offset.
1506    ///
1507    /// # Errors
1508    ///
1509    /// This returns an error if there weren't enough components to construct
1510    /// an instant. This requires either an explicitly set timestamp (via
1511    /// [`BrokenDownTime::set_timestamp`]) or enough data set to form a civil
1512    /// datetime _and_ a UTC offset.
1513    ///
1514    /// # Example
1515    ///
1516    /// This example shows how to parse a timestamp from a broken down time:
1517    ///
1518    /// ```
1519    /// use jiff::fmt::strtime;
1520    ///
1521    /// let ts = strtime::parse(
1522    ///     "%F %H:%M %:z",
1523    ///     "2024-07-14 21:14 -04:00",
1524    /// )?.to_timestamp()?;
1525    /// assert_eq!(ts.to_string(), "2024-07-15T01:14:00Z");
1526    ///
1527    /// # Ok::<(), Box<dyn std::error::Error>>(())
1528    /// ```
1529    ///
1530    /// # Example: conflicting data
1531    ///
1532    /// It is possible to parse both a timestamp and a civil datetime with an
1533    /// offset in the same string. This means there could be two potentially
1534    /// different ways to derive a timestamp from the parsed data. When that
1535    /// happens, any explicitly parsed timestamp (via `%s`) takes precedence
1536    /// for this method:
1537    ///
1538    /// ```
1539    /// use jiff::fmt::strtime;
1540    ///
1541    /// // The `%s` parse wins:
1542    /// let ts = strtime::parse(
1543    ///     "%F %H:%M %:z and also %s",
1544    ///     "2024-07-14 21:14 -04:00 and also 1760377242",
1545    /// )?.to_timestamp()?;
1546    /// assert_eq!(ts.to_string(), "2025-10-13T17:40:42Z");
1547    ///
1548    /// // Even when it is parsed first:
1549    /// let ts = strtime::parse(
1550    ///     "%s and also %F %H:%M %:z",
1551    ///     "1760377242 and also 2024-07-14 21:14 -04:00",
1552    /// )?.to_timestamp()?;
1553    /// assert_eq!(ts.to_string(), "2025-10-13T17:40:42Z");
1554    ///
1555    /// # Ok::<(), Box<dyn std::error::Error>>(())
1556    /// ```
1557    ///
1558    /// If you need access to the instant parsed by a civil datetime with an
1559    /// offset, then that is still available:
1560    ///
1561    /// ```
1562    /// use jiff::fmt::strtime;
1563    ///
1564    /// let tm = strtime::parse(
1565    ///     "%F %H:%M %:z and also %s",
1566    ///     "2024-07-14 21:14 -04:00 and also 1760377242",
1567    /// )?;
1568    /// assert_eq!(tm.to_timestamp()?.to_string(), "2025-10-13T17:40:42Z");
1569    ///
1570    /// let dt = tm.to_datetime()?;
1571    /// let offset = tm.offset().ok_or_else(|| "missing offset")?;
1572    /// let instant = offset.to_timestamp(dt)?;
1573    /// assert_eq!(instant.to_string(), "2024-07-15T01:14:00Z");
1574    ///
1575    /// # Ok::<(), Box<dyn std::error::Error>>(())
1576    /// ```
1577    #[inline]
1578    pub fn to_timestamp(&self) -> Result<Timestamp, Error> {
1579        // Previously, I had used this as the "fast path" and
1580        // put the conversion code below into a cold unlineable
1581        // function. But this "fast path" is actually the unusual
1582        // case. It's rare to parse a timestamp (as an integer
1583        // number of seconds since the Unix epoch) directly.
1584        // So the code below, while bigger, is the common case.
1585        // So it probably makes sense to keep it inlined.
1586        if let Some(timestamp) = self.timestamp() {
1587            return Ok(timestamp);
1588        }
1589        let dt =
1590            self.to_datetime().context(E::RequiredDateTimeForTimestamp)?;
1591        let offset = self.offset.ok_or(E::RequiredOffsetForTimestamp)?;
1592        offset.to_timestamp(dt).context(E::RangeTimestamp)
1593    }
1594
1595    /// Extracts a civil datetime from this broken down time.
1596    ///
1597    /// # Errors
1598    ///
1599    /// This returns an error if there weren't enough components to construct
1600    /// a civil datetime. This means there must be at least a year, month and
1601    /// day.
1602    ///
1603    /// It's okay if there are more units than are needed to construct a civil
1604    /// datetime. For example, if this broken down time contains an offset,
1605    /// then it won't prevent a conversion to a civil datetime.
1606    ///
1607    /// # Example
1608    ///
1609    /// This example shows how to parse a civil datetime from a broken down
1610    /// time:
1611    ///
1612    /// ```
1613    /// use jiff::fmt::strtime;
1614    ///
1615    /// let dt = strtime::parse("%F %H:%M", "2024-07-14 21:14")?.to_datetime()?;
1616    /// assert_eq!(dt.to_string(), "2024-07-14T21:14:00");
1617    ///
1618    /// # Ok::<(), Box<dyn std::error::Error>>(())
1619    /// ```
1620    #[inline]
1621    pub fn to_datetime(&self) -> Result<DateTime, Error> {
1622        let date = self.to_date().context(E::RequiredDateForDateTime)?;
1623        let time = self.to_time().context(E::RequiredTimeForDateTime)?;
1624        Ok(DateTime::from_parts(date, time))
1625    }
1626
1627    /// Extracts a civil date from this broken down time.
1628    ///
1629    /// This requires that the year (Gregorian or ISO 8601 week date year)
1630    /// is set along with a way to identify the day
1631    /// in the year. Typically identifying the day is done by setting the
1632    /// month and day, but this can also be done via a number of other means:
1633    ///
1634    /// * Via an ISO week date.
1635    /// * Via the day of the year.
1636    /// * Via a week date with Sunday as the start of the week.
1637    /// * Via a week date with Monday as the start of the week.
1638    ///
1639    /// # Errors
1640    ///
1641    /// This returns an error if there weren't enough components to construct
1642    /// a civil date, or if the components don't form into a valid date. This
1643    /// means there must be at least a year and a way to determine the day of
1644    /// the year.
1645    ///
1646    /// This will also return an error when there is a weekday component
1647    /// set to a value inconsistent with the date returned.
1648    ///
1649    /// It's okay if there are more units than are needed to construct a civil
1650    /// datetime. For example, if this broken down time contains a civil time,
1651    /// then it won't prevent a conversion to a civil date.
1652    ///
1653    /// # Example
1654    ///
1655    /// This example shows how to parse a civil date from a broken down time:
1656    ///
1657    /// ```
1658    /// use jiff::fmt::strtime;
1659    ///
1660    /// let date = strtime::parse("%m/%d/%y", "7/14/24")?.to_date()?;
1661    /// assert_eq!(date.to_string(), "2024-07-14");
1662    ///
1663    /// # Ok::<(), Box<dyn std::error::Error>>(())
1664    /// ```
1665    #[inline]
1666    pub fn to_date(&self) -> Result<Date, Error> {
1667        #[cold]
1668        #[inline(never)]
1669        fn to_date(tm: &BrokenDownTime) -> Result<Date, Error> {
1670            let Some(year) = tm.year else {
1671                // The Gregorian year and ISO week year may be parsed
1672                // separately. That is, they are two different fields. So if
1673                // the Gregorian year is absent, we might still have an ISO
1674                // 8601 week date.
1675                if let Some(date) = tm.to_date_from_iso()? {
1676                    return Ok(date);
1677                }
1678                return Err(Error::from(E::RequiredYearForDate));
1679            };
1680            let mut date = tm.to_date_from_gregorian(year)?;
1681            if date.is_none() {
1682                date = tm.to_date_from_iso()?;
1683            }
1684            if date.is_none() {
1685                date = tm.to_date_from_day_of_year(year)?;
1686            }
1687            if date.is_none() {
1688                date = tm.to_date_from_week_sun(year)?;
1689            }
1690            if date.is_none() {
1691                date = tm.to_date_from_week_mon(year)?;
1692            }
1693            let Some(date) = date else {
1694                return Err(Error::from(E::RequiredSomeDayForDate));
1695            };
1696            if let Some(weekday) = tm.weekday {
1697                if weekday != date.weekday() {
1698                    return Err(Error::from(E::MismatchWeekday {
1699                        parsed: weekday,
1700                        got: date.weekday(),
1701                    }));
1702                }
1703            }
1704            Ok(date)
1705        }
1706
1707        // The common case is a simple Gregorian date.
1708        // We put the rest behind a non-inlineable function
1709        // to avoid code bloat for very uncommon cases.
1710        let (Some(year), Some(month), Some(day)) =
1711            (self.year, self.month, self.day)
1712        else {
1713            return to_date(self);
1714        };
1715        let date =
1716            Date::new_ranged(year, month, day).context(E::InvalidDate)?;
1717        if let Some(weekday) = self.weekday {
1718            if weekday != date.weekday() {
1719                return Err(Error::from(E::MismatchWeekday {
1720                    parsed: weekday,
1721                    got: date.weekday(),
1722                }));
1723            }
1724        }
1725        Ok(date)
1726    }
1727
1728    #[inline]
1729    fn to_date_from_gregorian(
1730        &self,
1731        year: t::Year,
1732    ) -> Result<Option<Date>, Error> {
1733        let (Some(month), Some(day)) = (self.month, self.day) else {
1734            return Ok(None);
1735        };
1736        Ok(Some(Date::new_ranged(year, month, day).context(E::InvalidDate)?))
1737    }
1738
1739    #[inline]
1740    fn to_date_from_day_of_year(
1741        &self,
1742        year: t::Year,
1743    ) -> Result<Option<Date>, Error> {
1744        let Some(doy) = self.day_of_year else { return Ok(None) };
1745        Ok(Some({
1746            let first =
1747                Date::new_ranged(year, C(1).rinto(), C(1).rinto()).unwrap();
1748            first
1749                .with()
1750                .day_of_year(doy.get())
1751                .build()
1752                .context(E::InvalidDate)?
1753        }))
1754    }
1755
1756    #[inline]
1757    fn to_date_from_iso(&self) -> Result<Option<Date>, Error> {
1758        let (Some(y), Some(w), Some(d)) =
1759            (self.iso_week_year, self.iso_week, self.weekday)
1760        else {
1761            return Ok(None);
1762        };
1763        let wd =
1764            ISOWeekDate::new_ranged(y, w, d).context(E::InvalidISOWeekDate)?;
1765        Ok(Some(wd.date()))
1766    }
1767
1768    #[inline]
1769    fn to_date_from_week_sun(
1770        &self,
1771        year: t::Year,
1772    ) -> Result<Option<Date>, Error> {
1773        let (Some(week), Some(weekday)) = (self.week_sun, self.weekday) else {
1774            return Ok(None);
1775        };
1776        let week = i16::from(week);
1777        let wday = i16::from(weekday.to_sunday_zero_offset());
1778        let first_of_year = Date::new_ranged(year, C(1).rinto(), C(1).rinto())
1779            .context(E::InvalidDate)?;
1780        let first_sunday = first_of_year
1781            .nth_weekday_of_month(1, Weekday::Sunday)
1782            .map(|d| d.day_of_year())
1783            .context(E::InvalidDate)?;
1784        let doy = if week == 0 {
1785            let days_before_first_sunday = 7 - wday;
1786            let doy = first_sunday
1787                .checked_sub(days_before_first_sunday)
1788                .ok_or(E::InvalidWeekdaySunday { got: weekday })?;
1789            if doy == 0 {
1790                return Err(Error::from(E::InvalidWeekdaySunday {
1791                    got: weekday,
1792                }));
1793            }
1794            doy
1795        } else {
1796            let days_since_first_sunday = (week - 1) * 7 + wday;
1797            let doy = first_sunday + days_since_first_sunday;
1798            doy
1799        };
1800        let date = first_of_year
1801            .with()
1802            .day_of_year(doy)
1803            .build()
1804            .context(E::InvalidDate)?;
1805        Ok(Some(date))
1806    }
1807
1808    #[inline]
1809    fn to_date_from_week_mon(
1810        &self,
1811        year: t::Year,
1812    ) -> Result<Option<Date>, Error> {
1813        let (Some(week), Some(weekday)) = (self.week_mon, self.weekday) else {
1814            return Ok(None);
1815        };
1816        let week = i16::from(week);
1817        let wday = i16::from(weekday.to_monday_zero_offset());
1818        let first_of_year = Date::new_ranged(year, C(1).rinto(), C(1).rinto())
1819            .context(E::InvalidDate)?;
1820        let first_monday = first_of_year
1821            .nth_weekday_of_month(1, Weekday::Monday)
1822            .map(|d| d.day_of_year())
1823            .context(E::InvalidDate)?;
1824        let doy = if week == 0 {
1825            let days_before_first_monday = 7 - wday;
1826            let doy = first_monday
1827                .checked_sub(days_before_first_monday)
1828                .ok_or(E::InvalidWeekdayMonday { got: weekday })?;
1829            if doy == 0 {
1830                return Err(Error::from(E::InvalidWeekdayMonday {
1831                    got: weekday,
1832                }));
1833            }
1834            doy
1835        } else {
1836            let days_since_first_monday = (week - 1) * 7 + wday;
1837            let doy = first_monday + days_since_first_monday;
1838            doy
1839        };
1840        let date = first_of_year
1841            .with()
1842            .day_of_year(doy)
1843            .build()
1844            .context(E::InvalidDate)?;
1845        Ok(Some(date))
1846    }
1847
1848    /// Extracts a civil time from this broken down time.
1849    ///
1850    /// # Errors
1851    ///
1852    /// This returns an error if there weren't enough components to construct
1853    /// a civil time. Interestingly, this succeeds if there are no time units,
1854    /// since this will assume an absent time is midnight. However, this can
1855    /// still error when, for example, there are minutes but no hours.
1856    ///
1857    /// It's okay if there are more units than are needed to construct a civil
1858    /// time. For example, if this broken down time contains a date, then it
1859    /// won't prevent a conversion to a civil time.
1860    ///
1861    /// # Example
1862    ///
1863    /// This example shows how to parse a civil time from a broken down
1864    /// time:
1865    ///
1866    /// ```
1867    /// use jiff::fmt::strtime;
1868    ///
1869    /// let time = strtime::parse("%H:%M:%S", "21:14:59")?.to_time()?;
1870    /// assert_eq!(time.to_string(), "21:14:59");
1871    ///
1872    /// # Ok::<(), Box<dyn std::error::Error>>(())
1873    /// ```
1874    ///
1875    /// # Example: time defaults to midnight
1876    ///
1877    /// Since time defaults to midnight, one can parse an empty input string
1878    /// with an empty format string and still extract a `Time`:
1879    ///
1880    /// ```
1881    /// use jiff::fmt::strtime;
1882    ///
1883    /// let time = strtime::parse("", "")?.to_time()?;
1884    /// assert_eq!(time.to_string(), "00:00:00");
1885    ///
1886    /// # Ok::<(), Box<dyn std::error::Error>>(())
1887    /// ```
1888    ///
1889    /// # Example: invalid time
1890    ///
1891    /// Other than using illegal values (like `24` for hours), if lower units
1892    /// are parsed without higher units, then this results in an error:
1893    ///
1894    /// ```
1895    /// use jiff::fmt::strtime;
1896    ///
1897    /// assert!(strtime::parse("%M:%S", "15:36")?.to_time().is_err());
1898    ///
1899    /// # Ok::<(), Box<dyn std::error::Error>>(())
1900    /// ```
1901    ///
1902    /// # Example: invalid date
1903    ///
1904    /// Since validation of a date is only done when a date is requested, it is
1905    /// actually possible to parse an invalid date and extract the time without
1906    /// an error occurring:
1907    ///
1908    /// ```
1909    /// use jiff::fmt::strtime;
1910    ///
1911    /// // 31 is a legal day value, but not for June. However, this is
1912    /// // not validated unless you ask for a `Date` from the parsed
1913    /// // `BrokenDownTime`. Most other higher level accessors on this
1914    /// // type need to create a date, but this routine does not. So
1915    /// // asking for only a `time` will circumvent date validation!
1916    /// let tm = strtime::parse("%Y-%m-%d %H:%M:%S", "2024-06-31 21:14:59")?;
1917    /// let time = tm.to_time()?;
1918    /// assert_eq!(time.to_string(), "21:14:59");
1919    ///
1920    /// # Ok::<(), Box<dyn std::error::Error>>(())
1921    /// ```
1922    #[inline]
1923    pub fn to_time(&self) -> Result<Time, Error> {
1924        let Some(hour) = self.hour_ranged() else {
1925            if self.minute.is_some() {
1926                return Err(Error::from(E::MissingTimeHourForMinute));
1927            }
1928            if self.second.is_some() {
1929                return Err(Error::from(E::MissingTimeHourForSecond));
1930            }
1931            if self.subsec.is_some() {
1932                return Err(Error::from(E::MissingTimeHourForFractional));
1933            }
1934            return Ok(Time::midnight());
1935        };
1936        let Some(minute) = self.minute else {
1937            if self.second.is_some() {
1938                return Err(Error::from(E::MissingTimeMinuteForSecond));
1939            }
1940            if self.subsec.is_some() {
1941                return Err(Error::from(E::MissingTimeMinuteForFractional));
1942            }
1943            return Ok(Time::new_ranged(hour, C(0), C(0), C(0)));
1944        };
1945        let Some(second) = self.second else {
1946            if self.subsec.is_some() {
1947                return Err(Error::from(E::MissingTimeSecondForFractional));
1948            }
1949            return Ok(Time::new_ranged(hour, minute, C(0), C(0)));
1950        };
1951        let Some(subsec) = self.subsec else {
1952            return Ok(Time::new_ranged(hour, minute, second, C(0)));
1953        };
1954        Ok(Time::new_ranged(hour, minute, second, subsec))
1955    }
1956
1957    /// Returns the parsed year, if available.
1958    ///
1959    /// This is also set when a 2 digit year is parsed. (But that's limited to
1960    /// the years 1969 to 2068, inclusive.)
1961    ///
1962    /// # Example
1963    ///
1964    /// This shows how to parse just a year:
1965    ///
1966    /// ```
1967    /// use jiff::fmt::strtime::BrokenDownTime;
1968    ///
1969    /// let tm = BrokenDownTime::parse("%Y", "2024")?;
1970    /// assert_eq!(tm.year(), Some(2024));
1971    ///
1972    /// # Ok::<(), Box<dyn std::error::Error>>(())
1973    /// ```
1974    ///
1975    /// And 2-digit years are supported too:
1976    ///
1977    /// ```
1978    /// use jiff::fmt::strtime::BrokenDownTime;
1979    ///
1980    /// let tm = BrokenDownTime::parse("%y", "24")?;
1981    /// assert_eq!(tm.year(), Some(2024));
1982    /// let tm = BrokenDownTime::parse("%y", "00")?;
1983    /// assert_eq!(tm.year(), Some(2000));
1984    /// let tm = BrokenDownTime::parse("%y", "69")?;
1985    /// assert_eq!(tm.year(), Some(1969));
1986    ///
1987    /// // 2-digit years have limited range. They must
1988    /// // be in the range 0-99.
1989    /// assert!(BrokenDownTime::parse("%y", "2024").is_err());
1990    ///
1991    /// # Ok::<(), Box<dyn std::error::Error>>(())
1992    /// ```
1993    #[inline]
1994    pub fn year(&self) -> Option<i16> {
1995        self.year.map(|x| x.get())
1996    }
1997
1998    /// Returns the parsed month, if available.
1999    ///
2000    /// # Example
2001    ///
2002    /// This shows a few different ways of parsing just a month:
2003    ///
2004    /// ```
2005    /// use jiff::fmt::strtime::BrokenDownTime;
2006    ///
2007    /// let tm = BrokenDownTime::parse("%m", "12")?;
2008    /// assert_eq!(tm.month(), Some(12));
2009    ///
2010    /// let tm = BrokenDownTime::parse("%B", "December")?;
2011    /// assert_eq!(tm.month(), Some(12));
2012    ///
2013    /// let tm = BrokenDownTime::parse("%b", "Dec")?;
2014    /// assert_eq!(tm.month(), Some(12));
2015    ///
2016    /// # Ok::<(), Box<dyn std::error::Error>>(())
2017    /// ```
2018    #[inline]
2019    pub fn month(&self) -> Option<i8> {
2020        self.month.map(|x| x.get())
2021    }
2022
2023    /// Returns the parsed day, if available.
2024    ///
2025    /// # Example
2026    ///
2027    /// This shows how to parse the day of the month:
2028    ///
2029    /// ```
2030    /// use jiff::fmt::strtime::BrokenDownTime;
2031    ///
2032    /// let tm = BrokenDownTime::parse("%d", "5")?;
2033    /// assert_eq!(tm.day(), Some(5));
2034    ///
2035    /// let tm = BrokenDownTime::parse("%d", "05")?;
2036    /// assert_eq!(tm.day(), Some(5));
2037    ///
2038    /// let tm = BrokenDownTime::parse("%03d", "005")?;
2039    /// assert_eq!(tm.day(), Some(5));
2040    ///
2041    /// // Parsing a day only works for all possible legal
2042    /// // values, even if, e.g., 31 isn't valid for all
2043    /// // possible year/month combinations.
2044    /// let tm = BrokenDownTime::parse("%d", "31")?;
2045    /// assert_eq!(tm.day(), Some(31));
2046    /// // This is true even if you're parsing a full date:
2047    /// let tm = BrokenDownTime::parse("%Y-%m-%d", "2024-04-31")?;
2048    /// assert_eq!(tm.day(), Some(31));
2049    /// // An error only occurs when you try to extract a date:
2050    /// assert!(tm.to_date().is_err());
2051    /// // But parsing a value that is always illegal will
2052    /// // result in an error:
2053    /// assert!(BrokenDownTime::parse("%d", "32").is_err());
2054    ///
2055    /// # Ok::<(), Box<dyn std::error::Error>>(())
2056    /// ```
2057    #[inline]
2058    pub fn day(&self) -> Option<i8> {
2059        self.day.map(|x| x.get())
2060    }
2061
2062    /// Returns the parsed day of the year (1-366), if available.
2063    ///
2064    /// # Example
2065    ///
2066    /// This shows how to parse the day of the year:
2067    ///
2068    /// ```
2069    /// use jiff::fmt::strtime::BrokenDownTime;
2070    ///
2071    /// let tm = BrokenDownTime::parse("%j", "5")?;
2072    /// assert_eq!(tm.day_of_year(), Some(5));
2073    /// assert_eq!(tm.to_string("%j")?, "005");
2074    /// assert_eq!(tm.to_string("%-j")?, "5");
2075    ///
2076    /// // Parsing the day of the year works for all possible legal
2077    /// // values, even if, e.g., 366 isn't valid for all possible
2078    /// // year/month combinations.
2079    /// let tm = BrokenDownTime::parse("%j", "366")?;
2080    /// assert_eq!(tm.day_of_year(), Some(366));
2081    /// // This is true even if you're parsing a year:
2082    /// let tm = BrokenDownTime::parse("%Y/%j", "2023/366")?;
2083    /// assert_eq!(tm.day_of_year(), Some(366));
2084    /// // An error only occurs when you try to extract a date:
2085    /// assert_eq!(
2086    ///     tm.to_date().unwrap_err().to_string(),
2087    ///     "invalid date: number of days for `2023` is invalid, \
2088    ///      must be in range `1..=365`",
2089    /// );
2090    /// // But parsing a value that is always illegal will
2091    /// // result in an error:
2092    /// assert!(BrokenDownTime::parse("%j", "0").is_err());
2093    /// assert!(BrokenDownTime::parse("%j", "367").is_err());
2094    ///
2095    /// # Ok::<(), Box<dyn std::error::Error>>(())
2096    /// ```
2097    ///
2098    /// # Example: extract a [`Date`]
2099    ///
2100    /// This example shows how parsing a year and a day of the year enables
2101    /// the extraction of a date:
2102    ///
2103    /// ```
2104    /// use jiff::{civil::date, fmt::strtime::BrokenDownTime};
2105    ///
2106    /// let tm = BrokenDownTime::parse("%Y-%j", "2024-60")?;
2107    /// assert_eq!(tm.to_date()?, date(2024, 2, 29));
2108    ///
2109    /// # Ok::<(), Box<dyn std::error::Error>>(())
2110    /// ```
2111    ///
2112    /// When all of `%m`, `%d` and `%j` are used, then `%m` and `%d` take
2113    /// priority over `%j` when extracting a `Date` from a `BrokenDownTime`.
2114    /// However, `%j` is still parsed and accessible:
2115    ///
2116    /// ```
2117    /// use jiff::{civil::date, fmt::strtime::BrokenDownTime};
2118    ///
2119    /// let tm = BrokenDownTime::parse(
2120    ///     "%Y-%m-%d (day of year: %j)",
2121    ///     "2024-02-29 (day of year: 1)",
2122    /// )?;
2123    /// assert_eq!(tm.to_date()?, date(2024, 2, 29));
2124    /// assert_eq!(tm.day_of_year(), Some(1));
2125    ///
2126    /// # Ok::<(), Box<dyn std::error::Error>>(())
2127    /// ```
2128    #[inline]
2129    pub fn day_of_year(&self) -> Option<i16> {
2130        self.day_of_year.map(|x| x.get())
2131    }
2132
2133    /// Returns the parsed ISO 8601 week-based year, if available.
2134    ///
2135    /// This is also set when a 2 digit ISO 8601 week-based year is parsed.
2136    /// (But that's limited to the years 1969 to 2068, inclusive.)
2137    ///
2138    /// # Example
2139    ///
2140    /// This shows how to parse just an ISO 8601 week-based year:
2141    ///
2142    /// ```
2143    /// use jiff::fmt::strtime::BrokenDownTime;
2144    ///
2145    /// let tm = BrokenDownTime::parse("%G", "2024")?;
2146    /// assert_eq!(tm.iso_week_year(), Some(2024));
2147    ///
2148    /// # Ok::<(), Box<dyn std::error::Error>>(())
2149    /// ```
2150    ///
2151    /// And 2-digit years are supported too:
2152    ///
2153    /// ```
2154    /// use jiff::fmt::strtime::BrokenDownTime;
2155    ///
2156    /// let tm = BrokenDownTime::parse("%g", "24")?;
2157    /// assert_eq!(tm.iso_week_year(), Some(2024));
2158    /// let tm = BrokenDownTime::parse("%g", "00")?;
2159    /// assert_eq!(tm.iso_week_year(), Some(2000));
2160    /// let tm = BrokenDownTime::parse("%g", "69")?;
2161    /// assert_eq!(tm.iso_week_year(), Some(1969));
2162    ///
2163    /// // 2-digit years have limited range. They must
2164    /// // be in the range 0-99.
2165    /// assert!(BrokenDownTime::parse("%g", "2024").is_err());
2166    ///
2167    /// # Ok::<(), Box<dyn std::error::Error>>(())
2168    /// ```
2169    #[inline]
2170    pub fn iso_week_year(&self) -> Option<i16> {
2171        self.iso_week_year.map(|x| x.get())
2172    }
2173
2174    /// Returns the parsed ISO 8601 week-based number, if available.
2175    ///
2176    /// The week number is guaranteed to be in the range `1..53`. Week `1` is
2177    /// the first week of the year to contain 4 days.
2178    ///
2179    ///
2180    /// # Example
2181    ///
2182    /// This shows how to parse just an ISO 8601 week-based dates:
2183    ///
2184    /// ```
2185    /// use jiff::{civil::{Weekday, date}, fmt::strtime::BrokenDownTime};
2186    ///
2187    /// let tm = BrokenDownTime::parse("%G-W%V-%u", "2020-W01-1")?;
2188    /// assert_eq!(tm.iso_week_year(), Some(2020));
2189    /// assert_eq!(tm.iso_week(), Some(1));
2190    /// assert_eq!(tm.weekday(), Some(Weekday::Monday));
2191    /// assert_eq!(tm.to_date()?, date(2019, 12, 30));
2192    ///
2193    /// # Ok::<(), Box<dyn std::error::Error>>(())
2194    /// ```
2195    #[inline]
2196    pub fn iso_week(&self) -> Option<i8> {
2197        self.iso_week.map(|x| x.get())
2198    }
2199
2200    /// Returns the Sunday based week number.
2201    ///
2202    /// The week number returned is always in the range `0..=53`. Week `1`
2203    /// begins on the first Sunday of the year. Any days in the year prior to
2204    /// week `1` are in week `0`.
2205    ///
2206    /// # Example
2207    ///
2208    /// ```
2209    /// use jiff::{civil::{Weekday, date}, fmt::strtime::BrokenDownTime};
2210    ///
2211    /// let tm = BrokenDownTime::parse("%Y-%U-%w", "2025-01-0")?;
2212    /// assert_eq!(tm.year(), Some(2025));
2213    /// assert_eq!(tm.sunday_based_week(), Some(1));
2214    /// assert_eq!(tm.weekday(), Some(Weekday::Sunday));
2215    /// assert_eq!(tm.to_date()?, date(2025, 1, 5));
2216    ///
2217    /// # Ok::<(), Box<dyn std::error::Error>>(())
2218    /// ```
2219    #[inline]
2220    pub fn sunday_based_week(&self) -> Option<i8> {
2221        self.week_sun.map(|x| x.get())
2222    }
2223
2224    /// Returns the Monday based week number.
2225    ///
2226    /// The week number returned is always in the range `0..=53`. Week `1`
2227    /// begins on the first Monday of the year. Any days in the year prior to
2228    /// week `1` are in week `0`.
2229    ///
2230    /// # Example
2231    ///
2232    /// ```
2233    /// use jiff::{civil::{Weekday, date}, fmt::strtime::BrokenDownTime};
2234    ///
2235    /// let tm = BrokenDownTime::parse("%Y-%U-%w", "2025-01-1")?;
2236    /// assert_eq!(tm.year(), Some(2025));
2237    /// assert_eq!(tm.sunday_based_week(), Some(1));
2238    /// assert_eq!(tm.weekday(), Some(Weekday::Monday));
2239    /// assert_eq!(tm.to_date()?, date(2025, 1, 6));
2240    ///
2241    /// # Ok::<(), Box<dyn std::error::Error>>(())
2242    /// ```
2243    #[inline]
2244    pub fn monday_based_week(&self) -> Option<i8> {
2245        self.week_mon.map(|x| x.get())
2246    }
2247
2248    /// Returns the parsed hour, if available.
2249    ///
2250    /// The hour returned incorporates [`BrokenDownTime::meridiem`] if it's
2251    /// set. That is, if the actual parsed hour value is `1` but the meridiem
2252    /// is `PM`, then the hour returned by this method will be `13`.
2253    ///
2254    /// # Example
2255    ///
2256    /// This shows a how to parse an hour:
2257    ///
2258    /// ```
2259    /// use jiff::fmt::strtime::BrokenDownTime;
2260    ///
2261    /// let tm = BrokenDownTime::parse("%H", "13")?;
2262    /// assert_eq!(tm.hour(), Some(13));
2263    ///
2264    /// // When parsing a 12-hour clock without a
2265    /// // meridiem, the hour value is as parsed.
2266    /// let tm = BrokenDownTime::parse("%I", "1")?;
2267    /// assert_eq!(tm.hour(), Some(1));
2268    ///
2269    /// // If a meridiem is parsed, then it is used
2270    /// // to calculate the correct hour value.
2271    /// let tm = BrokenDownTime::parse("%I%P", "1pm")?;
2272    /// assert_eq!(tm.hour(), Some(13));
2273    ///
2274    /// // This works even if the hour and meridiem are
2275    /// // inconsistent with each other:
2276    /// let tm = BrokenDownTime::parse("%H%P", "13am")?;
2277    /// assert_eq!(tm.hour(), Some(1));
2278    ///
2279    /// # Ok::<(), Box<dyn std::error::Error>>(())
2280    /// ```
2281    #[inline]
2282    pub fn hour(&self) -> Option<i8> {
2283        self.hour_ranged().map(|x| x.get())
2284    }
2285
2286    #[inline]
2287    fn hour_ranged(&self) -> Option<t::Hour> {
2288        self.hour
2289    }
2290
2291    /// Returns the parsed minute, if available.
2292    ///
2293    /// # Example
2294    ///
2295    /// This shows how to parse the minute:
2296    ///
2297    /// ```
2298    /// use jiff::fmt::strtime::BrokenDownTime;
2299    ///
2300    /// let tm = BrokenDownTime::parse("%M", "5")?;
2301    /// assert_eq!(tm.minute(), Some(5));
2302    ///
2303    /// # Ok::<(), Box<dyn std::error::Error>>(())
2304    /// ```
2305    #[inline]
2306    pub fn minute(&self) -> Option<i8> {
2307        self.minute.map(|x| x.get())
2308    }
2309
2310    /// Returns the parsed second, if available.
2311    ///
2312    /// # Example
2313    ///
2314    /// This shows how to parse the second:
2315    ///
2316    /// ```
2317    /// use jiff::fmt::strtime::BrokenDownTime;
2318    ///
2319    /// let tm = BrokenDownTime::parse("%S", "5")?;
2320    /// assert_eq!(tm.second(), Some(5));
2321    ///
2322    /// # Ok::<(), Box<dyn std::error::Error>>(())
2323    /// ```
2324    #[inline]
2325    pub fn second(&self) -> Option<i8> {
2326        self.second.map(|x| x.get())
2327    }
2328
2329    /// Returns the parsed subsecond nanosecond, if available.
2330    ///
2331    /// # Example
2332    ///
2333    /// This shows how to parse fractional seconds:
2334    ///
2335    /// ```
2336    /// use jiff::fmt::strtime::BrokenDownTime;
2337    ///
2338    /// let tm = BrokenDownTime::parse("%f", "123456")?;
2339    /// assert_eq!(tm.subsec_nanosecond(), Some(123_456_000));
2340    ///
2341    /// # Ok::<(), Box<dyn std::error::Error>>(())
2342    /// ```
2343    ///
2344    /// Note that when using `%.f`, the fractional component is optional!
2345    ///
2346    /// ```
2347    /// use jiff::fmt::strtime::BrokenDownTime;
2348    ///
2349    /// let tm = BrokenDownTime::parse("%S%.f", "1")?;
2350    /// assert_eq!(tm.second(), Some(1));
2351    /// assert_eq!(tm.subsec_nanosecond(), None);
2352    ///
2353    /// let tm = BrokenDownTime::parse("%S%.f", "1.789")?;
2354    /// assert_eq!(tm.second(), Some(1));
2355    /// assert_eq!(tm.subsec_nanosecond(), Some(789_000_000));
2356    ///
2357    /// # Ok::<(), Box<dyn std::error::Error>>(())
2358    /// ```
2359    #[inline]
2360    pub fn subsec_nanosecond(&self) -> Option<i32> {
2361        self.subsec.map(|x| x.get())
2362    }
2363
2364    /// Returns the parsed offset, if available.
2365    ///
2366    /// # Example
2367    ///
2368    /// This shows how to parse the offset:
2369    ///
2370    /// ```
2371    /// use jiff::{fmt::strtime::BrokenDownTime, tz::Offset};
2372    ///
2373    /// let tm = BrokenDownTime::parse("%z", "-0430")?;
2374    /// assert_eq!(
2375    ///     tm.offset(),
2376    ///     Some(Offset::from_seconds(-4 * 60 * 60 - 30 * 60).unwrap()),
2377    /// );
2378    /// let tm = BrokenDownTime::parse("%z", "-043059")?;
2379    /// assert_eq!(
2380    ///     tm.offset(),
2381    ///     Some(Offset::from_seconds(-4 * 60 * 60 - 30 * 60 - 59).unwrap()),
2382    /// );
2383    ///
2384    /// // Or, if you want colons:
2385    /// let tm = BrokenDownTime::parse("%:z", "-04:30")?;
2386    /// assert_eq!(
2387    ///     tm.offset(),
2388    ///     Some(Offset::from_seconds(-4 * 60 * 60 - 30 * 60).unwrap()),
2389    /// );
2390    ///
2391    /// # Ok::<(), Box<dyn std::error::Error>>(())
2392    /// ```
2393    #[inline]
2394    pub fn offset(&self) -> Option<Offset> {
2395        self.offset
2396    }
2397
2398    /// Returns the time zone IANA identifier, if available.
2399    ///
2400    /// Note that when `alloc` is disabled, this always returns `None`. (And
2401    /// there is no way to set it.)
2402    ///
2403    /// # Example
2404    ///
2405    /// This shows how to parse an IANA time zone identifier:
2406    ///
2407    /// ```
2408    /// use jiff::{fmt::strtime::BrokenDownTime, tz};
2409    ///
2410    /// let tm = BrokenDownTime::parse("%Q", "US/Eastern")?;
2411    /// assert_eq!(tm.iana_time_zone(), Some("US/Eastern"));
2412    /// assert_eq!(tm.offset(), None);
2413    ///
2414    /// // Note that %Q (and %:Q) also support parsing an offset
2415    /// // as a fallback. If that occurs, an IANA time zone
2416    /// // identifier is not available.
2417    /// let tm = BrokenDownTime::parse("%Q", "-0400")?;
2418    /// assert_eq!(tm.iana_time_zone(), None);
2419    /// assert_eq!(tm.offset(), Some(tz::offset(-4)));
2420    ///
2421    /// # Ok::<(), Box<dyn std::error::Error>>(())
2422    /// ```
2423    #[inline]
2424    pub fn iana_time_zone(&self) -> Option<&str> {
2425        #[cfg(feature = "alloc")]
2426        {
2427            self.iana.as_deref()
2428        }
2429        #[cfg(not(feature = "alloc"))]
2430        {
2431            None
2432        }
2433    }
2434
2435    /// Returns the parsed weekday, if available.
2436    ///
2437    /// # Example
2438    ///
2439    /// This shows a few different ways of parsing just a weekday:
2440    ///
2441    /// ```
2442    /// use jiff::{civil::Weekday, fmt::strtime::BrokenDownTime};
2443    ///
2444    /// let tm = BrokenDownTime::parse("%A", "Saturday")?;
2445    /// assert_eq!(tm.weekday(), Some(Weekday::Saturday));
2446    ///
2447    /// let tm = BrokenDownTime::parse("%a", "Sat")?;
2448    /// assert_eq!(tm.weekday(), Some(Weekday::Saturday));
2449    ///
2450    /// // A weekday is only available if it is explicitly parsed!
2451    /// let tm = BrokenDownTime::parse("%F", "2024-07-27")?;
2452    /// assert_eq!(tm.weekday(), None);
2453    /// // If you need a weekday derived from a parsed date, then:
2454    /// assert_eq!(tm.to_date()?.weekday(), Weekday::Saturday);
2455    ///
2456    /// # Ok::<(), Box<dyn std::error::Error>>(())
2457    /// ```
2458    ///
2459    /// Note that this will return the parsed weekday even if
2460    /// it's inconsistent with a parsed date:
2461    ///
2462    /// ```
2463    /// use jiff::{civil::{Weekday, date}, fmt::strtime::BrokenDownTime};
2464    ///
2465    /// let mut tm = BrokenDownTime::parse("%a, %F", "Wed, 2024-07-27")?;
2466    /// // 2024-07-27 is a Saturday, but Wednesday was parsed:
2467    /// assert_eq!(tm.weekday(), Some(Weekday::Wednesday));
2468    /// // An error only occurs when extracting a date:
2469    /// assert!(tm.to_date().is_err());
2470    /// // To skip the weekday, error checking, zero it out first:
2471    /// tm.set_weekday(None);
2472    /// assert_eq!(tm.to_date()?, date(2024, 7, 27));
2473    ///
2474    /// # Ok::<(), Box<dyn std::error::Error>>(())
2475    /// ```
2476    #[inline]
2477    pub fn weekday(&self) -> Option<Weekday> {
2478        self.weekday
2479    }
2480
2481    /// Returns the parsed meridiem, if available.
2482    ///
2483    /// When there is a conflict between the meridiem and the hour value, the
2484    /// meridiem takes precedence.
2485    ///
2486    /// # Example
2487    ///
2488    /// This shows a how to parse the meridiem:
2489    ///
2490    /// ```
2491    /// use jiff::fmt::strtime::{BrokenDownTime, Meridiem};
2492    ///
2493    /// let tm = BrokenDownTime::parse("%p", "AM")?;
2494    /// assert_eq!(tm.meridiem(), Some(Meridiem::AM));
2495    /// let tm = BrokenDownTime::parse("%P", "pm")?;
2496    /// assert_eq!(tm.meridiem(), Some(Meridiem::PM));
2497    ///
2498    /// // A meridiem takes precedence.
2499    /// let tm = BrokenDownTime::parse("%H%P", "13am")?;
2500    /// assert_eq!(tm.hour(), Some(1));
2501    /// assert_eq!(tm.meridiem(), Some(Meridiem::AM));
2502    ///
2503    /// # Ok::<(), Box<dyn std::error::Error>>(())
2504    /// ```
2505    #[inline]
2506    pub fn meridiem(&self) -> Option<Meridiem> {
2507        self.meridiem
2508    }
2509
2510    /// Returns the parsed timestamp, if available.
2511    ///
2512    /// Unlike [`BrokenDownTime::to_timestamp`], this only returns a timestamp
2513    /// that has been set explicitly via [`BrokenDownTime::set_timestamp`].
2514    /// For example, this occurs when parsing a `%s` conversion specifier.
2515    ///
2516    /// # Example
2517    ///
2518    /// This shows a how to parse the timestamp:
2519    ///
2520    /// ```
2521    /// use jiff::{fmt::strtime::BrokenDownTime, Timestamp};
2522    ///
2523    /// let tm = BrokenDownTime::parse("%s", "1760723100")?;
2524    /// assert_eq!(tm.timestamp(), Some(Timestamp::constant(1760723100, 0)));
2525    ///
2526    /// # Ok::<(), Box<dyn std::error::Error>>(())
2527    /// ```
2528    ///
2529    /// # Example: difference between `timestamp` and `to_timestamp`
2530    ///
2531    /// This shows how [`BrokenDownTime::to_timestamp`] will try to return
2532    /// a timestamp when one could be formed from other data, while
2533    /// [`BrokenDownTime::timestamp`] only returns a timestamp that has been
2534    /// explicitly set.
2535    ///
2536    /// ```
2537    /// use jiff::{fmt::strtime::BrokenDownTime, tz, Timestamp};
2538    ///
2539    /// let mut tm = BrokenDownTime::default();
2540    /// tm.set_year(Some(2025))?;
2541    /// tm.set_month(Some(10))?;
2542    /// tm.set_day(Some(17))?;
2543    /// tm.set_hour(Some(13))?;
2544    /// tm.set_minute(Some(45))?;
2545    /// tm.set_offset(Some(tz::offset(-4)));
2546    /// assert_eq!(tm.to_timestamp()?, Timestamp::constant(1760723100, 0));
2547    /// // No timestamp set!
2548    /// assert_eq!(tm.timestamp(), None);
2549    /// // A timestamp can be set, and it may not be consistent
2550    /// // with other data in `BrokenDownTime`.
2551    /// tm.set_timestamp(Some(Timestamp::UNIX_EPOCH));
2552    /// assert_eq!(tm.timestamp(), Some(Timestamp::UNIX_EPOCH));
2553    /// // And note that `BrokenDownTime::to_timestamp` will prefer
2554    /// // an explicitly set timestamp whenever possible.
2555    /// assert_eq!(tm.to_timestamp()?, Timestamp::UNIX_EPOCH);
2556    ///
2557    /// # Ok::<(), Box<dyn std::error::Error>>(())
2558    /// ```
2559    #[inline]
2560    pub fn timestamp(&self) -> Option<Timestamp> {
2561        self.timestamp
2562    }
2563
2564    /// Set the year on this broken down time.
2565    ///
2566    /// # Errors
2567    ///
2568    /// This returns an error if the given year is out of range.
2569    ///
2570    /// # Example
2571    ///
2572    /// ```
2573    /// use jiff::fmt::strtime::BrokenDownTime;
2574    ///
2575    /// let mut tm = BrokenDownTime::default();
2576    /// // out of range
2577    /// assert!(tm.set_year(Some(10_000)).is_err());
2578    /// tm.set_year(Some(2024))?;
2579    /// assert_eq!(tm.to_string("%Y")?, "2024");
2580    ///
2581    /// # Ok::<(), Box<dyn std::error::Error>>(())
2582    /// ```
2583    #[inline]
2584    pub fn set_year(&mut self, year: Option<i16>) -> Result<(), Error> {
2585        self.year = match year {
2586            None => None,
2587            Some(year) => Some(t::Year::try_new("year", year)?),
2588        };
2589        Ok(())
2590    }
2591
2592    /// Set the month on this broken down time.
2593    ///
2594    /// # Errors
2595    ///
2596    /// This returns an error if the given month is out of range.
2597    ///
2598    /// # Example
2599    ///
2600    /// ```
2601    /// use jiff::fmt::strtime::BrokenDownTime;
2602    ///
2603    /// let mut tm = BrokenDownTime::default();
2604    /// // out of range
2605    /// assert!(tm.set_month(Some(0)).is_err());
2606    /// tm.set_month(Some(12))?;
2607    /// assert_eq!(tm.to_string("%B")?, "December");
2608    ///
2609    /// # Ok::<(), Box<dyn std::error::Error>>(())
2610    /// ```
2611    #[inline]
2612    pub fn set_month(&mut self, month: Option<i8>) -> Result<(), Error> {
2613        self.month = match month {
2614            None => None,
2615            Some(month) => Some(t::Month::try_new("month", month)?),
2616        };
2617        Ok(())
2618    }
2619
2620    /// Set the day on this broken down time.
2621    ///
2622    /// # Errors
2623    ///
2624    /// This returns an error if the given day is out of range.
2625    ///
2626    /// Note that setting a day to a value that is legal in any context is
2627    /// always valid, even if it isn't valid for the year and month
2628    /// components already set.
2629    ///
2630    /// # Example
2631    ///
2632    /// ```
2633    /// use jiff::fmt::strtime::BrokenDownTime;
2634    ///
2635    /// let mut tm = BrokenDownTime::default();
2636    /// // out of range
2637    /// assert!(tm.set_day(Some(32)).is_err());
2638    /// tm.set_day(Some(31))?;
2639    /// assert_eq!(tm.to_string("%d")?, "31");
2640    ///
2641    /// // Works even if the resulting date is invalid.
2642    /// let mut tm = BrokenDownTime::default();
2643    /// tm.set_year(Some(2024))?;
2644    /// tm.set_month(Some(4))?;
2645    /// tm.set_day(Some(31))?; // April has 30 days, not 31
2646    /// assert_eq!(tm.to_string("%F")?, "2024-04-31");
2647    ///
2648    /// # Ok::<(), Box<dyn std::error::Error>>(())
2649    /// ```
2650    #[inline]
2651    pub fn set_day(&mut self, day: Option<i8>) -> Result<(), Error> {
2652        self.day = match day {
2653            None => None,
2654            Some(day) => Some(t::Day::try_new("day", day)?),
2655        };
2656        Ok(())
2657    }
2658
2659    /// Set the day of year on this broken down time.
2660    ///
2661    /// # Errors
2662    ///
2663    /// This returns an error if the given day is out of range.
2664    ///
2665    /// Note that setting a day to a value that is legal in any context
2666    /// is always valid, even if it isn't valid for the year, month and
2667    /// day-of-month components already set.
2668    ///
2669    /// # Example
2670    ///
2671    /// ```
2672    /// use jiff::fmt::strtime::BrokenDownTime;
2673    ///
2674    /// let mut tm = BrokenDownTime::default();
2675    /// // out of range
2676    /// assert!(tm.set_day_of_year(Some(367)).is_err());
2677    /// tm.set_day_of_year(Some(31))?;
2678    /// assert_eq!(tm.to_string("%j")?, "031");
2679    ///
2680    /// // Works even if the resulting date is invalid.
2681    /// let mut tm = BrokenDownTime::default();
2682    /// tm.set_year(Some(2023))?;
2683    /// tm.set_day_of_year(Some(366))?; // 2023 wasn't a leap year
2684    /// assert_eq!(tm.to_string("%Y/%j")?, "2023/366");
2685    ///
2686    /// # Ok::<(), Box<dyn std::error::Error>>(())
2687    /// ```
2688    #[inline]
2689    pub fn set_day_of_year(&mut self, day: Option<i16>) -> Result<(), Error> {
2690        self.day_of_year = match day {
2691            None => None,
2692            Some(day) => Some(t::DayOfYear::try_new("day-of-year", day)?),
2693        };
2694        Ok(())
2695    }
2696
2697    /// Set the ISO 8601 week-based year on this broken down time.
2698    ///
2699    /// # Errors
2700    ///
2701    /// This returns an error if the given year is out of range.
2702    ///
2703    /// # Example
2704    ///
2705    /// ```
2706    /// use jiff::fmt::strtime::BrokenDownTime;
2707    ///
2708    /// let mut tm = BrokenDownTime::default();
2709    /// // out of range
2710    /// assert!(tm.set_iso_week_year(Some(10_000)).is_err());
2711    /// tm.set_iso_week_year(Some(2024))?;
2712    /// assert_eq!(tm.to_string("%G")?, "2024");
2713    ///
2714    /// # Ok::<(), Box<dyn std::error::Error>>(())
2715    /// ```
2716    #[inline]
2717    pub fn set_iso_week_year(
2718        &mut self,
2719        year: Option<i16>,
2720    ) -> Result<(), Error> {
2721        self.iso_week_year = match year {
2722            None => None,
2723            Some(year) => Some(t::ISOYear::try_new("year", year)?),
2724        };
2725        Ok(())
2726    }
2727
2728    /// Set the ISO 8601 week-based number on this broken down time.
2729    ///
2730    /// The week number must be in the range `1..53`. Week `1` is
2731    /// the first week of the year to contain 4 days.
2732    ///
2733    /// # Errors
2734    ///
2735    /// This returns an error if the given week number is out of range.
2736    ///
2737    /// # Example
2738    ///
2739    /// ```
2740    /// use jiff::{civil::Weekday, fmt::strtime::BrokenDownTime};
2741    ///
2742    /// let mut tm = BrokenDownTime::default();
2743    /// // out of range
2744    /// assert!(tm.set_iso_week(Some(0)).is_err());
2745    /// // out of range
2746    /// assert!(tm.set_iso_week(Some(54)).is_err());
2747    ///
2748    /// tm.set_iso_week_year(Some(2020))?;
2749    /// tm.set_iso_week(Some(1))?;
2750    /// tm.set_weekday(Some(Weekday::Monday));
2751    /// assert_eq!(tm.to_string("%G-W%V-%u")?, "2020-W01-1");
2752    /// assert_eq!(tm.to_string("%F")?, "2019-12-30");
2753    ///
2754    /// # Ok::<(), Box<dyn std::error::Error>>(())
2755    /// ```
2756    #[inline]
2757    pub fn set_iso_week(
2758        &mut self,
2759        week_number: Option<i8>,
2760    ) -> Result<(), Error> {
2761        self.iso_week = match week_number {
2762            None => None,
2763            Some(wk) => Some(t::ISOWeek::try_new("week-number", wk)?),
2764        };
2765        Ok(())
2766    }
2767
2768    /// Set the Sunday based week number.
2769    ///
2770    /// The week number returned is always in the range `0..=53`. Week `1`
2771    /// begins on the first Sunday of the year. Any days in the year prior to
2772    /// week `1` are in week `0`.
2773    ///
2774    /// # Example
2775    ///
2776    /// ```
2777    /// use jiff::fmt::strtime::BrokenDownTime;
2778    ///
2779    /// let mut tm = BrokenDownTime::default();
2780    /// // out of range
2781    /// assert!(tm.set_sunday_based_week(Some(56)).is_err());
2782    /// tm.set_sunday_based_week(Some(9))?;
2783    /// assert_eq!(tm.to_string("%U")?, "09");
2784    ///
2785    /// # Ok::<(), Box<dyn std::error::Error>>(())
2786    /// ```
2787    #[inline]
2788    pub fn set_sunday_based_week(
2789        &mut self,
2790        week_number: Option<i8>,
2791    ) -> Result<(), Error> {
2792        self.week_sun = match week_number {
2793            None => None,
2794            Some(wk) => Some(t::WeekNum::try_new("week-number", wk)?),
2795        };
2796        Ok(())
2797    }
2798
2799    /// Set the Monday based week number.
2800    ///
2801    /// The week number returned is always in the range `0..=53`. Week `1`
2802    /// begins on the first Monday of the year. Any days in the year prior to
2803    /// week `1` are in week `0`.
2804    ///
2805    /// # Example
2806    ///
2807    /// ```
2808    /// use jiff::fmt::strtime::BrokenDownTime;
2809    ///
2810    /// let mut tm = BrokenDownTime::default();
2811    /// // out of range
2812    /// assert!(tm.set_monday_based_week(Some(56)).is_err());
2813    /// tm.set_monday_based_week(Some(9))?;
2814    /// assert_eq!(tm.to_string("%W")?, "09");
2815    ///
2816    /// # Ok::<(), Box<dyn std::error::Error>>(())
2817    /// ```
2818    #[inline]
2819    pub fn set_monday_based_week(
2820        &mut self,
2821        week_number: Option<i8>,
2822    ) -> Result<(), Error> {
2823        self.week_mon = match week_number {
2824            None => None,
2825            Some(wk) => Some(t::WeekNum::try_new("week-number", wk)?),
2826        };
2827        Ok(())
2828    }
2829
2830    /// Set the hour on this broken down time.
2831    ///
2832    /// # Errors
2833    ///
2834    /// This returns an error if the given hour is out of range.
2835    ///
2836    /// # Example
2837    ///
2838    /// ```
2839    /// use jiff::fmt::strtime::BrokenDownTime;
2840    ///
2841    /// let mut tm = BrokenDownTime::default();
2842    /// // out of range
2843    /// assert!(tm.set_hour(Some(24)).is_err());
2844    /// tm.set_hour(Some(0))?;
2845    /// assert_eq!(tm.to_string("%H")?, "00");
2846    /// assert_eq!(tm.to_string("%-H")?, "0");
2847    ///
2848    /// # Ok::<(), Box<dyn std::error::Error>>(())
2849    /// ```
2850    #[inline]
2851    pub fn set_hour(&mut self, hour: Option<i8>) -> Result<(), Error> {
2852        self.set_hour_ranged(match hour {
2853            None => None,
2854            Some(hour) => Some(t::Hour::try_new("hour", hour)?),
2855        });
2856        Ok(())
2857    }
2858
2859    #[inline]
2860    fn set_hour_ranged(&mut self, hour: Option<t::Hour>) {
2861        if let Some(meridiem) = self.meridiem {
2862            self.hour = hour.map(|hour| meridiem.adjust_hour(hour));
2863        } else {
2864            self.hour = hour;
2865        }
2866    }
2867
2868    /// Set the minute on this broken down time.
2869    ///
2870    /// # Errors
2871    ///
2872    /// This returns an error if the given minute is out of range.
2873    ///
2874    /// # Example
2875    ///
2876    /// ```
2877    /// use jiff::fmt::strtime::BrokenDownTime;
2878    ///
2879    /// let mut tm = BrokenDownTime::default();
2880    /// // out of range
2881    /// assert!(tm.set_minute(Some(60)).is_err());
2882    /// tm.set_minute(Some(59))?;
2883    /// assert_eq!(tm.to_string("%M")?, "59");
2884    /// assert_eq!(tm.to_string("%03M")?, "059");
2885    /// assert_eq!(tm.to_string("%_3M")?, " 59");
2886    ///
2887    /// # Ok::<(), Box<dyn std::error::Error>>(())
2888    /// ```
2889    #[inline]
2890    pub fn set_minute(&mut self, minute: Option<i8>) -> Result<(), Error> {
2891        self.minute = match minute {
2892            None => None,
2893            Some(minute) => Some(t::Minute::try_new("minute", minute)?),
2894        };
2895        Ok(())
2896    }
2897
2898    /// Set the second on this broken down time.
2899    ///
2900    /// # Errors
2901    ///
2902    /// This returns an error if the given second is out of range.
2903    ///
2904    /// Jiff does not support leap seconds, so the range of valid seconds is
2905    /// `0` to `59`, inclusive. Note though that when parsing, a parsed value
2906    /// of `60` is automatically constrained to `59`.
2907    ///
2908    /// # Example
2909    ///
2910    /// ```
2911    /// use jiff::fmt::strtime::BrokenDownTime;
2912    ///
2913    /// let mut tm = BrokenDownTime::default();
2914    /// // out of range
2915    /// assert!(tm.set_second(Some(60)).is_err());
2916    /// tm.set_second(Some(59))?;
2917    /// assert_eq!(tm.to_string("%S")?, "59");
2918    ///
2919    /// # Ok::<(), Box<dyn std::error::Error>>(())
2920    /// ```
2921    #[inline]
2922    pub fn set_second(&mut self, second: Option<i8>) -> Result<(), Error> {
2923        self.second = match second {
2924            None => None,
2925            Some(second) => Some(t::Second::try_new("second", second)?),
2926        };
2927        Ok(())
2928    }
2929
2930    /// Set the subsecond nanosecond on this broken down time.
2931    ///
2932    /// # Errors
2933    ///
2934    /// This returns an error if the given number of nanoseconds is out of
2935    /// range. It must be non-negative and less than 1 whole second.
2936    ///
2937    /// # Example
2938    ///
2939    /// ```
2940    /// use jiff::fmt::strtime::BrokenDownTime;
2941    ///
2942    /// let mut tm = BrokenDownTime::default();
2943    /// // out of range
2944    /// assert!(tm.set_subsec_nanosecond(Some(1_000_000_000)).is_err());
2945    /// tm.set_subsec_nanosecond(Some(123_000_000))?;
2946    /// assert_eq!(tm.to_string("%f")?, "123");
2947    /// assert_eq!(tm.to_string("%.6f")?, ".123000");
2948    ///
2949    /// # Ok::<(), Box<dyn std::error::Error>>(())
2950    /// ```
2951    #[inline]
2952    pub fn set_subsec_nanosecond(
2953        &mut self,
2954        subsec_nanosecond: Option<i32>,
2955    ) -> Result<(), Error> {
2956        self.subsec = match subsec_nanosecond {
2957            None => None,
2958            Some(subsec_nanosecond) => Some(t::SubsecNanosecond::try_new(
2959                "subsecond-nanosecond",
2960                subsec_nanosecond,
2961            )?),
2962        };
2963        Ok(())
2964    }
2965
2966    /// Set the time zone offset on this broken down time.
2967    ///
2968    /// This can be useful for setting the offset after parsing if the offset
2969    /// is known from the context or from some out-of-band information.
2970    ///
2971    /// Note that one can set any legal offset value, regardless of whether
2972    /// it's consistent with the IANA time zone identifier on this broken down
2973    /// time (if it's set). Similarly, setting the offset does not actually
2974    /// change any other value in this broken down time.
2975    ///
2976    /// # Example: setting the offset after parsing
2977    ///
2978    /// One use case for this routine is when parsing a datetime _without_
2979    /// an offset, but where one wants to set an offset based on the context.
2980    /// For example, while it's usually not correct to assume a datetime is
2981    /// in UTC, if you know it is, then you can parse it into a [`Timestamp`]
2982    /// like so:
2983    ///
2984    /// ```
2985    /// use jiff::{fmt::strtime::BrokenDownTime, tz::Offset};
2986    ///
2987    /// let mut tm = BrokenDownTime::parse(
2988    ///     "%Y-%m-%d at %H:%M:%S",
2989    ///     "1970-01-01 at 01:00:00",
2990    /// )?;
2991    /// tm.set_offset(Some(Offset::UTC));
2992    /// // Normally this would fail since the parse
2993    /// // itself doesn't include an offset. It only
2994    /// // works here because we explicitly set the
2995    /// // offset after parsing.
2996    /// assert_eq!(tm.to_timestamp()?.to_string(), "1970-01-01T01:00:00Z");
2997    ///
2998    /// # Ok::<(), Box<dyn std::error::Error>>(())
2999    /// ```
3000    ///
3001    /// # Example: setting the offset is not "smart"
3002    ///
3003    /// This example shows how setting the offset on an existing broken down
3004    /// time does not impact any other field, even if the result printed is
3005    /// non-sensical:
3006    ///
3007    /// ```
3008    /// use jiff::{civil::date, fmt::strtime::BrokenDownTime, tz};
3009    ///
3010    /// let zdt = date(2024, 8, 28).at(14, 56, 0, 0).in_tz("US/Eastern")?;
3011    /// let mut tm = BrokenDownTime::from(&zdt);
3012    /// tm.set_offset(Some(tz::offset(12)));
3013    /// assert_eq!(
3014    ///     tm.to_string("%Y-%m-%d at %H:%M:%S in %Q %:z")?,
3015    ///     "2024-08-28 at 14:56:00 in US/Eastern +12:00",
3016    /// );
3017    ///
3018    /// # Ok::<(), Box<dyn std::error::Error>>(())
3019    /// ```
3020    #[inline]
3021    pub fn set_offset(&mut self, offset: Option<Offset>) {
3022        self.offset = offset;
3023    }
3024
3025    /// Set the IANA time zone identifier on this broken down time.
3026    ///
3027    /// This can be useful for setting the time zone after parsing if the time
3028    /// zone is known from the context or from some out-of-band information.
3029    ///
3030    /// Note that one can set any string value, regardless of whether it's
3031    /// consistent with the offset on this broken down time (if it's set).
3032    /// Similarly, setting the IANA time zone identifier does not actually
3033    /// change any other value in this broken down time.
3034    ///
3035    /// # Example: setting the IANA time zone identifier after parsing
3036    ///
3037    /// One use case for this routine is when parsing a datetime _without_ a
3038    /// time zone, but where one wants to set a time zone based on the context.
3039    ///
3040    /// ```
3041    /// use jiff::{fmt::strtime::BrokenDownTime};
3042    ///
3043    /// let mut tm = BrokenDownTime::parse(
3044    ///     "%Y-%m-%d at %H:%M:%S",
3045    ///     "1970-01-01 at 01:00:00",
3046    /// )?;
3047    /// tm.set_iana_time_zone(Some(String::from("US/Eastern")));
3048    /// // Normally this would fail since the parse
3049    /// // itself doesn't include an offset or a time
3050    /// // zone. It only works here because we
3051    /// // explicitly set the time zone after parsing.
3052    /// assert_eq!(
3053    ///     tm.to_zoned()?.to_string(),
3054    ///     "1970-01-01T01:00:00-05:00[US/Eastern]",
3055    /// );
3056    ///
3057    /// # Ok::<(), Box<dyn std::error::Error>>(())
3058    /// ```
3059    ///
3060    /// # Example: setting the IANA time zone identifier is not "smart"
3061    ///
3062    /// This example shows how setting the IANA time zone identifier on an
3063    /// existing broken down time does not impact any other field, even if the
3064    /// result printed is non-sensical:
3065    ///
3066    /// ```
3067    /// use jiff::{civil::date, fmt::strtime::BrokenDownTime};
3068    ///
3069    /// let zdt = date(2024, 8, 28).at(14, 56, 0, 0).in_tz("US/Eastern")?;
3070    /// let mut tm = BrokenDownTime::from(&zdt);
3071    /// tm.set_iana_time_zone(Some(String::from("Australia/Tasmania")));
3072    /// assert_eq!(
3073    ///     tm.to_string("%Y-%m-%d at %H:%M:%S in %Q %:z")?,
3074    ///     "2024-08-28 at 14:56:00 in Australia/Tasmania -04:00",
3075    /// );
3076    ///
3077    /// // In fact, it's not even required that the string
3078    /// // given be a valid IANA time zone identifier!
3079    /// let mut tm = BrokenDownTime::from(&zdt);
3080    /// tm.set_iana_time_zone(Some(String::from("Clearly/Invalid")));
3081    /// assert_eq!(
3082    ///     tm.to_string("%Y-%m-%d at %H:%M:%S in %Q %:z")?,
3083    ///     "2024-08-28 at 14:56:00 in Clearly/Invalid -04:00",
3084    /// );
3085    ///
3086    /// # Ok::<(), Box<dyn std::error::Error>>(())
3087    /// ```
3088    #[cfg(feature = "alloc")]
3089    #[inline]
3090    pub fn set_iana_time_zone(&mut self, id: Option<alloc::string::String>) {
3091        self.iana = id;
3092    }
3093
3094    /// Set the weekday on this broken down time.
3095    ///
3096    /// # Example
3097    ///
3098    /// ```
3099    /// use jiff::{civil::Weekday, fmt::strtime::BrokenDownTime};
3100    ///
3101    /// let mut tm = BrokenDownTime::default();
3102    /// tm.set_weekday(Some(Weekday::Saturday));
3103    /// assert_eq!(tm.to_string("%A")?, "Saturday");
3104    /// assert_eq!(tm.to_string("%a")?, "Sat");
3105    /// assert_eq!(tm.to_string("%^a")?, "SAT");
3106    ///
3107    /// # Ok::<(), Box<dyn std::error::Error>>(())
3108    /// ```
3109    ///
3110    /// Note that one use case for this routine is to enable parsing of
3111    /// weekdays in datetime, but skip checking that the weekday is valid for
3112    /// the parsed date.
3113    ///
3114    /// ```
3115    /// use jiff::{civil::date, fmt::strtime::BrokenDownTime};
3116    ///
3117    /// let mut tm = BrokenDownTime::parse("%a, %F", "Wed, 2024-07-27")?;
3118    /// // 2024-07-27 was a Saturday, so asking for a date fails:
3119    /// assert!(tm.to_date().is_err());
3120    /// // But we can remove the weekday from our broken down time:
3121    /// tm.set_weekday(None);
3122    /// assert_eq!(tm.to_date()?, date(2024, 7, 27));
3123    ///
3124    /// # Ok::<(), Box<dyn std::error::Error>>(())
3125    /// ```
3126    ///
3127    /// The advantage of this approach is that it still ensures the parsed
3128    /// weekday is a valid weekday (for example, `Wat` will cause parsing to
3129    /// fail), but doesn't require it to be consistent with the date. This
3130    /// is useful for interacting with systems that don't do strict error
3131    /// checking.
3132    #[inline]
3133    pub fn set_weekday(&mut self, weekday: Option<Weekday>) {
3134        self.weekday = weekday;
3135    }
3136
3137    /// Set the meridiem (AM/PM). This is most useful when doing custom
3138    /// parsing that involves 12-hour time.
3139    ///
3140    /// When there is a conflict between the meridiem and the hour value, the
3141    /// meridiem takes precedence.
3142    ///
3143    /// # Example
3144    ///
3145    /// This shows how to set a meridiem and its impact on the hour value:
3146    ///
3147    /// ```
3148    /// use jiff::{fmt::strtime::{BrokenDownTime, Meridiem}};
3149    ///
3150    /// let mut tm = BrokenDownTime::default();
3151    /// tm.set_hour(Some(3))?;
3152    /// tm.set_meridiem(Some(Meridiem::PM));
3153    /// let time = tm.to_time()?;
3154    /// assert_eq!(time.hour(), 15); // 3:00 PM = 15:00 in 24-hour time
3155    ///
3156    /// # Ok::<(), Box<dyn std::error::Error>>(())
3157    /// ```
3158    ///
3159    /// This shows how setting a meridiem influences formatting:
3160    ///
3161    /// ```
3162    /// use jiff::{fmt::strtime::{BrokenDownTime, Meridiem}};
3163    ///
3164    /// let mut tm = BrokenDownTime::default();
3165    /// tm.set_hour(Some(3))?;
3166    /// tm.set_minute(Some(4))?;
3167    /// tm.set_second(Some(5))?;
3168    /// tm.set_meridiem(Some(Meridiem::PM));
3169    /// assert_eq!(tm.to_string("%T")?, "15:04:05");
3170    ///
3171    /// # Ok::<(), Box<dyn std::error::Error>>(())
3172    /// ```
3173    ///
3174    /// And this shows how a conflict between the hour and meridiem is
3175    /// handled. Notably, the set meridiem still applies.
3176    ///
3177    /// ```
3178    /// use jiff::{fmt::strtime::{BrokenDownTime, Meridiem}};
3179    ///
3180    /// let mut tm = BrokenDownTime::default();
3181    /// tm.set_hour(Some(13))?;
3182    /// tm.set_minute(Some(4))?;
3183    /// tm.set_second(Some(5))?;
3184    /// tm.set_meridiem(Some(Meridiem::AM));
3185    /// assert_eq!(tm.to_string("%T")?, "01:04:05");
3186    ///
3187    /// # Ok::<(), Box<dyn std::error::Error>>(())
3188    /// ```
3189    #[inline]
3190    pub fn set_meridiem(&mut self, meridiem: Option<Meridiem>) {
3191        if let Some(meridiem) = meridiem {
3192            self.hour = self.hour.map(|hour| meridiem.adjust_hour(hour));
3193        }
3194        self.meridiem = meridiem;
3195    }
3196
3197    /// Set an explicit timestamp for this `BrokenDownTime`.
3198    ///
3199    /// An explicitly set timestamp takes precedence when using higher
3200    /// level convenience accessors such as [`BrokenDownTime::to_timestamp`]
3201    /// and [`BrokenDownTime::to_zoned`].
3202    ///
3203    /// # Example
3204    ///
3205    /// This shows how [`BrokenDownTime::to_timestamp`] will try to return
3206    /// a timestamp when one could be formed from other data, while
3207    /// [`BrokenDownTime::timestamp`] only returns a timestamp that has been
3208    /// explicitly set.
3209    ///
3210    /// ```
3211    /// use jiff::{fmt::strtime::BrokenDownTime, tz, Timestamp};
3212    ///
3213    /// let mut tm = BrokenDownTime::default();
3214    /// tm.set_year(Some(2025))?;
3215    /// tm.set_month(Some(10))?;
3216    /// tm.set_day(Some(17))?;
3217    /// tm.set_hour(Some(13))?;
3218    /// tm.set_minute(Some(45))?;
3219    /// tm.set_offset(Some(tz::offset(-4)));
3220    /// assert_eq!(tm.to_timestamp()?, Timestamp::constant(1760723100, 0));
3221    /// // No timestamp set!
3222    /// assert_eq!(tm.timestamp(), None);
3223    /// // A timestamp can be set, and it may not be consistent
3224    /// // with other data in `BrokenDownTime`.
3225    /// tm.set_timestamp(Some(Timestamp::UNIX_EPOCH));
3226    /// assert_eq!(tm.timestamp(), Some(Timestamp::UNIX_EPOCH));
3227    /// // And note that `BrokenDownTime::to_timestamp` will prefer
3228    /// // an explicitly set timestamp whenever possible.
3229    /// assert_eq!(tm.to_timestamp()?, Timestamp::UNIX_EPOCH);
3230    ///
3231    /// # Ok::<(), Box<dyn std::error::Error>>(())
3232    /// ```
3233    #[inline]
3234    pub fn set_timestamp(&mut self, timestamp: Option<Timestamp>) {
3235        self.timestamp = timestamp;
3236    }
3237}
3238
3239impl<'a> From<&'a Zoned> for BrokenDownTime {
3240    fn from(zdt: &'a Zoned) -> BrokenDownTime {
3241        // let offset_info = zdt.time_zone().to_offset_info(zdt.timestamp());
3242        #[cfg(feature = "alloc")]
3243        let iana = {
3244            use alloc::string::ToString;
3245            zdt.time_zone().iana_name().map(|s| s.to_string())
3246        };
3247        BrokenDownTime {
3248            offset: Some(zdt.offset()),
3249            timestamp: Some(zdt.timestamp()),
3250            tz: Some(zdt.time_zone().clone()),
3251            #[cfg(feature = "alloc")]
3252            iana,
3253            ..BrokenDownTime::from(zdt.datetime())
3254        }
3255    }
3256}
3257
3258impl From<Timestamp> for BrokenDownTime {
3259    fn from(ts: Timestamp) -> BrokenDownTime {
3260        let dt = Offset::UTC.to_datetime(ts);
3261        BrokenDownTime {
3262            offset: Some(Offset::UTC),
3263            timestamp: Some(ts),
3264            ..BrokenDownTime::from(dt)
3265        }
3266    }
3267}
3268
3269impl From<DateTime> for BrokenDownTime {
3270    fn from(dt: DateTime) -> BrokenDownTime {
3271        let (d, t) = (dt.date(), dt.time());
3272        BrokenDownTime {
3273            year: Some(d.year_ranged()),
3274            month: Some(d.month_ranged()),
3275            day: Some(d.day_ranged()),
3276            hour: Some(t.hour_ranged()),
3277            minute: Some(t.minute_ranged()),
3278            second: Some(t.second_ranged()),
3279            subsec: Some(t.subsec_nanosecond_ranged()),
3280            meridiem: Some(Meridiem::from(t)),
3281            ..BrokenDownTime::default()
3282        }
3283    }
3284}
3285
3286impl From<Date> for BrokenDownTime {
3287    fn from(d: Date) -> BrokenDownTime {
3288        BrokenDownTime {
3289            year: Some(d.year_ranged()),
3290            month: Some(d.month_ranged()),
3291            day: Some(d.day_ranged()),
3292            ..BrokenDownTime::default()
3293        }
3294    }
3295}
3296
3297impl From<ISOWeekDate> for BrokenDownTime {
3298    fn from(wd: ISOWeekDate) -> BrokenDownTime {
3299        BrokenDownTime {
3300            iso_week_year: Some(wd.year_ranged()),
3301            iso_week: Some(wd.week_ranged()),
3302            weekday: Some(wd.weekday()),
3303            ..BrokenDownTime::default()
3304        }
3305    }
3306}
3307
3308impl From<Time> for BrokenDownTime {
3309    fn from(t: Time) -> BrokenDownTime {
3310        BrokenDownTime {
3311            hour: Some(t.hour_ranged()),
3312            minute: Some(t.minute_ranged()),
3313            second: Some(t.second_ranged()),
3314            subsec: Some(t.subsec_nanosecond_ranged()),
3315            meridiem: Some(Meridiem::from(t)),
3316            ..BrokenDownTime::default()
3317        }
3318    }
3319}
3320
3321/// A "lazy" implementation of `std::fmt::Display` for `strftime`.
3322///
3323/// Values of this type are created by the `strftime` methods on the various
3324/// datetime types in this crate. For example, [`Zoned::strftime`].
3325///
3326/// A `Display` captures the information needed from the datetime and waits to
3327/// do the actual formatting when this type's `std::fmt::Display` trait
3328/// implementation is actually used.
3329///
3330/// # Errors and panics
3331///
3332/// This trait implementation returns an error when the underlying formatting
3333/// can fail. Formatting can fail either because of an invalid format string,
3334/// or if formatting requires a field in `BrokenDownTime` to be set that isn't.
3335/// For example, trying to format a [`DateTime`] with the `%z` specifier will
3336/// fail because a `DateTime` has no time zone or offset information associated
3337/// with it.
3338///
3339/// Note though that the `std::fmt::Display` API doesn't support surfacing
3340/// arbitrary errors. All errors collapse into the unit `std::fmt::Error`
3341/// struct. To see the actual error, use [`BrokenDownTime::format`],
3342/// [`BrokenDownTime::to_string`] or [`strtime::format`](format()).
3343/// Unfortunately, the `std::fmt::Display` trait is used in many places where
3344/// there is no way to report errors other than panicking.
3345///
3346/// Therefore, only use this type if you know your formatting string is valid
3347/// and that the datetime type being formatted has all of the information
3348/// required by the format string. Moreover, the `strftime` implementation in
3349/// this crate is specifically designed to never error based on the specific
3350/// values. For example, even though `%y` can only _parse_ years in the
3351/// `1969-2068` range, it can format any valid year supported by Jiff.
3352///
3353/// # Example
3354///
3355/// This example shows how to format a zoned datetime using
3356/// [`Zoned::strftime`]:
3357///
3358/// ```
3359/// use jiff::civil::date;
3360///
3361/// let zdt = date(2024, 7, 15).at(16, 24, 59, 0).in_tz("America/New_York")?;
3362/// let string = zdt.strftime("%a, %-d %b %Y %T %z").to_string();
3363/// assert_eq!(string, "Mon, 15 Jul 2024 16:24:59 -0400");
3364///
3365/// # Ok::<(), Box<dyn std::error::Error>>(())
3366/// ```
3367///
3368/// Or use it directly when writing to something:
3369///
3370/// ```
3371/// use jiff::{civil::date, fmt::strtime};
3372///
3373/// let zdt = date(2024, 7, 15).at(16, 24, 59, 0).in_tz("America/New_York")?;
3374///
3375/// let string = format!("the date is: {}", zdt.strftime("%-m/%-d/%-Y"));
3376/// assert_eq!(string, "the date is: 7/15/2024");
3377///
3378/// # Ok::<(), Box<dyn std::error::Error>>(())
3379/// ```
3380pub struct Display<'f> {
3381    pub(crate) fmt: &'f [u8],
3382    pub(crate) tm: BrokenDownTime,
3383}
3384
3385impl<'f> core::fmt::Display for Display<'f> {
3386    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
3387        use crate::fmt::StdFmtWrite;
3388
3389        self.tm.format(self.fmt, StdFmtWrite(f)).map_err(|_| core::fmt::Error)
3390    }
3391}
3392
3393impl<'f> core::fmt::Debug for Display<'f> {
3394    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
3395        f.debug_struct("Display")
3396            .field("fmt", &escape::Bytes(self.fmt))
3397            .field("tm", &self.tm)
3398            .finish()
3399    }
3400}
3401
3402/// A label to disambiguate hours on a 12-hour clock.
3403///
3404/// This can be accessed on a [`BrokenDownTime`] via
3405/// [`BrokenDownTime::meridiem`].
3406#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
3407pub enum Meridiem {
3408    /// "ante meridiem" or "before midday."
3409    ///
3410    /// Specifically, this describes hours less than 12 on a 24-hour clock.
3411    AM,
3412    /// "post meridiem" or "after midday."
3413    ///
3414    /// Specifically, this describes hours greater than 11 on a 24-hour clock.
3415    PM,
3416}
3417
3418impl Meridiem {
3419    /// Adjusts 12-hour to 24-hour based on meridiem.
3420    fn adjust_hour(self, hour: t::Hour) -> t::Hour {
3421        match self {
3422            Meridiem::AM => hour % C(12),
3423            Meridiem::PM => (hour % C(12)) + C(12),
3424        }
3425    }
3426}
3427
3428impl From<Time> for Meridiem {
3429    fn from(t: Time) -> Meridiem {
3430        if t.hour() < 12 {
3431            Meridiem::AM
3432        } else {
3433            Meridiem::PM
3434        }
3435    }
3436}
3437
3438/// These are "extensions" to the standard `strftime` conversion specifiers.
3439///
3440/// This type represents which flags and/or padding were provided with a
3441/// specifier. For example, `%_3d` uses 3 spaces of padding.
3442///
3443/// Currently, this type provides no structured introspection facilities. It
3444/// is exported and available only via implementations of the [`Custom`] trait
3445/// for reasons of semver compatible API evolution. If you have use cases for
3446/// introspecting this type, please open an issue.
3447#[derive(Clone, Debug)]
3448pub struct Extension {
3449    flag: Option<Flag>,
3450    width: Option<u8>,
3451    colons: u8,
3452}
3453
3454impl Extension {
3455    /// Parses an optional directive flag from the beginning of `fmt`. This
3456    /// assumes `fmt` is not empty and guarantees that the return unconsumed
3457    /// slice is also non-empty.
3458    #[cfg_attr(feature = "perf-inline", inline(always))]
3459    fn parse_flag<'i>(
3460        fmt: &'i [u8],
3461    ) -> Result<(Option<Flag>, &'i [u8]), Error> {
3462        let (&byte, tail) = fmt.split_first().unwrap();
3463        let flag = match byte {
3464            b'_' => Flag::PadSpace,
3465            b'0' => Flag::PadZero,
3466            b'-' => Flag::NoPad,
3467            b'^' => Flag::Uppercase,
3468            b'#' => Flag::Swapcase,
3469            _ => return Ok((None, fmt)),
3470        };
3471        if tail.is_empty() {
3472            return Err(Error::from(E::ExpectedDirectiveAfterFlag {
3473                flag: byte,
3474            }));
3475        }
3476        Ok((Some(flag), tail))
3477    }
3478
3479    /// Parses an optional width that comes after a (possibly absent) flag and
3480    /// before the specifier directive itself. And if a width is parsed, the
3481    /// slice returned does not contain it. (If that slice is empty, then an
3482    /// error is returned.)
3483    ///
3484    /// Note that this is also used to parse precision settings for `%f`
3485    /// and `%.f`. In the former case, the width is just re-interpreted as
3486    /// a precision setting. In the latter case, something like `%5.9f` is
3487    /// technically valid, but the `5` is ignored.
3488    #[cfg_attr(feature = "perf-inline", inline(always))]
3489    fn parse_width<'i>(
3490        fmt: &'i [u8],
3491    ) -> Result<(Option<u8>, &'i [u8]), Error> {
3492        let mut digits = 0;
3493        while digits < fmt.len() && fmt[digits].is_ascii_digit() {
3494            digits += 1;
3495        }
3496        if digits == 0 {
3497            return Ok((None, fmt));
3498        }
3499        let (digits, fmt) = util::parse::split(fmt, digits).unwrap();
3500        let width = util::parse::i64(digits).context(E::FailedWidth)?;
3501        let width = u8::try_from(width).map_err(|_| E::RangeWidth)?;
3502        if fmt.is_empty() {
3503            return Err(Error::from(E::ExpectedDirectiveAfterWidth));
3504        }
3505        Ok((Some(width), fmt))
3506    }
3507
3508    /// Parses an optional number of colons.
3509    ///
3510    /// This is meant to be used immediately before the conversion specifier
3511    /// (after the flag and width has been parsed).
3512    ///
3513    /// This supports parsing up to 3 colons. The colons are used in some cases
3514    /// for alternate specifiers. e.g., `%:Q` or `%:::z`.
3515    #[cfg_attr(feature = "perf-inline", inline(always))]
3516    fn parse_colons<'i>(fmt: &'i [u8]) -> Result<(u8, &'i [u8]), Error> {
3517        let mut colons = 0;
3518        while colons < 3 && colons < fmt.len() && fmt[colons] == b':' {
3519            colons += 1;
3520        }
3521        let fmt = &fmt[usize::from(colons)..];
3522        if colons > 0 && fmt.is_empty() {
3523            return Err(Error::from(E::ExpectedDirectiveAfterColons));
3524        }
3525        Ok((u8::try_from(colons).unwrap(), fmt))
3526    }
3527}
3528
3529/// The different flags one can set. They are mutually exclusive.
3530#[derive(Clone, Copy, Debug)]
3531enum Flag {
3532    PadSpace,
3533    PadZero,
3534    NoPad,
3535    Uppercase,
3536    Swapcase,
3537}
3538
3539/// Returns the "full" weekday name.
3540#[cfg_attr(feature = "perf-inline", inline(always))]
3541fn weekday_name_full(wd: Weekday) -> &'static str {
3542    match wd {
3543        Weekday::Sunday => "Sunday",
3544        Weekday::Monday => "Monday",
3545        Weekday::Tuesday => "Tuesday",
3546        Weekday::Wednesday => "Wednesday",
3547        Weekday::Thursday => "Thursday",
3548        Weekday::Friday => "Friday",
3549        Weekday::Saturday => "Saturday",
3550    }
3551}
3552
3553/// Returns an abbreviated weekday name.
3554#[cfg_attr(feature = "perf-inline", inline(always))]
3555fn weekday_name_abbrev(wd: Weekday) -> &'static str {
3556    match wd {
3557        Weekday::Sunday => "Sun",
3558        Weekday::Monday => "Mon",
3559        Weekday::Tuesday => "Tue",
3560        Weekday::Wednesday => "Wed",
3561        Weekday::Thursday => "Thu",
3562        Weekday::Friday => "Fri",
3563        Weekday::Saturday => "Sat",
3564    }
3565}
3566
3567/// Returns the "full" month name.
3568#[cfg_attr(feature = "perf-inline", inline(always))]
3569fn month_name_full(month: t::Month) -> &'static str {
3570    match month.get() {
3571        1 => "January",
3572        2 => "February",
3573        3 => "March",
3574        4 => "April",
3575        5 => "May",
3576        6 => "June",
3577        7 => "July",
3578        8 => "August",
3579        9 => "September",
3580        10 => "October",
3581        11 => "November",
3582        12 => "December",
3583        unk => unreachable!("invalid month {unk}"),
3584    }
3585}
3586
3587/// Returns the abbreviated month name.
3588#[cfg_attr(feature = "perf-inline", inline(always))]
3589fn month_name_abbrev(month: t::Month) -> &'static str {
3590    match month.get() {
3591        1 => "Jan",
3592        2 => "Feb",
3593        3 => "Mar",
3594        4 => "Apr",
3595        5 => "May",
3596        6 => "Jun",
3597        7 => "Jul",
3598        8 => "Aug",
3599        9 => "Sep",
3600        10 => "Oct",
3601        11 => "Nov",
3602        12 => "Dec",
3603        unk => unreachable!("invalid month {unk}"),
3604    }
3605}
3606
3607#[cfg(test)]
3608mod tests {
3609    use super::*;
3610
3611    // See: https://github.com/BurntSushi/jiff/issues/62
3612    #[test]
3613    fn parse_non_delimited() {
3614        insta::assert_snapshot!(
3615            Timestamp::strptime("%Y%m%d-%H%M%S%z", "20240730-005625+0400").unwrap(),
3616            @"2024-07-29T20:56:25Z",
3617        );
3618        insta::assert_snapshot!(
3619            Zoned::strptime("%Y%m%d-%H%M%S%z", "20240730-005625+0400").unwrap(),
3620            @"2024-07-30T00:56:25+04:00[+04:00]",
3621        );
3622    }
3623
3624    // Regression test for format strings with non-ASCII in them.
3625    //
3626    // We initially didn't support non-ASCII because I had thought it wouldn't
3627    // be used. i.e., If someone wanted to do something with non-ASCII, then
3628    // I thought they'd want to be using something more sophisticated that took
3629    // locale into account. But apparently not.
3630    //
3631    // See: https://github.com/BurntSushi/jiff/issues/155
3632    #[test]
3633    fn ok_non_ascii() {
3634        let fmt = "%Y年%m月%d日,%H时%M分%S秒";
3635        let dt = crate::civil::date(2022, 2, 4).at(3, 58, 59, 0);
3636        insta::assert_snapshot!(
3637            dt.strftime(fmt),
3638            @"2022年02月04日,03时58分59秒",
3639        );
3640        insta::assert_debug_snapshot!(
3641            DateTime::strptime(fmt, "2022年02月04日,03时58分59秒").unwrap(),
3642            @"2022-02-04T03:58:59",
3643        );
3644    }
3645}