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::{fmt::Error as E, Error},
170 util::escape,
171};
172
173mod buffer;
174pub mod friendly;
175mod offset;
176pub mod rfc2822;
177mod rfc9557;
178#[cfg(feature = "serde")]
179pub mod serde;
180pub mod strtime;
181pub mod temporal;
182mod util;
183
184/// The result of parsing a value out of a slice of bytes.
185///
186/// This contains both the parsed value and the offset at which the value
187/// ended in the input given. This makes it possible to parse, for example, a
188/// datetime value as a prefix of some larger string without knowing ahead of
189/// time where it ends.
190#[derive(Clone)]
191pub(crate) struct Parsed<'i, V> {
192 /// The value parsed.
193 value: V,
194 /// The remaining unparsed input.
195 input: &'i [u8],
196}
197
198impl<'i, V> Parsed<'i, V> {
199 #[inline]
200 fn and_then<U>(
201 self,
202 map: impl FnOnce(V) -> Result<U, Error>,
203 ) -> Result<Parsed<'i, U>, Error> {
204 let Parsed { value, input } = self;
205 Ok(Parsed { value: map(value)?, input })
206 }
207}
208
209impl<'i, V: core::fmt::Display> Parsed<'i, V> {
210 /// Ensures that the parsed value represents the entire input. This occurs
211 /// precisely when the `input` on this parsed value is empty.
212 ///
213 /// This is useful when one expects a parsed value to consume the entire
214 /// input, and to consider it an error if it doesn't.
215 #[inline]
216 fn into_full(self) -> Result<V, Error> {
217 if self.input.is_empty() {
218 return Ok(self.value);
219 }
220 Err(Error::from(E::into_full_error(&self.value, self.input)))
221 }
222}
223
224impl<'i, V> Parsed<'i, V> {
225 /// Ensures that the parsed value represents the entire input. This occurs
226 /// precisely when the `input` on this parsed value is empty.
227 ///
228 /// This is useful when one expects a parsed value to consume the entire
229 /// input, and to consider it an error if it doesn't.
230 ///
231 /// This is like `Parsed::into_full`, but lets the caller provide a custom
232 /// `Display` implementation.
233 #[inline]
234 fn into_full_with(
235 self,
236 display: impl core::fmt::Display,
237 ) -> Result<V, Error> {
238 if self.input.is_empty() {
239 return Ok(self.value);
240 }
241 Err(Error::from(E::into_full_error(&display, self.input)))
242 }
243}
244
245impl<'i, V: core::fmt::Debug> core::fmt::Debug for Parsed<'i, V> {
246 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
247 f.debug_struct("Parsed")
248 .field("value", &self.value)
249 .field("input", &escape::Bytes(self.input))
250 .finish()
251 }
252}
253
254/// A trait for printing datetimes or spans into Unicode-accepting buffers or
255/// streams.
256///
257/// The most useful implementations of this trait are for the `String` and
258/// `Vec<u8>` types. But any implementation of [`std::fmt::Write`] and
259/// [`std::io::Write`] can be used via the [`StdFmtWrite`] and [`StdIoWrite`]
260/// adapters, respectively.
261///
262/// Most users of Jiff should not need to interact with this trait directly.
263/// Instead, printing is handled via the [`Display`](std::fmt::Display)
264/// implementation of the relevant type.
265///
266/// # Design
267///
268/// This trait is a near-clone of the `std::fmt::Write` trait. It's also very
269/// similar to the `std::io::Write` trait, but like `std::fmt::Write`, this
270/// trait is limited to writing valid UTF-8. The UTF-8 restriction was adopted
271/// because we really want to support printing datetimes and spans to `String`
272/// buffers. If we permitted writing `&[u8]` data, then writing to a `String`
273/// buffer would always require a costly UTF-8 validation check.
274///
275/// The `std::fmt::Write` trait wasn't used itself because:
276///
277/// 1. Using a custom trait allows us to require using Jiff's error type.
278/// (Although this extra flexibility isn't currently used much, since printing
279/// rarely fails for any reason other than the underlying `jiff::fmt::Write`
280/// implementation failing.)
281/// 2. Using a custom trait allows us more control over the implementations of
282/// the trait. For example, a custom trait means we can format directly into
283/// a `Vec<u8>` buffer, which isn't possible with `std::fmt::Write` because
284/// there is no `std::fmt::Write` trait implementation for `Vec<u8>`.
285pub trait Write {
286 /// Write the given string to this writer, returning whether the write
287 /// succeeded or not.
288 fn write_str(&mut self, string: &str) -> Result<(), Error>;
289
290 /// Write the given character to this writer, returning whether the write
291 /// succeeded or not.
292 #[inline]
293 fn write_char(&mut self, char: char) -> Result<(), Error> {
294 self.write_str(char.encode_utf8(&mut [0; 4]))
295 }
296
297 /// Returns a `Vec<u8>` backing store for this implementation.
298 ///
299 /// Consumers of this trait may call this method to get a view directly
300 /// into a buffer for more optimized writing.
301 ///
302 /// The default implementation always returns `None`.
303 ///
304 /// This method is only available when Jiff's `alloc` feature is enabled.
305 ///
306 /// # Safety
307 ///
308 /// Callers must ensure that only valid UTF-8 is written to the buffer
309 /// returned.
310 #[cfg(feature = "alloc")]
311 #[inline]
312 unsafe fn as_mut_vec(&mut self) -> Option<&mut alloc::vec::Vec<u8>> {
313 None
314 }
315}
316
317#[cfg(any(test, feature = "alloc"))]
318impl Write for alloc::string::String {
319 #[inline]
320 fn write_str(&mut self, string: &str) -> Result<(), Error> {
321 self.push_str(string);
322 Ok(())
323 }
324
325 #[cfg(feature = "alloc")]
326 #[inline]
327 unsafe fn as_mut_vec(&mut self) -> Option<&mut alloc::vec::Vec<u8>> {
328 // SAFETY: The conditions are forwarded to the trait interface.
329 unsafe { Some(alloc::string::String::as_mut_vec(self)) }
330 }
331}
332
333#[cfg(any(test, feature = "alloc"))]
334impl Write for alloc::vec::Vec<u8> {
335 #[inline]
336 fn write_str(&mut self, string: &str) -> Result<(), Error> {
337 self.extend_from_slice(string.as_bytes());
338 Ok(())
339 }
340
341 #[cfg(feature = "alloc")]
342 #[inline]
343 unsafe fn as_mut_vec(&mut self) -> Option<&mut alloc::vec::Vec<u8>> {
344 Some(self)
345 }
346}
347
348impl<W: Write> Write for &mut W {
349 fn write_str(&mut self, string: &str) -> Result<(), Error> {
350 (**self).write_str(string)
351 }
352
353 #[inline]
354 fn write_char(&mut self, char: char) -> Result<(), Error> {
355 (**self).write_char(char)
356 }
357
358 #[cfg(feature = "alloc")]
359 #[inline]
360 unsafe fn as_mut_vec(&mut self) -> Option<&mut alloc::vec::Vec<u8>> {
361 // SAFETY: The conditions are forwarded to the trait interface.
362 unsafe { (**self).as_mut_vec() }
363 }
364}
365
366impl Write for &mut dyn Write {
367 fn write_str(&mut self, string: &str) -> Result<(), Error> {
368 (**self).write_str(string)
369 }
370
371 #[inline]
372 fn write_char(&mut self, char: char) -> Result<(), Error> {
373 (**self).write_char(char)
374 }
375
376 #[cfg(feature = "alloc")]
377 #[inline]
378 unsafe fn as_mut_vec(&mut self) -> Option<&mut alloc::vec::Vec<u8>> {
379 // SAFETY: The conditions are forwarded to the trait interface.
380 unsafe { (**self).as_mut_vec() }
381 }
382}
383
384/// An adapter for using `std::io::Write` implementations with `fmt::Write`.
385///
386/// This is useful when one wants to format a datetime or span directly
387/// to something with a `std::io::Write` trait implementation but not a
388/// `fmt::Write` implementation.
389///
390/// # Example
391///
392/// ```no_run
393/// use std::{fs::File, io::{BufWriter, Write}, path::Path};
394///
395/// use jiff::{civil::date, fmt::{StdIoWrite, temporal::DateTimePrinter}};
396///
397/// let zdt = date(2024, 6, 15).at(7, 0, 0, 0).in_tz("America/New_York")?;
398///
399/// let path = Path::new("/tmp/output");
400/// let mut file = BufWriter::new(File::create(path)?);
401/// DateTimePrinter::new().print_zoned(&zdt, StdIoWrite(&mut file)).unwrap();
402/// file.flush()?;
403/// assert_eq!(
404/// std::fs::read_to_string(path)?,
405/// "2024-06-15T07:00:00-04:00[America/New_York]",
406/// );
407///
408/// # Ok::<(), Box<dyn std::error::Error>>(())
409/// ```
410#[cfg(feature = "std")]
411#[derive(Clone, Debug)]
412pub struct StdIoWrite<W>(pub W);
413
414#[cfg(feature = "std")]
415impl<W: std::io::Write> Write for StdIoWrite<W> {
416 #[inline]
417 fn write_str(&mut self, string: &str) -> Result<(), Error> {
418 self.0.write_all(string.as_bytes()).map_err(Error::io)
419 }
420}
421
422/// An adapter for using `std::fmt::Write` implementations with `fmt::Write`.
423///
424/// This is useful when one wants to format a datetime or span directly
425/// to something with a `std::fmt::Write` trait implementation but not a
426/// `fmt::Write` implementation.
427///
428/// (Despite using `Std` in this name, this type is available in `core`-only
429/// configurations.)
430///
431/// # Example
432///
433/// This example shows the `std::fmt::Display` trait implementation for
434/// [`civil::DateTime`](crate::civil::DateTime) (but using a wrapper type).
435///
436/// ```
437/// use jiff::{civil::DateTime, fmt::{temporal::DateTimePrinter, StdFmtWrite}};
438///
439/// struct MyDateTime(DateTime);
440///
441/// impl std::fmt::Display for MyDateTime {
442/// fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
443///
444/// static P: DateTimePrinter = DateTimePrinter::new();
445/// P.print_datetime(&self.0, StdFmtWrite(f))
446/// .map_err(|_| std::fmt::Error)
447/// }
448/// }
449///
450/// let dt = MyDateTime(DateTime::constant(2024, 6, 15, 17, 30, 0, 0));
451/// assert_eq!(dt.to_string(), "2024-06-15T17:30:00");
452/// ```
453#[derive(Clone, Debug)]
454pub struct StdFmtWrite<W>(pub W);
455
456impl<W: core::fmt::Write> Write for StdFmtWrite<W> {
457 #[inline]
458 fn write_str(&mut self, string: &str) -> Result<(), Error> {
459 self.0
460 .write_str(string)
461 .map_err(|_| Error::from(E::StdFmtWriteAdapter))
462 }
463}
464
465impl<W: Write> core::fmt::Write for StdFmtWrite<W> {
466 #[inline]
467 fn write_str(&mut self, string: &str) -> Result<(), core::fmt::Error> {
468 self.0.write_str(string).map_err(|_| core::fmt::Error)
469 }
470}