jiff/fmt/mod.rs
1/*!
2Configurable support for printing and parsing datetimes and durations.
3
4Note that for most use cases, you should be using the corresponding
5[`Display`](std::fmt::Display) or [`FromStr`](std::str::FromStr) trait
6implementations for printing and parsing respectively. The APIs in this module
7provide more configurable support for printing and parsing.
8
9# Tables of examples
10
11The tables below attempt to show some examples of datetime and duration
12formatting, along with names and links to relevant routines and types. The
13point of these tables is to give a general overview of the formatting and
14parsing functionality in these sub-modules.
15
16## Support for `FromStr` and `Display`
17
18This table lists the formats supported by the [`FromStr`] and [`Display`]
19trait implementations on the datetime and duration types in Jiff.
20
21In all of these cases, the trait implementations are mere conveniences for
22functionality provided by the [`temporal`] sub-module (and, in a couple cases,
23the [`friendly`] sub-module). The sub-modules provide lower level control
24(such as parsing from `&[u8]`) and more configuration (such as controlling the
25disambiguation strategy used when parsing zoned datetime [RFC-9557] strings).
26
27| Example | Format | Links |
28| ------- | ------ | ----- |
29| `2025-08-20T17:35:00Z` | [RFC-3339] | [`Timestamp`] |
30| `2025-08-20T17:35:00-05` | [RFC-3339] | [`FromStr`] impl and<br>[`Timestamp::display_with_offset`] |
31| `2025-08-20T17:35:00+02[Poland]` | [RFC-9557] | [`Zoned`] |
32| `2025-08-20T17:35:00+02:00[+02:00]` | [RFC-9557] | [`Zoned`] |
33| `2025-08-20T17:35:00` | [ISO-8601] | [`civil::DateTime`] |
34| `2025-08-20` | [ISO-8601] | [`civil::Date`] |
35| `17:35:00` | [ISO-8601] | [`civil::Time`] |
36| `P1Y2M3W4DT5H6M7S` | [ISO-8601], [Temporal] | [`Span`] |
37| `PT1H2M3S` | [ISO-8601] | [`SignedDuration`], [`Span`] |
38| `PT1H2M3.123456789S` | [ISO-8601] | [`SignedDuration`], [`Span`] |
39| `1d 2h 3m 5s` | [`friendly`] | [`FromStr`] impl and alternative [`Display`]<br>via `{:#}` for [`SignedDuration`], [`Span`] |
40
41Note that for datetimes like `2025-08-20T17:35:00`, the following variants are
42also accepted:
43
44```text
452025-08-20 17:35:00
462025-08-20T17:35:00.123456789
472025-08-20T17:35
482025-08-20T17
49```
50
51This applies to RFC 3339 and RFC 9557 timestamps as well.
52
53Also, for ISO 8601 durations, the unit designator labels are matched
54case insensitively. For example, `PT1h2m3s` is recognized by Jiff.
55
56## The "friendly" duration format
57
58This table lists a few examples of the [`friendly`] duration format. Briefly,
59it is a bespoke format for Jiff, but is meant to match similar bespoke formats
60used elsewhere and be easier to read than the standard ISO 8601 duration
61format.
62
63All examples below can be parsed via a [`Span`]'s [`FromStr`] trait
64implementation. All examples with units no bigger than hours can be parsed via
65a [`SignedDuration`]'s [`FromStr`] trait implementation. This table otherwise
66shows the options for printing durations in the format shown.
67
68| Example | Print configuration |
69| ------- | ------------------- |
70| `1year 2months` | [`Designator::Verbose`] via [`SpanPrinter::designator`] |
71| `1yr 2mos` | [`Designator::Short`] via [`SpanPrinter::designator`] |
72| `1y 2mo` | [`Designator::Compact`] via [`SpanPrinter::designator`] (default) |
73| `1h2m3s` | [`Spacing::None`] via [`SpanPrinter::spacing`] |
74| `1h 2m 3s` | [`Spacing::BetweenUnits`] via [`SpanPrinter::spacing`] (default) |
75| `1 h 2 m 3 s` | [`Spacing::BetweenUnitsAndDesignators`] via [`SpanPrinter::spacing`] |
76| `2d 3h ago` | [`Direction::Auto`] via [`SpanPrinter::direction`] (default) |
77| `-2d 3h` | [`Direction::Sign`] via [`SpanPrinter::direction`] |
78| `+2d 3h` | [`Direction::ForceSign`] via [`SpanPrinter::direction`] |
79| `2d 3h ago` | [`Direction::Suffix`] via [`SpanPrinter::direction`] |
80| `9.123456789s` | [`FractionalUnit::Second`] via [`SpanPrinter::fractional`] |
81| `1y, 2mo` | [`SpanPrinter::comma_after_designator`] |
82| `15d 02:59:15.123` | [`SpanPrinter::hours_minutes_seconds`] |
83
84## Bespoke datetime formats via `strptime` and `strftime`
85
86Every datetime type has bespoke formatting routines defined on it. For
87example, [`Zoned::strptime`] and [`civil::Date::strftime`]. Additionally, the
88[`strtime`] sub-module also provides convenience routines, [`strtime::format`]
89and [`strtime::parse`], where the former is generic over any datetime type in
90Jiff and the latter provides a [`BrokenDownTime`] for granular parsing.
91
92| Example | Format string |
93| ------- | ------------- |
94| `2025-05-20` | `%Y-%m-%d` |
95| `2025-05-20` | `%F` |
96| `2025-W21-2` | `%G-W%V-%u` |
97| `05/20/25` | `%m/%d/%y` |
98| `Monday, February 10, 2025 at 9:01pm -0500` | `%A, %B %d, %Y at %-I:%M%P %z` |
99| `Monday, February 10, 2025 at 9:01pm EST` | `%A, %B %d, %Y at %-I:%M%P %Z` |
100| `Monday, February 10, 2025 at 9:01pm America/New_York` | `%A, %B %d, %Y at %-I:%M%P %Q` |
101
102The specific conversion specifiers supported are documented in the [`strtime`]
103sub-module. While precise POSIX compatibility is not guaranteed, the conversion
104specifiers are generally meant to match prevailing implementations. (Although
105there are many such implementations and they each tend to have their own quirks
106and features.)
107
108## RFC 2822 parsing and printing
109
110[RFC-2822] support is provided by the [`rfc2822`] sub-module.
111
112| Example | Links |
113| ------- | ----- |
114| `Thu, 29 Feb 2024 05:34 -0500` | [`rfc2822::parse`] and [`rfc2822::to_string`] |
115| `Thu, 01 Jan 1970 00:00:01 GMT` | [`DateTimePrinter::timestamp_to_rfc9110_string`] |
116
117[Temporal]: https://tc39.es/proposal-temporal/#sec-temporal-iso8601grammar
118[ISO-8601]: https://www.iso.org/iso-8601-date-and-time-format.html
119[RFC-3339]: https://www.rfc-editor.org/rfc/rfc3339
120[RFC-9557]: https://www.rfc-editor.org/rfc/rfc9557.html
121[ISO-8601]: https://www.iso.org/iso-8601-date-and-time-format.html
122[RFC-2822]: https://datatracker.ietf.org/doc/html/rfc2822
123[RFC-9110]: https://datatracker.ietf.org/doc/html/rfc9110#section-5.6.7-15
124[`Display`]: std::fmt::Display
125[`FromStr`]: std::str::FromStr
126[`friendly`]: crate::fmt::friendly
127[`temporal`]: crate::fmt::temporal
128[`rfc2822`]: crate::fmt::rfc2822
129[`strtime`]: crate::fmt::strtime
130[`civil::DateTime`]: crate::civil::DateTime
131[`civil::Date`]: crate::civil::Date
132[`civil::Date::strftime`]: crate::civil::Date::strftime
133[`civil::Time`]: crate::civil::Time
134[`SignedDuration`]: crate::SignedDuration
135[`Span`]: crate::Span
136[`Timestamp`]: crate::Timestamp
137[`Timestamp::display_with_offset`]: crate::Timestamp::display_with_offset
138[`Zoned`]: crate::Zoned
139[`Zoned::strptime`]: crate::Zoned::strptime
140
141[`Designator::Verbose`]: crate::fmt::friendly::Designator::Verbose
142[`Designator::Short`]: crate::fmt::friendly::Designator::Short
143[`Designator::Compact`]: crate::fmt::friendly::Designator::Compact
144[`Spacing::None`]: crate::fmt::friendly::Spacing::None
145[`Spacing::BetweenUnits`]: crate::fmt::friendly::Spacing::BetweenUnits
146[`Spacing::BetweenUnitsAndDesignators`]: crate::fmt::friendly::Spacing::BetweenUnitsAndDesignators
147[`Direction::Auto`]: crate::fmt::friendly::Direction::Auto
148[`Direction::Sign`]: crate::fmt::friendly::Direction::Sign
149[`Direction::ForceSign`]: crate::fmt::friendly::Direction::ForceSign
150[`Direction::Suffix`]: crate::fmt::friendly::Direction::Suffix
151[`FractionalUnit::Second`]: crate::fmt::friendly::FractionalUnit::Second
152[`SpanPrinter::designator`]: crate::fmt::friendly::SpanPrinter::designator
153[`SpanPrinter::spacing`]: crate::fmt::friendly::SpanPrinter::spacing
154[`SpanPrinter::direction`]: crate::fmt::friendly::SpanPrinter::direction
155[`SpanPrinter::fractional`]: crate::fmt::friendly::SpanPrinter::fractional
156[`SpanPrinter::comma_after_designator`]: crate::fmt::friendly::SpanPrinter::comma_after_designator
157[`SpanPrinter::hours_minutes_seconds`]: crate::fmt::friendly::SpanPrinter::hours_minutes_seconds
158
159[`BrokenDownTime`]: crate::fmt::strtime::BrokenDownTime
160[`strtime::parse`]: crate::fmt::strtime::parse
161[`strtime::format`]: crate::fmt::strtime::format
162
163[`rfc2822::parse`]: crate::fmt::rfc2822::parse
164[`rfc2822::to_string`]: crate::fmt::rfc2822::to_string
165[`DateTimePrinter::timestamp_to_rfc9110_string`]: crate::fmt::rfc2822::DateTimePrinter::timestamp_to_rfc9110_string
166*/
167
168use crate::{
169 error::{err, Error},
170 util::escape,
171};
172
173use self::util::{Decimal, DecimalFormatter, Fractional, FractionalFormatter};
174
175pub mod friendly;
176mod offset;
177pub mod rfc2822;
178mod rfc9557;
179#[cfg(feature = "serde")]
180pub mod serde;
181pub mod strtime;
182pub mod temporal;
183mod util;
184
185/// The result of parsing a value out of a slice of bytes.
186///
187/// This contains both the parsed value and the offset at which the value
188/// ended in the input given. This makes it possible to parse, for example, a
189/// datetime value as a prefix of some larger string without knowing ahead of
190/// time where it ends.
191#[derive(Clone)]
192pub(crate) struct Parsed<'i, V> {
193 /// The value parsed.
194 value: V,
195 /// The remaining unparsed input.
196 input: &'i [u8],
197}
198
199impl<'i, V> Parsed<'i, V> {
200 #[inline]
201 fn and_then<U>(
202 self,
203 map: impl FnOnce(V) -> Result<U, Error>,
204 ) -> Result<Parsed<'i, U>, Error> {
205 let Parsed { value, input } = self;
206 Ok(Parsed { value: map(value)?, input })
207 }
208}
209
210impl<'i, V: core::fmt::Display> Parsed<'i, V> {
211 /// Ensures that the parsed value represents the entire input. This occurs
212 /// precisely when the `input` on this parsed value is empty.
213 ///
214 /// This is useful when one expects a parsed value to consume the entire
215 /// input, and to consider it an error if it doesn't.
216 #[inline]
217 fn into_full(self) -> Result<V, Error> {
218 if self.input.is_empty() {
219 return Ok(self.value);
220 }
221 Err(err!(
222 "parsed value '{value}', but unparsed input {unparsed:?} \
223 remains (expected no unparsed input)",
224 value = self.value,
225 unparsed = escape::Bytes(self.input),
226 ))
227 }
228}
229
230impl<'i, V> Parsed<'i, V> {
231 /// Ensures that the parsed value represents the entire input. This occurs
232 /// precisely when the `input` on this parsed value is empty.
233 ///
234 /// This is useful when one expects a parsed value to consume the entire
235 /// input, and to consider it an error if it doesn't.
236 ///
237 /// This is like `Parsed::into_full`, but lets the caller provide a custom
238 /// `Display` implementation.
239 #[inline]
240 fn into_full_with(
241 self,
242 display: impl core::fmt::Display,
243 ) -> Result<V, Error> {
244 if self.input.is_empty() {
245 return Ok(self.value);
246 }
247 Err(err!(
248 "parsed value '{value}', but unparsed input {unparsed:?} \
249 remains (expected no unparsed input)",
250 value = display,
251 unparsed = escape::Bytes(self.input),
252 ))
253 }
254}
255
256impl<'i, V: core::fmt::Debug> core::fmt::Debug for Parsed<'i, V> {
257 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
258 f.debug_struct("Parsed")
259 .field("value", &self.value)
260 .field("input", &escape::Bytes(self.input))
261 .finish()
262 }
263}
264
265/// A trait for printing datetimes or spans into Unicode-accepting buffers or
266/// streams.
267///
268/// The most useful implementations of this trait are for the `String` and
269/// `Vec<u8>` types. But any implementation of [`std::fmt::Write`] and
270/// [`std::io::Write`] can be used via the [`StdFmtWrite`] and [`StdIoWrite`]
271/// adapters, respectively.
272///
273/// Most users of Jiff should not need to interact with this trait directly.
274/// Instead, printing is handled via the [`Display`](std::fmt::Display)
275/// implementation of the relevant type.
276///
277/// # Design
278///
279/// This trait is a near-clone of the `std::fmt::Write` trait. It's also very
280/// similar to the `std::io::Write` trait, but like `std::fmt::Write`, this
281/// trait is limited to writing valid UTF-8. The UTF-8 restriction was adopted
282/// because we really want to support printing datetimes and spans to `String`
283/// buffers. If we permitted writing `&[u8]` data, then writing to a `String`
284/// buffer would always require a costly UTF-8 validation check.
285///
286/// The `std::fmt::Write` trait wasn't used itself because:
287///
288/// 1. Using a custom trait allows us to require using Jiff's error type.
289/// (Although this extra flexibility isn't currently used, since printing only
290/// fails when writing to the underlying buffer or stream fails.)
291/// 2. Using a custom trait allows us more control over the implementations of
292/// the trait. For example, a custom trait means we can format directly into
293/// a `Vec<u8>` buffer, which isn't possible with `std::fmt::Write` because
294/// there is no `std::fmt::Write` trait implementation for `Vec<u8>`.
295pub trait Write {
296 /// Write the given string to this writer, returning whether the write
297 /// succeeded or not.
298 fn write_str(&mut self, string: &str) -> Result<(), Error>;
299
300 /// Write the given character to this writer, returning whether the write
301 /// succeeded or not.
302 #[inline]
303 fn write_char(&mut self, char: char) -> Result<(), Error> {
304 self.write_str(char.encode_utf8(&mut [0; 4]))
305 }
306}
307
308#[cfg(any(test, feature = "alloc"))]
309impl Write for alloc::string::String {
310 #[inline]
311 fn write_str(&mut self, string: &str) -> Result<(), Error> {
312 self.push_str(string);
313 Ok(())
314 }
315}
316
317#[cfg(any(test, feature = "alloc"))]
318impl Write for alloc::vec::Vec<u8> {
319 #[inline]
320 fn write_str(&mut self, string: &str) -> Result<(), Error> {
321 self.extend_from_slice(string.as_bytes());
322 Ok(())
323 }
324}
325
326impl<W: Write> Write for &mut W {
327 fn write_str(&mut self, string: &str) -> Result<(), Error> {
328 (**self).write_str(string)
329 }
330
331 #[inline]
332 fn write_char(&mut self, char: char) -> Result<(), Error> {
333 (**self).write_char(char)
334 }
335}
336
337/// An adapter for using `std::io::Write` implementations with `fmt::Write`.
338///
339/// This is useful when one wants to format a datetime or span directly
340/// to something with a `std::io::Write` trait implementation but not a
341/// `fmt::Write` implementation.
342///
343/// # Example
344///
345/// ```no_run
346/// use std::{fs::File, io::{BufWriter, Write}, path::Path};
347///
348/// use jiff::{civil::date, fmt::{StdIoWrite, temporal::DateTimePrinter}};
349///
350/// let zdt = date(2024, 6, 15).at(7, 0, 0, 0).in_tz("America/New_York")?;
351///
352/// let path = Path::new("/tmp/output");
353/// let mut file = BufWriter::new(File::create(path)?);
354/// DateTimePrinter::new().print_zoned(&zdt, StdIoWrite(&mut file)).unwrap();
355/// file.flush()?;
356/// assert_eq!(
357/// std::fs::read_to_string(path)?,
358/// "2024-06-15T07:00:00-04:00[America/New_York]",
359/// );
360///
361/// # Ok::<(), Box<dyn std::error::Error>>(())
362/// ```
363#[cfg(feature = "std")]
364#[derive(Clone, Debug)]
365pub struct StdIoWrite<W>(pub W);
366
367#[cfg(feature = "std")]
368impl<W: std::io::Write> Write for StdIoWrite<W> {
369 #[inline]
370 fn write_str(&mut self, string: &str) -> Result<(), Error> {
371 self.0.write_all(string.as_bytes()).map_err(Error::adhoc)
372 }
373}
374
375/// An adapter for using `std::fmt::Write` implementations with `fmt::Write`.
376///
377/// This is useful when one wants to format a datetime or span directly
378/// to something with a `std::fmt::Write` trait implementation but not a
379/// `fmt::Write` implementation.
380///
381/// (Despite using `Std` in this name, this type is available in `core`-only
382/// configurations.)
383///
384/// # Example
385///
386/// This example shows the `std::fmt::Display` trait implementation for
387/// [`civil::DateTime`](crate::civil::DateTime) (but using a wrapper type).
388///
389/// ```
390/// use jiff::{civil::DateTime, fmt::{temporal::DateTimePrinter, StdFmtWrite}};
391///
392/// struct MyDateTime(DateTime);
393///
394/// impl std::fmt::Display for MyDateTime {
395/// fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
396///
397/// static P: DateTimePrinter = DateTimePrinter::new();
398/// P.print_datetime(&self.0, StdFmtWrite(f))
399/// .map_err(|_| std::fmt::Error)
400/// }
401/// }
402///
403/// let dt = MyDateTime(DateTime::constant(2024, 6, 15, 17, 30, 0, 0));
404/// assert_eq!(dt.to_string(), "2024-06-15T17:30:00");
405/// ```
406#[derive(Clone, Debug)]
407pub struct StdFmtWrite<W>(pub W);
408
409impl<W: core::fmt::Write> Write for StdFmtWrite<W> {
410 #[inline]
411 fn write_str(&mut self, string: &str) -> Result<(), Error> {
412 self.0
413 .write_str(string)
414 .map_err(|_| err!("an error occurred when formatting an argument"))
415 }
416}
417
418impl<W: Write> core::fmt::Write for StdFmtWrite<W> {
419 #[inline]
420 fn write_str(&mut self, string: &str) -> Result<(), core::fmt::Error> {
421 self.0.write_str(string).map_err(|_| core::fmt::Error)
422 }
423}
424
425/// An extension trait to `Write` that provides crate internal routines.
426///
427/// These routines aren't exposed because they make use of crate internal
428/// types. Those types could perhaps be exposed if there was strong demand,
429/// but I'm skeptical.
430trait WriteExt: Write {
431 /// Write the given number as a signed decimal using ASCII digits to this
432 /// buffer. The given formatter controls how the decimal is formatted.
433 #[inline]
434 fn write_int(
435 &mut self,
436 formatter: &DecimalFormatter,
437 n: impl Into<i64>,
438 ) -> Result<(), Error> {
439 self.write_decimal(&formatter.format_signed(n.into()))
440 }
441
442 /// Write the given number as an unsigned decimal using ASCII digits to
443 /// this buffer. The given formatter controls how the decimal is formatted.
444 #[inline]
445 fn write_uint(
446 &mut self,
447 formatter: &DecimalFormatter,
448 n: impl Into<u64>,
449 ) -> Result<(), Error> {
450 self.write_decimal(&formatter.format_unsigned(n.into()))
451 }
452
453 /// Write the given fractional number using ASCII digits to this buffer.
454 /// The given formatter controls how the fractional number is formatted.
455 #[inline]
456 fn write_fraction(
457 &mut self,
458 formatter: &FractionalFormatter,
459 n: impl Into<u32>,
460 ) -> Result<(), Error> {
461 self.write_fractional(&Fractional::new(formatter, n.into()))
462 }
463
464 /// Write the given decimal number to this buffer.
465 #[inline]
466 fn write_decimal(&mut self, decimal: &Decimal) -> Result<(), Error> {
467 self.write_str(decimal.as_str())
468 }
469
470 /// Write the given fractional number to this buffer.
471 #[inline]
472 fn write_fractional(
473 &mut self,
474 fractional: &Fractional,
475 ) -> Result<(), Error> {
476 self.write_str(fractional.as_str())
477 }
478}
479
480impl<W: Write> WriteExt for W {}