Skip to main content

jiff/fmt/temporal/
mod.rs

1/*!
2A hybrid format derived from [RFC 3339], [RFC 9557] and [ISO 8601].
3
4This module provides an implementation of the [Temporal ISO 8601 grammar]. The
5API is spread out over parsers and printers for datetimes and spans.
6
7Note that for most use cases, you should be using the corresponding
8[`Display`](std::fmt::Display) or [`FromStr`](std::str::FromStr) trait
9implementations for printing and parsing respectively. This module provides
10a "lower level" API for configuring the behavior of printing and parsing,
11including the ability to parse from byte strings (i.e., `&[u8]`).
12
13# Date and time format
14
15The specific format supported depends on what kind of type you're trying to
16parse into. Here are some examples to give a general idea:
17
18* `02:21:58` parses into a [`civil::Time`].
19* `2020-08-21` parses into a [`civil::Date`].
20* `2020-08-21T02:21:58` and `2020-08-21 02:21:58` both parse into a
21  [`civil::DateTime`].
22* `2020-08-21T02:21:58-04` parses into an [`Timestamp`].
23* `2020-08-21T02:21:58-04[America/New_York]` parses into a [`Zoned`].
24
25Smaller types can generally be parsed from strings representing a bigger type.
26For example, a `civil::Date` can be parsed from `2020-08-21T02:21:58`.
27
28As mentioned above, the datetime format supported by Jiff is a hybrid of the
29"best" parts of [RFC 3339], [RFC 9557] and [ISO 8601]. Generally speaking, [RFC
303339] and [RFC 9557] are supported in their entirety, but not all of ISO 8601
31is. For example, `2024-06-16T10.5` is a valid ISO 8601 datetime, but isn't
32supported by Jiff. (Only fractional seconds are supported.)
33
34Some additional details worth noting:
35
36* Parsing `Zoned` values requires a datetime string with a time zone
37annotation like `[America/New_York]` or `[-07:00]`. If you need to parse a
38datetime without a time zone annotation (but with an offset), then you should
39parse it as an [`Timestamp`]. From there, it can be converted to a `Zoned` via
40[`Timestamp::to_zoned`].
41* When parsing `Zoned` values, ambiguous datetimes are handled via the
42[`DateTimeParser::disambiguation`] configuration. By default, a "compatible"
43mode is used where the earlier time is selected in a backward transition, while
44the later time is selected in a forward transition.
45* When parsing `Zoned` values, conflicts between the offset and the time zone
46in the datetime string are handled via the [`DateTimeParser::offset_conflict`]
47configuration. By default, any inconsistency between the offset and the time
48zone results in a parse error.
49* When parsing civil types like `civil::DateTime`, it's always an error if the
50datetime string has a `Z` (Zulu) offset. It's an error since interpreting such
51strings as civil time is usually a bug.
52* In all cases, the `T` designator separating the date and time may be an ASCII
53space instead.
54
55The complete datetime format supported is described by the
56[Temporal ISO 8601 grammar].
57
58# Span format
59
60To a first approximation, the span format supported roughly corresponds to this
61regular expression:
62
63```text
64P(\d+y)?(\d+m)?(\d+w)?(\d+d)?(T(\d+h)?(\d+m)?(\d+s)?)?
65```
66
67But there are some details not easily captured by a simple regular expression:
68
69* At least one unit must be specified. To write a zero span, specify `0` for
70any unit. For example, `P0d` and `PT0s` are equivalent.
71* The format is case insensitive. The printer will by default capitalize all
72designators, but the unit designators can be configured to use lowercase with
73[`SpanPrinter::lowercase`]. For example, `P3y1m10dT5h` instead of
74`P3Y1M10DT5H`. You might prefer lowercase since you may find it easier to read.
75However, it is an extension to ISO 8601 and isn't as broadly supported.
76* Hours, minutes or seconds may be fractional. And the only units that may be
77fractional are the lowest units.
78* A span like `P99999999999y` is invalid because it exceeds the allowable range
79of time representable by a [`Span`].
80
81This is, roughly speaking, a subset of what [ISO 8601] specifies. It isn't
82strictly speaking a subset since Jiff (like Temporal) permits week units to be
83mixed with other units.
84
85Here are some examples:
86
87```
88use jiff::{Span, ToSpan};
89
90let spans = [
91    ("P40D", 40.days()),
92    ("P1y1d", 1.year().days(1)),
93    ("P3dT4h59m", 3.days().hours(4).minutes(59)),
94    ("PT2H30M", 2.hours().minutes(30)),
95    ("P1m", 1.month()),
96    ("P1w", 1.week()),
97    ("P1w4d", 1.week().days(4)),
98    ("PT1m", 1.minute()),
99    ("PT0.0021s", 2.milliseconds().microseconds(100)),
100    ("PT0s", 0.seconds()),
101    ("P0d", 0.seconds()),
102    (
103        "P1y1m1dT1h1m1.1s",
104        1.year().months(1).days(1).hours(1).minutes(1).seconds(1).milliseconds(100),
105    ),
106];
107for (string, span) in spans {
108    let parsed: Span = string.parse()?;
109    assert_eq!(
110        span.fieldwise(),
111        parsed.fieldwise(),
112        "result of parsing {string:?}",
113    );
114}
115
116# Ok::<(), Box<dyn std::error::Error>>(())
117```
118
119One can also parse ISO 8601 durations into a [`SignedDuration`], but units are
120limited to hours or smaller:
121
122```
123use jiff::SignedDuration;
124
125let durations = [
126    ("PT2H30M", SignedDuration::from_secs(2 * 60 * 60 + 30 * 60)),
127    ("PT2.5h", SignedDuration::from_secs(2 * 60 * 60 + 30 * 60)),
128    ("PT1m", SignedDuration::from_mins(1)),
129    ("PT1.5m", SignedDuration::from_secs(90)),
130    ("PT0.0021s", SignedDuration::new(0, 2_100_000)),
131    ("PT0s", SignedDuration::ZERO),
132    ("PT0.000000001s", SignedDuration::from_nanos(1)),
133];
134for (string, duration) in durations {
135    let parsed: SignedDuration = string.parse()?;
136    assert_eq!(duration, parsed, "result of parsing {string:?}");
137}
138
139# Ok::<(), Box<dyn std::error::Error>>(())
140```
141
142The complete span format supported is described by the [Temporal ISO 8601
143grammar].
144
145# Differences with Temporal
146
147Jiff implements Temporal's grammar pretty closely, but there are a few
148differences at the time of writing. It is a specific goal that all differences
149should be rooted in what Jiff itself supports, and not in the grammar itself.
150
151* The maximum UTC offset value expressible is `25:59:59` in Jiff, where as in
152Temporal it's `23:59:59.999999999`. Jiff supports a slightly bigger maximum
153to account for all valid values of POSIX time zone strings. Jiff also lacks
154nanosecond precision for UTC offsets, as it's not clear how useful that is in
155practice.
156* Jiff doesn't support a datetime range as big as Temporal. For example,
157in Temporal, `+202024-06-14T17:30[America/New_York]` is valid. But in Jiff,
158since the maximum supported year is `9999`, parsing will fail. Jiff's datetime
159range may be expanded in the future, but it is a non-goal to match Temporal's
160range precisely.
161* Jiff doesn't support RFC 9557 calendar annotations because Jiff only supports
162the Gregorian calendar.
163
164There is some more [background on Temporal's format] available.
165
166[Temporal ISO 8601 grammar]: https://tc39.es/proposal-temporal/#sec-temporal-iso8601grammar
167[RFC 3339]: https://www.rfc-editor.org/rfc/rfc3339
168[RFC 9557]: https://www.rfc-editor.org/rfc/rfc9557.html
169[ISO 8601]: https://www.iso.org/iso-8601-date-and-time-format.html
170[background on Temporal's format]: https://github.com/tc39/proposal-temporal/issues/2843
171*/
172
173use crate::{
174    civil::{self, ISOWeekDate},
175    error::Error,
176    fmt::Write,
177    span::Span,
178    tz::{Disambiguation, Offset, OffsetConflict, TimeZone, TimeZoneDatabase},
179    SignedDuration, Timestamp, Zoned,
180};
181
182pub use self::pieces::{
183    Pieces, PiecesNumericOffset, PiecesOffset, TimeZoneAnnotation,
184    TimeZoneAnnotationKind, TimeZoneAnnotationName,
185};
186
187mod parser;
188mod pieces;
189mod printer;
190
191/// The default date time parser that we use throughout Jiff.
192pub(crate) static DEFAULT_DATETIME_PARSER: DateTimeParser =
193    DateTimeParser::new();
194
195/// The default date time printer that we use throughout Jiff.
196pub(crate) static DEFAULT_DATETIME_PRINTER: DateTimePrinter =
197    DateTimePrinter::new();
198
199/// The default date time parser that we use throughout Jiff.
200pub(crate) static DEFAULT_SPAN_PARSER: SpanParser = SpanParser::new();
201
202/// The default date time printer that we use throughout Jiff.
203pub(crate) static DEFAULT_SPAN_PRINTER: SpanPrinter = SpanPrinter::new();
204
205/// A parser for Temporal datetimes.
206///
207/// This parser converts a machine (but also human) readable format of a
208/// datetime to the various types found in Jiff: [`Zoned`], [`Timestamp`],
209/// [`civil::DateTime`], [`civil::Date`] or [`civil::Time`]. Note that all
210/// of those types provide [`FromStr`](core::str::FromStr) implementations
211/// that utilize the default configuration of this parser. However, this parser
212/// can be configured to behave differently and can also parse directly from
213/// a `&[u8]`.
214///
215/// See the [`fmt::temporal`](crate::fmt::temporal) module documentation for
216/// more information on the specific format used.
217///
218/// # Example
219///
220/// This example shows how to parse a `Zoned` datetime from a byte string.
221/// (That is, `&[u8]` and not a `&str`.)
222///
223/// ```
224/// use jiff::{civil::date, fmt::temporal::DateTimeParser, tz};
225///
226/// // A parser can be created in a const context.
227/// static PARSER: DateTimeParser = DateTimeParser::new();
228///
229/// let zdt = PARSER.parse_zoned(b"2024-06-15T07-04[America/New_York]")?;
230/// assert_eq!(zdt.datetime(), date(2024, 6, 15).at(7, 0, 0, 0));
231/// assert_eq!(zdt.time_zone(), &tz::db().get("America/New_York")?);
232///
233/// # Ok::<(), Box<dyn std::error::Error>>(())
234/// ```
235///
236/// Note that an ASCII space instead of the `T` separator is automatically
237/// supported too:
238///
239/// ```
240/// use jiff::{civil::date, fmt::temporal::DateTimeParser, tz};
241///
242/// // A parser can be created in a const context.
243/// static PARSER: DateTimeParser = DateTimeParser::new();
244///
245/// let zdt = PARSER.parse_zoned(b"2024-06-15 07-04[America/New_York]")?;
246/// assert_eq!(zdt.datetime(), date(2024, 6, 15).at(7, 0, 0, 0));
247/// assert_eq!(zdt.time_zone(), &tz::db().get("America/New_York")?);
248///
249/// # Ok::<(), Box<dyn std::error::Error>>(())
250/// ```
251#[derive(Debug)]
252pub struct DateTimeParser {
253    p: parser::DateTimeParser,
254    offset_conflict: OffsetConflict,
255    disambiguation: Disambiguation,
256}
257
258impl DateTimeParser {
259    /// Create a new Temporal datetime parser with the default configuration.
260    #[inline]
261    pub const fn new() -> DateTimeParser {
262        DateTimeParser {
263            p: parser::DateTimeParser::new(),
264            offset_conflict: OffsetConflict::Reject,
265            disambiguation: Disambiguation::Compatible,
266        }
267    }
268
269    /// Set the conflict resolution strategy for when an offset in a datetime
270    /// string is inconsistent with the time zone.
271    ///
272    /// See the documentation on [`OffsetConflict`] for more details about the
273    /// different strategies one can choose.
274    ///
275    /// This only applies when parsing [`Zoned`] values.
276    ///
277    /// The default is [`OffsetConflict::Reject`], which results in an error
278    /// whenever parsing a datetime with an offset that is inconsistent with
279    /// the time zone.
280    ///
281    /// # Example: respecting offsets even when they're invalid
282    ///
283    /// ```
284    /// use jiff::{civil::date, fmt::temporal::DateTimeParser, tz};
285    ///
286    /// static PARSER: DateTimeParser = DateTimeParser::new()
287    ///     .offset_conflict(tz::OffsetConflict::AlwaysOffset);
288    ///
289    /// let zdt = PARSER.parse_zoned("2024-06-09T07:00-05[America/New_York]")?;
290    /// // Notice that the time *and* offset have been corrected. The offset
291    /// // given was invalid for `America/New_York` at the given time, so
292    /// // it cannot be kept, but the instant returned is equivalent to
293    /// // `2024-06-09T07:00-05`. It is just adjusted automatically to be
294    /// // correct in the `America/New_York` time zone.
295    /// assert_eq!(zdt.datetime(), date(2024, 6, 9).at(8, 0, 0, 0));
296    /// assert_eq!(zdt.offset(), tz::offset(-4));
297    ///
298    /// # Ok::<(), Box<dyn std::error::Error>>(())
299    /// ```
300    ///
301    /// # Example: all offsets are invalid for gaps in civil time by default
302    ///
303    /// When parsing a datetime with an offset for a gap in civil time, the
304    /// offset is treated as invalid. This results in parsing failing. For
305    /// example, some parts of Indiana in the US didn't start using daylight
306    /// saving time until 2006. If a datetime for 2006 were serialized before
307    /// the updated daylight saving time rules were known, then this parse
308    /// error will prevent you from silently changing the originally intended
309    /// time:
310    ///
311    /// ```
312    /// use jiff::{fmt::temporal::DateTimeParser};
313    ///
314    /// static PARSER: DateTimeParser = DateTimeParser::new();
315    ///
316    /// // DST in Indiana/Vevay began at 2006-04-02T02:00 local time.
317    /// // The last time Indiana/Vevay observed DST was in 1972.
318    /// let result = PARSER.parse_zoned(
319    ///     "2006-04-02T02:30-05[America/Indiana/Vevay]",
320    /// );
321    /// assert_eq!(
322    ///     result.unwrap_err().to_string(),
323    ///     "datetime could not resolve to timestamp since `reject` \
324    ///      conflict resolution was chosen, and because datetime \
325    ///      has offset `-05`, but the time zone `America/Indiana/Vevay` \
326    ///      for the given datetime falls in a gap (between offsets \
327    ///      `-05` and `-04`), and all offsets for a gap are \
328    ///      regarded as invalid",
329    /// );
330    /// ```
331    ///
332    /// If one doesn't want an error here, then you can either prioritize the
333    /// instant in time by respecting the offset:
334    ///
335    /// ```
336    /// use jiff::{fmt::temporal::DateTimeParser, tz};
337    ///
338    /// static PARSER: DateTimeParser = DateTimeParser::new()
339    ///     .offset_conflict(tz::OffsetConflict::AlwaysOffset);
340    ///
341    /// let zdt = PARSER.parse_zoned(
342    ///     "2006-04-02T02:30-05[America/Indiana/Vevay]",
343    /// )?;
344    /// assert_eq!(
345    ///     zdt.to_string(),
346    ///     "2006-04-02T03:30:00-04:00[America/Indiana/Vevay]",
347    /// );
348    ///
349    /// # Ok::<(), Box<dyn std::error::Error>>(())
350    /// ```
351    ///
352    /// or you can force your own disambiguation rules, e.g., by taking the
353    /// earlier time:
354    ///
355    /// ```
356    /// use jiff::{fmt::temporal::DateTimeParser, tz};
357    ///
358    /// static PARSER: DateTimeParser = DateTimeParser::new()
359    ///     .disambiguation(tz::Disambiguation::Earlier)
360    ///     .offset_conflict(tz::OffsetConflict::AlwaysTimeZone);
361    ///
362    /// let zdt = PARSER.parse_zoned(
363    ///     "2006-04-02T02:30-05[America/Indiana/Vevay]",
364    /// )?;
365    /// assert_eq!(
366    ///     zdt.to_string(),
367    ///     "2006-04-02T01:30:00-05:00[America/Indiana/Vevay]",
368    /// );
369    ///
370    /// # Ok::<(), Box<dyn std::error::Error>>(())
371    /// ```
372    ///
373    /// # Example: a `Z` never results in an offset conflict
374    ///
375    /// [RFC 9557] specifies that `Z` indicates that the offset from UTC to
376    /// get local time is unknown. Since it doesn't prescribe a particular
377    /// offset, when a `Z` is parsed with a time zone annotation, the
378    /// `OffsetConflict::ALwaysOffset` strategy is used regardless of what
379    /// is set here. For example:
380    ///
381    /// ```
382    /// use jiff::fmt::temporal::DateTimeParser;
383    ///
384    /// // NOTE: The default is reject.
385    /// static PARSER: DateTimeParser = DateTimeParser::new();
386    ///
387    /// let zdt = PARSER.parse_zoned(
388    ///     "2025-06-20T17:30Z[America/New_York]",
389    /// )?;
390    /// assert_eq!(
391    ///     zdt.to_string(),
392    ///     "2025-06-20T13:30:00-04:00[America/New_York]",
393    /// );
394    ///
395    /// # Ok::<(), Box<dyn std::error::Error>>(())
396    /// ```
397    ///
398    /// Conversely, if the `+00:00` offset was used, then an error would
399    /// occur because of the offset conflict:
400    ///
401    /// ```
402    /// use jiff::fmt::temporal::DateTimeParser;
403    ///
404    /// // NOTE: The default is reject.
405    /// static PARSER: DateTimeParser = DateTimeParser::new();
406    ///
407    /// let result = PARSER.parse_zoned(
408    ///     "2025-06-20T17:30+00[America/New_York]",
409    /// );
410    /// assert_eq!(
411    ///     result.unwrap_err().to_string(),
412    ///     "datetime could not resolve to a timestamp since `reject` \
413    ///      conflict resolution was chosen, and because datetime has \
414    ///      offset `+00`, but the time zone `America/New_York` \
415    ///      for the given datetime unambiguously has offset `-04`",
416    /// );
417    /// ```
418    ///
419    /// [RFC 9557]: https://datatracker.ietf.org/doc/rfc9557/
420    #[inline]
421    pub const fn offset_conflict(
422        self,
423        strategy: OffsetConflict,
424    ) -> DateTimeParser {
425        DateTimeParser { offset_conflict: strategy, ..self }
426    }
427
428    /// Set the disambiguation strategy for when a datetime falls into a time
429    /// zone transition "fold" or "gap."
430    ///
431    /// The most common manifestation of such time zone transitions is daylight
432    /// saving time. In most cases, the transition into daylight saving time
433    /// moves the civil time ("the time you see on the clock") ahead one hour.
434    /// This is called a "gap" because an hour on the clock is skipped. While
435    /// the transition out of daylight saving time moves the civil time back
436    /// one hour. This is called a "fold" because an hour on the clock is
437    /// repeated.
438    ///
439    /// In the case of a gap, an ambiguous datetime manifests as a time that
440    /// never appears on a clock. (For example, `02:30` on `2024-03-10` in New
441    /// York.) In the case of a fold, an ambiguous datetime manifests as a
442    /// time that repeats itself. (For example, `01:30` on `2024-11-03` in New
443    /// York.) So when a fold occurs, you don't know whether it's the "first"
444    /// occurrence of that time or the "second."
445    ///
446    /// Time zone transitions are not just limited to daylight saving time,
447    /// although those are the most common. In other cases, a transition occurs
448    /// because of a change in the offset of the time zone itself. (See the
449    /// examples below.)
450    ///
451    /// # Example
452    ///
453    /// This example shows how to set the disambiguation configuration while
454    /// parsing a [`Zoned`] datetime. In this example, we always prefer the
455    /// earlier time.
456    ///
457    /// ```
458    /// use jiff::{civil::date, fmt::temporal::DateTimeParser, tz};
459    ///
460    /// static PARSER: DateTimeParser = DateTimeParser::new()
461    ///     .disambiguation(tz::Disambiguation::Earlier);
462    ///
463    /// let zdt = PARSER.parse_zoned("2024-03-10T02:05[America/New_York]")?;
464    /// assert_eq!(zdt.datetime(), date(2024, 3, 10).at(1, 5, 0, 0));
465    /// assert_eq!(zdt.offset(), tz::offset(-5));
466    ///
467    /// # Ok::<(), Box<dyn std::error::Error>>(())
468    /// ```
469    ///
470    /// # Example: time zone offset change
471    ///
472    /// In this example, we explore a time zone offset change in Hawaii that
473    /// occurred on `1947-06-08`. Namely, Hawaii went from a `-10:30` offset
474    /// to a `-10:00` offset at `02:00`. This results in a 30 minute gap in
475    /// civil time.
476    ///
477    /// ```
478    /// use jiff::{civil::date, fmt::temporal::DateTimeParser, tz, ToSpan};
479    ///
480    /// static PARSER: DateTimeParser = DateTimeParser::new()
481    ///     .disambiguation(tz::Disambiguation::Later);
482    ///
483    /// // 02:05 didn't exist on clocks on 1947-06-08.
484    /// let zdt = PARSER.parse_zoned(
485    ///     "1947-06-08T02:05[Pacific/Honolulu]",
486    /// )?;
487    /// // Our parser is configured to select the later time, so we jump to
488    /// // 02:35. But if we used `Disambiguation::Earlier`, then we'd get
489    /// // 01:35.
490    /// assert_eq!(zdt.datetime(), date(1947, 6, 8).at(2, 35, 0, 0));
491    /// assert_eq!(zdt.offset(), tz::offset(-10));
492    ///
493    /// // If we subtract 10 minutes from 02:35, notice that we (correctly)
494    /// // jump to 01:55 *and* our offset is corrected to -10:30.
495    /// let zdt = zdt.checked_sub(10.minutes())?;
496    /// assert_eq!(zdt.datetime(), date(1947, 6, 8).at(1, 55, 0, 0));
497    /// assert_eq!(zdt.offset(), tz::offset(-10).saturating_sub(30.minutes()));
498    ///
499    /// # Ok::<(), Box<dyn std::error::Error>>(())
500    /// ```
501    #[inline]
502    pub const fn disambiguation(
503        self,
504        strategy: Disambiguation,
505    ) -> DateTimeParser {
506        DateTimeParser { disambiguation: strategy, ..self }
507    }
508
509    /// Parse a datetime string with a time zone annotation into a [`Zoned`]
510    /// value using the system time zone database.
511    ///
512    /// # Errors
513    ///
514    /// This returns an error if the datetime string given is invalid or if it
515    /// is valid but doesn't fit in the datetime range supported by Jiff.
516    ///
517    /// The [`DateTimeParser::offset_conflict`] and
518    /// [`DateTimeParser::disambiguation`] settings can also influence
519    /// whether an error occurs or not. Namely, if [`OffsetConflict::Reject`]
520    /// is used (which is the default), then an error occurs when there
521    /// is an inconsistency between the offset and the time zone. And if
522    /// [`Disambiguation::Reject`] is used, then an error occurs when the civil
523    /// time in the string is ambiguous.
524    ///
525    /// # Example: parsing without an IANA time zone
526    ///
527    /// Note that when parsing a `Zoned` value, it is required for the datetime
528    /// string to contain a time zone annotation in brackets. For example,
529    /// this fails to parse even though it refers to a precise instant in time:
530    ///
531    /// ```
532    /// use jiff::fmt::temporal::DateTimeParser;
533    ///
534    /// static PARSER: DateTimeParser = DateTimeParser::new();
535    ///
536    /// assert!(PARSER.parse_zoned("2024-06-08T07:00-04").is_err());
537    /// ```
538    ///
539    /// While it is better to include a time zone name, if the only thing
540    /// that's available is an offset, the offset can be repeated as a time
541    /// zone annotation:
542    ///
543    /// ```
544    /// use jiff::{civil::date, fmt::temporal::DateTimeParser, tz};
545    ///
546    /// static PARSER: DateTimeParser = DateTimeParser::new();
547    ///
548    /// let zdt = PARSER.parse_zoned("2024-06-08T07:00-04[-04]")?;
549    /// assert_eq!(zdt.datetime(), date(2024, 6, 8).at(7, 0, 0, 0));
550    /// assert_eq!(zdt.offset(), tz::offset(-4));
551    ///
552    /// # Ok::<(), Box<dyn std::error::Error>>(())
553    /// ```
554    ///
555    /// Otherwise, if you need to be able to parse something like
556    /// `2024-06-08T07:00-04` as-is, you should parse it into an [`Timestamp`]:
557    ///
558    /// ```
559    /// use jiff::{civil::date, fmt::temporal::DateTimeParser, tz};
560    ///
561    /// static PARSER: DateTimeParser = DateTimeParser::new();
562    ///
563    /// let timestamp = PARSER.parse_timestamp("2024-06-08T07:00-04")?;
564    /// let zdt = timestamp.to_zoned(tz::TimeZone::UTC);
565    /// assert_eq!(zdt.datetime(), date(2024, 6, 8).at(11, 0, 0, 0));
566    /// assert_eq!(zdt.offset(), tz::offset(0));
567    ///
568    /// # Ok::<(), Box<dyn std::error::Error>>(())
569    /// ```
570    ///
571    /// If you _really_ need to parse something like `2024-06-08T07:00-04`
572    /// into a `Zoned` with a fixed offset of `-04:00` as its `TimeZone`,
573    /// then you'll need to use lower level parsing routines. See the
574    /// documentation on [`Pieces`] for a case study of how to achieve this.
575    pub fn parse_zoned<I: AsRef<[u8]>>(
576        &self,
577        input: I,
578    ) -> Result<Zoned, Error> {
579        self.parse_zoned_with(crate::tz::db(), input)
580    }
581
582    /// Parse a datetime string with a time zone annotation into a [`Zoned`]
583    /// value using the time zone database given.
584    ///
585    /// # Errors
586    ///
587    /// This returns an error if the datetime string given is invalid or if it
588    /// is valid but doesn't fit in the datetime range supported by Jiff.
589    ///
590    /// The [`DateTimeParser::offset_conflict`] and
591    /// [`DateTimeParser::disambiguation`] settings can also influence
592    /// whether an error occurs or not. Namely, if [`OffsetConflict::Reject`]
593    /// is used (which is the default), then an error occurs when there
594    /// is an inconsistency between the offset and the time zone. And if
595    /// [`Disambiguation::Reject`] is used, then an error occurs when the civil
596    /// time in the string is ambiguous.
597    ///
598    /// # Example
599    ///
600    /// This example demonstrates the utility of this routine by parsing a
601    /// datetime using an older copy of the IANA Time Zone Database. This
602    /// example leverages the fact that the 2018 copy of `tzdb` preceded
603    /// Brazil's announcement that daylight saving time would be abolished.
604    /// This meant that datetimes in the future, when parsed with the older
605    /// copy of `tzdb`, would still follow the old daylight saving time rules.
606    /// But a mere update of `tzdb` would otherwise change the meaning of the
607    /// datetime.
608    ///
609    /// This scenario can come up if one stores datetimes in the future.
610    /// This is also why the default offset conflict resolution strategy
611    /// is [`OffsetConflict::Reject`], which prevents one from silently
612    /// re-interpreting datetimes to a different timestamp.
613    ///
614    /// ```no_run
615    /// use jiff::{fmt::temporal::DateTimeParser, tz::{self, TimeZoneDatabase}};
616    ///
617    /// static PARSER: DateTimeParser = DateTimeParser::new();
618    ///
619    /// // Open a version of tzdb from before Brazil announced its abolition
620    /// // of daylight saving time.
621    /// let tzdb2018 = TimeZoneDatabase::from_dir("path/to/tzdb-2018b")?;
622    /// // Open the system tzdb.
623    /// let tzdb = tz::db();
624    ///
625    /// // Parse the same datetime string with the same parser, but using two
626    /// // different versions of tzdb.
627    /// let dt = "2020-01-15T12:00[America/Sao_Paulo]";
628    /// let zdt2018 = PARSER.parse_zoned_with(&tzdb2018, dt)?;
629    /// let zdt = PARSER.parse_zoned_with(tzdb, dt)?;
630    ///
631    /// // Before DST was abolished, 2020-01-15 was in DST, which corresponded
632    /// // to UTC offset -02. Since DST rules applied to datetimes in the
633    /// // future, the 2018 version of tzdb would lead one to interpret
634    /// // 2020-01-15 as being in DST.
635    /// assert_eq!(zdt2018.offset(), tz::offset(-2));
636    /// // But DST was abolished in 2019, which means that 2020-01-15 was no
637    /// // no longer in DST. So after a tzdb update, the same datetime as above
638    /// // now has a different offset.
639    /// assert_eq!(zdt.offset(), tz::offset(-3));
640    ///
641    /// // So if you try to parse a datetime serialized from an older copy of
642    /// // tzdb, you'll get an error under the default configuration because
643    /// // of `OffsetConflict::Reject`. This would succeed if you parsed it
644    /// // using tzdb2018!
645    /// assert!(PARSER.parse_zoned_with(tzdb, zdt2018.to_string()).is_err());
646    ///
647    /// # Ok::<(), Box<dyn std::error::Error>>(())
648    /// ```
649    pub fn parse_zoned_with<I: AsRef<[u8]>>(
650        &self,
651        db: &TimeZoneDatabase,
652        input: I,
653    ) -> Result<Zoned, Error> {
654        let input = input.as_ref();
655        let parsed = self.p.parse_temporal_datetime(input)?;
656        let dt = parsed.into_full()?;
657        let zoned =
658            dt.to_zoned(db, self.offset_conflict, self.disambiguation)?;
659        Ok(zoned)
660    }
661
662    /// Parse a datetime string into a [`Timestamp`].
663    ///
664    /// The datetime string must correspond to a specific instant in time. This
665    /// requires an offset in the datetime string.
666    ///
667    /// # Errors
668    ///
669    /// This returns an error if the datetime string given is invalid or if it
670    /// is valid but doesn't fit in the datetime range supported by Jiff.
671    ///
672    /// # Example
673    ///
674    /// This shows a basic example of parsing an `Timestamp`.
675    ///
676    /// ```
677    /// use jiff::fmt::temporal::DateTimeParser;
678    ///
679    /// static PARSER: DateTimeParser = DateTimeParser::new();
680    ///
681    /// let timestamp = PARSER.parse_timestamp("2024-03-10T02:05-04")?;
682    /// assert_eq!(timestamp.to_string(), "2024-03-10T06:05:00Z");
683    ///
684    /// # Ok::<(), Box<dyn std::error::Error>>(())
685    /// ```
686    ///
687    /// # Example: parsing a timestamp from a datetime with a time zone
688    ///
689    /// A timestamp can also be parsed from a time zone aware datetime string.
690    /// The time zone is ignored and the offset is always used.
691    ///
692    /// ```
693    /// use jiff::fmt::temporal::DateTimeParser;
694    ///
695    /// static PARSER: DateTimeParser = DateTimeParser::new();
696    ///
697    /// let timestamp = PARSER.parse_timestamp(
698    ///     "2024-03-10T02:05-04[America/New_York]",
699    /// )?;
700    /// assert_eq!(timestamp.to_string(), "2024-03-10T06:05:00Z");
701    ///
702    /// # Ok::<(), Box<dyn std::error::Error>>(())
703    /// ```
704    pub fn parse_timestamp<I: AsRef<[u8]>>(
705        &self,
706        input: I,
707    ) -> Result<Timestamp, Error> {
708        let input = input.as_ref();
709        let parsed = self.p.parse_temporal_datetime(input)?;
710        let dt = parsed.into_full()?;
711        let timestamp = dt.to_timestamp()?;
712        Ok(timestamp)
713    }
714
715    /// Parse a civil datetime string into a [`civil::DateTime`].
716    ///
717    /// A civil datetime can be parsed from anything that contains a datetime.
718    /// For example, a time zone aware string.
719    ///
720    /// # Errors
721    ///
722    /// This returns an error if the datetime string given is invalid or if it
723    /// is valid but doesn't fit in the datetime range supported by Jiff.
724    ///
725    /// This also returns an error if a `Z` (Zulu) offset is found, since
726    /// interpreting such strings as civil time is usually a bug.
727    ///
728    /// # Example
729    ///
730    /// This shows a basic example of parsing a `civil::DateTime`.
731    ///
732    /// ```
733    /// use jiff::{civil::date, fmt::temporal::DateTimeParser};
734    ///
735    /// static PARSER: DateTimeParser = DateTimeParser::new();
736    ///
737    /// let datetime = PARSER.parse_datetime("2024-03-10T02:05")?;
738    /// assert_eq!(datetime, date(2024, 3, 10).at(2, 5, 0, 0));
739    ///
740    /// # Ok::<(), Box<dyn std::error::Error>>(())
741    /// ```
742    ///
743    /// # Example: parsing fails if a `Z` (Zulu) offset is encountered
744    ///
745    /// Because parsing a datetime with a `Z` offset and interpreting it as
746    /// a civil time is usually a bug, it is forbidden:
747    ///
748    /// ```
749    /// use jiff::{civil::date, fmt::temporal::DateTimeParser};
750    ///
751    /// static PARSER: DateTimeParser = DateTimeParser::new();
752    ///
753    /// assert!(PARSER.parse_datetime("2024-03-10T02:05Z").is_err());
754    ///
755    /// // Note though that -00 and +00 offsets parse successfully.
756    /// let datetime = PARSER.parse_datetime("2024-03-10T02:05+00")?;
757    /// assert_eq!(datetime, date(2024, 3, 10).at(2, 5, 0, 0));
758    /// let datetime = PARSER.parse_datetime("2024-03-10T02:05-00")?;
759    /// assert_eq!(datetime, date(2024, 3, 10).at(2, 5, 0, 0));
760    ///
761    /// # Ok::<(), Box<dyn std::error::Error>>(())
762    /// ```
763    pub fn parse_datetime<I: AsRef<[u8]>>(
764        &self,
765        input: I,
766    ) -> Result<civil::DateTime, Error> {
767        let input = input.as_ref();
768        let parsed = self.p.parse_temporal_datetime(input)?;
769        let dt = parsed.into_full()?;
770        let datetime = dt.to_datetime()?;
771        Ok(datetime)
772    }
773
774    /// Parse a civil date string into a [`civil::Date`].
775    ///
776    /// A civil date can be parsed from anything that contains a date. For
777    /// example, a time zone aware string.
778    ///
779    /// # Errors
780    ///
781    /// This returns an error if the date string given is invalid or if it
782    /// is valid but doesn't fit in the date range supported by Jiff.
783    ///
784    /// This also returns an error if a `Z` (Zulu) offset is found, since
785    /// interpreting such strings as civil date or time is usually a bug.
786    ///
787    /// # Example
788    ///
789    /// This shows a basic example of parsing a `civil::Date`.
790    ///
791    /// ```
792    /// use jiff::{civil::date, fmt::temporal::DateTimeParser};
793    ///
794    /// static PARSER: DateTimeParser = DateTimeParser::new();
795    ///
796    /// let d = PARSER.parse_date("2024-03-10")?;
797    /// assert_eq!(d, date(2024, 3, 10));
798    ///
799    /// # Ok::<(), Box<dyn std::error::Error>>(())
800    /// ```
801    ///
802    /// # Example: parsing fails if a `Z` (Zulu) offset is encountered
803    ///
804    /// Because parsing a date with a `Z` offset and interpreting it as
805    /// a civil date or time is usually a bug, it is forbidden:
806    ///
807    /// ```
808    /// use jiff::{civil::date, fmt::temporal::DateTimeParser};
809    ///
810    /// static PARSER: DateTimeParser = DateTimeParser::new();
811    ///
812    /// assert!(PARSER.parse_date("2024-03-10T00:00:00Z").is_err());
813    ///
814    /// // Note though that -00 and +00 offsets parse successfully.
815    /// let d = PARSER.parse_date("2024-03-10T00:00:00+00")?;
816    /// assert_eq!(d, date(2024, 3, 10));
817    /// let d = PARSER.parse_date("2024-03-10T00:00:00-00")?;
818    /// assert_eq!(d, date(2024, 3, 10));
819    ///
820    /// # Ok::<(), Box<dyn std::error::Error>>(())
821    /// ```
822    pub fn parse_date<I: AsRef<[u8]>>(
823        &self,
824        input: I,
825    ) -> Result<civil::Date, Error> {
826        let input = input.as_ref();
827        let parsed = self.p.parse_temporal_datetime(input)?;
828        let dt = parsed.into_full()?;
829        let date = dt.to_date()?;
830        Ok(date)
831    }
832
833    /// Parse a civil time string into a [`civil::Time`].
834    ///
835    /// A civil time can be parsed from anything that contains a time.
836    /// For example, a time zone aware string.
837    ///
838    /// # Errors
839    ///
840    /// This returns an error if the time string given is invalid or if it
841    /// is valid but doesn't fit in the time range supported by Jiff.
842    ///
843    /// This also returns an error if a `Z` (Zulu) offset is found, since
844    /// interpreting such strings as civil time is usually a bug.
845    ///
846    /// # Example
847    ///
848    /// This shows a basic example of parsing a `civil::Time`.
849    ///
850    /// ```
851    /// use jiff::{civil::time, fmt::temporal::DateTimeParser};
852    ///
853    /// static PARSER: DateTimeParser = DateTimeParser::new();
854    ///
855    /// let t = PARSER.parse_time("02:05")?;
856    /// assert_eq!(t, time(2, 5, 0, 0));
857    ///
858    /// # Ok::<(), Box<dyn std::error::Error>>(())
859    /// ```
860    ///
861    /// # Example: parsing fails if a `Z` (Zulu) offset is encountered
862    ///
863    /// Because parsing a time with a `Z` offset and interpreting it as
864    /// a civil time is usually a bug, it is forbidden:
865    ///
866    /// ```
867    /// use jiff::{civil::time, fmt::temporal::DateTimeParser};
868    ///
869    /// static PARSER: DateTimeParser = DateTimeParser::new();
870    ///
871    /// assert!(PARSER.parse_time("02:05Z").is_err());
872    ///
873    /// // Note though that -00 and +00 offsets parse successfully.
874    /// let t = PARSER.parse_time("02:05+00")?;
875    /// assert_eq!(t, time(2, 5, 0, 0));
876    /// let t = PARSER.parse_time("02:05-00")?;
877    /// assert_eq!(t, time(2, 5, 0, 0));
878    ///
879    /// # Ok::<(), Box<dyn std::error::Error>>(())
880    /// ```
881    pub fn parse_time<I: AsRef<[u8]>>(
882        &self,
883        input: I,
884    ) -> Result<civil::Time, Error> {
885        let input = input.as_ref();
886        let parsed = self.p.parse_temporal_time(input)?;
887        let parsed_time = parsed.into_full()?;
888        let time = parsed_time.to_time();
889        Ok(time)
890    }
891
892    /// Parses a string representing a time zone into a [`TimeZone`].
893    ///
894    /// This will parse one of three different categories of strings:
895    ///
896    /// 1. An IANA Time Zone Database identifier. For example,
897    /// `America/New_York` or `UTC`.
898    /// 2. A fixed offset. For example, `-05:00` or `-00:44:30`.
899    /// 3. A POSIX time zone string. For example, `EST5EDT,M3.2.0,M11.1.0`.
900    ///
901    /// # Example
902    ///
903    /// This shows a few examples of parsing different kinds of time zones:
904    ///
905    /// ```
906    /// use jiff::{fmt::temporal::DateTimeParser, tz::{self, TimeZone}};
907    ///
908    /// static PARSER: DateTimeParser = DateTimeParser::new();
909    ///
910    /// assert_eq!(
911    ///     PARSER.parse_time_zone("-05:00")?,
912    ///     TimeZone::fixed(tz::offset(-5)),
913    /// );
914    /// assert_eq!(
915    ///     PARSER.parse_time_zone("+05:00:01")?,
916    ///     TimeZone::fixed(tz::Offset::from_seconds(5 * 60 * 60 + 1).unwrap()),
917    /// );
918    /// assert_eq!(
919    ///     PARSER.parse_time_zone("America/New_York")?,
920    ///     TimeZone::get("America/New_York")?,
921    /// );
922    /// assert_eq!(
923    ///     PARSER.parse_time_zone("Israel")?,
924    ///     TimeZone::get("Israel")?,
925    /// );
926    /// assert_eq!(
927    ///     PARSER.parse_time_zone("EST5EDT,M3.2.0,M11.1.0")?,
928    ///     TimeZone::posix("EST5EDT,M3.2.0,M11.1.0")?,
929    /// );
930    ///
931    /// // Some error cases!
932    /// assert!(PARSER.parse_time_zone("Z").is_err());
933    /// assert!(PARSER.parse_time_zone("05:00").is_err());
934    /// assert!(PARSER.parse_time_zone("+05:00:01.5").is_err());
935    /// assert!(PARSER.parse_time_zone("Does/Not/Exist").is_err());
936    ///
937    /// # Ok::<(), Box<dyn std::error::Error>>(())
938    /// ```
939    pub fn parse_time_zone<'i, I: AsRef<[u8]>>(
940        &self,
941        input: I,
942    ) -> Result<TimeZone, Error> {
943        self.parse_time_zone_with(crate::tz::db(), input)
944    }
945
946    /// Parses a string representing a time zone into a [`TimeZone`] and
947    /// performs any time zone database lookups using the [`TimeZoneDatabase`]
948    /// given.
949    ///
950    /// This is like [`DateTimeParser::parse_time_zone`], but uses the time
951    /// zone database given instead of the implicit global time zone database.
952    ///
953    /// This will parse one of three different categories of strings:
954    ///
955    /// 1. An IANA Time Zone Database identifier. For example,
956    /// `America/New_York` or `UTC`.
957    /// 2. A fixed offset. For example, `-05:00` or `-00:44:30`.
958    /// 3. A POSIX time zone string. For example, `EST5EDT,M3.2.0,M11.1.0`.
959    ///
960    /// # Example
961    ///
962    /// ```
963    /// use jiff::{fmt::temporal::DateTimeParser, tz::TimeZone};
964    ///
965    /// static PARSER: DateTimeParser = DateTimeParser::new();
966    ///
967    /// let db = jiff::tz::db();
968    /// assert_eq!(
969    ///     PARSER.parse_time_zone_with(db, "America/New_York")?,
970    ///     TimeZone::get("America/New_York")?,
971    /// );
972    ///
973    /// # Ok::<(), Box<dyn std::error::Error>>(())
974    /// ```
975    ///
976    /// See also the example for [`DateTimeParser::parse_zoned_with`] for a
977    /// more interesting example using a time zone database other than the
978    /// default.
979    pub fn parse_time_zone_with<'i, I: AsRef<[u8]>>(
980        &self,
981        db: &TimeZoneDatabase,
982        input: I,
983    ) -> Result<TimeZone, Error> {
984        let input = input.as_ref();
985        let parsed = self.p.parse_time_zone(input)?.into_full()?;
986        parsed.into_time_zone(db)
987    }
988
989    /// Parse a Temporal datetime string into [`Pieces`].
990    ///
991    /// This is a lower level routine meant to give callers raw access to the
992    /// individual "pieces" of a parsed Temporal ISO 8601 datetime string.
993    /// Note that this only includes strings that have a date component.
994    ///
995    /// The benefit of this routine is that it only checks that the datetime
996    /// is itself valid. It doesn't do any automatic diambiguation, offset
997    /// conflict resolution or attempt to prevent you from shooting yourself
998    /// in the foot. For example, this routine will let you parse a fixed
999    /// offset datetime into a `Zoned` without a time zone abbreviation.
1000    ///
1001    /// Note that when using this routine, the
1002    /// [`DateTimeParser::offset_conflict`] and
1003    /// [`DateTimeParser::disambiguation`] configuration knobs are completely
1004    /// ignored. This is because with the lower level `Pieces`, callers must
1005    /// handle offset conflict resolution (if they want it) themselves. See
1006    /// the [`Pieces`] documentation for a case study on how to do this if
1007    /// you need it.
1008    ///
1009    /// # Errors
1010    ///
1011    /// This returns an error if the datetime string given is invalid or if it
1012    /// is valid but doesn't fit in the date range supported by Jiff.
1013    ///
1014    /// # Example
1015    ///
1016    /// This shows how to parse a fixed offset timestamp into a `Zoned`.
1017    ///
1018    /// ```
1019    /// use jiff::{fmt::temporal::DateTimeParser, tz::TimeZone};
1020    ///
1021    /// static PARSER: DateTimeParser = DateTimeParser::new();
1022    ///
1023    /// let timestamp = "2025-01-02T15:13-05";
1024    ///
1025    /// // Normally this operation will fail.
1026    /// assert_eq!(
1027    ///     PARSER.parse_zoned(timestamp).unwrap_err().to_string(),
1028    ///     "failed to find time zone annotation in square brackets, \
1029    ///      which is required for parsing a zoned datetime",
1030    /// );
1031    ///
1032    /// // But you can work-around this with `Pieces`, which gives you direct
1033    /// // access to the components parsed from the string.
1034    /// let pieces = PARSER.parse_pieces(timestamp)?;
1035    /// let time = pieces.time().unwrap_or_else(jiff::civil::Time::midnight);
1036    /// let dt = pieces.date().to_datetime(time);
1037    /// let tz = match pieces.to_time_zone()? {
1038    ///     Some(tz) => tz,
1039    ///     None => {
1040    ///         let Some(offset) = pieces.to_numeric_offset() else {
1041    ///             let msg = format!(
1042    ///                 "timestamp `{timestamp}` has no time zone \
1043    ///                  or offset, and thus cannot be parsed into \
1044    ///                  an instant",
1045    ///             );
1046    ///             return Err(msg.into());
1047    ///         };
1048    ///         TimeZone::fixed(offset)
1049    ///     }
1050    /// };
1051    /// // We don't bother with offset conflict resolution. And note that
1052    /// // this uses automatic "compatible" disambiguation in the case of
1053    /// // discontinuities. Of course, this is all moot if `TimeZone` is
1054    /// // fixed. The above code handles the case where it isn't!
1055    /// let zdt = tz.to_zoned(dt)?;
1056    /// assert_eq!(zdt.to_string(), "2025-01-02T15:13:00-05:00[-05:00]");
1057    ///
1058    /// # Ok::<(), Box<dyn std::error::Error>>(())
1059    /// ```
1060    ///
1061    /// # Example: work around errors when a `Z` (Zulu) offset is encountered
1062    ///
1063    /// Because parsing a date with a `Z` offset and interpreting it as
1064    /// a civil date or time is usually a bug, it is forbidden:
1065    ///
1066    /// ```
1067    /// use jiff::{civil::date, fmt::temporal::DateTimeParser};
1068    ///
1069    /// static PARSER: DateTimeParser = DateTimeParser::new();
1070    ///
1071    /// assert_eq!(
1072    ///     PARSER.parse_date("2024-03-10T00:00:00Z").unwrap_err().to_string(),
1073    ///     "cannot parse civil date/time from string with a Zulu offset, \
1074    ///      parse as a `jiff::Timestamp` first and convert to a civil date/time instead",
1075    /// );
1076    ///
1077    /// # Ok::<(), Box<dyn std::error::Error>>(())
1078    /// ```
1079    ///
1080    /// But this sort of error checking doesn't happen when you parse into a
1081    /// [`Pieces`]. You just get what was parsed, which lets you extract a
1082    /// date even if the higher level APIs forbid it:
1083    ///
1084    /// ```
1085    /// use jiff::{civil, fmt::temporal::DateTimeParser, tz::Offset};
1086    ///
1087    /// static PARSER: DateTimeParser = DateTimeParser::new();
1088    ///
1089    /// let pieces = PARSER.parse_pieces("2024-03-10T00:00:00Z")?;
1090    /// assert_eq!(pieces.date(), civil::date(2024, 3, 10));
1091    /// assert_eq!(pieces.time(), Some(civil::time(0, 0, 0, 0)));
1092    /// assert_eq!(pieces.to_numeric_offset(), Some(Offset::UTC));
1093    /// assert_eq!(pieces.to_time_zone()?, None);
1094    ///
1095    /// # Ok::<(), Box<dyn std::error::Error>>(())
1096    /// ```
1097    ///
1098    /// This is usually not the right thing to do. It isn't even suggested in
1099    /// the error message above. But if you know it's the right thing, then
1100    /// `Pieces` will let you do it.
1101    pub fn parse_pieces<'i, I: ?Sized + AsRef<[u8]> + 'i>(
1102        &self,
1103        input: &'i I,
1104    ) -> Result<Pieces<'i>, Error> {
1105        let input = input.as_ref();
1106        let parsed = self.p.parse_temporal_datetime(input)?.into_full()?;
1107        let pieces = parsed.to_pieces()?;
1108        Ok(pieces)
1109    }
1110
1111    /// Parses an ISO 8601 week date.
1112    ///
1113    /// This isn't exported because it's not clear that it's worth it.
1114    /// Moreover, this isn't part of the Temporal spec, so it's a little odd
1115    /// to have it here. If this really needs to be exported, we probably need
1116    /// a new module that wraps and re-uses this module's internal parser to
1117    /// avoid too much code duplication.
1118    pub(crate) fn parse_iso_week_date<I: AsRef<[u8]>>(
1119        &self,
1120        input: I,
1121    ) -> Result<ISOWeekDate, Error> {
1122        let input = input.as_ref();
1123        let wd = self.p.parse_iso_week_date(input)?.into_full()?;
1124        Ok(wd)
1125    }
1126}
1127
1128/// A printer for Temporal datetimes.
1129///
1130/// This printer converts an in memory representation of a datetime related
1131/// type to a machine (but also human) readable format. Using this printer, one
1132/// can convert [`Zoned`], [`Timestamp`], [`civil::DateTime`], [`civil::Date`]
1133/// or [`civil::Time`] values to a string. Note that all of those types provide
1134/// [`Display`](core::fmt::Display) implementations that utilize the default
1135/// configuration of this printer. However, this printer can be configured to
1136/// behave differently and can also print directly to anything that implements
1137/// the [`fmt::Write`](Write) trait.
1138///
1139/// See the [`fmt::temporal`](crate::fmt::temporal) module documentation for
1140/// more information on the specific format used. Note that the Temporal
1141/// datetime parser is strictly more flexible than what is supported by this
1142/// printer. For example, parsing `2024-06-15T07:00-04[America/New_York]` will
1143/// work just fine, even though the seconds are omitted. However, this printer
1144/// provides no way to write a datetime without the second component.
1145///
1146/// # Example
1147///
1148/// This example shows how to print a `Zoned` value with a space separating
1149/// the date and time instead of the more standard `T` separator.
1150///
1151/// ```
1152/// use jiff::{civil::date, fmt::temporal::DateTimePrinter};
1153///
1154/// // A printer can be created in a const context.
1155/// const PRINTER: DateTimePrinter = DateTimePrinter::new().separator(b' ');
1156///
1157/// let zdt = date(2024, 6, 15).at(7, 0, 0, 123456789).in_tz("America/New_York")?;
1158///
1159/// let mut buf = String::new();
1160/// // Printing to a `String` can never fail.
1161/// PRINTER.print_zoned(&zdt, &mut buf).unwrap();
1162/// assert_eq!(buf, "2024-06-15 07:00:00.123456789-04:00[America/New_York]");
1163///
1164/// # Ok::<(), Box<dyn std::error::Error>>(())
1165/// ```
1166///
1167/// # Example: using adapters with `std::io::Write` and `std::fmt::Write`
1168///
1169/// By using the [`StdIoWrite`](super::StdIoWrite) and
1170/// [`StdFmtWrite`](super::StdFmtWrite) adapters, one can print datetimes
1171/// directly to implementations of `std::io::Write` and `std::fmt::Write`,
1172/// respectively. The example below demonstrates writing to anything
1173/// that implements `std::io::Write`. Similar code can be written for
1174/// `std::fmt::Write`.
1175///
1176/// ```no_run
1177/// use std::{fs::File, io::{BufWriter, Write}, path::Path};
1178///
1179/// use jiff::{civil::date, fmt::{StdIoWrite, temporal::DateTimePrinter}};
1180///
1181/// let zdt = date(2024, 6, 15).at(7, 0, 0, 0).in_tz("America/New_York")?;
1182///
1183/// let path = Path::new("/tmp/output");
1184/// let mut file = BufWriter::new(File::create(path)?);
1185/// DateTimePrinter::new().print_zoned(&zdt, StdIoWrite(&mut file)).unwrap();
1186/// file.flush()?;
1187/// assert_eq!(
1188///     std::fs::read_to_string(path)?,
1189///     "2024-06-15T07:00:00-04:00[America/New_York]",
1190/// );
1191///
1192/// # Ok::<(), Box<dyn std::error::Error>>(())
1193/// ```
1194#[derive(Debug)]
1195pub struct DateTimePrinter {
1196    p: printer::DateTimePrinter,
1197}
1198
1199impl DateTimePrinter {
1200    /// Create a new Temporal datetime printer with the default configuration.
1201    pub const fn new() -> DateTimePrinter {
1202        DateTimePrinter { p: printer::DateTimePrinter::new() }
1203    }
1204
1205    /// Use lowercase for the datetime separator and the `Z` (Zulu) UTC offset.
1206    ///
1207    /// This is disabled by default.
1208    ///
1209    /// # Example
1210    ///
1211    /// This example shows how to print a `Zoned` value with a lowercase
1212    /// datetime separator.
1213    ///
1214    /// ```
1215    /// use jiff::{civil::date, fmt::temporal::DateTimePrinter};
1216    ///
1217    /// const PRINTER: DateTimePrinter = DateTimePrinter::new().lowercase(true);
1218    ///
1219    /// let zdt = date(2024, 6, 15).at(7, 0, 0, 0).in_tz("America/New_York")?;
1220    ///
1221    /// let mut buf = String::new();
1222    /// // Printing to a `String` can never fail.
1223    /// PRINTER.print_zoned(&zdt, &mut buf).unwrap();
1224    /// assert_eq!(buf, "2024-06-15t07:00:00-04:00[America/New_York]");
1225    ///
1226    /// # Ok::<(), Box<dyn std::error::Error>>(())
1227    /// ```
1228    #[inline]
1229    pub const fn lowercase(mut self, yes: bool) -> DateTimePrinter {
1230        self.p = self.p.lowercase(yes);
1231        self
1232    }
1233
1234    /// Use the given ASCII character to separate the date and time when
1235    /// printing [`Zoned`], [`Timestamp`] or [`civil::DateTime`] values.
1236    ///
1237    /// This is set to `T` by default.
1238    ///
1239    /// # Example
1240    ///
1241    /// This example shows how to print a `Zoned` value with a different
1242    /// datetime separator.
1243    ///
1244    /// ```
1245    /// use jiff::{civil::date, fmt::temporal::DateTimePrinter};
1246    ///
1247    /// // We use a weird non-standard character here, but typically one would
1248    /// // use this method with an ASCII space.
1249    /// const PRINTER: DateTimePrinter = DateTimePrinter::new().separator(b'~');
1250    ///
1251    /// let zdt = date(2024, 6, 15).at(7, 0, 0, 0).in_tz("America/New_York")?;
1252    ///
1253    /// let mut buf = String::new();
1254    /// // Printing to a `String` can never fail.
1255    /// PRINTER.print_zoned(&zdt, &mut buf).unwrap();
1256    /// assert_eq!(buf, "2024-06-15~07:00:00-04:00[America/New_York]");
1257    ///
1258    /// # Ok::<(), Box<dyn std::error::Error>>(())
1259    /// ```
1260    #[inline]
1261    pub const fn separator(mut self, ascii_char: u8) -> DateTimePrinter {
1262        self.p = self.p.separator(ascii_char);
1263        self
1264    }
1265
1266    /// Set the precision to use for formatting the fractional second component
1267    /// of a time.
1268    ///
1269    /// The default is `None`, which will automatically set the precision based
1270    /// on the value.
1271    ///
1272    /// When the precision is set to `N`, you'll always get precisely `N`
1273    /// digits after a decimal point (unless `N==0`, then no fractional
1274    /// component is printed), even if they are `0`.
1275    ///
1276    /// # Example
1277    ///
1278    /// ```
1279    /// use jiff::{civil::date, fmt::temporal::DateTimePrinter};
1280    ///
1281    /// const PRINTER: DateTimePrinter =
1282    ///     DateTimePrinter::new().precision(Some(3));
1283    ///
1284    /// let zdt = date(2024, 6, 15).at(7, 0, 0, 123_456_789).in_tz("US/Eastern")?;
1285    ///
1286    /// let mut buf = String::new();
1287    /// // Printing to a `String` can never fail.
1288    /// PRINTER.print_zoned(&zdt, &mut buf).unwrap();
1289    /// assert_eq!(buf, "2024-06-15T07:00:00.123-04:00[US/Eastern]");
1290    ///
1291    /// # Ok::<(), Box<dyn std::error::Error>>(())
1292    /// ```
1293    ///
1294    /// # Example: available via formatting machinery
1295    ///
1296    /// When formatting datetime types that may contain a fractional second
1297    /// component, this can be set via Rust's formatting DSL. Specifically,
1298    /// it corresponds to the [`std::fmt::Formatter::precision`] setting.
1299    ///
1300    /// ```
1301    /// use jiff::civil::date;
1302    ///
1303    /// let zdt = date(2024, 6, 15).at(7, 0, 0, 123_000_000).in_tz("US/Eastern")?;
1304    /// assert_eq!(
1305    ///     format!("{zdt:.6}"),
1306    ///     "2024-06-15T07:00:00.123000-04:00[US/Eastern]",
1307    /// );
1308    /// // Precision values greater than 9 are clamped to 9.
1309    /// assert_eq!(
1310    ///     format!("{zdt:.300}"),
1311    ///     "2024-06-15T07:00:00.123000000-04:00[US/Eastern]",
1312    /// );
1313    /// // A precision of 0 implies the entire fractional
1314    /// // component is always truncated.
1315    /// assert_eq!(
1316    ///     format!("{zdt:.0}"),
1317    ///     "2024-06-15T07:00:00-04:00[US/Eastern]",
1318    /// );
1319    ///
1320    /// # Ok::<(), Box<dyn std::error::Error>>(())
1321    /// ```
1322    #[inline]
1323    pub const fn precision(
1324        mut self,
1325        precision: Option<u8>,
1326    ) -> DateTimePrinter {
1327        self.p = self.p.precision(precision);
1328        self
1329    }
1330
1331    /// Format a `Zoned` datetime into a string.
1332    ///
1333    /// This is a convenience routine for [`DateTimePrinter::print_zoned`] with
1334    /// a `String`.
1335    ///
1336    /// # Example
1337    ///
1338    /// ```
1339    /// use jiff::{civil::date, fmt::temporal::DateTimePrinter};
1340    ///
1341    /// const PRINTER: DateTimePrinter = DateTimePrinter::new();
1342    ///
1343    /// let zdt = date(2024, 6, 15).at(7, 0, 0, 0).in_tz("America/New_York")?;
1344    /// assert_eq!(
1345    ///     PRINTER.zoned_to_string(&zdt),
1346    ///     "2024-06-15T07:00:00-04:00[America/New_York]",
1347    /// );
1348    ///
1349    /// # Ok::<(), Box<dyn std::error::Error>>(())
1350    /// ```
1351    #[cfg(feature = "alloc")]
1352    pub fn zoned_to_string(&self, zdt: &Zoned) -> alloc::string::String {
1353        let mut buf = alloc::string::String::new();
1354        // OK because writing to `String` never fails.
1355        self.print_zoned(zdt, &mut buf).unwrap();
1356        buf
1357    }
1358
1359    /// Format a `Timestamp` datetime into a string.
1360    ///
1361    /// This will always return an RFC 3339 compatible string with a `Z` or
1362    /// Zulu offset. Zulu is chosen in accordance with RFC 9557's update to
1363    /// RFC 3339 that establishes the `-00:00` offset as equivalent to Zulu:
1364    ///
1365    /// > If the time in UTC is known, but the offset to local time is
1366    /// > unknown, this can be represented with an offset of "Z".  (The
1367    /// > original version of this specification provided -00:00 for this
1368    /// > purpose, which is not allowed by ISO8601:2000 and therefore is
1369    /// > less interoperable; Section 3.3 of RFC5322 describes a related
1370    /// > convention for email, which does not have this problem).  This
1371    /// > differs semantically from an offset of +00:00, which implies that
1372    /// > UTC is the preferred reference point for the specified time.
1373    ///
1374    /// In other words, both Zulu time and `-00:00` mean "the time in UTC is
1375    /// known, but the offset to local time is unknown."
1376    ///
1377    /// If you need to format an RFC 3339 timestamp with a specific offset,
1378    /// use [`DateTimePrinter::timestamp_with_offset_to_string`].
1379    ///
1380    /// This is a convenience routine for [`DateTimePrinter::print_timestamp`]
1381    /// with a `String`.
1382    ///
1383    /// # Example
1384    ///
1385    /// ```
1386    /// use jiff::{fmt::temporal::DateTimePrinter, Timestamp};
1387    ///
1388    /// let timestamp = Timestamp::new(0, 1)
1389    ///     .expect("one nanosecond after Unix epoch is always valid");
1390    /// assert_eq!(
1391    ///     DateTimePrinter::new().timestamp_to_string(&timestamp),
1392    ///     "1970-01-01T00:00:00.000000001Z",
1393    /// );
1394    /// ```
1395    #[cfg(feature = "alloc")]
1396    pub fn timestamp_to_string(
1397        &self,
1398        timestamp: &Timestamp,
1399    ) -> alloc::string::String {
1400        let mut buf = alloc::string::String::new();
1401        // OK because writing to `String` never fails.
1402        self.print_timestamp(timestamp, &mut buf).unwrap();
1403        buf
1404    }
1405
1406    /// Format a `Timestamp` datetime into a string with the given offset.
1407    ///
1408    /// This will always return an RFC 3339 compatible string with an offset.
1409    ///
1410    /// This will never use either `Z` (for Zulu time) or `-00:00` as an
1411    /// offset. This is because Zulu time (and `-00:00`) mean "the time in UTC
1412    /// is known, but the offset to local time is unknown." Since this routine
1413    /// accepts an explicit offset, the offset is known. For example,
1414    /// `Offset::UTC` will be formatted as `+00:00`.
1415    ///
1416    /// To format an RFC 3339 string in Zulu time, use
1417    /// [`DateTimePrinter::timestamp_to_string`].
1418    ///
1419    /// This is a convenience routine for
1420    /// [`DateTimePrinter::print_timestamp_with_offset`] with a `String`.
1421    ///
1422    /// # Example
1423    ///
1424    /// ```
1425    /// use jiff::{fmt::temporal::DateTimePrinter, tz, Timestamp};
1426    ///
1427    /// const PRINTER: DateTimePrinter = DateTimePrinter::new();
1428    ///
1429    /// let timestamp = Timestamp::new(0, 1)
1430    ///     .expect("one nanosecond after Unix epoch is always valid");
1431    /// assert_eq!(
1432    ///     PRINTER.timestamp_with_offset_to_string(&timestamp, tz::offset(-5)),
1433    ///     "1969-12-31T19:00:00.000000001-05:00",
1434    /// );
1435    /// ```
1436    ///
1437    /// # Example: `Offset::UTC` formats as `+00:00`
1438    ///
1439    /// ```
1440    /// use jiff::{fmt::temporal::DateTimePrinter, tz::Offset, Timestamp};
1441    ///
1442    /// const PRINTER: DateTimePrinter = DateTimePrinter::new();
1443    ///
1444    /// let timestamp = Timestamp::new(0, 1)
1445    ///     .expect("one nanosecond after Unix epoch is always valid");
1446    /// assert_eq!(
1447    ///     PRINTER.timestamp_with_offset_to_string(&timestamp, Offset::UTC),
1448    ///     "1970-01-01T00:00:00.000000001+00:00",
1449    /// );
1450    /// ```
1451    #[cfg(feature = "alloc")]
1452    pub fn timestamp_with_offset_to_string(
1453        &self,
1454        timestamp: &Timestamp,
1455        offset: Offset,
1456    ) -> alloc::string::String {
1457        let mut buf = alloc::string::String::new();
1458        // OK because writing to `String` never fails.
1459        self.print_timestamp_with_offset(timestamp, offset, &mut buf).unwrap();
1460        buf
1461    }
1462
1463    /// Format a `civil::DateTime` into a string.
1464    ///
1465    /// This is a convenience routine for [`DateTimePrinter::print_datetime`]
1466    /// with a `String`.
1467    ///
1468    /// # Example
1469    ///
1470    /// ```
1471    /// use jiff::{civil::date, fmt::temporal::DateTimePrinter};
1472    ///
1473    /// const PRINTER: DateTimePrinter = DateTimePrinter::new();
1474    ///
1475    /// let dt = date(2024, 6, 15).at(7, 0, 0, 0);
1476    /// assert_eq!(PRINTER.datetime_to_string(&dt), "2024-06-15T07:00:00");
1477    /// ```
1478    #[cfg(feature = "alloc")]
1479    pub fn datetime_to_string(
1480        &self,
1481        dt: &civil::DateTime,
1482    ) -> alloc::string::String {
1483        let mut buf = alloc::string::String::new();
1484        // OK because writing to `String` never fails.
1485        self.print_datetime(dt, &mut buf).unwrap();
1486        buf
1487    }
1488
1489    /// Format a `civil::Date` into a string.
1490    ///
1491    /// This is a convenience routine for [`DateTimePrinter::print_date`]
1492    /// with a `String`.
1493    ///
1494    /// # Example
1495    ///
1496    /// ```
1497    /// use jiff::{civil::date, fmt::temporal::DateTimePrinter};
1498    ///
1499    /// const PRINTER: DateTimePrinter = DateTimePrinter::new();
1500    ///
1501    /// let d = date(2024, 6, 15);
1502    /// assert_eq!(PRINTER.date_to_string(&d), "2024-06-15");
1503    /// ```
1504    #[cfg(feature = "alloc")]
1505    pub fn date_to_string(&self, date: &civil::Date) -> alloc::string::String {
1506        let mut buf = alloc::string::String::new();
1507        // OK because writing to `String` never fails.
1508        self.print_date(date, &mut buf).unwrap();
1509        buf
1510    }
1511
1512    /// Format a `civil::Time` into a string.
1513    ///
1514    /// This is a convenience routine for [`DateTimePrinter::print_time`]
1515    /// with a `String`.
1516    ///
1517    /// # Example
1518    ///
1519    /// ```
1520    /// use jiff::{civil::time, fmt::temporal::DateTimePrinter};
1521    ///
1522    /// const PRINTER: DateTimePrinter = DateTimePrinter::new();
1523    ///
1524    /// let t = time(7, 0, 0, 0);
1525    /// assert_eq!(PRINTER.time_to_string(&t), "07:00:00");
1526    /// ```
1527    #[cfg(feature = "alloc")]
1528    pub fn time_to_string(&self, time: &civil::Time) -> alloc::string::String {
1529        let mut buf = alloc::string::String::new();
1530        // OK because writing to `String` never fails.
1531        self.print_time(time, &mut buf).unwrap();
1532        buf
1533    }
1534
1535    /// Format a `TimeZone` into a string.
1536    ///
1537    /// This is a convenience routine for [`DateTimePrinter::print_time_zone`].
1538    ///
1539    /// # Errors
1540    ///
1541    /// In some rare cases, serialization may fail when there is no succinct
1542    /// representation of a time zone. One specific case in which this
1543    /// occurs is when `TimeZone` is a user's system time zone derived from
1544    /// `/etc/localtime`, but where an IANA time zone identifier could not
1545    /// be found. This can occur, for example, when `/etc/localtime` is not
1546    /// symlinked to an entry in `/usr/share/zoneinfo`.
1547    ///
1548    /// # Example
1549    ///
1550    /// ```
1551    /// use jiff::{fmt::temporal::DateTimePrinter, tz::TimeZone};
1552    ///
1553    /// const PRINTER: DateTimePrinter = DateTimePrinter::new();
1554    ///
1555    /// // IANA time zone
1556    /// let tz = TimeZone::get("US/Eastern")?;
1557    /// assert_eq!(PRINTER.time_zone_to_string(&tz)?, "US/Eastern");
1558    ///
1559    /// # Ok::<(), Box<dyn std::error::Error>>(())
1560    /// ```
1561    #[cfg(feature = "alloc")]
1562    pub fn time_zone_to_string(
1563        &self,
1564        tz: &TimeZone,
1565    ) -> Result<alloc::string::String, Error> {
1566        let mut buf = alloc::string::String::new();
1567        // Writing to a `String` itself will never fail, but this could fail
1568        // as described above in the docs.
1569        self.print_time_zone(tz, &mut buf)?;
1570        Ok(buf)
1571    }
1572
1573    /// Format `Pieces` of a Temporal datetime.
1574    ///
1575    /// This is a convenience routine for [`DateTimePrinter::print_pieces`]
1576    /// with a `String`.
1577    ///
1578    /// # Example
1579    ///
1580    /// ```
1581    /// use jiff::{
1582    ///     fmt::temporal::{DateTimePrinter, Pieces},
1583    ///     tz::offset,
1584    ///     Timestamp,
1585    /// };
1586    ///
1587    /// const PRINTER: DateTimePrinter = DateTimePrinter::new();
1588    ///
1589    /// let pieces = Pieces::from(Timestamp::UNIX_EPOCH);
1590    /// assert_eq!(
1591    ///     PRINTER.pieces_to_string(&pieces),
1592    ///     "1970-01-01T00:00:00Z",
1593    /// );
1594    ///
1595    /// let pieces = Pieces::from((Timestamp::UNIX_EPOCH, offset(0)));
1596    /// assert_eq!(
1597    ///     PRINTER.pieces_to_string(&pieces),
1598    ///     "1970-01-01T00:00:00+00:00",
1599    /// );
1600    ///
1601    /// let pieces = Pieces::from((Timestamp::UNIX_EPOCH, offset(-5)));
1602    /// assert_eq!(
1603    ///     PRINTER.pieces_to_string(&pieces),
1604    ///     "1969-12-31T19:00:00-05:00",
1605    /// );
1606    ///
1607    /// # Ok::<(), Box<dyn std::error::Error>>(())
1608    /// ```
1609    #[cfg(feature = "alloc")]
1610    pub fn pieces_to_string(&self, pieces: &Pieces) -> alloc::string::String {
1611        let mut buf = alloc::string::String::new();
1612        // OK because writing to `String` never fails.
1613        self.print_pieces(pieces, &mut buf).unwrap();
1614        buf
1615    }
1616
1617    /// Print a `Zoned` datetime to the given writer.
1618    ///
1619    /// # Errors
1620    ///
1621    /// This only returns an error when writing to the given [`Write`]
1622    /// implementation would fail. Some such implementations, like for `String`
1623    /// and `Vec<u8>`, never fail (unless memory allocation fails). In such
1624    /// cases, it would be appropriate to call `unwrap()` on the result.
1625    ///
1626    /// # Example
1627    ///
1628    /// ```
1629    /// use jiff::{civil::date, fmt::temporal::DateTimePrinter};
1630    ///
1631    /// const PRINTER: DateTimePrinter = DateTimePrinter::new();
1632    ///
1633    /// let zdt = date(2024, 6, 15).at(7, 0, 0, 0).in_tz("America/New_York")?;
1634    ///
1635    /// let mut buf = String::new();
1636    /// // Printing to a `String` can never fail.
1637    /// PRINTER.print_zoned(&zdt, &mut buf).unwrap();
1638    /// assert_eq!(buf, "2024-06-15T07:00:00-04:00[America/New_York]");
1639    ///
1640    /// # Ok::<(), Box<dyn std::error::Error>>(())
1641    /// ```
1642    pub fn print_zoned<W: Write>(
1643        &self,
1644        zdt: &Zoned,
1645        mut wtr: W,
1646    ) -> Result<(), Error> {
1647        self.p.print_zoned(zdt, &mut wtr)
1648    }
1649
1650    /// Print a `Timestamp` datetime to the given writer.
1651    ///
1652    /// This will always write an RFC 3339 compatible string with a `Z` or
1653    /// Zulu offset. Zulu is chosen in accordance with RFC 9557's update to
1654    /// RFC 3339 that establishes the `-00:00` offset as equivalent to Zulu:
1655    ///
1656    /// > If the time in UTC is known, but the offset to local time is
1657    /// > unknown, this can be represented with an offset of "Z".  (The
1658    /// > original version of this specification provided -00:00 for this
1659    /// > purpose, which is not allowed by ISO8601:2000 and therefore is
1660    /// > less interoperable; Section 3.3 of RFC5322 describes a related
1661    /// > convention for email, which does not have this problem).  This
1662    /// > differs semantically from an offset of +00:00, which implies that
1663    /// > UTC is the preferred reference point for the specified time.
1664    ///
1665    /// In other words, both Zulu time and `-00:00` mean "the time in UTC is
1666    /// known, but the offset to local time is unknown."
1667    ///
1668    /// If you need to write an RFC 3339 timestamp with a specific offset,
1669    /// use [`DateTimePrinter::print_timestamp_with_offset`].
1670    ///
1671    /// # Errors
1672    ///
1673    /// This only returns an error when writing to the given [`Write`]
1674    /// implementation would fail. Some such implementations, like for `String`
1675    /// and `Vec<u8>`, never fail (unless memory allocation fails). In such
1676    /// cases, it would be appropriate to call `unwrap()` on the result.
1677    ///
1678    /// # Example
1679    ///
1680    /// ```
1681    /// use jiff::{fmt::temporal::DateTimePrinter, Timestamp};
1682    ///
1683    /// let timestamp = Timestamp::new(0, 1)
1684    ///     .expect("one nanosecond after Unix epoch is always valid");
1685    ///
1686    /// let mut buf = String::new();
1687    /// // Printing to a `String` can never fail.
1688    /// DateTimePrinter::new().print_timestamp(&timestamp, &mut buf).unwrap();
1689    /// assert_eq!(buf, "1970-01-01T00:00:00.000000001Z");
1690    ///
1691    /// # Ok::<(), Box<dyn std::error::Error>>(())
1692    /// ```
1693    pub fn print_timestamp<W: Write>(
1694        &self,
1695        timestamp: &Timestamp,
1696        mut wtr: W,
1697    ) -> Result<(), Error> {
1698        self.p.print_timestamp(timestamp, &mut wtr)
1699    }
1700
1701    /// Print a `Timestamp` datetime to the given writer with the given offset.
1702    ///
1703    /// This will always write an RFC 3339 compatible string with an offset.
1704    ///
1705    /// This will never write either `Z` (for Zulu time) or `-00:00` as an
1706    /// offset. This is because Zulu time (and `-00:00`) mean "the time in UTC
1707    /// is known, but the offset to local time is unknown." Since this routine
1708    /// accepts an explicit offset, the offset is known. For example,
1709    /// `Offset::UTC` will be formatted as `+00:00`.
1710    ///
1711    /// To write an RFC 3339 string in Zulu time, use
1712    /// [`DateTimePrinter::print_timestamp`].
1713    ///
1714    /// # Errors
1715    ///
1716    /// This only returns an error when writing to the given [`Write`]
1717    /// implementation would fail. Some such implementations, like for `String`
1718    /// and `Vec<u8>`, never fail (unless memory allocation fails). In such
1719    /// cases, it would be appropriate to call `unwrap()` on the result.
1720    ///
1721    /// # Example
1722    ///
1723    /// ```
1724    /// use jiff::{fmt::temporal::DateTimePrinter, tz, Timestamp};
1725    ///
1726    /// let timestamp = Timestamp::new(0, 1)
1727    ///     .expect("one nanosecond after Unix epoch is always valid");
1728    ///
1729    /// let mut buf = String::new();
1730    /// // Printing to a `String` can never fail.
1731    /// DateTimePrinter::new().print_timestamp_with_offset(
1732    ///     &timestamp,
1733    ///     tz::offset(-5),
1734    ///     &mut buf,
1735    /// ).unwrap();
1736    /// assert_eq!(buf, "1969-12-31T19:00:00.000000001-05:00");
1737    ///
1738    /// # Ok::<(), Box<dyn std::error::Error>>(())
1739    /// ```
1740    ///
1741    /// # Example: `Offset::UTC` formats as `+00:00`
1742    ///
1743    /// ```
1744    /// use jiff::{fmt::temporal::DateTimePrinter, tz::Offset, Timestamp};
1745    ///
1746    /// let timestamp = Timestamp::new(0, 1)
1747    ///     .expect("one nanosecond after Unix epoch is always valid");
1748    ///
1749    /// let mut buf = String::new();
1750    /// // Printing to a `String` can never fail.
1751    /// DateTimePrinter::new().print_timestamp_with_offset(
1752    ///     &timestamp,
1753    ///     Offset::UTC, // equivalent to `Offset::from_hours(0)`
1754    ///     &mut buf,
1755    /// ).unwrap();
1756    /// assert_eq!(buf, "1970-01-01T00:00:00.000000001+00:00");
1757    ///
1758    /// # Ok::<(), Box<dyn std::error::Error>>(())
1759    /// ```
1760    pub fn print_timestamp_with_offset<W: Write>(
1761        &self,
1762        timestamp: &Timestamp,
1763        offset: Offset,
1764        mut wtr: W,
1765    ) -> Result<(), Error> {
1766        self.p.print_timestamp_with_offset(timestamp, offset, &mut wtr)
1767    }
1768
1769    /// Print a `civil::DateTime` to the given writer.
1770    ///
1771    /// # Errors
1772    ///
1773    /// This only returns an error when writing to the given [`Write`]
1774    /// implementation would fail. Some such implementations, like for `String`
1775    /// and `Vec<u8>`, never fail (unless memory allocation fails). In such
1776    /// cases, it would be appropriate to call `unwrap()` on the result.
1777    ///
1778    /// # Example
1779    ///
1780    /// ```
1781    /// use jiff::{civil::date, fmt::temporal::DateTimePrinter};
1782    ///
1783    /// const PRINTER: DateTimePrinter = DateTimePrinter::new();
1784    ///
1785    /// let d = date(2024, 6, 15).at(7, 0, 0, 0);
1786    ///
1787    /// let mut buf = String::new();
1788    /// // Printing to a `String` can never fail.
1789    /// PRINTER.print_datetime(&d, &mut buf).unwrap();
1790    /// assert_eq!(buf, "2024-06-15T07:00:00");
1791    ///
1792    /// # Ok::<(), Box<dyn std::error::Error>>(())
1793    /// ```
1794    pub fn print_datetime<W: Write>(
1795        &self,
1796        dt: &civil::DateTime,
1797        mut wtr: W,
1798    ) -> Result<(), Error> {
1799        self.p.print_datetime(dt, &mut wtr)
1800    }
1801
1802    /// Print a `civil::Date` to the given writer.
1803    ///
1804    /// # Errors
1805    ///
1806    /// This only returns an error when writing to the given [`Write`]
1807    /// implementation would fail. Some such implementations, like for `String`
1808    /// and `Vec<u8>`, never fail (unless memory allocation fails). In such
1809    /// cases, it would be appropriate to call `unwrap()` on the result.
1810    ///
1811    /// # Example
1812    ///
1813    /// ```
1814    /// use jiff::{civil::date, fmt::temporal::DateTimePrinter};
1815    ///
1816    /// const PRINTER: DateTimePrinter = DateTimePrinter::new();
1817    ///
1818    /// let d = date(2024, 6, 15);
1819    ///
1820    /// let mut buf = String::new();
1821    /// // Printing to a `String` can never fail.
1822    /// PRINTER.print_date(&d, &mut buf).unwrap();
1823    /// assert_eq!(buf, "2024-06-15");
1824    ///
1825    /// # Ok::<(), Box<dyn std::error::Error>>(())
1826    /// ```
1827    pub fn print_date<W: Write>(
1828        &self,
1829        date: &civil::Date,
1830        mut wtr: W,
1831    ) -> Result<(), Error> {
1832        self.p.print_date(date, &mut wtr)
1833    }
1834
1835    /// Print a `civil::Time` to the given writer.
1836    ///
1837    /// # Errors
1838    ///
1839    /// This only returns an error when writing to the given [`Write`]
1840    /// implementation would fail. Some such implementations, like for `String`
1841    /// and `Vec<u8>`, never fail (unless memory allocation fails). In such
1842    /// cases, it would be appropriate to call `unwrap()` on the result.
1843    ///
1844    /// # Example
1845    ///
1846    /// ```
1847    /// use jiff::{civil::time, fmt::temporal::DateTimePrinter};
1848    ///
1849    /// const PRINTER: DateTimePrinter = DateTimePrinter::new();
1850    ///
1851    /// let t = time(7, 0, 0, 0);
1852    ///
1853    /// let mut buf = String::new();
1854    /// // Printing to a `String` can never fail.
1855    /// PRINTER.print_time(&t, &mut buf).unwrap();
1856    /// assert_eq!(buf, "07:00:00");
1857    ///
1858    /// # Ok::<(), Box<dyn std::error::Error>>(())
1859    /// ```
1860    pub fn print_time<W: Write>(
1861        &self,
1862        time: &civil::Time,
1863        mut wtr: W,
1864    ) -> Result<(), Error> {
1865        self.p.print_time(time, &mut wtr)
1866    }
1867
1868    /// Print a `TimeZone`.
1869    ///
1870    /// This will emit one of three different categories of strings:
1871    ///
1872    /// 1. An IANA Time Zone Database identifier. For example,
1873    /// `America/New_York` or `UTC`.
1874    /// 2. A fixed offset. For example, `-05:00` or `-00:44:30`.
1875    /// 3. A POSIX time zone string. For example, `EST5EDT,M3.2.0,M11.1.0`.
1876    ///
1877    /// # Differences with RFC 9557 annotations
1878    ///
1879    /// Jiff's [`Offset`] has second precision. If a `TimeZone` is a fixed
1880    /// offset and has fractional minutes, then they will be expressed in the
1881    /// `[+-]HH:MM:SS` format. Otherwise, the `:SS` will be omitted.
1882    ///
1883    /// This differs from RFC 3339 and RFC 9557 because neither support
1884    /// sub-minute resolution in UTC offsets. Indeed, if one were to format
1885    /// a `Zoned` with an offset that contains fractional minutes, the offset
1886    /// would be rounded to the nearest minute to preserve compatibility with
1887    /// RFC 3339 and RFC 9557. However, this routine does no such rounding.
1888    /// This is because there is no RFC standardizing the serialization of
1889    /// a lone time zone, and there is otherwise no need to reduce an offset's
1890    /// precision.
1891    ///
1892    /// # Errors
1893    ///
1894    /// In some rare cases, serialization may fail when there is no succinct
1895    /// representation of a time zone. One specific case in which this
1896    /// occurs is when `TimeZone` is a user's system time zone derived from
1897    /// `/etc/localtime`, but where an IANA time zone identifier could not
1898    /// be found. This can occur, for example, when `/etc/localtime` is not
1899    /// symlinked to an entry in `/usr/share/zoneinfo`.
1900    ///
1901    /// An error can also occur when writing to the given [`Write`]
1902    /// implementation would fail. Some such implementations, like for `String`
1903    /// and `Vec<u8>`, never fail (unless memory allocation fails).
1904    ///
1905    /// # Example
1906    ///
1907    /// ```
1908    /// use jiff::{fmt::temporal::DateTimePrinter, tz::{self, TimeZone}};
1909    ///
1910    /// const PRINTER: DateTimePrinter = DateTimePrinter::new();
1911    ///
1912    /// // IANA time zone
1913    /// let tz = TimeZone::get("US/Eastern")?;
1914    /// let mut buf = String::new();
1915    /// PRINTER.print_time_zone(&tz, &mut buf)?;
1916    /// assert_eq!(buf, "US/Eastern");
1917    ///
1918    /// // Fixed offset
1919    /// let tz = TimeZone::fixed(tz::offset(-5));
1920    /// let mut buf = String::new();
1921    /// PRINTER.print_time_zone(&tz, &mut buf)?;
1922    /// assert_eq!(buf, "-05:00");
1923    ///
1924    /// // POSIX time zone
1925    /// let tz = TimeZone::posix("EST5EDT,M3.2.0,M11.1.0")?;
1926    /// let mut buf = String::new();
1927    /// PRINTER.print_time_zone(&tz, &mut buf)?;
1928    /// assert_eq!(buf, "EST5EDT,M3.2.0,M11.1.0");
1929    ///
1930    /// // The error case for a time zone that doesn't fall
1931    /// // into one of the three categories about is not easy
1932    /// // to create artificially. The only way, at time of
1933    /// // writing, to produce it is via `TimeZone::system()`
1934    /// // with a non-symlinked `/etc/timezone`. (Or `TZ` set
1935    /// // to the path of a similar file.)
1936    ///
1937    /// # Ok::<(), Box<dyn std::error::Error>>(())
1938    /// ```
1939    pub fn print_time_zone<W: Write>(
1940        &self,
1941        tz: &TimeZone,
1942        wtr: W,
1943    ) -> Result<(), Error> {
1944        self.p.print_time_zone(tz, wtr)
1945    }
1946
1947    /// Print the `Pieces` of a Temporal datetime.
1948    ///
1949    /// # Errors
1950    ///
1951    /// This only returns an error when writing to the given [`Write`]
1952    /// implementation would fail. Some such implementations, like for `String`
1953    /// and `Vec<u8>`, never fail (unless memory allocation fails). In such
1954    /// cases, it would be appropriate to call `unwrap()` on the result.
1955    ///
1956    /// # Example
1957    ///
1958    /// ```
1959    /// use jiff::{civil::date, fmt::temporal::{DateTimePrinter, Pieces}};
1960    ///
1961    /// const PRINTER: DateTimePrinter = DateTimePrinter::new();
1962    ///
1963    /// let pieces = Pieces::from(date(2024, 6, 15))
1964    ///     .with_time_zone_name("US/Eastern");
1965    ///
1966    /// let mut buf = String::new();
1967    /// // Printing to a `String` can never fail.
1968    /// PRINTER.print_pieces(&pieces, &mut buf).unwrap();
1969    /// assert_eq!(buf, "2024-06-15[US/Eastern]");
1970    ///
1971    /// # Ok::<(), Box<dyn std::error::Error>>(())
1972    /// ```
1973    pub fn print_pieces<W: Write>(
1974        &self,
1975        pieces: &Pieces,
1976        wtr: W,
1977    ) -> Result<(), Error> {
1978        self.p.print_pieces(pieces, wtr)
1979    }
1980
1981    /// Prints an ISO 8601 week date.
1982    ///
1983    /// This isn't exported because it's not clear that it's worth it.
1984    /// Moreover, this isn't part of the Temporal spec, so it's a little odd
1985    /// to have it here. But it's very convenient to have the ISO 8601 week
1986    /// date parser in this module, and so we stick the printer here along
1987    /// with it.
1988    ///
1989    /// Note that this printer will use `w` when `lowercase` is enabled. (It
1990    /// isn't possible to enable this using the current Jiff public API. But
1991    /// it's probably fine.)
1992    pub(crate) fn print_iso_week_date<W: Write>(
1993        &self,
1994        iso_week_date: &ISOWeekDate,
1995        mut wtr: W,
1996    ) -> Result<(), Error> {
1997        self.p.print_iso_week_date(iso_week_date, &mut wtr)
1998    }
1999}
2000
2001/// A parser for Temporal durations.
2002///
2003/// Note that in Jiff, a "Temporal duration" is called a "span."
2004///
2005/// See the [`fmt::temporal`](crate::fmt::temporal) module documentation for
2006/// more information on the specific format used.
2007///
2008/// # Example
2009///
2010/// This example shows how to parse a [`Span`] from a byte string. (That is,
2011/// `&[u8]` and not a `&str`.)
2012///
2013/// ```
2014/// use jiff::{fmt::temporal::SpanParser, ToSpan};
2015///
2016/// // A parser can be created in a const context.
2017/// static PARSER: SpanParser = SpanParser::new();
2018///
2019/// let span = PARSER.parse_span(b"P3y7m25dT7h36m")?;
2020/// assert_eq!(
2021///     span,
2022///     3.years().months(7).days(25).hours(7).minutes(36).fieldwise(),
2023/// );
2024///
2025/// # Ok::<(), Box<dyn std::error::Error>>(())
2026/// ```
2027#[derive(Debug)]
2028pub struct SpanParser {
2029    p: parser::SpanParser,
2030}
2031
2032impl SpanParser {
2033    /// Create a new Temporal datetime printer with the default configuration.
2034    #[inline]
2035    pub const fn new() -> SpanParser {
2036        SpanParser { p: parser::SpanParser::new() }
2037    }
2038
2039    /// Parse a span string into a [`Span`] value.
2040    ///
2041    /// # Errors
2042    ///
2043    /// This returns an error if the span string given is invalid or if it
2044    /// is valid but doesn't fit in the span range supported by Jiff.
2045    ///
2046    /// # Example
2047    ///
2048    /// This shows a basic example of using this routine.
2049    ///
2050    /// ```
2051    /// use jiff::{fmt::temporal::SpanParser, ToSpan};
2052    ///
2053    /// static PARSER: SpanParser = SpanParser::new();
2054    ///
2055    /// let span = PARSER.parse_span(b"PT48m")?;
2056    /// assert_eq!(span, 48.minutes().fieldwise());
2057    ///
2058    /// # Ok::<(), Box<dyn std::error::Error>>(())
2059    /// ```
2060    ///
2061    /// Note that unless you need to parse a span from a byte string,
2062    /// at time of writing, there is no other advantage to using this
2063    /// parser directly. It is likely more convenient to just use the
2064    /// [`FromStr`](std::str::FromStr) trait implementation on [`Span`]:
2065    ///
2066    /// ```
2067    /// use jiff::{Span, ToSpan};
2068    ///
2069    /// let span = "PT48m".parse::<Span>()?;
2070    /// assert_eq!(span, 48.minutes().fieldwise());
2071    ///
2072    /// # Ok::<(), Box<dyn std::error::Error>>(())
2073    /// ```
2074    #[inline]
2075    pub fn parse_span<I: AsRef<[u8]>>(&self, input: I) -> Result<Span, Error> {
2076        self.p.parse_span(input)
2077    }
2078
2079    /// Parse an ISO 8601 duration string into a [`SignedDuration`] value.
2080    ///
2081    /// # Errors
2082    ///
2083    /// This returns an error if the span string given is invalid or if it is
2084    /// valid but can't be converted to a `SignedDuration`. This can occur
2085    /// when the parsed time exceeds the minimum and maximum `SignedDuration`
2086    /// values, or if there are any non-zero units greater than hours.
2087    ///
2088    /// # Example
2089    ///
2090    /// This shows a basic example of using this routine.
2091    ///
2092    /// ```
2093    /// use jiff::{fmt::temporal::SpanParser, SignedDuration};
2094    ///
2095    /// static PARSER: SpanParser = SpanParser::new();
2096    ///
2097    /// let duration = PARSER.parse_duration(b"PT48m")?;
2098    /// assert_eq!(duration, SignedDuration::from_mins(48));
2099    ///
2100    /// # Ok::<(), Box<dyn std::error::Error>>(())
2101    /// ```
2102    ///
2103    /// Note that unless you need to parse a span from a byte string,
2104    /// at time of writing, there is no other advantage to using this
2105    /// parser directly. It is likely more convenient to just use
2106    /// the [`FromStr`](std::str::FromStr) trait implementation on
2107    /// [`SignedDuration`]:
2108    ///
2109    /// ```
2110    /// use jiff::SignedDuration;
2111    ///
2112    /// let duration = "PT48m".parse::<SignedDuration>()?;
2113    /// assert_eq!(duration, SignedDuration::from_mins(48));
2114    ///
2115    /// # Ok::<(), Box<dyn std::error::Error>>(())
2116    /// ```
2117    #[inline]
2118    pub fn parse_duration<I: AsRef<[u8]>>(
2119        &self,
2120        input: I,
2121    ) -> Result<SignedDuration, Error> {
2122        self.p.parse_signed_duration(input)
2123    }
2124
2125    /// Parse an ISO 8601 duration string into a [`std::time::Duration`] value.
2126    ///
2127    /// # Errors
2128    ///
2129    /// This returns an error if the span string given is invalid or if it is
2130    /// valid but can't be converted to a `std::time::Duration`. This can occur
2131    /// when the parsed time exceeds the maximum `std::time::Duration` value,
2132    /// or if there are any non-zero units greater than hours.
2133    ///
2134    /// # Example
2135    ///
2136    /// This shows a basic example of using this routine.
2137    ///
2138    /// ```
2139    /// use std::time::Duration;
2140    ///
2141    /// use jiff::fmt::temporal::SpanParser;
2142    ///
2143    /// static PARSER: SpanParser = SpanParser::new();
2144    ///
2145    /// let duration = PARSER.parse_unsigned_duration(b"PT48m")?;
2146    /// assert_eq!(duration, Duration::from_secs(48 * 60));
2147    ///
2148    /// # Ok::<(), Box<dyn std::error::Error>>(())
2149    /// ```
2150    #[inline]
2151    pub fn parse_unsigned_duration<I: AsRef<[u8]>>(
2152        &self,
2153        input: I,
2154    ) -> Result<core::time::Duration, Error> {
2155        self.p.parse_unsigned_duration(input)
2156    }
2157}
2158
2159/// A printer for Temporal durations.
2160///
2161/// Note that in Jiff, a "Temporal duration" is called a "span."
2162///
2163/// This printer converts an in memory representation of a duration of time
2164/// to a machine (but also human) readable format. Using this printer,
2165/// one can convert a [`Span`] to a string. Note that a `Span` provides a
2166/// [`Display`](std::fmt::Display) trait implementation that utilize the
2167/// default configuration of this printer. However, this printer can print
2168/// directly to anything that implements the [`fmt::Write`](Write) trait.
2169///
2170/// See the [`fmt::temporal`](crate::fmt::temporal) module documentation for
2171/// more information on the specific format used.
2172///
2173/// # Example
2174///
2175/// This is a basic example showing how to print a [`Span`] directly to a
2176/// `Vec<u8>`.
2177///
2178/// ```
2179/// use jiff::{fmt::temporal::SpanPrinter, ToSpan};
2180///
2181/// // A printer can be created in a const context.
2182/// const PRINTER: SpanPrinter = SpanPrinter::new();
2183///
2184/// let span = 48.minutes();
2185/// let mut buf = vec![];
2186/// // Printing to a `Vec<u8>` can never fail.
2187/// PRINTER.print_span(&span, &mut buf).unwrap();
2188/// assert_eq!(buf, "PT48M".as_bytes());
2189/// ```
2190///
2191/// # Example: using adapters with `std::io::Write` and `std::fmt::Write`
2192///
2193/// By using the [`StdIoWrite`](super::StdIoWrite) and
2194/// [`StdFmtWrite`](super::StdFmtWrite) adapters, one can print spans
2195/// directly to implementations of `std::io::Write` and `std::fmt::Write`,
2196/// respectively. The example below demonstrates writing to anything
2197/// that implements `std::io::Write`. Similar code can be written for
2198/// `std::fmt::Write`.
2199///
2200/// ```no_run
2201/// use std::{fs::File, io::{BufWriter, Write}, path::Path};
2202///
2203/// use jiff::{fmt::{StdIoWrite, temporal::SpanPrinter}, ToSpan};
2204///
2205/// let span = 48.minutes();
2206///
2207/// let path = Path::new("/tmp/output");
2208/// let mut file = BufWriter::new(File::create(path)?);
2209/// SpanPrinter::new().print_span(&span, StdIoWrite(&mut file)).unwrap();
2210/// file.flush()?;
2211/// assert_eq!(std::fs::read_to_string(path)?, "PT48m");
2212///
2213/// # Ok::<(), Box<dyn std::error::Error>>(())
2214/// ```
2215#[derive(Debug)]
2216pub struct SpanPrinter {
2217    p: printer::SpanPrinter,
2218}
2219
2220impl SpanPrinter {
2221    /// Create a new Temporal span printer with the default configuration.
2222    #[inline]
2223    pub const fn new() -> SpanPrinter {
2224        SpanPrinter { p: printer::SpanPrinter::new() }
2225    }
2226
2227    /// Use lowercase for unit designator labels.
2228    ///
2229    /// By default, unit designator labels are written in uppercase.
2230    ///
2231    /// # Example
2232    ///
2233    /// This shows the difference between the default (uppercase) and enabling
2234    /// lowercase. Lowercase unit designator labels tend to be easier to read
2235    /// (in this author's opinion), but they aren't as broadly supported since
2236    /// they are an extension to ISO 8601.
2237    ///
2238    /// ```
2239    /// use jiff::{fmt::temporal::SpanPrinter, ToSpan};
2240    ///
2241    /// let span = 5.years().days(10).hours(1);
2242    /// let printer = SpanPrinter::new();
2243    /// assert_eq!(printer.span_to_string(&span), "P5Y10DT1H");
2244    /// assert_eq!(printer.lowercase(true).span_to_string(&span), "P5y10dT1h");
2245    /// ```
2246    #[inline]
2247    pub const fn lowercase(self, yes: bool) -> SpanPrinter {
2248        SpanPrinter { p: self.p.lowercase(yes) }
2249    }
2250
2251    /// Format a `Span` into a string.
2252    ///
2253    /// This is a convenience routine for [`SpanPrinter::print_span`] with
2254    /// a `String`.
2255    ///
2256    /// # Example
2257    ///
2258    /// ```
2259    /// use jiff::{fmt::temporal::SpanPrinter, ToSpan};
2260    ///
2261    /// const PRINTER: SpanPrinter = SpanPrinter::new();
2262    ///
2263    /// let span = 3.years().months(5);
2264    /// assert_eq!(PRINTER.span_to_string(&span), "P3Y5M");
2265    ///
2266    /// # Ok::<(), Box<dyn std::error::Error>>(())
2267    /// ```
2268    #[cfg(feature = "alloc")]
2269    pub fn span_to_string(&self, span: &Span) -> alloc::string::String {
2270        let mut buf = alloc::string::String::new();
2271        // OK because writing to `String` never fails.
2272        self.print_span(span, &mut buf).unwrap();
2273        buf
2274    }
2275
2276    /// Format a `SignedDuration` into a string.
2277    ///
2278    /// This balances the units of the duration up to at most hours
2279    /// automatically.
2280    ///
2281    /// This is a convenience routine for [`SpanPrinter::print_duration`] with
2282    /// a `String`.
2283    ///
2284    /// # Example
2285    ///
2286    /// ```
2287    /// use jiff::{fmt::temporal::SpanPrinter, SignedDuration};
2288    ///
2289    /// const PRINTER: SpanPrinter = SpanPrinter::new();
2290    ///
2291    /// let dur = SignedDuration::new(86_525, 123_000_789);
2292    /// assert_eq!(PRINTER.duration_to_string(&dur), "PT24H2M5.123000789S");
2293    /// assert_eq!(PRINTER.duration_to_string(&-dur), "-PT24H2M5.123000789S");
2294    ///
2295    /// # Ok::<(), Box<dyn std::error::Error>>(())
2296    /// ```
2297    #[cfg(feature = "alloc")]
2298    pub fn duration_to_string(
2299        &self,
2300        duration: &SignedDuration,
2301    ) -> alloc::string::String {
2302        let mut buf = alloc::string::String::new();
2303        // OK because writing to `String` never fails.
2304        self.print_duration(duration, &mut buf).unwrap();
2305        buf
2306    }
2307
2308    /// Format a `std::time::Duration` into a string.
2309    ///
2310    /// This balances the units of the duration up to at most hours
2311    /// automatically.
2312    ///
2313    /// This is a convenience routine for
2314    /// [`SpanPrinter::print_unsigned_duration`] with a `String`.
2315    ///
2316    /// # Example
2317    ///
2318    /// ```
2319    /// use std::time::Duration;
2320    ///
2321    /// use jiff::fmt::temporal::SpanPrinter;
2322    ///
2323    /// const PRINTER: SpanPrinter = SpanPrinter::new();
2324    ///
2325    /// let dur = Duration::new(86_525, 123_000_789);
2326    /// assert_eq!(
2327    ///     PRINTER.unsigned_duration_to_string(&dur),
2328    ///     "PT24H2M5.123000789S",
2329    /// );
2330    ///
2331    /// # Ok::<(), Box<dyn std::error::Error>>(())
2332    /// ```
2333    #[cfg(feature = "alloc")]
2334    pub fn unsigned_duration_to_string(
2335        &self,
2336        duration: &core::time::Duration,
2337    ) -> alloc::string::String {
2338        let mut buf = alloc::string::String::new();
2339        // OK because writing to `String` never fails.
2340        self.print_unsigned_duration(duration, &mut buf).unwrap();
2341        buf
2342    }
2343
2344    /// Print a `Span` to the given writer.
2345    ///
2346    /// # Errors
2347    ///
2348    /// This only returns an error when writing to the given [`Write`]
2349    /// implementation would fail. Some such implementations, like for `String`
2350    /// and `Vec<u8>`, never fail (unless memory allocation fails). In such
2351    /// cases, it would be appropriate to call `unwrap()` on the result.
2352    ///
2353    /// # Example
2354    ///
2355    /// ```
2356    /// use jiff::{fmt::temporal::SpanPrinter, ToSpan};
2357    ///
2358    /// const PRINTER: SpanPrinter = SpanPrinter::new();
2359    ///
2360    /// let span = 3.years().months(5);
2361    ///
2362    /// let mut buf = String::new();
2363    /// // Printing to a `String` can never fail.
2364    /// PRINTER.print_span(&span, &mut buf).unwrap();
2365    /// assert_eq!(buf, "P3Y5M");
2366    ///
2367    /// # Ok::<(), Box<dyn std::error::Error>>(())
2368    /// ```
2369    pub fn print_span<W: Write>(
2370        &self,
2371        span: &Span,
2372        wtr: W,
2373    ) -> Result<(), Error> {
2374        self.p.print_span(span, wtr)
2375    }
2376
2377    /// Print a `SignedDuration` to the given writer.
2378    ///
2379    /// This balances the units of the duration up to at most hours
2380    /// automatically.
2381    ///
2382    /// # Errors
2383    ///
2384    /// This only returns an error when writing to the given [`Write`]
2385    /// implementation would fail. Some such implementations, like for `String`
2386    /// and `Vec<u8>`, never fail (unless memory allocation fails). In such
2387    /// cases, it would be appropriate to call `unwrap()` on the result.
2388    ///
2389    /// # Example
2390    ///
2391    /// ```
2392    /// use jiff::{fmt::temporal::SpanPrinter, SignedDuration};
2393    ///
2394    /// const PRINTER: SpanPrinter = SpanPrinter::new();
2395    ///
2396    /// let dur = SignedDuration::new(86_525, 123_000_789);
2397    ///
2398    /// let mut buf = String::new();
2399    /// // Printing to a `String` can never fail.
2400    /// PRINTER.print_duration(&dur, &mut buf).unwrap();
2401    /// assert_eq!(buf, "PT24H2M5.123000789S");
2402    ///
2403    /// // Negative durations are supported.
2404    /// buf.clear();
2405    /// PRINTER.print_duration(&-dur, &mut buf).unwrap();
2406    /// assert_eq!(buf, "-PT24H2M5.123000789S");
2407    ///
2408    /// # Ok::<(), Box<dyn std::error::Error>>(())
2409    /// ```
2410    pub fn print_duration<W: Write>(
2411        &self,
2412        duration: &SignedDuration,
2413        wtr: W,
2414    ) -> Result<(), Error> {
2415        self.p.print_signed_duration(duration, wtr)
2416    }
2417
2418    /// Print a `std::time::Duration` to the given writer.
2419    ///
2420    /// This balances the units of the duration up to at most hours
2421    /// automatically.
2422    ///
2423    /// # Errors
2424    ///
2425    /// This only returns an error when writing to the given [`Write`]
2426    /// implementation would fail. Some such implementations, like for `String`
2427    /// and `Vec<u8>`, never fail (unless memory allocation fails). In such
2428    /// cases, it would be appropriate to call `unwrap()` on the result.
2429    ///
2430    /// # Example
2431    ///
2432    /// ```
2433    /// use std::time::Duration;
2434    /// use jiff::fmt::temporal::SpanPrinter;
2435    ///
2436    /// const PRINTER: SpanPrinter = SpanPrinter::new();
2437    ///
2438    /// let dur = Duration::new(86_525, 123_000_789);
2439    ///
2440    /// let mut buf = String::new();
2441    /// // Printing to a `String` can never fail.
2442    /// PRINTER.print_unsigned_duration(&dur, &mut buf).unwrap();
2443    /// assert_eq!(buf, "PT24H2M5.123000789S");
2444    ///
2445    /// # Ok::<(), Box<dyn std::error::Error>>(())
2446    /// ```
2447    pub fn print_unsigned_duration<W: Write>(
2448        &self,
2449        duration: &core::time::Duration,
2450        wtr: W,
2451    ) -> Result<(), Error> {
2452        self.p.print_unsigned_duration(duration, wtr)
2453    }
2454}
2455
2456#[cfg(test)]
2457mod tests {
2458    use alloc::string::ToString;
2459
2460    use crate::Unit;
2461
2462    use super::*;
2463
2464    // This test ensures that strings like `2024-07-15+02` fail to parse.
2465    // Note though that `2024-07-15[America/New_York]` is okay!
2466    #[test]
2467    fn err_temporal_datetime_offset() {
2468        insta::assert_snapshot!(
2469            DateTimeParser::new().parse_date(b"2024-07-15+02").unwrap_err(),
2470            @r###"parsed value '2024-07-15', but unparsed input "+02" remains (expected no unparsed input)"###,
2471        );
2472        insta::assert_snapshot!(
2473            DateTimeParser::new().parse_date(b"2024-07-15-02").unwrap_err(),
2474            @r###"parsed value '2024-07-15', but unparsed input "-02" remains (expected no unparsed input)"###,
2475        );
2476    }
2477
2478    #[test]
2479    fn year_zero() {
2480        insta::assert_snapshot!(
2481            DateTimeParser::new().parse_date("0000-01-01").unwrap(),
2482            @"0000-01-01",
2483        );
2484        insta::assert_snapshot!(
2485            DateTimeParser::new().parse_date("+000000-01-01").unwrap(),
2486            @"0000-01-01",
2487        );
2488        insta::assert_snapshot!(
2489            DateTimeParser::new().parse_date("-000000-01-01").unwrap_err(),
2490            @"failed to parse year in date: year zero must be written without a sign or a positive sign, but not a negative sign",
2491        );
2492    }
2493
2494    // Regression test for: https://github.com/BurntSushi/jiff/issues/59
2495    #[test]
2496    fn fractional_duration_roundtrip() {
2497        let span1: Span = "Pt843517081,1H".parse().unwrap();
2498        let span2: Span = span1.to_string().parse().unwrap();
2499        assert_eq!(
2500            span1.total(Unit::Hour).unwrap(),
2501            span2.total(Unit::Hour).unwrap()
2502        );
2503    }
2504
2505    #[test]
2506    fn minimum_offset_roundtrip() {
2507        let zdt = civil::date(2025, 12, 25)
2508            .at(17, 0, 0, 0)
2509            .to_zoned(TimeZone::fixed(Offset::MIN))
2510            .unwrap();
2511        let string = zdt.to_string();
2512        assert_eq!(string, "2025-12-25T17:00:00-25:59[-25:59]");
2513
2514        let got: Zoned = string.parse().unwrap();
2515        // Since we started with a zoned datetime with a minimal offset
2516        // (to second precision) and RFC 9557 only supports minute precision
2517        // in time zone offsets, printing the zoned datetime rounds the offset.
2518        // But this would normally result in an offset beyond Jiff's limits,
2519        // so in this case, the offset truncates to the minimum supported
2520        // value by both Jiff and RFC 9557. That's what we test for here.
2521        let expected = civil::date(2025, 12, 25)
2522            .at(17, 0, 0, 0)
2523            .to_zoned(TimeZone::fixed(-Offset::hms(25, 59, 0)))
2524            .unwrap();
2525        assert_eq!(expected, got);
2526    }
2527
2528    #[test]
2529    fn maximum_offset_roundtrip() {
2530        let zdt = civil::date(2025, 12, 25)
2531            .at(17, 0, 0, 0)
2532            .to_zoned(TimeZone::fixed(Offset::MAX))
2533            .unwrap();
2534        let string = zdt.to_string();
2535        assert_eq!(string, "2025-12-25T17:00:00+25:59[+25:59]");
2536
2537        let got: Zoned = string.parse().unwrap();
2538        // Since we started with a zoned datetime with a maximal offset
2539        // (to second precision) and RFC 9557 only supports minute precision
2540        // in time zone offsets, printing the zoned datetime rounds the offset.
2541        // But this would normally result in an offset beyond Jiff's limits,
2542        // so in this case, the offset truncates to the maximum supported
2543        // value by both Jiff and RFC 9557. That's what we test for here.
2544        let expected = civil::date(2025, 12, 25)
2545            .at(17, 0, 0, 0)
2546            .to_zoned(TimeZone::fixed(Offset::hms(25, 59, 0)))
2547            .unwrap();
2548        assert_eq!(expected, got);
2549    }
2550}