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