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: core::fmt::Display> Parsed<'i, V> {
200    /// Ensures that the parsed value represents the entire input. This occurs
201    /// precisely when the `input` on this parsed value is empty.
202    ///
203    /// This is useful when one expects a parsed value to consume the entire
204    /// input, and to consider it an error if it doesn't.
205    #[inline]
206    fn into_full(self) -> Result<V, Error> {
207        if self.input.is_empty() {
208            return Ok(self.value);
209        }
210        Err(err!(
211            "parsed value '{value}', but unparsed input {unparsed:?} \
212             remains (expected no unparsed input)",
213            value = self.value,
214            unparsed = escape::Bytes(self.input),
215        ))
216    }
217}
218
219impl<'i, V: core::fmt::Debug> core::fmt::Debug for Parsed<'i, V> {
220    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
221        f.debug_struct("Parsed")
222            .field("value", &self.value)
223            .field("input", &escape::Bytes(self.input))
224            .finish()
225    }
226}
227
228/// A trait for printing datetimes or spans into Unicode-accepting buffers or
229/// streams.
230///
231/// The most useful implementations of this trait are for the `String` and
232/// `Vec<u8>` types. But any implementation of [`std::fmt::Write`] and
233/// [`std::io::Write`] can be used via the [`StdFmtWrite`] and [`StdIoWrite`]
234/// adapters, respectively.
235///
236/// Most users of Jiff should not need to interact with this trait directly.
237/// Instead, printing is handled via the [`Display`](std::fmt::Display)
238/// implementation of the relevant type.
239///
240/// # Design
241///
242/// This trait is a near-clone of the `std::fmt::Write` trait. It's also very
243/// similar to the `std::io::Write` trait, but like `std::fmt::Write`, this
244/// trait is limited to writing valid UTF-8. The UTF-8 restriction was adopted
245/// because we really want to support printing datetimes and spans to `String`
246/// buffers. If we permitted writing `&[u8]` data, then writing to a `String`
247/// buffer would always require a costly UTF-8 validation check.
248///
249/// The `std::fmt::Write` trait wasn't used itself because:
250///
251/// 1. Using a custom trait allows us to require using Jiff's error type.
252/// (Although this extra flexibility isn't currently used, since printing only
253/// fails when writing to the underlying buffer or stream fails.)
254/// 2. Using a custom trait allows us more control over the implementations of
255/// the trait. For example, a custom trait means we can format directly into
256/// a `Vec<u8>` buffer, which isn't possible with `std::fmt::Write` because
257/// there is no `std::fmt::Write` trait implementation for `Vec<u8>`.
258pub trait Write {
259    /// Write the given string to this writer, returning whether the write
260    /// succeeded or not.
261    fn write_str(&mut self, string: &str) -> Result<(), Error>;
262
263    /// Write the given character to this writer, returning whether the write
264    /// succeeded or not.
265    #[inline]
266    fn write_char(&mut self, char: char) -> Result<(), Error> {
267        self.write_str(char.encode_utf8(&mut [0; 4]))
268    }
269}
270
271#[cfg(any(test, feature = "alloc"))]
272impl Write for alloc::string::String {
273    #[inline]
274    fn write_str(&mut self, string: &str) -> Result<(), Error> {
275        self.push_str(string);
276        Ok(())
277    }
278}
279
280#[cfg(any(test, feature = "alloc"))]
281impl Write for alloc::vec::Vec<u8> {
282    #[inline]
283    fn write_str(&mut self, string: &str) -> Result<(), Error> {
284        self.extend_from_slice(string.as_bytes());
285        Ok(())
286    }
287}
288
289impl<W: Write> Write for &mut W {
290    fn write_str(&mut self, string: &str) -> Result<(), Error> {
291        (**self).write_str(string)
292    }
293
294    #[inline]
295    fn write_char(&mut self, char: char) -> Result<(), Error> {
296        (**self).write_char(char)
297    }
298}
299
300/// An adapter for using `std::io::Write` implementations with `fmt::Write`.
301///
302/// This is useful when one wants to format a datetime or span directly
303/// to something with a `std::io::Write` trait implementation but not a
304/// `fmt::Write` implementation.
305///
306/// # Example
307///
308/// ```no_run
309/// use std::{fs::File, io::{BufWriter, Write}, path::Path};
310///
311/// use jiff::{civil::date, fmt::{StdIoWrite, temporal::DateTimePrinter}};
312///
313/// let zdt = date(2024, 6, 15).at(7, 0, 0, 0).in_tz("America/New_York")?;
314///
315/// let path = Path::new("/tmp/output");
316/// let mut file = BufWriter::new(File::create(path)?);
317/// DateTimePrinter::new().print_zoned(&zdt, StdIoWrite(&mut file)).unwrap();
318/// file.flush()?;
319/// assert_eq!(
320///     std::fs::read_to_string(path)?,
321///     "2024-06-15T07:00:00-04:00[America/New_York]",
322/// );
323///
324/// # Ok::<(), Box<dyn std::error::Error>>(())
325/// ```
326#[cfg(feature = "std")]
327#[derive(Clone, Debug)]
328pub struct StdIoWrite<W>(pub W);
329
330#[cfg(feature = "std")]
331impl<W: std::io::Write> Write for StdIoWrite<W> {
332    #[inline]
333    fn write_str(&mut self, string: &str) -> Result<(), Error> {
334        self.0.write_all(string.as_bytes()).map_err(Error::adhoc)
335    }
336}
337
338/// An adapter for using `std::fmt::Write` implementations with `fmt::Write`.
339///
340/// This is useful when one wants to format a datetime or span directly
341/// to something with a `std::fmt::Write` trait implementation but not a
342/// `fmt::Write` implementation.
343///
344/// (Despite using `Std` in this name, this type is available in `core`-only
345/// configurations.)
346///
347/// # Example
348///
349/// This example shows the `std::fmt::Display` trait implementation for
350/// [`civil::DateTime`](crate::civil::DateTime) (but using a wrapper type).
351///
352/// ```
353/// use jiff::{civil::DateTime, fmt::{temporal::DateTimePrinter, StdFmtWrite}};
354///
355/// struct MyDateTime(DateTime);
356///
357/// impl std::fmt::Display for MyDateTime {
358///     fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
359///
360///         static P: DateTimePrinter = DateTimePrinter::new();
361///         P.print_datetime(&self.0, StdFmtWrite(f))
362///             .map_err(|_| std::fmt::Error)
363///     }
364/// }
365///
366/// let dt = MyDateTime(DateTime::constant(2024, 6, 15, 17, 30, 0, 0));
367/// assert_eq!(dt.to_string(), "2024-06-15T17:30:00");
368/// ```
369#[derive(Clone, Debug)]
370pub struct StdFmtWrite<W>(pub W);
371
372impl<W: core::fmt::Write> Write for StdFmtWrite<W> {
373    #[inline]
374    fn write_str(&mut self, string: &str) -> Result<(), Error> {
375        self.0
376            .write_str(string)
377            .map_err(|_| err!("an error occurred when formatting an argument"))
378    }
379}
380
381impl<W: Write> core::fmt::Write for StdFmtWrite<W> {
382    #[inline]
383    fn write_str(&mut self, string: &str) -> Result<(), core::fmt::Error> {
384        self.0.write_str(string).map_err(|_| core::fmt::Error)
385    }
386}
387
388/// An extension trait to `Write` that provides crate internal routines.
389///
390/// These routines aren't exposed because they make use of crate internal
391/// types. Those types could perhaps be exposed if there was strong demand,
392/// but I'm skeptical.
393trait WriteExt: Write {
394    /// Write the given number as a decimal using ASCII digits to this buffer.
395    /// The given formatter controls how the decimal is formatted.
396    #[inline]
397    fn write_int(
398        &mut self,
399        formatter: &DecimalFormatter,
400        n: impl Into<i64>,
401    ) -> Result<(), Error> {
402        self.write_decimal(&Decimal::new(formatter, n.into()))
403    }
404
405    /// Write the given fractional number using ASCII digits to this buffer.
406    /// The given formatter controls how the fractional number is formatted.
407    #[inline]
408    fn write_fraction(
409        &mut self,
410        formatter: &FractionalFormatter,
411        n: impl Into<i64>,
412    ) -> Result<(), Error> {
413        self.write_fractional(&Fractional::new(formatter, n.into()))
414    }
415
416    /// Write the given decimal number to this buffer.
417    #[inline]
418    fn write_decimal(&mut self, decimal: &Decimal) -> Result<(), Error> {
419        self.write_str(decimal.as_str())
420    }
421
422    /// Write the given fractional number to this buffer.
423    #[inline]
424    fn write_fractional(
425        &mut self,
426        fractional: &Fractional,
427    ) -> Result<(), Error> {
428        self.write_str(fractional.as_str())
429    }
430}
431
432impl<W: Write> WriteExt for W {}