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