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