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