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