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