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