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