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| `%D` | `7/14/24` | Equivalent to `%m/%d/%y`. |
166| `%d`, `%e` | `25`, ` 5` | The day of the month. `%d` is zero-padded, `%e` is space padded. |
167| `%F` | `2024-07-14` | Equivalent to `%Y-%m-%d`. |
168| `%f` | `000456` | Fractional seconds, up to nanosecond precision. |
169| `%.f` | `.000456` | Optional fractional seconds, with dot, up to nanosecond precision. |
170| `%G` | `2024` | An [ISO 8601 week-based] year. Zero padded to 4 digits. |
171| `%g` | `24` | A two-digit [ISO 8601 week-based] year. Represents only 1969-2068. Zero padded. |
172| `%H` | `23` | The hour in a 24 hour clock. Zero padded. |
173| `%I` | `11` | The hour in a 12 hour clock. Zero padded. |
174| `%j` | `060` | The day of the year. Range is `1..=366`. Zero padded to 3 digits. |
175| `%k` | `15` | The hour in a 24 hour clock. Space padded. |
176| `%l` | ` 3` | The hour in a 12 hour clock. Space padded. |
177| `%M` | `04` | The minute. Zero padded. |
178| `%m` | `01` | The month. Zero padded. |
179| `%n` | `\n` | Formats as a newline character. Parses arbitrary whitespace. |
180| `%P` | `am` | Whether the time is in the AM or PM, lowercase. |
181| `%p` | `PM` | Whether the time is in the AM or PM, uppercase. |
182| `%Q` | `America/New_York`, `+0530` | An IANA time zone identifier, or `%z` if one doesn't exist. |
183| `%:Q` | `America/New_York`, `+05:30` | An IANA time zone identifier, or `%:z` if one doesn't exist. |
184| `%R` | `23:30` | Equivalent to `%H:%M`. |
185| `%S` | `59` | The second. Zero padded. |
186| `%s` | `1737396540` | A Unix timestamp, in seconds. |
187| `%T` | `23:30:59` | Equivalent to `%H:%M:%S`. |
188| `%t` | `\t` | Formats as a tab character. Parses arbitrary whitespace. |
189| `%U` | `03` | Week number. Week 1 is the first week starting with a Sunday. Zero padded. |
190| `%u` | `7` | The day of the week beginning with Monday at `1`. |
191| `%V` | `05` | Week number in the [ISO 8601 week-based] calendar. Zero padded. |
192| `%W` | `03` | Week number. Week 1 is the first week starting with a Monday. Zero padded. |
193| `%w` | `0` | The day of the week beginning with Sunday at `0`. |
194| `%Y` | `2024` | A full year, including century. Zero padded to 4 digits. |
195| `%y` | `24` | A two-digit year. Represents only 1969-2068. Zero padded. |
196| `%Z` | `EDT` | A time zone abbreviation. Supported when formatting only. |
197| `%z` | `+0530` | A time zone offset in the format `[+-]HHMM[SS]`. |
198| `%:z` | `+05:30` | A time zone offset in the format `[+-]HH:MM[:SS]`. |
199
200When formatting, the following flags can be inserted immediately after the `%`
201and before the directive:
202
203* `_` - Pad a numeric result to the left with spaces.
204* `-` - Do not pad a numeric result.
205* `0` - Pad a numeric result to the left with zeros.
206* `^` - Use alphabetic uppercase for all relevant strings.
207* `#` - Swap the case of the result string. This is typically only useful with
208`%p` or `%Z`, since they are the only conversion specifiers that emit strings
209entirely in uppercase by default.
210
211The above flags override the "default" settings of a specifier. For example,
212`%_d` pads with spaces instead of zeros, and `%0e` pads with zeros instead of
213spaces. The exceptions are the `%z` and `%:z` specifiers. They are unaffected
214by any flags.
215
216Moreover, any number of decimal digits can be inserted after the (possibly
217absent) flag and before the directive, so long as the parsed number is less
218than 256. The number formed by these digits will correspond to the minimum
219amount of padding (to the left).
220
221The flags and padding amount above may be used when parsing as well. Most
222settings are ignored during parsing except for padding. For example, if one
223wanted to parse `003` as the day `3`, then one should use `%03d`. Otherwise, by
224default, `%d` will only try to consume at most 2 digits.
225
226The `%f` and `%.f` flags also support specifying the precision, up to
227nanoseconds. For example, `%3f` and `%.3f` will both always print a fractional
228second component to exactly 3 decimal places. When no precision is specified,
229then `%f` will always emit at least one digit, even if it's zero. But `%.f`
230will emit the empty string when the fractional component is zero. Otherwise, it
231will include the leading `.`. For parsing, `%f` does not include the leading
232dot, but `%.f` does. Note that all of the options above are still parsed for
233`%f` and `%.f`, but they are all no-ops (except for the padding for `%f`, which
234is instead interpreted as a precision setting). When using a precision setting,
235truncation is used. If you need a different rounding mode, you should use
236higher level APIs like [`Timestamp::round`] or [`Zoned::round`].
237
238# Conditionally unsupported
239
240Jiff does not support `%Q` or `%:Q` (IANA time zone identifier) when the
241`alloc` crate feature is not enabled. This is because a time zone identifier
242is variable width data. If you have a use case for this, please
243[detail it in a new issue](https://github.com/BurntSushi/jiff/issues/new).
244
245# Unsupported
246
247The following things are currently unsupported:
248
249* Parsing or formatting fractional seconds in the time time zone offset.
250* Locale oriented conversion specifiers, such as `%c`, `%r` and `%+`, are not
251  supported by Jiff. For locale oriented datetime formatting, please use the
252  [`icu`] crate via [`jiff-icu`].
253
254[`strftime`]: https://pubs.opengroup.org/onlinepubs/009695399/functions/strftime.html
255[`strptime`]: https://pubs.opengroup.org/onlinepubs/009695399/functions/strptime.html
256[ISO 8601 week-based]: https://en.wikipedia.org/wiki/ISO_week_date
257[`icu`]: https://docs.rs/icu
258[`jiff-icu`]: https://docs.rs/jiff-icu
259*/
260
261use crate::{
262    civil::{Date, DateTime, ISOWeekDate, Time, Weekday},
263    error::{err, ErrorContext},
264    fmt::{
265        strtime::{format::Formatter, parse::Parser},
266        Write,
267    },
268    tz::{Offset, OffsetConflict, TimeZone, TimeZoneDatabase},
269    util::{
270        self,
271        array_str::Abbreviation,
272        escape,
273        rangeint::RInto,
274        t::{self, C},
275    },
276    Error, Timestamp, Zoned,
277};
278
279mod format;
280mod parse;
281
282/// Parse the given `input` according to the given `format` string.
283///
284/// See the [module documentation](self) for details on what's supported.
285///
286/// This routine is the same as [`BrokenDownTime::parse`], but may be more
287/// convenient to call.
288///
289/// # Errors
290///
291/// This returns an error when parsing failed. This might happen because
292/// the format string itself was invalid, or because the input didn't match
293/// the format string.
294///
295/// # Example
296///
297/// This example shows how to parse something resembling a RFC 2822 datetime:
298///
299/// ```
300/// use jiff::{civil::date, fmt::strtime, tz};
301///
302/// let zdt = strtime::parse(
303///     "%a, %d %b %Y %T %z",
304///     "Mon, 15 Jul 2024 16:24:59 -0400",
305/// )?.to_zoned()?;
306///
307/// let tz = tz::offset(-4).to_time_zone();
308/// assert_eq!(zdt, date(2024, 7, 15).at(16, 24, 59, 0).to_zoned(tz)?);
309///
310/// # Ok::<(), Box<dyn std::error::Error>>(())
311/// ```
312///
313/// Of course, one should prefer using the [`fmt::rfc2822`](super::rfc2822)
314/// module, which contains a dedicated RFC 2822 parser. For example, the above
315/// format string does not part all valid RFC 2822 datetimes, since, e.g.,
316/// the leading weekday is optional and so are the seconds in the time, but
317/// `strptime`-like APIs have no way of expressing such requirements.
318///
319/// [RFC 2822]: https://datatracker.ietf.org/doc/html/rfc2822
320///
321/// # Example: parse RFC 3339 timestamp with fractional seconds
322///
323/// ```
324/// use jiff::{civil::date, fmt::strtime};
325///
326/// let zdt = strtime::parse(
327///     "%Y-%m-%dT%H:%M:%S%.f%:z",
328///     "2024-07-15T16:24:59.123456789-04:00",
329/// )?.to_zoned()?;
330/// assert_eq!(
331///     zdt,
332///     date(2024, 7, 15).at(16, 24, 59, 123_456_789).in_tz("America/New_York")?,
333/// );
334///
335/// # Ok::<(), Box<dyn std::error::Error>>(())
336/// ```
337#[inline]
338pub fn parse(
339    format: impl AsRef<[u8]>,
340    input: impl AsRef<[u8]>,
341) -> Result<BrokenDownTime, Error> {
342    BrokenDownTime::parse(format, input)
343}
344
345/// Format the given broken down time using the format string given.
346///
347/// See the [module documentation](self) for details on what's supported.
348///
349/// This routine is like [`BrokenDownTime::format`], but may be more
350/// convenient to call. Also, it returns a `String` instead of accepting a
351/// [`fmt::Write`](super::Write) trait implementation to write to.
352///
353/// Note that `broken_down_time` can be _anything_ that can be converted into
354/// it. This includes, for example, [`Zoned`], [`Timestamp`], [`DateTime`],
355/// [`Date`] and [`Time`].
356///
357/// # Errors
358///
359/// This returns an error when formatting failed. Formatting can fail either
360/// because of an invalid format string, or if formatting requires a field in
361/// `BrokenDownTime` to be set that isn't. For example, trying to format a
362/// [`DateTime`] with the `%z` specifier will fail because a `DateTime` has no
363/// time zone or offset information associated with it.
364///
365/// # Example
366///
367/// This example shows how to format a `Zoned` into something resembling a RFC
368/// 2822 datetime:
369///
370/// ```
371/// use jiff::{civil::date, fmt::strtime, tz};
372///
373/// let zdt = date(2024, 7, 15).at(16, 24, 59, 0).in_tz("America/New_York")?;
374/// let string = strtime::format("%a, %-d %b %Y %T %z", &zdt)?;
375/// assert_eq!(string, "Mon, 15 Jul 2024 16:24:59 -0400");
376///
377/// # Ok::<(), Box<dyn std::error::Error>>(())
378/// ```
379///
380/// Of course, one should prefer using the [`fmt::rfc2822`](super::rfc2822)
381/// module, which contains a dedicated RFC 2822 printer.
382///
383/// [RFC 2822]: https://datatracker.ietf.org/doc/html/rfc2822
384///
385/// # Example: `date`-like output
386///
387/// While the output of the Unix `date` command is likely locale specific,
388/// this is what it looks like on my system:
389///
390/// ```
391/// use jiff::{civil::date, fmt::strtime, tz};
392///
393/// let zdt = date(2024, 7, 15).at(16, 24, 59, 0).in_tz("America/New_York")?;
394/// let string = strtime::format("%a %b %e %I:%M:%S %p %Z %Y", &zdt)?;
395/// assert_eq!(string, "Mon Jul 15 04:24:59 PM EDT 2024");
396///
397/// # Ok::<(), Box<dyn std::error::Error>>(())
398/// ```
399///
400/// # Example: RFC 3339 compatible output with fractional seconds
401///
402/// ```
403/// use jiff::{civil::date, fmt::strtime, tz};
404///
405/// let zdt = date(2024, 7, 15)
406///     .at(16, 24, 59, 123_456_789)
407///     .in_tz("America/New_York")?;
408/// let string = strtime::format("%Y-%m-%dT%H:%M:%S%.f%:z", &zdt)?;
409/// assert_eq!(string, "2024-07-15T16:24:59.123456789-04:00");
410///
411/// # Ok::<(), Box<dyn std::error::Error>>(())
412/// ```
413#[cfg(any(test, feature = "alloc"))]
414#[inline]
415pub fn format(
416    format: impl AsRef<[u8]>,
417    broken_down_time: impl Into<BrokenDownTime>,
418) -> Result<alloc::string::String, Error> {
419    let broken_down_time: BrokenDownTime = broken_down_time.into();
420
421    let mut buf = alloc::string::String::new();
422    broken_down_time.format(format, &mut buf)?;
423    Ok(buf)
424}
425
426/// The "broken down time" used by parsing and formatting.
427///
428/// This is a lower level aspect of the `strptime` and `strftime` APIs that you
429/// probably won't need to use directly. The main use case is if you want to
430/// observe formatting errors or if you want to format a datetime to something
431/// other than a `String` via the [`fmt::Write`](super::Write) trait.
432///
433/// Otherwise, typical use of this module happens indirectly via APIs like
434/// [`Zoned::strptime`] and [`Zoned::strftime`].
435///
436/// # Design
437///
438/// This is the type that parsing writes to and formatting reads from. That
439/// is, parsing proceeds by writing individual parsed fields to this type, and
440/// then converting the fields to datetime types like [`Zoned`] only after
441/// parsing is complete. Similarly, formatting always begins by converting
442/// datetime types like `Zoned` into a `BrokenDownTime`, and then formatting
443/// the individual fields from there.
444// Design:
445//
446// This is meant to be very similar to libc's `struct tm` in that it
447// represents civil time, although may have an offset attached to it, in which
448// case it represents an absolute time. The main difference is that each field
449// is explicitly optional, where as in C, there's no way to tell whether a
450// field is "set" or not. In C, this isn't so much a problem, because the
451// caller needs to explicitly pass in a pointer to a `struct tm`, and so the
452// API makes it clear that it's going to mutate the time.
453//
454// But in Rust, we really just want to accept a format string, an input and
455// return a fresh datetime. (Nevermind the fact that we don't provide a way
456// to mutate datetimes in place.) We could just use "default" units like you
457// might in C, but it would be very surprising if `%m-%d` just decided to fill
458// in the year for you with some default value. So we track which pieces have
459// been set individually and return errors when requesting, e.g., a `Date`
460// when no `year` has been parsed.
461//
462// We do permit time units to be filled in by default, as-is consistent with
463// the rest of Jiff's API. e.g., If a `DateTime` is requested but the format
464// string has no directives for time, we'll happy default to midnight. The
465// only catch is that you can't omit time units bigger than any present time
466// unit. For example, only `%M` doesn't fly. If you want to parse minutes, you
467// also have to parse hours.
468//
469// This design does also let us possibly do "incomplete" parsing by asking
470// the caller for a datetime to "seed" a `Fields` struct, and then execute
471// parsing. But Jiff doesn't currently expose an API to do that. But this
472// implementation was intentionally designed to support that use case, C
473// style, if it comes up.
474#[derive(Debug, Default)]
475pub struct BrokenDownTime {
476    year: Option<t::Year>,
477    month: Option<t::Month>,
478    day: Option<t::Day>,
479    day_of_year: Option<t::DayOfYear>,
480    iso_week_year: Option<t::ISOYear>,
481    iso_week: Option<t::ISOWeek>,
482    week_sun: Option<t::WeekNum>,
483    week_mon: Option<t::WeekNum>,
484    hour: Option<t::Hour>,
485    minute: Option<t::Minute>,
486    second: Option<t::Second>,
487    subsec: Option<t::SubsecNanosecond>,
488    offset: Option<Offset>,
489    // Used to confirm that it is consistent
490    // with the date given. It usually isn't
491    // used to pick a date on its own, but can
492    // be for week dates.
493    weekday: Option<Weekday>,
494    // Only generally useful with %I. But can still
495    // be used with, say, %H. In that case, AM will
496    // turn 13 o'clock to 1 o'clock.
497    meridiem: Option<Meridiem>,
498    // The time zone abbreviation. Used only when
499    // formatting a `Zoned`.
500    tzabbrev: Option<Abbreviation>,
501    // The IANA time zone identifier. Used only when
502    // formatting a `Zoned`.
503    #[cfg(feature = "alloc")]
504    iana: Option<alloc::string::String>,
505}
506
507impl BrokenDownTime {
508    /// Parse the given `input` according to the given `format` string.
509    ///
510    /// See the [module documentation](self) for details on what's supported.
511    ///
512    /// This routine is the same as the module level free function
513    /// [`strtime::parse`](parse()).
514    ///
515    /// # Errors
516    ///
517    /// This returns an error when parsing failed. This might happen because
518    /// the format string itself was invalid, or because the input didn't match
519    /// the format string.
520    ///
521    /// # Example
522    ///
523    /// ```
524    /// use jiff::{civil, fmt::strtime::BrokenDownTime};
525    ///
526    /// let tm = BrokenDownTime::parse("%m/%d/%y", "7/14/24")?;
527    /// let date = tm.to_date()?;
528    /// assert_eq!(date, civil::date(2024, 7, 14));
529    ///
530    /// # Ok::<(), Box<dyn std::error::Error>>(())
531    /// ```
532    #[inline]
533    pub fn parse(
534        format: impl AsRef<[u8]>,
535        input: impl AsRef<[u8]>,
536    ) -> Result<BrokenDownTime, Error> {
537        BrokenDownTime::parse_mono(format.as_ref(), input.as_ref())
538    }
539
540    #[inline]
541    fn parse_mono(fmt: &[u8], inp: &[u8]) -> Result<BrokenDownTime, Error> {
542        let mut pieces = BrokenDownTime::default();
543        let mut p = Parser { fmt, inp, tm: &mut pieces };
544        p.parse().context("strptime parsing failed")?;
545        if !p.inp.is_empty() {
546            return Err(err!(
547                "strptime expects to consume the entire input, but \
548                 {remaining:?} remains unparsed",
549                remaining = escape::Bytes(p.inp),
550            ));
551        }
552        Ok(pieces)
553    }
554
555    /// Parse a prefix of the given `input` according to the given `format`
556    /// string. The offset returned corresponds to the number of bytes parsed.
557    /// That is, the length of the prefix (which may be the length of the
558    /// entire input if there are no unparsed bytes remaining).
559    ///
560    /// See the [module documentation](self) for details on what's supported.
561    ///
562    /// This is like [`BrokenDownTime::parse`], but it won't return an error
563    /// if there is input remaining after parsing the format directives.
564    ///
565    /// # Errors
566    ///
567    /// This returns an error when parsing failed. This might happen because
568    /// the format string itself was invalid, or because the input didn't match
569    /// the format string.
570    ///
571    /// # Example
572    ///
573    /// ```
574    /// use jiff::{civil, fmt::strtime::BrokenDownTime};
575    ///
576    /// // %y only parses two-digit years, so the 99 following
577    /// // 24 is unparsed!
578    /// let input = "7/14/2499";
579    /// let (tm, offset) = BrokenDownTime::parse_prefix("%m/%d/%y", input)?;
580    /// let date = tm.to_date()?;
581    /// assert_eq!(date, civil::date(2024, 7, 14));
582    /// assert_eq!(offset, 7);
583    /// assert_eq!(&input[offset..], "99");
584    ///
585    /// # Ok::<(), Box<dyn std::error::Error>>(())
586    /// ```
587    ///
588    /// If the entire input is parsed, then the offset is the length of the
589    /// input:
590    ///
591    /// ```
592    /// use jiff::{civil, fmt::strtime::BrokenDownTime};
593    ///
594    /// let (tm, offset) = BrokenDownTime::parse_prefix(
595    ///     "%m/%d/%y", "7/14/24",
596    /// )?;
597    /// let date = tm.to_date()?;
598    /// assert_eq!(date, civil::date(2024, 7, 14));
599    /// assert_eq!(offset, 7);
600    ///
601    /// # Ok::<(), Box<dyn std::error::Error>>(())
602    /// ```
603    ///
604    /// # Example: how to parse a only parse of a timestamp
605    ///
606    /// If you only need, for example, the date from a timestamp, then you
607    /// can parse it as a prefix:
608    ///
609    /// ```
610    /// use jiff::{civil, fmt::strtime::BrokenDownTime};
611    ///
612    /// let input = "2024-01-20T17:55Z";
613    /// let (tm, offset) = BrokenDownTime::parse_prefix("%Y-%m-%d", input)?;
614    /// let date = tm.to_date()?;
615    /// assert_eq!(date, civil::date(2024, 1, 20));
616    /// assert_eq!(offset, 10);
617    /// assert_eq!(&input[offset..], "T17:55Z");
618    ///
619    /// # Ok::<(), Box<dyn std::error::Error>>(())
620    /// ```
621    ///
622    /// Note though that Jiff's default parsing functions are already quite
623    /// flexible, and one can just parse a civil date directly from a timestamp
624    /// automatically:
625    ///
626    /// ```
627    /// use jiff::civil;
628    ///
629    /// let input = "2024-01-20T17:55-05";
630    /// let date: civil::Date = input.parse()?;
631    /// assert_eq!(date, civil::date(2024, 1, 20));
632    ///
633    /// # Ok::<(), Box<dyn std::error::Error>>(())
634    /// ```
635    ///
636    /// Although in this case, you don't get the length of the prefix parsed.
637    #[inline]
638    pub fn parse_prefix(
639        format: impl AsRef<[u8]>,
640        input: impl AsRef<[u8]>,
641    ) -> Result<(BrokenDownTime, usize), Error> {
642        BrokenDownTime::parse_prefix_mono(format.as_ref(), input.as_ref())
643    }
644
645    #[inline]
646    fn parse_prefix_mono(
647        fmt: &[u8],
648        inp: &[u8],
649    ) -> Result<(BrokenDownTime, usize), Error> {
650        let mkoffset = util::parse::offseter(inp);
651        let mut pieces = BrokenDownTime::default();
652        let mut p = Parser { fmt, inp, tm: &mut pieces };
653        p.parse().context("strptime parsing failed")?;
654        let remainder = mkoffset(p.inp);
655        Ok((pieces, remainder))
656    }
657
658    /// Format this broken down time using the format string given.
659    ///
660    /// See the [module documentation](self) for details on what's supported.
661    ///
662    /// This routine is like the module level free function
663    /// [`strtime::format`](parse()), except it takes a
664    /// [`fmt::Write`](super::Write) trait implementations instead of assuming
665    /// you want a `String`.
666    ///
667    /// # Errors
668    ///
669    /// This returns an error when formatting failed. Formatting can fail
670    /// either because of an invalid format string, or if formatting requires
671    /// a field in `BrokenDownTime` to be set that isn't. For example, trying
672    /// to format a [`DateTime`] with the `%z` specifier will fail because a
673    /// `DateTime` has no time zone or offset information associated with it.
674    ///
675    /// Formatting also fails if writing to the given writer fails.
676    ///
677    /// # Example
678    ///
679    /// This example shows a formatting option, `%Z`, that isn't available
680    /// during parsing. Namely, `%Z` inserts a time zone abbreviation. This
681    /// is generally only intended for display purposes, since it can be
682    /// ambiguous when parsing.
683    ///
684    /// ```
685    /// use jiff::{civil::date, fmt::strtime::BrokenDownTime};
686    ///
687    /// let zdt = date(2024, 7, 9).at(16, 24, 0, 0).in_tz("America/New_York")?;
688    /// let tm = BrokenDownTime::from(&zdt);
689    ///
690    /// let mut buf = String::new();
691    /// tm.format("%a %b %e %I:%M:%S %p %Z %Y", &mut buf)?;
692    ///
693    /// assert_eq!(buf, "Tue Jul  9 04:24:00 PM EDT 2024");
694    ///
695    /// # Ok::<(), Box<dyn std::error::Error>>(())
696    /// ```
697    #[inline]
698    pub fn format<W: Write>(
699        &self,
700        format: impl AsRef<[u8]>,
701        mut wtr: W,
702    ) -> Result<(), Error> {
703        let fmt = format.as_ref();
704        let mut formatter = Formatter { fmt, tm: self, wtr: &mut wtr };
705        formatter.format().context("strftime formatting failed")?;
706        Ok(())
707    }
708
709    /// Format this broken down time using the format string given into a new
710    /// `String`.
711    ///
712    /// See the [module documentation](self) for details on what's supported.
713    ///
714    /// This is like [`BrokenDownTime::format`], but always uses a `String` to
715    /// format the time into. If you need to reuse allocations or write a
716    /// formatted time into a different type, then you should use
717    /// [`BrokenDownTime::format`] instead.
718    ///
719    /// # Errors
720    ///
721    /// This returns an error when formatting failed. Formatting can fail
722    /// either because of an invalid format string, or if formatting requires
723    /// a field in `BrokenDownTime` to be set that isn't. For example, trying
724    /// to format a [`DateTime`] with the `%z` specifier will fail because a
725    /// `DateTime` has no time zone or offset information associated with it.
726    ///
727    /// # Example
728    ///
729    /// This example shows a formatting option, `%Z`, that isn't available
730    /// during parsing. Namely, `%Z` inserts a time zone abbreviation. This
731    /// is generally only intended for display purposes, since it can be
732    /// ambiguous when parsing.
733    ///
734    /// ```
735    /// use jiff::{civil::date, fmt::strtime::BrokenDownTime};
736    ///
737    /// let zdt = date(2024, 7, 9).at(16, 24, 0, 0).in_tz("America/New_York")?;
738    /// let tm = BrokenDownTime::from(&zdt);
739    /// let string = tm.to_string("%a %b %e %I:%M:%S %p %Z %Y")?;
740    /// assert_eq!(string, "Tue Jul  9 04:24:00 PM EDT 2024");
741    ///
742    /// # Ok::<(), Box<dyn std::error::Error>>(())
743    /// ```
744    #[cfg(feature = "alloc")]
745    #[inline]
746    pub fn to_string(
747        &self,
748        format: impl AsRef<[u8]>,
749    ) -> Result<alloc::string::String, Error> {
750        let mut buf = alloc::string::String::new();
751        self.format(format, &mut buf)?;
752        Ok(buf)
753    }
754
755    /// Extracts a zoned datetime from this broken down time.
756    ///
757    /// When an IANA time zone identifier is
758    /// present but an offset is not, then the
759    /// [`Disambiguation::Compatible`](crate::tz::Disambiguation::Compatible)
760    /// strategy is used if the parsed datetime is ambiguous in the time zone.
761    ///
762    /// If you need to use a custom time zone database for doing IANA time
763    /// zone identifier lookups (via the `%Q` directive), then use
764    /// [`BrokenDownTime::to_zoned_with`].
765    ///
766    /// # Warning
767    ///
768    /// The `strtime` module APIs do not require an IANA time zone identifier
769    /// to parse a `Zoned`. If one is not used, then if you format a zoned
770    /// datetime in a time zone like `America/New_York` and then parse it back
771    /// again, the zoned datetime you get back will be a "fixed offset" zoned
772    /// datetime. This in turn means it will not perform daylight saving time
773    /// safe arithmetic.
774    ///
775    /// However, the `%Q` directive may be used to both format and parse an
776    /// IANA time zone identifier. It is strongly recommended to use this
777    /// directive whenever one is formatting or parsing `Zoned` values.
778    ///
779    /// # Errors
780    ///
781    /// This returns an error if there weren't enough components to construct
782    /// a civil datetime _and_ either a UTC offset or a IANA time zone
783    /// identifier. When both a UTC offset and an IANA time zone identifier
784    /// are found, then [`OffsetConflict::Reject`] is used to detect any
785    /// inconsistency between the offset and the time zone.
786    ///
787    /// # Example
788    ///
789    /// This example shows how to parse a zoned datetime:
790    ///
791    /// ```
792    /// use jiff::fmt::strtime;
793    ///
794    /// let zdt = strtime::parse(
795    ///     "%F %H:%M %:z %:Q",
796    ///     "2024-07-14 21:14 -04:00 US/Eastern",
797    /// )?.to_zoned()?;
798    /// assert_eq!(zdt.to_string(), "2024-07-14T21:14:00-04:00[US/Eastern]");
799    ///
800    /// # Ok::<(), Box<dyn std::error::Error>>(())
801    /// ```
802    ///
803    /// This shows that an error is returned when the offset is inconsistent
804    /// with the time zone. For example, `US/Eastern` is in daylight saving
805    /// time in July 2024:
806    ///
807    /// ```
808    /// use jiff::fmt::strtime;
809    ///
810    /// let result = strtime::parse(
811    ///     "%F %H:%M %:z %:Q",
812    ///     "2024-07-14 21:14 -05:00 US/Eastern",
813    /// )?.to_zoned();
814    /// assert_eq!(
815    ///     result.unwrap_err().to_string(),
816    ///     "datetime 2024-07-14T21:14:00 could not resolve to a \
817    ///      timestamp since 'reject' conflict resolution was chosen, \
818    ///      and because datetime has offset -05, but the time zone \
819    ///      US/Eastern for the given datetime unambiguously has offset -04",
820    /// );
821    ///
822    /// # Ok::<(), Box<dyn std::error::Error>>(())
823    /// ```
824    #[inline]
825    pub fn to_zoned(&self) -> Result<Zoned, Error> {
826        self.to_zoned_with(crate::tz::db())
827    }
828
829    /// Extracts a zoned datetime from this broken down time and uses the time
830    /// zone database given for any IANA time zone identifier lookups.
831    ///
832    /// An IANA time zone identifier lookup is only performed when this
833    /// `BrokenDownTime` contains an IANA time zone identifier. An IANA time
834    /// zone identifier can be parsed with the `%Q` directive.
835    ///
836    /// When an IANA time zone identifier is
837    /// present but an offset is not, then the
838    /// [`Disambiguation::Compatible`](crate::tz::Disambiguation::Compatible)
839    /// strategy is used if the parsed datetime is ambiguous in the time zone.
840    ///
841    /// # Warning
842    ///
843    /// The `strtime` module APIs do not require an IANA time zone identifier
844    /// to parse a `Zoned`. If one is not used, then if you format a zoned
845    /// datetime in a time zone like `America/New_York` and then parse it back
846    /// again, the zoned datetime you get back will be a "fixed offset" zoned
847    /// datetime. This in turn means it will not perform daylight saving time
848    /// safe arithmetic.
849    ///
850    /// However, the `%Q` directive may be used to both format and parse an
851    /// IANA time zone identifier. It is strongly recommended to use this
852    /// directive whenever one is formatting or parsing `Zoned` values.
853    ///
854    /// # Errors
855    ///
856    /// This returns an error if there weren't enough components to construct
857    /// a civil datetime _and_ either a UTC offset or a IANA time zone
858    /// identifier. When both a UTC offset and an IANA time zone identifier
859    /// are found, then [`OffsetConflict::Reject`] is used to detect any
860    /// inconsistency between the offset and the time zone.
861    ///
862    /// # Example
863    ///
864    /// This example shows how to parse a zoned datetime:
865    ///
866    /// ```
867    /// use jiff::fmt::strtime;
868    ///
869    /// let zdt = strtime::parse(
870    ///     "%F %H:%M %:z %:Q",
871    ///     "2024-07-14 21:14 -04:00 US/Eastern",
872    /// )?.to_zoned_with(jiff::tz::db())?;
873    /// assert_eq!(zdt.to_string(), "2024-07-14T21:14:00-04:00[US/Eastern]");
874    ///
875    /// # Ok::<(), Box<dyn std::error::Error>>(())
876    /// ```
877    #[inline]
878    pub fn to_zoned_with(
879        &self,
880        db: &TimeZoneDatabase,
881    ) -> Result<Zoned, Error> {
882        let dt = self
883            .to_datetime()
884            .context("datetime required to parse zoned datetime")?;
885        match (self.offset, self.iana_time_zone()) {
886            (None, None) => Err(err!(
887                "either offset (from %z) or IANA time zone identifier \
888                 (from %Q) is required for parsing zoned datetime",
889            )),
890            (Some(offset), None) => {
891                let ts = offset.to_timestamp(dt).with_context(|| {
892                    err!(
893                        "parsed datetime {dt} and offset {offset}, \
894                         but combining them into a zoned datetime is outside \
895                         Jiff's supported timestamp range",
896                    )
897                })?;
898                Ok(ts.to_zoned(TimeZone::fixed(offset)))
899            }
900            (None, Some(iana)) => {
901                let tz = db.get(iana)?;
902                let zdt = tz.to_zoned(dt)?;
903                Ok(zdt)
904            }
905            (Some(offset), Some(iana)) => {
906                let tz = db.get(iana)?;
907                let azdt = OffsetConflict::Reject.resolve(dt, offset, tz)?;
908                // Guaranteed that if OffsetConflict::Reject doesn't reject,
909                // then we get back an unambiguous zoned datetime.
910                let zdt = azdt.unambiguous().unwrap();
911                Ok(zdt)
912            }
913        }
914    }
915
916    /// Extracts a timestamp from this broken down time.
917    ///
918    /// # Errors
919    ///
920    /// This returns an error if there weren't enough components to construct
921    /// a civil datetime _and_ a UTC offset.
922    ///
923    /// # Example
924    ///
925    /// This example shows how to parse a timestamp from a broken down time:
926    ///
927    /// ```
928    /// use jiff::fmt::strtime;
929    ///
930    /// let ts = strtime::parse(
931    ///     "%F %H:%M %:z",
932    ///     "2024-07-14 21:14 -04:00",
933    /// )?.to_timestamp()?;
934    /// assert_eq!(ts.to_string(), "2024-07-15T01:14:00Z");
935    ///
936    /// # Ok::<(), Box<dyn std::error::Error>>(())
937    /// ```
938    #[inline]
939    pub fn to_timestamp(&self) -> Result<Timestamp, Error> {
940        let dt = self
941            .to_datetime()
942            .context("datetime required to parse timestamp")?;
943        let offset =
944            self.to_offset().context("offset required to parse timestamp")?;
945        offset.to_timestamp(dt).with_context(|| {
946            err!(
947                "parsed datetime {dt} and offset {offset}, \
948                 but combining them into a timestamp is outside \
949                 Jiff's supported timestamp range",
950            )
951        })
952    }
953
954    #[inline]
955    fn to_offset(&self) -> Result<Offset, Error> {
956        let Some(offset) = self.offset else {
957            return Err(err!(
958                "parsing format did not include time zone offset directive",
959            ));
960        };
961        Ok(offset)
962    }
963
964    /// Extracts a civil datetime from this broken down time.
965    ///
966    /// # Errors
967    ///
968    /// This returns an error if there weren't enough components to construct
969    /// a civil datetime. This means there must be at least a year, month and
970    /// day.
971    ///
972    /// It's okay if there are more units than are needed to construct a civil
973    /// datetime. For example, if this broken down time contains an offset,
974    /// then it won't prevent a conversion to a civil datetime.
975    ///
976    /// # Example
977    ///
978    /// This example shows how to parse a civil datetime from a broken down
979    /// time:
980    ///
981    /// ```
982    /// use jiff::fmt::strtime;
983    ///
984    /// let dt = strtime::parse("%F %H:%M", "2024-07-14 21:14")?.to_datetime()?;
985    /// assert_eq!(dt.to_string(), "2024-07-14T21:14:00");
986    ///
987    /// # Ok::<(), Box<dyn std::error::Error>>(())
988    /// ```
989    #[inline]
990    pub fn to_datetime(&self) -> Result<DateTime, Error> {
991        let date =
992            self.to_date().context("date required to parse datetime")?;
993        let time =
994            self.to_time().context("time required to parse datetime")?;
995        Ok(DateTime::from_parts(date, time))
996    }
997
998    /// Extracts a civil date from this broken down time.
999    ///
1000    /// This requires that the year is set along with a way to identify the day
1001    /// in the year. This can be done by either setting the month and the day
1002    /// of the month (`%m` and `%d`), or by setting the day of the year (`%j`).
1003    ///
1004    /// # Errors
1005    ///
1006    /// This returns an error if there weren't enough components to construct
1007    /// a civil date. This means there must be at least a year and either the
1008    /// month and day or the day of the year.
1009    ///
1010    /// It's okay if there are more units than are needed to construct a civil
1011    /// datetime. For example, if this broken down time contain a civil time,
1012    /// then it won't prevent a conversion to a civil date.
1013    ///
1014    /// # Example
1015    ///
1016    /// This example shows how to parse a civil date from a broken down time:
1017    ///
1018    /// ```
1019    /// use jiff::fmt::strtime;
1020    ///
1021    /// let date = strtime::parse("%m/%d/%y", "7/14/24")?.to_date()?;
1022    /// assert_eq!(date.to_string(), "2024-07-14");
1023    ///
1024    /// # Ok::<(), Box<dyn std::error::Error>>(())
1025    /// ```
1026    #[inline]
1027    pub fn to_date(&self) -> Result<Date, Error> {
1028        let Some(year) = self.year else {
1029            // The Gregorian year and ISO week year may be parsed separately.
1030            // That is, they are two different fields. So if the Gregorian year
1031            // is absent, we might still have an ISO 8601 week date.
1032            if let Some(date) = self.to_date_from_iso()? {
1033                return Ok(date);
1034            }
1035            return Err(err!("missing year, date cannot be created"));
1036        };
1037        let mut date = self.to_date_from_gregorian(year)?;
1038        if date.is_none() {
1039            date = self.to_date_from_iso()?;
1040        }
1041        if date.is_none() {
1042            date = self.to_date_from_day_of_year(year)?;
1043        }
1044        if date.is_none() {
1045            date = self.to_date_from_week_sun(year)?;
1046        }
1047        if date.is_none() {
1048            date = self.to_date_from_week_mon(year)?;
1049        }
1050        let Some(date) = date else {
1051            return Err(err!(
1052                "a month/day, day-of-year or week date must be \
1053                 present to create a date, but none were found",
1054            ));
1055        };
1056        if let Some(weekday) = self.weekday {
1057            if weekday != date.weekday() {
1058                return Err(err!(
1059                    "parsed weekday {weekday} does not match \
1060                     weekday {got} from parsed date {date}",
1061                    weekday = weekday_name_full(weekday),
1062                    got = weekday_name_full(date.weekday()),
1063                ));
1064            }
1065        }
1066        Ok(date)
1067    }
1068
1069    #[inline]
1070    fn to_date_from_gregorian(
1071        &self,
1072        year: t::Year,
1073    ) -> Result<Option<Date>, Error> {
1074        let (Some(month), Some(day)) = (self.month, self.day) else {
1075            return Ok(None);
1076        };
1077        Ok(Some(Date::new_ranged(year, month, day).context("invalid date")?))
1078    }
1079
1080    #[inline]
1081    fn to_date_from_day_of_year(
1082        &self,
1083        year: t::Year,
1084    ) -> Result<Option<Date>, Error> {
1085        let Some(doy) = self.day_of_year else { return Ok(None) };
1086        Ok(Some({
1087            let first =
1088                Date::new_ranged(year, C(1).rinto(), C(1).rinto()).unwrap();
1089            first
1090                .with()
1091                .day_of_year(doy.get())
1092                .build()
1093                .context("invalid date")?
1094        }))
1095    }
1096
1097    #[inline]
1098    fn to_date_from_iso(&self) -> Result<Option<Date>, Error> {
1099        let (Some(y), Some(w), Some(d)) =
1100            (self.iso_week_year, self.iso_week, self.weekday)
1101        else {
1102            return Ok(None);
1103        };
1104        let wd = ISOWeekDate::new_ranged(y, w, d)
1105            .context("invalid ISO 8601 week date")?;
1106        Ok(Some(wd.date()))
1107    }
1108
1109    #[inline]
1110    fn to_date_from_week_sun(
1111        &self,
1112        year: t::Year,
1113    ) -> Result<Option<Date>, Error> {
1114        let (Some(week), Some(weekday)) = (self.week_sun, self.weekday) else {
1115            return Ok(None);
1116        };
1117        let week = i16::from(week);
1118        let wday = i16::from(weekday.to_sunday_zero_offset());
1119        let first_of_year = Date::new_ranged(year, C(1).rinto(), C(1).rinto())
1120            .context("invalid date")?;
1121        let first_sunday = first_of_year
1122            .nth_weekday_of_month(1, Weekday::Sunday)
1123            .map(|d| d.day_of_year())
1124            .context("invalid date")?;
1125        let doy = if week == 0 {
1126            let days_before_first_sunday = 7 - wday;
1127            let doy = first_sunday
1128                .checked_sub(days_before_first_sunday)
1129                .ok_or_else(|| {
1130                    err!(
1131                        "weekday `{weekday:?}` is not valid for \
1132                         Sunday based week number `{week}` \
1133                         in year `{year}`",
1134                    )
1135                })?;
1136            if doy == 0 {
1137                return Err(err!(
1138                    "weekday `{weekday:?}` is not valid for \
1139                     Sunday based week number `{week}` \
1140                     in year `{year}`",
1141                ));
1142            }
1143            doy
1144        } else {
1145            let days_since_first_sunday = (week - 1) * 7 + wday;
1146            let doy = first_sunday + days_since_first_sunday;
1147            doy
1148        };
1149        let date = first_of_year
1150            .with()
1151            .day_of_year(doy)
1152            .build()
1153            .context("invalid date")?;
1154        Ok(Some(date))
1155    }
1156
1157    #[inline]
1158    fn to_date_from_week_mon(
1159        &self,
1160        year: t::Year,
1161    ) -> Result<Option<Date>, Error> {
1162        let (Some(week), Some(weekday)) = (self.week_mon, self.weekday) else {
1163            return Ok(None);
1164        };
1165        let week = i16::from(week);
1166        let wday = i16::from(weekday.to_monday_zero_offset());
1167        let first_of_year = Date::new_ranged(year, C(1).rinto(), C(1).rinto())
1168            .context("invalid date")?;
1169        let first_monday = first_of_year
1170            .nth_weekday_of_month(1, Weekday::Monday)
1171            .map(|d| d.day_of_year())
1172            .context("invalid date")?;
1173        let doy = if week == 0 {
1174            let days_before_first_monday = 7 - wday;
1175            let doy = first_monday
1176                .checked_sub(days_before_first_monday)
1177                .ok_or_else(|| {
1178                    err!(
1179                        "weekday `{weekday:?}` is not valid for \
1180                         Monday based week number `{week}` \
1181                         in year `{year}`",
1182                    )
1183                })?;
1184            if doy == 0 {
1185                return Err(err!(
1186                    "weekday `{weekday:?}` is not valid for \
1187                     Monday based week number `{week}` \
1188                     in year `{year}`",
1189                ));
1190            }
1191            doy
1192        } else {
1193            let days_since_first_monday = (week - 1) * 7 + wday;
1194            let doy = first_monday + days_since_first_monday;
1195            doy
1196        };
1197        let date = first_of_year
1198            .with()
1199            .day_of_year(doy)
1200            .build()
1201            .context("invalid date")?;
1202        Ok(Some(date))
1203    }
1204
1205    /// Extracts a civil time from this broken down time.
1206    ///
1207    /// # Errors
1208    ///
1209    /// This returns an error if there weren't enough components to construct
1210    /// a civil time. Interestingly, this succeeds if there are no time units,
1211    /// since this will assume an absent time is midnight. However, this can
1212    /// still error when, for example, there are minutes but no hours.
1213    ///
1214    /// It's okay if there are more units than are needed to construct a civil
1215    /// time. For example, if this broken down time contains a date, then it
1216    /// won't prevent a conversion to a civil time.
1217    ///
1218    /// # Example
1219    ///
1220    /// This example shows how to parse a civil time from a broken down
1221    /// time:
1222    ///
1223    /// ```
1224    /// use jiff::fmt::strtime;
1225    ///
1226    /// let time = strtime::parse("%H:%M:%S", "21:14:59")?.to_time()?;
1227    /// assert_eq!(time.to_string(), "21:14:59");
1228    ///
1229    /// # Ok::<(), Box<dyn std::error::Error>>(())
1230    /// ```
1231    ///
1232    /// # Example: time defaults to midnight
1233    ///
1234    /// Since time defaults to midnight, one can parse an empty input string
1235    /// with an empty format string and still extract a `Time`:
1236    ///
1237    /// ```
1238    /// use jiff::fmt::strtime;
1239    ///
1240    /// let time = strtime::parse("", "")?.to_time()?;
1241    /// assert_eq!(time.to_string(), "00:00:00");
1242    ///
1243    /// # Ok::<(), Box<dyn std::error::Error>>(())
1244    /// ```
1245    ///
1246    /// # Example: invalid time
1247    ///
1248    /// Other than using illegal values (like `24` for hours), if lower units
1249    /// are parsed without higher units, then this results in an error:
1250    ///
1251    /// ```
1252    /// use jiff::fmt::strtime;
1253    ///
1254    /// assert!(strtime::parse("%M:%S", "15:36")?.to_time().is_err());
1255    ///
1256    /// # Ok::<(), Box<dyn std::error::Error>>(())
1257    /// ```
1258    ///
1259    /// # Example: invalid date
1260    ///
1261    /// Since validation of a date is only done when a date is requested, it is
1262    /// actually possible to parse an invalid date and extract the time without
1263    /// an error occurring:
1264    ///
1265    /// ```
1266    /// use jiff::fmt::strtime;
1267    ///
1268    /// // 31 is a legal day value, but not for June.
1269    /// // However, this is not validated unless you
1270    /// // ask for a `Date` from the parsed `BrokenDownTime`.
1271    /// // Everything except for `BrokenDownTime::time`
1272    /// // creates a date, so asking for only a `time`
1273    /// // will circumvent date validation!
1274    /// let tm = strtime::parse("%Y-%m-%d %H:%M:%S", "2024-06-31 21:14:59")?;
1275    /// let time = tm.to_time()?;
1276    /// assert_eq!(time.to_string(), "21:14:59");
1277    ///
1278    /// # Ok::<(), Box<dyn std::error::Error>>(())
1279    /// ```
1280    #[inline]
1281    pub fn to_time(&self) -> Result<Time, Error> {
1282        let Some(hour) = self.hour_ranged() else {
1283            if self.minute.is_some() {
1284                return Err(err!(
1285                    "parsing format did not include hour directive, \
1286                     but did include minute directive (cannot have \
1287                     smaller time units with bigger time units missing)",
1288                ));
1289            }
1290            if self.second.is_some() {
1291                return Err(err!(
1292                    "parsing format did not include hour directive, \
1293                     but did include second directive (cannot have \
1294                     smaller time units with bigger time units missing)",
1295                ));
1296            }
1297            if self.subsec.is_some() {
1298                return Err(err!(
1299                    "parsing format did not include hour directive, \
1300                     but did include fractional second directive (cannot have \
1301                     smaller time units with bigger time units missing)",
1302                ));
1303            }
1304            return Ok(Time::midnight());
1305        };
1306        let Some(minute) = self.minute else {
1307            if self.second.is_some() {
1308                return Err(err!(
1309                    "parsing format did not include minute directive, \
1310                     but did include second directive (cannot have \
1311                     smaller time units with bigger time units missing)",
1312                ));
1313            }
1314            if self.subsec.is_some() {
1315                return Err(err!(
1316                    "parsing format did not include minute directive, \
1317                     but did include fractional second directive (cannot have \
1318                     smaller time units with bigger time units missing)",
1319                ));
1320            }
1321            return Ok(Time::new_ranged(hour, C(0), C(0), C(0)));
1322        };
1323        let Some(second) = self.second else {
1324            if self.subsec.is_some() {
1325                return Err(err!(
1326                    "parsing format did not include second directive, \
1327                     but did include fractional second directive (cannot have \
1328                     smaller time units with bigger time units missing)",
1329                ));
1330            }
1331            return Ok(Time::new_ranged(hour, minute, C(0), C(0)));
1332        };
1333        let Some(subsec) = self.subsec else {
1334            return Ok(Time::new_ranged(hour, minute, second, C(0)));
1335        };
1336        Ok(Time::new_ranged(hour, minute, second, subsec))
1337    }
1338
1339    /// Returns the parsed year, if available.
1340    ///
1341    /// This is also set when a 2 digit year is parsed. (But that's limited to
1342    /// the years 1969 to 2068, inclusive.)
1343    ///
1344    /// # Example
1345    ///
1346    /// This shows how to parse just a year:
1347    ///
1348    /// ```
1349    /// use jiff::fmt::strtime::BrokenDownTime;
1350    ///
1351    /// let tm = BrokenDownTime::parse("%Y", "2024")?;
1352    /// assert_eq!(tm.year(), Some(2024));
1353    ///
1354    /// # Ok::<(), Box<dyn std::error::Error>>(())
1355    /// ```
1356    ///
1357    /// And 2-digit years are supported too:
1358    ///
1359    /// ```
1360    /// use jiff::fmt::strtime::BrokenDownTime;
1361    ///
1362    /// let tm = BrokenDownTime::parse("%y", "24")?;
1363    /// assert_eq!(tm.year(), Some(2024));
1364    /// let tm = BrokenDownTime::parse("%y", "00")?;
1365    /// assert_eq!(tm.year(), Some(2000));
1366    /// let tm = BrokenDownTime::parse("%y", "69")?;
1367    /// assert_eq!(tm.year(), Some(1969));
1368    ///
1369    /// // 2-digit years have limited range. They must
1370    /// // be in the range 0-99.
1371    /// assert!(BrokenDownTime::parse("%y", "2024").is_err());
1372    ///
1373    /// # Ok::<(), Box<dyn std::error::Error>>(())
1374    /// ```
1375    #[inline]
1376    pub fn year(&self) -> Option<i16> {
1377        self.year.map(|x| x.get())
1378    }
1379
1380    /// Returns the parsed month, if available.
1381    ///
1382    /// # Example
1383    ///
1384    /// This shows a few different ways of parsing just a month:
1385    ///
1386    /// ```
1387    /// use jiff::fmt::strtime::BrokenDownTime;
1388    ///
1389    /// let tm = BrokenDownTime::parse("%m", "12")?;
1390    /// assert_eq!(tm.month(), Some(12));
1391    ///
1392    /// let tm = BrokenDownTime::parse("%B", "December")?;
1393    /// assert_eq!(tm.month(), Some(12));
1394    ///
1395    /// let tm = BrokenDownTime::parse("%b", "Dec")?;
1396    /// assert_eq!(tm.month(), Some(12));
1397    ///
1398    /// # Ok::<(), Box<dyn std::error::Error>>(())
1399    /// ```
1400    #[inline]
1401    pub fn month(&self) -> Option<i8> {
1402        self.month.map(|x| x.get())
1403    }
1404
1405    /// Returns the parsed day, if available.
1406    ///
1407    /// # Example
1408    ///
1409    /// This shows how to parse the day of the month:
1410    ///
1411    /// ```
1412    /// use jiff::fmt::strtime::BrokenDownTime;
1413    ///
1414    /// let tm = BrokenDownTime::parse("%d", "5")?;
1415    /// assert_eq!(tm.day(), Some(5));
1416    ///
1417    /// let tm = BrokenDownTime::parse("%d", "05")?;
1418    /// assert_eq!(tm.day(), Some(5));
1419    ///
1420    /// let tm = BrokenDownTime::parse("%03d", "005")?;
1421    /// assert_eq!(tm.day(), Some(5));
1422    ///
1423    /// // Parsing a day only works for all possible legal
1424    /// // values, even if, e.g., 31 isn't valid for all
1425    /// // possible year/month combinations.
1426    /// let tm = BrokenDownTime::parse("%d", "31")?;
1427    /// assert_eq!(tm.day(), Some(31));
1428    /// // This is true even if you're parsing a full date:
1429    /// let tm = BrokenDownTime::parse("%Y-%m-%d", "2024-04-31")?;
1430    /// assert_eq!(tm.day(), Some(31));
1431    /// // An error only occurs when you try to extract a date:
1432    /// assert!(tm.to_date().is_err());
1433    /// // But parsing a value that is always illegal will
1434    /// // result in an error:
1435    /// assert!(BrokenDownTime::parse("%d", "32").is_err());
1436    ///
1437    /// # Ok::<(), Box<dyn std::error::Error>>(())
1438    /// ```
1439    #[inline]
1440    pub fn day(&self) -> Option<i8> {
1441        self.day.map(|x| x.get())
1442    }
1443
1444    /// Returns the parsed day of the year (1-366), if available.
1445    ///
1446    /// # Example
1447    ///
1448    /// This shows how to parse the day of the year:
1449    ///
1450    /// ```
1451    /// use jiff::fmt::strtime::BrokenDownTime;
1452    ///
1453    /// let tm = BrokenDownTime::parse("%j", "5")?;
1454    /// assert_eq!(tm.day_of_year(), Some(5));
1455    /// assert_eq!(tm.to_string("%j")?, "005");
1456    /// assert_eq!(tm.to_string("%-j")?, "5");
1457    ///
1458    /// // Parsing the day of the year works for all possible legal
1459    /// // values, even if, e.g., 366 isn't valid for all possible
1460    /// // year/month combinations.
1461    /// let tm = BrokenDownTime::parse("%j", "366")?;
1462    /// assert_eq!(tm.day_of_year(), Some(366));
1463    /// // This is true even if you're parsing a year:
1464    /// let tm = BrokenDownTime::parse("%Y/%j", "2023/366")?;
1465    /// assert_eq!(tm.day_of_year(), Some(366));
1466    /// // An error only occurs when you try to extract a date:
1467    /// assert_eq!(
1468    ///     tm.to_date().unwrap_err().to_string(),
1469    ///     "invalid date: day-of-year=366 is out of range \
1470    ///      for year=2023, must be in range 1..=365",
1471    /// );
1472    /// // But parsing a value that is always illegal will
1473    /// // result in an error:
1474    /// assert!(BrokenDownTime::parse("%j", "0").is_err());
1475    /// assert!(BrokenDownTime::parse("%j", "367").is_err());
1476    ///
1477    /// # Ok::<(), Box<dyn std::error::Error>>(())
1478    /// ```
1479    ///
1480    /// # Example: extract a [`Date`]
1481    ///
1482    /// This example shows how parsing a year and a day of the year enables
1483    /// the extraction of a date:
1484    ///
1485    /// ```
1486    /// use jiff::{civil::date, fmt::strtime::BrokenDownTime};
1487    ///
1488    /// let tm = BrokenDownTime::parse("%Y-%j", "2024-60")?;
1489    /// assert_eq!(tm.to_date()?, date(2024, 2, 29));
1490    ///
1491    /// # Ok::<(), Box<dyn std::error::Error>>(())
1492    /// ```
1493    ///
1494    /// When all of `%m`, `%d` and `%j` are used, then `%m` and `%d` take
1495    /// priority over `%j` when extracting a `Date` from a `BrokenDownTime`.
1496    /// However, `%j` is still parsed and accessible:
1497    ///
1498    /// ```
1499    /// use jiff::{civil::date, fmt::strtime::BrokenDownTime};
1500    ///
1501    /// let tm = BrokenDownTime::parse(
1502    ///     "%Y-%m-%d (day of year: %j)",
1503    ///     "2024-02-29 (day of year: 1)",
1504    /// )?;
1505    /// assert_eq!(tm.to_date()?, date(2024, 2, 29));
1506    /// assert_eq!(tm.day_of_year(), Some(1));
1507    ///
1508    /// # Ok::<(), Box<dyn std::error::Error>>(())
1509    /// ```
1510    #[inline]
1511    pub fn day_of_year(&self) -> Option<i16> {
1512        self.day_of_year.map(|x| x.get())
1513    }
1514
1515    /// Returns the parsed ISO 8601 week-based year, if available.
1516    ///
1517    /// This is also set when a 2 digit ISO 8601 week-based year is parsed.
1518    /// (But that's limited to the years 1969 to 2068, inclusive.)
1519    ///
1520    /// # Example
1521    ///
1522    /// This shows how to parse just an ISO 8601 week-based year:
1523    ///
1524    /// ```
1525    /// use jiff::fmt::strtime::BrokenDownTime;
1526    ///
1527    /// let tm = BrokenDownTime::parse("%G", "2024")?;
1528    /// assert_eq!(tm.iso_week_year(), Some(2024));
1529    ///
1530    /// # Ok::<(), Box<dyn std::error::Error>>(())
1531    /// ```
1532    ///
1533    /// And 2-digit years are supported too:
1534    ///
1535    /// ```
1536    /// use jiff::fmt::strtime::BrokenDownTime;
1537    ///
1538    /// let tm = BrokenDownTime::parse("%g", "24")?;
1539    /// assert_eq!(tm.iso_week_year(), Some(2024));
1540    /// let tm = BrokenDownTime::parse("%g", "00")?;
1541    /// assert_eq!(tm.iso_week_year(), Some(2000));
1542    /// let tm = BrokenDownTime::parse("%g", "69")?;
1543    /// assert_eq!(tm.iso_week_year(), Some(1969));
1544    ///
1545    /// // 2-digit years have limited range. They must
1546    /// // be in the range 0-99.
1547    /// assert!(BrokenDownTime::parse("%g", "2024").is_err());
1548    ///
1549    /// # Ok::<(), Box<dyn std::error::Error>>(())
1550    /// ```
1551    #[inline]
1552    pub fn iso_week_year(&self) -> Option<i16> {
1553        self.iso_week_year.map(|x| x.get())
1554    }
1555
1556    /// Returns the parsed ISO 8601 week-based number, if available.
1557    ///
1558    /// The week number is guaranteed to be in the range `1..53`. Week `1` is
1559    /// the first week of the year to contain 4 days.
1560    ///
1561    ///
1562    /// # Example
1563    ///
1564    /// This shows how to parse just an ISO 8601 week-based dates:
1565    ///
1566    /// ```
1567    /// use jiff::{civil::{Weekday, date}, fmt::strtime::BrokenDownTime};
1568    ///
1569    /// let tm = BrokenDownTime::parse("%G-W%V-%u", "2020-W01-1")?;
1570    /// assert_eq!(tm.iso_week_year(), Some(2020));
1571    /// assert_eq!(tm.iso_week(), Some(1));
1572    /// assert_eq!(tm.weekday(), Some(Weekday::Monday));
1573    /// assert_eq!(tm.to_date()?, date(2019, 12, 30));
1574    ///
1575    /// # Ok::<(), Box<dyn std::error::Error>>(())
1576    /// ```
1577    #[inline]
1578    pub fn iso_week(&self) -> Option<i8> {
1579        self.iso_week.map(|x| x.get())
1580    }
1581
1582    /// Returns the Sunday based week number.
1583    ///
1584    /// The week number returned is always in the range `0..=53`. Week `1`
1585    /// begins on the first Sunday of the year. Any days in the year prior to
1586    /// week `1` are in week `0`.
1587    ///
1588    /// # Example
1589    ///
1590    /// ```
1591    /// use jiff::{civil::{Weekday, date}, fmt::strtime::BrokenDownTime};
1592    ///
1593    /// let tm = BrokenDownTime::parse("%Y-%U-%w", "2025-01-0")?;
1594    /// assert_eq!(tm.year(), Some(2025));
1595    /// assert_eq!(tm.sunday_based_week(), Some(1));
1596    /// assert_eq!(tm.weekday(), Some(Weekday::Sunday));
1597    /// assert_eq!(tm.to_date()?, date(2025, 1, 5));
1598    ///
1599    /// # Ok::<(), Box<dyn std::error::Error>>(())
1600    /// ```
1601    #[inline]
1602    pub fn sunday_based_week(&self) -> Option<i8> {
1603        self.week_sun.map(|x| x.get())
1604    }
1605
1606    /// Returns the Monday based week number.
1607    ///
1608    /// The week number returned is always in the range `0..=53`. Week `1`
1609    /// begins on the first Monday of the year. Any days in the year prior to
1610    /// week `1` are in week `0`.
1611    ///
1612    /// # Example
1613    ///
1614    /// ```
1615    /// use jiff::{civil::{Weekday, date}, fmt::strtime::BrokenDownTime};
1616    ///
1617    /// let tm = BrokenDownTime::parse("%Y-%U-%w", "2025-01-1")?;
1618    /// assert_eq!(tm.year(), Some(2025));
1619    /// assert_eq!(tm.sunday_based_week(), Some(1));
1620    /// assert_eq!(tm.weekday(), Some(Weekday::Monday));
1621    /// assert_eq!(tm.to_date()?, date(2025, 1, 6));
1622    ///
1623    /// # Ok::<(), Box<dyn std::error::Error>>(())
1624    /// ```
1625    #[inline]
1626    pub fn monday_based_week(&self) -> Option<i8> {
1627        self.week_mon.map(|x| x.get())
1628    }
1629
1630    /// Returns the parsed hour, if available.
1631    ///
1632    /// The hour returned incorporates [`BrokenDownTime::meridiem`] if it's
1633    /// set. That is, if the actual parsed hour value is `1` but the meridiem
1634    /// is `PM`, then the hour returned by this method will be `13`.
1635    ///
1636    /// # Example
1637    ///
1638    /// This shows a how to parse an hour:
1639    ///
1640    /// ```
1641    /// use jiff::fmt::strtime::BrokenDownTime;
1642    ///
1643    /// let tm = BrokenDownTime::parse("%H", "13")?;
1644    /// assert_eq!(tm.hour(), Some(13));
1645    ///
1646    /// // When parsing a 12-hour clock without a
1647    /// // meridiem, the hour value is as parsed.
1648    /// let tm = BrokenDownTime::parse("%I", "1")?;
1649    /// assert_eq!(tm.hour(), Some(1));
1650    ///
1651    /// // If a meridiem is parsed, then it is used
1652    /// // to calculate the correct hour value.
1653    /// let tm = BrokenDownTime::parse("%I%P", "1pm")?;
1654    /// assert_eq!(tm.hour(), Some(13));
1655    ///
1656    /// // This works even if the hour and meridiem are
1657    /// // inconsistent with each other:
1658    /// let tm = BrokenDownTime::parse("%H%P", "13am")?;
1659    /// assert_eq!(tm.hour(), Some(1));
1660    ///
1661    /// # Ok::<(), Box<dyn std::error::Error>>(())
1662    /// ```
1663    #[inline]
1664    pub fn hour(&self) -> Option<i8> {
1665        self.hour_ranged().map(|x| x.get())
1666    }
1667
1668    #[inline]
1669    fn hour_ranged(&self) -> Option<t::Hour> {
1670        let hour = self.hour?;
1671        Some(match self.meridiem() {
1672            None => hour,
1673            Some(Meridiem::AM) => hour % C(12),
1674            Some(Meridiem::PM) => (hour % C(12)) + C(12),
1675        })
1676    }
1677
1678    /// Returns the parsed minute, if available.
1679    ///
1680    /// # Example
1681    ///
1682    /// This shows how to parse the minute:
1683    ///
1684    /// ```
1685    /// use jiff::fmt::strtime::BrokenDownTime;
1686    ///
1687    /// let tm = BrokenDownTime::parse("%M", "5")?;
1688    /// assert_eq!(tm.minute(), Some(5));
1689    ///
1690    /// # Ok::<(), Box<dyn std::error::Error>>(())
1691    /// ```
1692    #[inline]
1693    pub fn minute(&self) -> Option<i8> {
1694        self.minute.map(|x| x.get())
1695    }
1696
1697    /// Returns the parsed second, if available.
1698    ///
1699    /// # Example
1700    ///
1701    /// This shows how to parse the second:
1702    ///
1703    /// ```
1704    /// use jiff::fmt::strtime::BrokenDownTime;
1705    ///
1706    /// let tm = BrokenDownTime::parse("%S", "5")?;
1707    /// assert_eq!(tm.second(), Some(5));
1708    ///
1709    /// # Ok::<(), Box<dyn std::error::Error>>(())
1710    /// ```
1711    #[inline]
1712    pub fn second(&self) -> Option<i8> {
1713        self.second.map(|x| x.get())
1714    }
1715
1716    /// Returns the parsed subsecond nanosecond, if available.
1717    ///
1718    /// # Example
1719    ///
1720    /// This shows how to parse fractional seconds:
1721    ///
1722    /// ```
1723    /// use jiff::fmt::strtime::BrokenDownTime;
1724    ///
1725    /// let tm = BrokenDownTime::parse("%f", "123456")?;
1726    /// assert_eq!(tm.subsec_nanosecond(), Some(123_456_000));
1727    ///
1728    /// # Ok::<(), Box<dyn std::error::Error>>(())
1729    /// ```
1730    ///
1731    /// Note that when using `%.f`, the fractional component is optional!
1732    ///
1733    /// ```
1734    /// use jiff::fmt::strtime::BrokenDownTime;
1735    ///
1736    /// let tm = BrokenDownTime::parse("%S%.f", "1")?;
1737    /// assert_eq!(tm.second(), Some(1));
1738    /// assert_eq!(tm.subsec_nanosecond(), None);
1739    ///
1740    /// let tm = BrokenDownTime::parse("%S%.f", "1.789")?;
1741    /// assert_eq!(tm.second(), Some(1));
1742    /// assert_eq!(tm.subsec_nanosecond(), Some(789_000_000));
1743    ///
1744    /// # Ok::<(), Box<dyn std::error::Error>>(())
1745    /// ```
1746    #[inline]
1747    pub fn subsec_nanosecond(&self) -> Option<i32> {
1748        self.subsec.map(|x| x.get())
1749    }
1750
1751    /// Returns the parsed offset, if available.
1752    ///
1753    /// # Example
1754    ///
1755    /// This shows how to parse the offset:
1756    ///
1757    /// ```
1758    /// use jiff::{fmt::strtime::BrokenDownTime, tz::Offset};
1759    ///
1760    /// let tm = BrokenDownTime::parse("%z", "-0430")?;
1761    /// assert_eq!(
1762    ///     tm.offset(),
1763    ///     Some(Offset::from_seconds(-4 * 60 * 60 - 30 * 60).unwrap()),
1764    /// );
1765    /// let tm = BrokenDownTime::parse("%z", "-043059")?;
1766    /// assert_eq!(
1767    ///     tm.offset(),
1768    ///     Some(Offset::from_seconds(-4 * 60 * 60 - 30 * 60 - 59).unwrap()),
1769    /// );
1770    ///
1771    /// // Or, if you want colons:
1772    /// let tm = BrokenDownTime::parse("%:z", "-04:30")?;
1773    /// assert_eq!(
1774    ///     tm.offset(),
1775    ///     Some(Offset::from_seconds(-4 * 60 * 60 - 30 * 60).unwrap()),
1776    /// );
1777    ///
1778    /// # Ok::<(), Box<dyn std::error::Error>>(())
1779    /// ```
1780    #[inline]
1781    pub fn offset(&self) -> Option<Offset> {
1782        self.offset
1783    }
1784
1785    /// Returns the time zone IANA identifier, if available.
1786    ///
1787    /// Note that when `alloc` is disabled, this always returns `None`. (And
1788    /// there is no way to set it.)
1789    ///
1790    /// # Example
1791    ///
1792    /// This shows how to parse an IANA time zone identifier:
1793    ///
1794    /// ```
1795    /// use jiff::{fmt::strtime::BrokenDownTime, tz};
1796    ///
1797    /// let tm = BrokenDownTime::parse("%Q", "US/Eastern")?;
1798    /// assert_eq!(tm.iana_time_zone(), Some("US/Eastern"));
1799    /// assert_eq!(tm.offset(), None);
1800    ///
1801    /// // Note that %Q (and %:Q) also support parsing an offset
1802    /// // as a fallback. If that occurs, an IANA time zone
1803    /// // identifier is not available.
1804    /// let tm = BrokenDownTime::parse("%Q", "-0400")?;
1805    /// assert_eq!(tm.iana_time_zone(), None);
1806    /// assert_eq!(tm.offset(), Some(tz::offset(-4)));
1807    ///
1808    /// # Ok::<(), Box<dyn std::error::Error>>(())
1809    /// ```
1810    #[inline]
1811    pub fn iana_time_zone(&self) -> Option<&str> {
1812        #[cfg(feature = "alloc")]
1813        {
1814            self.iana.as_deref()
1815        }
1816        #[cfg(not(feature = "alloc"))]
1817        {
1818            None
1819        }
1820    }
1821
1822    /// Returns the parsed weekday, if available.
1823    ///
1824    /// # Example
1825    ///
1826    /// This shows a few different ways of parsing just a weekday:
1827    ///
1828    /// ```
1829    /// use jiff::{civil::Weekday, fmt::strtime::BrokenDownTime};
1830    ///
1831    /// let tm = BrokenDownTime::parse("%A", "Saturday")?;
1832    /// assert_eq!(tm.weekday(), Some(Weekday::Saturday));
1833    ///
1834    /// let tm = BrokenDownTime::parse("%a", "Sat")?;
1835    /// assert_eq!(tm.weekday(), Some(Weekday::Saturday));
1836    ///
1837    /// // A weekday is only available if it is explicitly parsed!
1838    /// let tm = BrokenDownTime::parse("%F", "2024-07-27")?;
1839    /// assert_eq!(tm.weekday(), None);
1840    /// // If you need a weekday derived from a parsed date, then:
1841    /// assert_eq!(tm.to_date()?.weekday(), Weekday::Saturday);
1842    ///
1843    /// # Ok::<(), Box<dyn std::error::Error>>(())
1844    /// ```
1845    ///
1846    /// Note that this will return the parsed weekday even if
1847    /// it's inconsistent with a parsed date:
1848    ///
1849    /// ```
1850    /// use jiff::{civil::{Weekday, date}, fmt::strtime::BrokenDownTime};
1851    ///
1852    /// let mut tm = BrokenDownTime::parse("%a, %F", "Wed, 2024-07-27")?;
1853    /// // 2024-07-27 is a Saturday, but Wednesday was parsed:
1854    /// assert_eq!(tm.weekday(), Some(Weekday::Wednesday));
1855    /// // An error only occurs when extracting a date:
1856    /// assert!(tm.to_date().is_err());
1857    /// // To skip the weekday, error checking, zero it out first:
1858    /// tm.set_weekday(None);
1859    /// assert_eq!(tm.to_date()?, date(2024, 7, 27));
1860    ///
1861    /// # Ok::<(), Box<dyn std::error::Error>>(())
1862    /// ```
1863    #[inline]
1864    pub fn weekday(&self) -> Option<Weekday> {
1865        self.weekday
1866    }
1867
1868    /// Returns the parsed meridiem, if available.
1869    ///
1870    /// Note that unlike other fields, there is no
1871    /// `BrokenDownTime::set_meridiem`. Instead, when formatting, the meridiem
1872    /// label (if it's used in the formatting string) is determined purely as a
1873    /// function of the hour in a 24 hour clock.
1874    ///
1875    /// # Example
1876    ///
1877    /// This shows a how to parse the meridiem:
1878    ///
1879    /// ```
1880    /// use jiff::fmt::strtime::{BrokenDownTime, Meridiem};
1881    ///
1882    /// let tm = BrokenDownTime::parse("%p", "AM")?;
1883    /// assert_eq!(tm.meridiem(), Some(Meridiem::AM));
1884    /// let tm = BrokenDownTime::parse("%P", "pm")?;
1885    /// assert_eq!(tm.meridiem(), Some(Meridiem::PM));
1886    ///
1887    /// # Ok::<(), Box<dyn std::error::Error>>(())
1888    /// ```
1889    #[inline]
1890    pub fn meridiem(&self) -> Option<Meridiem> {
1891        self.meridiem
1892    }
1893
1894    /// Set the year on this broken down time.
1895    ///
1896    /// # Errors
1897    ///
1898    /// This returns an error if the given year is out of range.
1899    ///
1900    /// # Example
1901    ///
1902    /// ```
1903    /// use jiff::fmt::strtime::BrokenDownTime;
1904    ///
1905    /// let mut tm = BrokenDownTime::default();
1906    /// // out of range
1907    /// assert!(tm.set_year(Some(10_000)).is_err());
1908    /// tm.set_year(Some(2024))?;
1909    /// assert_eq!(tm.to_string("%Y")?, "2024");
1910    ///
1911    /// # Ok::<(), Box<dyn std::error::Error>>(())
1912    /// ```
1913    #[inline]
1914    pub fn set_year(&mut self, year: Option<i16>) -> Result<(), Error> {
1915        self.year = match year {
1916            None => None,
1917            Some(year) => Some(t::Year::try_new("year", year)?),
1918        };
1919        Ok(())
1920    }
1921
1922    /// Set the month on this broken down time.
1923    ///
1924    /// # Errors
1925    ///
1926    /// This returns an error if the given month is out of range.
1927    ///
1928    /// # Example
1929    ///
1930    /// ```
1931    /// use jiff::fmt::strtime::BrokenDownTime;
1932    ///
1933    /// let mut tm = BrokenDownTime::default();
1934    /// // out of range
1935    /// assert!(tm.set_month(Some(0)).is_err());
1936    /// tm.set_month(Some(12))?;
1937    /// assert_eq!(tm.to_string("%B")?, "December");
1938    ///
1939    /// # Ok::<(), Box<dyn std::error::Error>>(())
1940    /// ```
1941    #[inline]
1942    pub fn set_month(&mut self, month: Option<i8>) -> Result<(), Error> {
1943        self.month = match month {
1944            None => None,
1945            Some(month) => Some(t::Month::try_new("month", month)?),
1946        };
1947        Ok(())
1948    }
1949
1950    /// Set the day on this broken down time.
1951    ///
1952    /// # Errors
1953    ///
1954    /// This returns an error if the given day is out of range.
1955    ///
1956    /// Note that setting a day to a value that is legal in any context is
1957    /// always valid, even if it isn't valid for the year and month
1958    /// components already set.
1959    ///
1960    /// # Example
1961    ///
1962    /// ```
1963    /// use jiff::fmt::strtime::BrokenDownTime;
1964    ///
1965    /// let mut tm = BrokenDownTime::default();
1966    /// // out of range
1967    /// assert!(tm.set_day(Some(32)).is_err());
1968    /// tm.set_day(Some(31))?;
1969    /// assert_eq!(tm.to_string("%d")?, "31");
1970    ///
1971    /// // Works even if the resulting date is invalid.
1972    /// let mut tm = BrokenDownTime::default();
1973    /// tm.set_year(Some(2024))?;
1974    /// tm.set_month(Some(4))?;
1975    /// tm.set_day(Some(31))?; // April has 30 days, not 31
1976    /// assert_eq!(tm.to_string("%F")?, "2024-04-31");
1977    ///
1978    /// # Ok::<(), Box<dyn std::error::Error>>(())
1979    /// ```
1980    #[inline]
1981    pub fn set_day(&mut self, day: Option<i8>) -> Result<(), Error> {
1982        self.day = match day {
1983            None => None,
1984            Some(day) => Some(t::Day::try_new("day", day)?),
1985        };
1986        Ok(())
1987    }
1988
1989    /// Set the day of year on this broken down time.
1990    ///
1991    /// # Errors
1992    ///
1993    /// This returns an error if the given day is out of range.
1994    ///
1995    /// Note that setting a day to a value that is legal in any context
1996    /// is always valid, even if it isn't valid for the year, month and
1997    /// day-of-month components already set.
1998    ///
1999    /// # Example
2000    ///
2001    /// ```
2002    /// use jiff::fmt::strtime::BrokenDownTime;
2003    ///
2004    /// let mut tm = BrokenDownTime::default();
2005    /// // out of range
2006    /// assert!(tm.set_day_of_year(Some(367)).is_err());
2007    /// tm.set_day_of_year(Some(31))?;
2008    /// assert_eq!(tm.to_string("%j")?, "031");
2009    ///
2010    /// // Works even if the resulting date is invalid.
2011    /// let mut tm = BrokenDownTime::default();
2012    /// tm.set_year(Some(2023))?;
2013    /// tm.set_day_of_year(Some(366))?; // 2023 wasn't a leap year
2014    /// assert_eq!(tm.to_string("%Y/%j")?, "2023/366");
2015    ///
2016    /// # Ok::<(), Box<dyn std::error::Error>>(())
2017    /// ```
2018    #[inline]
2019    pub fn set_day_of_year(&mut self, day: Option<i16>) -> Result<(), Error> {
2020        self.day_of_year = match day {
2021            None => None,
2022            Some(day) => Some(t::DayOfYear::try_new("day-of-year", day)?),
2023        };
2024        Ok(())
2025    }
2026
2027    /// Set the ISO 8601 week-based year on this broken down time.
2028    ///
2029    /// # Errors
2030    ///
2031    /// This returns an error if the given year is out of range.
2032    ///
2033    /// # Example
2034    ///
2035    /// ```
2036    /// use jiff::fmt::strtime::BrokenDownTime;
2037    ///
2038    /// let mut tm = BrokenDownTime::default();
2039    /// // out of range
2040    /// assert!(tm.set_iso_week_year(Some(10_000)).is_err());
2041    /// tm.set_iso_week_year(Some(2024))?;
2042    /// assert_eq!(tm.to_string("%G")?, "2024");
2043    ///
2044    /// # Ok::<(), Box<dyn std::error::Error>>(())
2045    /// ```
2046    #[inline]
2047    pub fn set_iso_week_year(
2048        &mut self,
2049        year: Option<i16>,
2050    ) -> Result<(), Error> {
2051        self.iso_week_year = match year {
2052            None => None,
2053            Some(year) => Some(t::ISOYear::try_new("year", year)?),
2054        };
2055        Ok(())
2056    }
2057
2058    /// Set the ISO 8601 week-based number on this broken down time.
2059    ///
2060    /// The week number must be in the range `1..53`. Week `1` is
2061    /// the first week of the year to contain 4 days.
2062    ///
2063    /// # Errors
2064    ///
2065    /// This returns an error if the given week number is out of range.
2066    ///
2067    /// # Example
2068    ///
2069    /// ```
2070    /// use jiff::{civil::Weekday, fmt::strtime::BrokenDownTime};
2071    ///
2072    /// let mut tm = BrokenDownTime::default();
2073    /// // out of range
2074    /// assert!(tm.set_iso_week(Some(0)).is_err());
2075    /// // out of range
2076    /// assert!(tm.set_iso_week(Some(54)).is_err());
2077    ///
2078    /// tm.set_iso_week_year(Some(2020))?;
2079    /// tm.set_iso_week(Some(1))?;
2080    /// tm.set_weekday(Some(Weekday::Monday));
2081    /// assert_eq!(tm.to_string("%G-W%V-%u")?, "2020-W01-1");
2082    /// assert_eq!(tm.to_string("%F")?, "2019-12-30");
2083    ///
2084    /// # Ok::<(), Box<dyn std::error::Error>>(())
2085    /// ```
2086    #[inline]
2087    pub fn set_iso_week(
2088        &mut self,
2089        week_number: Option<i8>,
2090    ) -> Result<(), Error> {
2091        self.iso_week = match week_number {
2092            None => None,
2093            Some(wk) => Some(t::ISOWeek::try_new("week-number", wk)?),
2094        };
2095        Ok(())
2096    }
2097
2098    /// Set the Sunday based week number.
2099    ///
2100    /// The week number returned is always in the range `0..=53`. Week `1`
2101    /// begins on the first Sunday of the year. Any days in the year prior to
2102    /// week `1` are in week `0`.
2103    ///
2104    /// # Example
2105    ///
2106    /// ```
2107    /// use jiff::fmt::strtime::BrokenDownTime;
2108    ///
2109    /// let mut tm = BrokenDownTime::default();
2110    /// // out of range
2111    /// assert!(tm.set_sunday_based_week(Some(56)).is_err());
2112    /// tm.set_sunday_based_week(Some(9))?;
2113    /// assert_eq!(tm.to_string("%U")?, "09");
2114    ///
2115    /// # Ok::<(), Box<dyn std::error::Error>>(())
2116    /// ```
2117    #[inline]
2118    pub fn set_sunday_based_week(
2119        &mut self,
2120        week_number: Option<i8>,
2121    ) -> Result<(), Error> {
2122        self.week_sun = match week_number {
2123            None => None,
2124            Some(wk) => Some(t::WeekNum::try_new("week-number", wk)?),
2125        };
2126        Ok(())
2127    }
2128
2129    /// Set the Monday based week number.
2130    ///
2131    /// The week number returned is always in the range `0..=53`. Week `1`
2132    /// begins on the first Monday of the year. Any days in the year prior to
2133    /// week `1` are in week `0`.
2134    ///
2135    /// # Example
2136    ///
2137    /// ```
2138    /// use jiff::fmt::strtime::BrokenDownTime;
2139    ///
2140    /// let mut tm = BrokenDownTime::default();
2141    /// // out of range
2142    /// assert!(tm.set_monday_based_week(Some(56)).is_err());
2143    /// tm.set_monday_based_week(Some(9))?;
2144    /// assert_eq!(tm.to_string("%W")?, "09");
2145    ///
2146    /// # Ok::<(), Box<dyn std::error::Error>>(())
2147    /// ```
2148    #[inline]
2149    pub fn set_monday_based_week(
2150        &mut self,
2151        week_number: Option<i8>,
2152    ) -> Result<(), Error> {
2153        self.week_mon = match week_number {
2154            None => None,
2155            Some(wk) => Some(t::WeekNum::try_new("week-number", wk)?),
2156        };
2157        Ok(())
2158    }
2159
2160    /// Set the hour on this broken down time.
2161    ///
2162    /// # Errors
2163    ///
2164    /// This returns an error if the given hour is out of range.
2165    ///
2166    /// # Example
2167    ///
2168    /// ```
2169    /// use jiff::fmt::strtime::BrokenDownTime;
2170    ///
2171    /// let mut tm = BrokenDownTime::default();
2172    /// // out of range
2173    /// assert!(tm.set_hour(Some(24)).is_err());
2174    /// tm.set_hour(Some(0))?;
2175    /// assert_eq!(tm.to_string("%H")?, "00");
2176    /// assert_eq!(tm.to_string("%-H")?, "0");
2177    ///
2178    /// # Ok::<(), Box<dyn std::error::Error>>(())
2179    /// ```
2180    #[inline]
2181    pub fn set_hour(&mut self, hour: Option<i8>) -> Result<(), Error> {
2182        self.hour = match hour {
2183            None => None,
2184            Some(hour) => Some(t::Hour::try_new("hour", hour)?),
2185        };
2186        Ok(())
2187    }
2188
2189    /// Set the minute on this broken down time.
2190    ///
2191    /// # Errors
2192    ///
2193    /// This returns an error if the given minute is out of range.
2194    ///
2195    /// # Example
2196    ///
2197    /// ```
2198    /// use jiff::fmt::strtime::BrokenDownTime;
2199    ///
2200    /// let mut tm = BrokenDownTime::default();
2201    /// // out of range
2202    /// assert!(tm.set_minute(Some(60)).is_err());
2203    /// tm.set_minute(Some(59))?;
2204    /// assert_eq!(tm.to_string("%M")?, "59");
2205    /// assert_eq!(tm.to_string("%03M")?, "059");
2206    /// assert_eq!(tm.to_string("%_3M")?, " 59");
2207    ///
2208    /// # Ok::<(), Box<dyn std::error::Error>>(())
2209    /// ```
2210    #[inline]
2211    pub fn set_minute(&mut self, minute: Option<i8>) -> Result<(), Error> {
2212        self.minute = match minute {
2213            None => None,
2214            Some(minute) => Some(t::Minute::try_new("minute", minute)?),
2215        };
2216        Ok(())
2217    }
2218
2219    /// Set the second on this broken down time.
2220    ///
2221    /// # Errors
2222    ///
2223    /// This returns an error if the given second is out of range.
2224    ///
2225    /// Jiff does not support leap seconds, so the range of valid seconds is
2226    /// `0` to `59`, inclusive. Note though that when parsing, a parsed value
2227    /// of `60` is automatically constrained to `59`.
2228    ///
2229    /// # Example
2230    ///
2231    /// ```
2232    /// use jiff::fmt::strtime::BrokenDownTime;
2233    ///
2234    /// let mut tm = BrokenDownTime::default();
2235    /// // out of range
2236    /// assert!(tm.set_second(Some(60)).is_err());
2237    /// tm.set_second(Some(59))?;
2238    /// assert_eq!(tm.to_string("%S")?, "59");
2239    ///
2240    /// # Ok::<(), Box<dyn std::error::Error>>(())
2241    /// ```
2242    #[inline]
2243    pub fn set_second(&mut self, second: Option<i8>) -> Result<(), Error> {
2244        self.second = match second {
2245            None => None,
2246            Some(second) => Some(t::Second::try_new("second", second)?),
2247        };
2248        Ok(())
2249    }
2250
2251    /// Set the subsecond nanosecond on this broken down time.
2252    ///
2253    /// # Errors
2254    ///
2255    /// This returns an error if the given number of nanoseconds is out of
2256    /// range. It must be non-negative and less than 1 whole second.
2257    ///
2258    /// # Example
2259    ///
2260    /// ```
2261    /// use jiff::fmt::strtime::BrokenDownTime;
2262    ///
2263    /// let mut tm = BrokenDownTime::default();
2264    /// // out of range
2265    /// assert!(tm.set_subsec_nanosecond(Some(1_000_000_000)).is_err());
2266    /// tm.set_subsec_nanosecond(Some(123_000_000))?;
2267    /// assert_eq!(tm.to_string("%f")?, "123");
2268    /// assert_eq!(tm.to_string("%.6f")?, ".123000");
2269    ///
2270    /// # Ok::<(), Box<dyn std::error::Error>>(())
2271    /// ```
2272    #[inline]
2273    pub fn set_subsec_nanosecond(
2274        &mut self,
2275        subsec_nanosecond: Option<i32>,
2276    ) -> Result<(), Error> {
2277        self.subsec = match subsec_nanosecond {
2278            None => None,
2279            Some(subsec_nanosecond) => Some(t::SubsecNanosecond::try_new(
2280                "subsecond-nanosecond",
2281                subsec_nanosecond,
2282            )?),
2283        };
2284        Ok(())
2285    }
2286
2287    /// Set the time zone offset on this broken down time.
2288    ///
2289    /// This can be useful for setting the offset after parsing if the offset
2290    /// is known from the context or from some out-of-band information.
2291    ///
2292    /// Note that one can set any legal offset value, regardless of whether
2293    /// it's consistent with the IANA time zone identifier on this broken down
2294    /// time (if it's set). Similarly, setting the offset does not actually
2295    /// change any other value in this broken down time.
2296    ///
2297    /// # Example: setting the offset after parsing
2298    ///
2299    /// One use case for this routine is when parsing a datetime _without_
2300    /// an offset, but where one wants to set an offset based on the context.
2301    /// For example, while it's usually not correct to assume a datetime is
2302    /// in UTC, if you know it is, then you can parse it into a [`Timestamp`]
2303    /// like so:
2304    ///
2305    /// ```
2306    /// use jiff::{fmt::strtime::BrokenDownTime, tz::Offset};
2307    ///
2308    /// let mut tm = BrokenDownTime::parse(
2309    ///     "%Y-%m-%d at %H:%M:%S",
2310    ///     "1970-01-01 at 01:00:00",
2311    /// )?;
2312    /// tm.set_offset(Some(Offset::UTC));
2313    /// // Normally this would fail since the parse
2314    /// // itself doesn't include an offset. It only
2315    /// // works here because we explicitly set the
2316    /// // offset after parsing.
2317    /// assert_eq!(tm.to_timestamp()?.to_string(), "1970-01-01T01:00:00Z");
2318    ///
2319    /// # Ok::<(), Box<dyn std::error::Error>>(())
2320    /// ```
2321    ///
2322    /// # Example: setting the offset is not "smart"
2323    ///
2324    /// This example shows how setting the offset on an existing broken down
2325    /// time does not impact any other field, even if the result printed is
2326    /// non-sensical:
2327    ///
2328    /// ```
2329    /// use jiff::{civil::date, fmt::strtime::BrokenDownTime, tz};
2330    ///
2331    /// let zdt = date(2024, 8, 28).at(14, 56, 0, 0).in_tz("US/Eastern")?;
2332    /// let mut tm = BrokenDownTime::from(&zdt);
2333    /// tm.set_offset(Some(tz::offset(12)));
2334    /// assert_eq!(
2335    ///     tm.to_string("%Y-%m-%d at %H:%M:%S in %Q %:z")?,
2336    ///     "2024-08-28 at 14:56:00 in US/Eastern +12:00",
2337    /// );
2338    ///
2339    /// # Ok::<(), Box<dyn std::error::Error>>(())
2340    /// ```
2341    #[inline]
2342    pub fn set_offset(&mut self, offset: Option<Offset>) {
2343        self.offset = offset;
2344    }
2345
2346    /// Set the IANA time zone identifier on this broken down time.
2347    ///
2348    /// This can be useful for setting the time zone after parsing if the time
2349    /// zone is known from the context or from some out-of-band information.
2350    ///
2351    /// Note that one can set any string value, regardless of whether it's
2352    /// consistent with the offset on this broken down time (if it's set).
2353    /// Similarly, setting the IANA time zone identifier does not actually
2354    /// change any other value in this broken down time.
2355    ///
2356    /// # Example: setting the IANA time zone identifier after parsing
2357    ///
2358    /// One use case for this routine is when parsing a datetime _without_ a
2359    /// time zone, but where one wants to set a time zone based on the context.
2360    ///
2361    /// ```
2362    /// use jiff::{fmt::strtime::BrokenDownTime, tz::Offset};
2363    ///
2364    /// let mut tm = BrokenDownTime::parse(
2365    ///     "%Y-%m-%d at %H:%M:%S",
2366    ///     "1970-01-01 at 01:00:00",
2367    /// )?;
2368    /// tm.set_iana_time_zone(Some(String::from("US/Eastern")));
2369    /// // Normally this would fail since the parse
2370    /// // itself doesn't include an offset or a time
2371    /// // zone. It only works here because we
2372    /// // explicitly set the time zone after parsing.
2373    /// assert_eq!(
2374    ///     tm.to_zoned()?.to_string(),
2375    ///     "1970-01-01T01:00:00-05:00[US/Eastern]",
2376    /// );
2377    ///
2378    /// # Ok::<(), Box<dyn std::error::Error>>(())
2379    /// ```
2380    ///
2381    /// # Example: setting the IANA time zone identifier is not "smart"
2382    ///
2383    /// This example shows how setting the IANA time zone identifier on an
2384    /// existing broken down time does not impact any other field, even if the
2385    /// result printed is non-sensical:
2386    ///
2387    /// ```
2388    /// use jiff::{civil::date, fmt::strtime::BrokenDownTime, tz};
2389    ///
2390    /// let zdt = date(2024, 8, 28).at(14, 56, 0, 0).in_tz("US/Eastern")?;
2391    /// let mut tm = BrokenDownTime::from(&zdt);
2392    /// tm.set_iana_time_zone(Some(String::from("Australia/Tasmania")));
2393    /// assert_eq!(
2394    ///     tm.to_string("%Y-%m-%d at %H:%M:%S in %Q %:z")?,
2395    ///     "2024-08-28 at 14:56:00 in Australia/Tasmania -04:00",
2396    /// );
2397    ///
2398    /// // In fact, it's not even required that the string
2399    /// // given be a valid IANA time zone identifier!
2400    /// let mut tm = BrokenDownTime::from(&zdt);
2401    /// tm.set_iana_time_zone(Some(String::from("Clearly/Invalid")));
2402    /// assert_eq!(
2403    ///     tm.to_string("%Y-%m-%d at %H:%M:%S in %Q %:z")?,
2404    ///     "2024-08-28 at 14:56:00 in Clearly/Invalid -04:00",
2405    /// );
2406    ///
2407    /// # Ok::<(), Box<dyn std::error::Error>>(())
2408    /// ```
2409    #[cfg(feature = "alloc")]
2410    #[inline]
2411    pub fn set_iana_time_zone(&mut self, id: Option<alloc::string::String>) {
2412        self.iana = id;
2413    }
2414
2415    /// Set the weekday on this broken down time.
2416    ///
2417    /// # Example
2418    ///
2419    /// ```
2420    /// use jiff::{civil::Weekday, fmt::strtime::BrokenDownTime};
2421    ///
2422    /// let mut tm = BrokenDownTime::default();
2423    /// tm.set_weekday(Some(Weekday::Saturday));
2424    /// assert_eq!(tm.to_string("%A")?, "Saturday");
2425    /// assert_eq!(tm.to_string("%a")?, "Sat");
2426    /// assert_eq!(tm.to_string("%^a")?, "SAT");
2427    ///
2428    /// # Ok::<(), Box<dyn std::error::Error>>(())
2429    /// ```
2430    ///
2431    /// Note that one use case for this routine is to enable parsing of
2432    /// weekdays in datetime, but skip checking that the weekday is valid for
2433    /// the parsed date.
2434    ///
2435    /// ```
2436    /// use jiff::{civil::date, fmt::strtime::BrokenDownTime};
2437    ///
2438    /// let mut tm = BrokenDownTime::parse("%a, %F", "Wed, 2024-07-27")?;
2439    /// // 2024-07-27 was a Saturday, so asking for a date fails:
2440    /// assert!(tm.to_date().is_err());
2441    /// // But we can remove the weekday from our broken down time:
2442    /// tm.set_weekday(None);
2443    /// assert_eq!(tm.to_date()?, date(2024, 7, 27));
2444    ///
2445    /// # Ok::<(), Box<dyn std::error::Error>>(())
2446    /// ```
2447    ///
2448    /// The advantage of this approach is that it still ensures the parsed
2449    /// weekday is a valid weekday (for example, `Wat` will cause parsing to
2450    /// fail), but doesn't require it to be consistent with the date. This
2451    /// is useful for interacting with systems that don't do strict error
2452    /// checking.
2453    #[inline]
2454    pub fn set_weekday(&mut self, weekday: Option<Weekday>) {
2455        self.weekday = weekday;
2456    }
2457}
2458
2459impl<'a> From<&'a Zoned> for BrokenDownTime {
2460    fn from(zdt: &'a Zoned) -> BrokenDownTime {
2461        let offset_info = zdt.time_zone().to_offset_info(zdt.timestamp());
2462        #[cfg(feature = "alloc")]
2463        let iana = {
2464            use alloc::string::ToString;
2465            zdt.time_zone().iana_name().map(|s| s.to_string())
2466        };
2467        BrokenDownTime {
2468            offset: Some(zdt.offset()),
2469            // In theory, this could fail, but I've never seen a time zone
2470            // abbreviation longer than a few bytes. Please file an issue if
2471            // this is a problem for you.
2472            tzabbrev: Abbreviation::new(offset_info.abbreviation()),
2473            #[cfg(feature = "alloc")]
2474            iana,
2475            ..BrokenDownTime::from(zdt.datetime())
2476        }
2477    }
2478}
2479
2480impl From<Timestamp> for BrokenDownTime {
2481    fn from(ts: Timestamp) -> BrokenDownTime {
2482        let dt = Offset::UTC.to_datetime(ts);
2483        BrokenDownTime {
2484            offset: Some(Offset::UTC),
2485            ..BrokenDownTime::from(dt)
2486        }
2487    }
2488}
2489
2490impl From<DateTime> for BrokenDownTime {
2491    fn from(dt: DateTime) -> BrokenDownTime {
2492        let (d, t) = (dt.date(), dt.time());
2493        BrokenDownTime {
2494            year: Some(d.year_ranged()),
2495            month: Some(d.month_ranged()),
2496            day: Some(d.day_ranged()),
2497            hour: Some(t.hour_ranged()),
2498            minute: Some(t.minute_ranged()),
2499            second: Some(t.second_ranged()),
2500            subsec: Some(t.subsec_nanosecond_ranged()),
2501            meridiem: Some(Meridiem::from(t)),
2502            ..BrokenDownTime::default()
2503        }
2504    }
2505}
2506
2507impl From<Date> for BrokenDownTime {
2508    fn from(d: Date) -> BrokenDownTime {
2509        BrokenDownTime {
2510            year: Some(d.year_ranged()),
2511            month: Some(d.month_ranged()),
2512            day: Some(d.day_ranged()),
2513            ..BrokenDownTime::default()
2514        }
2515    }
2516}
2517
2518impl From<ISOWeekDate> for BrokenDownTime {
2519    fn from(wd: ISOWeekDate) -> BrokenDownTime {
2520        BrokenDownTime {
2521            iso_week_year: Some(wd.year_ranged()),
2522            iso_week: Some(wd.week_ranged()),
2523            weekday: Some(wd.weekday()),
2524            ..BrokenDownTime::default()
2525        }
2526    }
2527}
2528
2529impl From<Time> for BrokenDownTime {
2530    fn from(t: Time) -> BrokenDownTime {
2531        BrokenDownTime {
2532            hour: Some(t.hour_ranged()),
2533            minute: Some(t.minute_ranged()),
2534            second: Some(t.second_ranged()),
2535            subsec: Some(t.subsec_nanosecond_ranged()),
2536            meridiem: Some(Meridiem::from(t)),
2537            ..BrokenDownTime::default()
2538        }
2539    }
2540}
2541
2542/// A "lazy" implementation of `std::fmt::Display` for `strftime`.
2543///
2544/// Values of this type are created by the `strftime` methods on the various
2545/// datetime types in this crate. For example, [`Zoned::strftime`].
2546///
2547/// A `Display` captures the information needed from the datetime and waits to
2548/// do the actual formatting when this type's `std::fmt::Display` trait
2549/// implementation is actually used.
2550///
2551/// # Errors and panics
2552///
2553/// This trait implementation returns an error when the underlying formatting
2554/// can fail. Formatting can fail either because of an invalid format string,
2555/// or if formatting requires a field in `BrokenDownTime` to be set that isn't.
2556/// For example, trying to format a [`DateTime`] with the `%z` specifier will
2557/// fail because a `DateTime` has no time zone or offset information associated
2558/// with it.
2559///
2560/// Note though that the `std::fmt::Display` API doesn't support surfacing
2561/// arbitrary errors. All errors collapse into the unit `std::fmt::Error`
2562/// struct. To see the actual error, use [`BrokenDownTime::format`],
2563/// [`BrokenDownTime::to_string`] or [`strtime::format`](format()).
2564/// Unfortunately, the `std::fmt::Display` trait is used in many places where
2565/// there is no way to report errors other than panicking.
2566///
2567/// Therefore, only use this type if you know your formatting string is valid
2568/// and that the datetime type being formatted has all of the information
2569/// required by the format string. For most conversion specifiers, this falls
2570/// in the category of things where "if it works, it works for all inputs."
2571/// Unfortunately, there are some exceptions to this. For example, the `%y`
2572/// modifier will only format a year if it falls in the range `1969-2068` and
2573/// will otherwise return an error.
2574///
2575/// # Example
2576///
2577/// This example shows how to format a zoned datetime using
2578/// [`Zoned::strftime`]:
2579///
2580/// ```
2581/// use jiff::{civil::date, fmt::strtime, tz};
2582///
2583/// let zdt = date(2024, 7, 15).at(16, 24, 59, 0).in_tz("America/New_York")?;
2584/// let string = zdt.strftime("%a, %-d %b %Y %T %z").to_string();
2585/// assert_eq!(string, "Mon, 15 Jul 2024 16:24:59 -0400");
2586///
2587/// # Ok::<(), Box<dyn std::error::Error>>(())
2588/// ```
2589///
2590/// Or use it directly when writing to something:
2591///
2592/// ```
2593/// use jiff::{civil::date, fmt::strtime, tz};
2594///
2595/// let zdt = date(2024, 7, 15).at(16, 24, 59, 0).in_tz("America/New_York")?;
2596///
2597/// let string = format!("the date is: {}", zdt.strftime("%-m/%-d/%-Y"));
2598/// assert_eq!(string, "the date is: 7/15/2024");
2599///
2600/// # Ok::<(), Box<dyn std::error::Error>>(())
2601/// ```
2602pub struct Display<'f> {
2603    pub(crate) fmt: &'f [u8],
2604    pub(crate) tm: BrokenDownTime,
2605}
2606
2607impl<'f> core::fmt::Display for Display<'f> {
2608    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
2609        use crate::fmt::StdFmtWrite;
2610
2611        self.tm.format(self.fmt, StdFmtWrite(f)).map_err(|_| core::fmt::Error)
2612    }
2613}
2614
2615impl<'f> core::fmt::Debug for Display<'f> {
2616    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
2617        f.debug_struct("Display")
2618            .field("fmt", &escape::Bytes(self.fmt))
2619            .field("tm", &self.tm)
2620            .finish()
2621    }
2622}
2623
2624/// A label to disambiguate hours on a 12-hour clock.
2625///
2626/// This can be accessed on a [`BrokenDownTime`] via
2627/// [`BrokenDownTime::meridiem`].
2628#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
2629pub enum Meridiem {
2630    /// "ante meridiem" or "before midday."
2631    ///
2632    /// Specifically, this describes hours less than 12 on a 24-hour clock.
2633    AM,
2634    /// "post meridiem" or "after midday."
2635    ///
2636    /// Specifically, this describes hours greater than 11 on a 24-hour clock.
2637    PM,
2638}
2639
2640impl From<Time> for Meridiem {
2641    fn from(t: Time) -> Meridiem {
2642        if t.hour() < 12 {
2643            Meridiem::AM
2644        } else {
2645            Meridiem::PM
2646        }
2647    }
2648}
2649
2650/// These are "extensions" to the standard `strftime` conversion specifiers.
2651///
2652/// Basically, these provide control over padding (zeros, spaces or none),
2653/// how much to pad and the case of string enumerations.
2654#[derive(Clone, Copy, Debug)]
2655struct Extension {
2656    flag: Option<Flag>,
2657    width: Option<u8>,
2658}
2659
2660impl Extension {
2661    /// Parses an optional directive flag from the beginning of `fmt`. This
2662    /// assumes `fmt` is not empty and guarantees that the return unconsumed
2663    /// slice is also non-empty.
2664    #[cfg_attr(feature = "perf-inline", inline(always))]
2665    fn parse_flag<'i>(
2666        fmt: &'i [u8],
2667    ) -> Result<(Option<Flag>, &'i [u8]), Error> {
2668        let byte = fmt[0];
2669        let flag = match byte {
2670            b'_' => Flag::PadSpace,
2671            b'0' => Flag::PadZero,
2672            b'-' => Flag::NoPad,
2673            b'^' => Flag::Uppercase,
2674            b'#' => Flag::Swapcase,
2675            _ => return Ok((None, fmt)),
2676        };
2677        let fmt = &fmt[1..];
2678        if fmt.is_empty() {
2679            return Err(err!(
2680                "expected to find specifier directive after flag \
2681                 {byte:?}, but found end of format string",
2682                byte = escape::Byte(byte),
2683            ));
2684        }
2685        Ok((Some(flag), fmt))
2686    }
2687
2688    /// Parses an optional width that comes after a (possibly absent) flag and
2689    /// before the specifier directive itself. And if a width is parsed, the
2690    /// slice returned does not contain it. (If that slice is empty, then an
2691    /// error is returned.)
2692    ///
2693    /// Note that this is also used to parse precision settings for `%f`
2694    /// and `%.f`. In the former case, the width is just re-interpreted as
2695    /// a precision setting. In the latter case, something like `%5.9f` is
2696    /// technically valid, but the `5` is ignored.
2697    #[cfg_attr(feature = "perf-inline", inline(always))]
2698    fn parse_width<'i>(
2699        fmt: &'i [u8],
2700    ) -> Result<(Option<u8>, &'i [u8]), Error> {
2701        let mut digits = 0;
2702        while digits < fmt.len() && fmt[digits].is_ascii_digit() {
2703            digits += 1;
2704        }
2705        if digits == 0 {
2706            return Ok((None, fmt));
2707        }
2708        let (digits, fmt) = util::parse::split(fmt, digits).unwrap();
2709        let width = util::parse::i64(digits)
2710            .context("failed to parse conversion specifier width")?;
2711        let width = u8::try_from(width).map_err(|_| {
2712            err!("{width} is too big, max is {max}", max = u8::MAX)
2713        })?;
2714        if fmt.is_empty() {
2715            return Err(err!(
2716                "expected to find specifier directive after width \
2717                 {width}, but found end of format string",
2718            ));
2719        }
2720        Ok((Some(width), fmt))
2721    }
2722}
2723
2724/// The different flags one can set. They are mutually exclusive.
2725#[derive(Clone, Copy, Debug)]
2726enum Flag {
2727    PadSpace,
2728    PadZero,
2729    NoPad,
2730    Uppercase,
2731    Swapcase,
2732}
2733
2734/// Returns the "full" weekday name.
2735fn weekday_name_full(wd: Weekday) -> &'static str {
2736    match wd {
2737        Weekday::Sunday => "Sunday",
2738        Weekday::Monday => "Monday",
2739        Weekday::Tuesday => "Tuesday",
2740        Weekday::Wednesday => "Wednesday",
2741        Weekday::Thursday => "Thursday",
2742        Weekday::Friday => "Friday",
2743        Weekday::Saturday => "Saturday",
2744    }
2745}
2746
2747/// Returns an abbreviated weekday name.
2748fn weekday_name_abbrev(wd: Weekday) -> &'static str {
2749    match wd {
2750        Weekday::Sunday => "Sun",
2751        Weekday::Monday => "Mon",
2752        Weekday::Tuesday => "Tue",
2753        Weekday::Wednesday => "Wed",
2754        Weekday::Thursday => "Thu",
2755        Weekday::Friday => "Fri",
2756        Weekday::Saturday => "Sat",
2757    }
2758}
2759
2760/// Returns the "full" month name.
2761fn month_name_full(month: t::Month) -> &'static str {
2762    match month.get() {
2763        1 => "January",
2764        2 => "February",
2765        3 => "March",
2766        4 => "April",
2767        5 => "May",
2768        6 => "June",
2769        7 => "July",
2770        8 => "August",
2771        9 => "September",
2772        10 => "October",
2773        11 => "November",
2774        12 => "December",
2775        unk => unreachable!("invalid month {unk}"),
2776    }
2777}
2778
2779/// Returns the abbreviated month name.
2780fn month_name_abbrev(month: t::Month) -> &'static str {
2781    match month.get() {
2782        1 => "Jan",
2783        2 => "Feb",
2784        3 => "Mar",
2785        4 => "Apr",
2786        5 => "May",
2787        6 => "Jun",
2788        7 => "Jul",
2789        8 => "Aug",
2790        9 => "Sep",
2791        10 => "Oct",
2792        11 => "Nov",
2793        12 => "Dec",
2794        unk => unreachable!("invalid month {unk}"),
2795    }
2796}
2797
2798#[cfg(test)]
2799mod tests {
2800    use super::*;
2801
2802    // See: https://github.com/BurntSushi/jiff/issues/62
2803    #[test]
2804    fn parse_non_delimited() {
2805        insta::assert_snapshot!(
2806            Timestamp::strptime("%Y%m%d-%H%M%S%z", "20240730-005625+0400").unwrap(),
2807            @"2024-07-29T20:56:25Z",
2808        );
2809        insta::assert_snapshot!(
2810            Zoned::strptime("%Y%m%d-%H%M%S%z", "20240730-005625+0400").unwrap(),
2811            @"2024-07-30T00:56:25+04:00[+04:00]",
2812        );
2813    }
2814
2815    // Regression test for format strings with non-ASCII in them.
2816    //
2817    // We initially didn't support non-ASCII because I had thought it wouldn't
2818    // be used. i.e., If someone wanted to do something with non-ASCII, then
2819    // I thought they'd want to be using something more sophisticated that took
2820    // locale into account. But apparently not.
2821    //
2822    // See: https://github.com/BurntSushi/jiff/issues/155
2823    #[test]
2824    fn ok_non_ascii() {
2825        let fmt = "%Y年%m月%d日,%H时%M分%S秒";
2826        let dt = crate::civil::date(2022, 2, 4).at(3, 58, 59, 0);
2827        insta::assert_snapshot!(
2828            dt.strftime(fmt),
2829            @"2022年02月04日,03时58分59秒",
2830        );
2831        insta::assert_debug_snapshot!(
2832            DateTime::strptime(fmt, "2022年02月04日,03时58分59秒").unwrap(),
2833            @"2022-02-04T03:58:59",
2834        );
2835    }
2836}