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