1 | /*! |
2 | A hybrid format derived from [RFC 3339], [RFC 9557] and [ISO 8601]. |
3 | |
4 | This module provides an implementation of the [Temporal ISO 8601 grammar]. The |
5 | API is spread out over parsers and printers for datetimes and spans. |
6 | |
7 | Note that for most use cases, you should be using the corresponding |
8 | [`Display`](std::fmt::Display) or [`FromStr`](std::str::FromStr) trait |
9 | implementations for printing and parsing respectively. This module provides |
10 | a "lower level" API for configuring the behavior of printing and parsing, |
11 | including the ability to parse from byte strings (i.e., `&[u8]`). |
12 | |
13 | # Date and time format |
14 | |
15 | The specific format supported depends on what kind of type you're trying to |
16 | parse 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 | |
25 | Smaller types can generally be parsed from strings representing a bigger type. |
26 | For example, a `civil::Date` can be parsed from `2020-08-21T02:21:58`. |
27 | |
28 | As 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 |
30 | 3339] and [RFC 9557] are supported in their entirety, but not all of ISO 8601 |
31 | is. For example, `2024-06-16T10.5` is a valid ISO 8601 datetime, but isn't |
32 | supported by Jiff. (Only fractional seconds are supported.) |
33 | |
34 | Some additional details worth noting: |
35 | |
36 | * Parsing `Zoned` values requires a datetime string with a time zone |
37 | annotation like `[America/New_York]` or `[-07:00]`. If you need to parse a |
38 | datetime without a time zone annotation (but with an offset), then you should |
39 | parse 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" |
43 | mode is used where the earlier time is selected in a backward transition, while |
44 | the later time is selected in a forward transition. |
45 | * When parsing `Zoned` values, conflicts between the offset and the time zone |
46 | in the datetime string are handled via the [`DateTimeParser::offset_conflict`] |
47 | configuration. By default, any inconsistency between the offset and the time |
48 | zone results in a parse error. |
49 | * When parsing civil types like `civil::DateTime`, it's always an error if the |
50 | datetime string has a `Z` (Zulu) offset. It's an error since interpreting such |
51 | strings as civil time is usually a bug. |
52 | * In all cases, the `T` designator separating the date and time may be an ASCII |
53 | space instead. |
54 | |
55 | The complete datetime format supported is described by the |
56 | [Temporal ISO 8601 grammar]. |
57 | |
58 | # Span format |
59 | |
60 | To a first approximation, the span format supported roughly corresponds to this |
61 | regular expression: |
62 | |
63 | ```text |
64 | P(\d+y)?(\d+m)?(\d+w)?(\d+d)?(T(\d+h)?(\d+m)?(\d+s)?)? |
65 | ``` |
66 | |
67 | But 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 |
70 | any unit. For example, `P0d` and `PT0s` are equivalent. |
71 | * The format is case insensitive. The printer will by default capitalize all |
72 | designators, 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. |
75 | However, 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 |
77 | fractional are the lowest units. |
78 | * A span like `P99999999999y` is invalid because it exceeds the allowable range |
79 | of time representable by a [`Span`]. |
80 | |
81 | This is, roughly speaking, a subset of what [ISO 8601] specifies. It isn't |
82 | strictly speaking a subset since Jiff (like Temporal) permits week units to be |
83 | mixed with other units. |
84 | |
85 | Here are some examples: |
86 | |
87 | ``` |
88 | use jiff::{Span, ToSpan}; |
89 | |
90 | let 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 | ]; |
107 | for (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 | |
119 | One can also parse ISO 8601 durations into a [`SignedDuration`], but units are |
120 | limited to hours or smaller: |
121 | |
122 | ``` |
123 | use jiff::SignedDuration; |
124 | |
125 | let 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 | ]; |
134 | for (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 | |
142 | The complete span format supported is described by the [Temporal ISO 8601 |
143 | grammar]. |
144 | |
145 | # Differences with Temporal |
146 | |
147 | Jiff implements Temporal's grammar pretty closely, but there are a few |
148 | differences at the time of writing. It is a specific goal that all differences |
149 | should 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 |
152 | Temporal it's `23:59:59.999999999`. Jiff supports a slightly bigger maximum |
153 | to account for all valid values of POSIX time zone strings. Jiff also lacks |
154 | nanosecond precision for UTC offsets, as it's not clear how useful that is in |
155 | practice. |
156 | * Jiff doesn't support a datetime range as big as Temporal. For example, |
157 | in Temporal, `+202024-06-14T17:30[America/New_York]` is valid. But in Jiff, |
158 | since the maximum supported year is `9999`, parsing will fail. Jiff's datetime |
159 | range may be expanded in the future, but it is a non-goal to match Temporal's |
160 | range precisely. |
161 | * Jiff doesn't support RFC 9557 calendar annotations because Jiff only supports |
162 | the Gregorian calendar. |
163 | |
164 | There 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 | |
173 | use 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 | |
182 | pub use self::pieces::{ |
183 | Pieces, PiecesNumericOffset, PiecesOffset, TimeZoneAnnotation, |
184 | TimeZoneAnnotationKind, TimeZoneAnnotationName, |
185 | }; |
186 | |
187 | mod parser; |
188 | mod pieces; |
189 | mod printer; |
190 | |
191 | /// The default date time parser that we use throughout Jiff. |
192 | pub(crate) static DEFAULT_DATETIME_PARSER: DateTimeParser = |
193 | DateTimeParser::new(); |
194 | |
195 | /// The default date time printer that we use throughout Jiff. |
196 | pub(crate) static DEFAULT_DATETIME_PRINTER: DateTimePrinter = |
197 | DateTimePrinter::new(); |
198 | |
199 | /// The default date time parser that we use throughout Jiff. |
200 | pub(crate) static DEFAULT_SPAN_PARSER: SpanParser = SpanParser::new(); |
201 | |
202 | /// The default date time printer that we use throughout Jiff. |
203 | pub(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)] |
252 | pub struct DateTimeParser { |
253 | p: parser::DateTimeParser, |
254 | offset_conflict: OffsetConflict, |
255 | disambiguation: Disambiguation, |
256 | } |
257 | |
258 | impl 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 fron 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::{self, 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 | /// [`Diplay`](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)] |
1182 | pub struct DateTimePrinter { |
1183 | p: printer::DateTimePrinter, |
1184 | } |
1185 | |
1186 | impl 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::{self, 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)] |
1996 | pub struct SpanParser { |
1997 | p: parser::SpanParser, |
1998 | } |
1999 | |
2000 | impl 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 | pub fn parse_span<I: AsRef<[u8]>>(&self, input: I) -> Result<Span, Error> { |
2043 | let input = input.as_ref(); |
2044 | let parsed = self.p.parse_temporal_duration(input)?; |
2045 | let span = parsed.into_full()?; |
2046 | Ok(span) |
2047 | } |
2048 | |
2049 | /// Parse an ISO 8601 duration string into a [`SignedDuration`] value. |
2050 | /// |
2051 | /// # Errors |
2052 | /// |
2053 | /// This returns an error if the span string given is invalid or if it is |
2054 | /// valid but can't be converted to a `SignedDuration`. This can occur |
2055 | /// when the parsed time exceeds the minimum and maximum `SignedDuration` |
2056 | /// values, or if there are any non-zero units greater than hours. |
2057 | /// |
2058 | /// # Example |
2059 | /// |
2060 | /// This shows a basic example of using this routine. |
2061 | /// |
2062 | /// ``` |
2063 | /// use jiff::{fmt::temporal::SpanParser, SignedDuration}; |
2064 | /// |
2065 | /// static PARSER: SpanParser = SpanParser::new(); |
2066 | /// |
2067 | /// let duration = PARSER.parse_duration(b"PT48m" )?; |
2068 | /// assert_eq!(duration, SignedDuration::from_mins(48)); |
2069 | /// |
2070 | /// # Ok::<(), Box<dyn std::error::Error>>(()) |
2071 | /// ``` |
2072 | /// |
2073 | /// Note that unless you need to parse a span from a byte string, |
2074 | /// at time of writing, there is no other advantage to using this |
2075 | /// parser directly. It is likely more convenient to just use |
2076 | /// the [`FromStr`](std::str::FromStr) trait implementation on |
2077 | /// [`SignedDuration`]: |
2078 | /// |
2079 | /// ``` |
2080 | /// use jiff::SignedDuration; |
2081 | /// |
2082 | /// let duration = "PT48m" .parse::<SignedDuration>()?; |
2083 | /// assert_eq!(duration, SignedDuration::from_mins(48)); |
2084 | /// |
2085 | /// # Ok::<(), Box<dyn std::error::Error>>(()) |
2086 | /// ``` |
2087 | pub fn parse_duration<I: AsRef<[u8]>>( |
2088 | &self, |
2089 | input: I, |
2090 | ) -> Result<SignedDuration, Error> { |
2091 | let input = input.as_ref(); |
2092 | let parsed = self.p.parse_signed_duration(input)?; |
2093 | let dur = parsed.into_full()?; |
2094 | Ok(dur) |
2095 | } |
2096 | } |
2097 | |
2098 | /// A printer for Temporal durations. |
2099 | /// |
2100 | /// Note that in Jiff, a "Temporal duration" is called a "span." |
2101 | /// |
2102 | /// This printer converts an in memory representation of a duration of time |
2103 | /// to a machine (but also human) readable format. Using this printer, |
2104 | /// one can convert a [`Span`] to a string. Note that a `Span` provides a |
2105 | /// [`Display`](std::fmt::Display) trait implementation that utilize the |
2106 | /// default configuration of this printer. However, this printer can print |
2107 | /// directly to anything that implements the [`fmt::Write`](Write) trait. |
2108 | /// |
2109 | /// See the [`fmt::temporal`](crate::fmt::temporal) module documentation for |
2110 | /// more information on the specific format used. |
2111 | /// |
2112 | /// # Example |
2113 | /// |
2114 | /// This is a basic example showing how to print a [`Span`] directly to a |
2115 | /// `Vec<u8>`. |
2116 | /// |
2117 | /// ``` |
2118 | /// use jiff::{fmt::temporal::SpanPrinter, ToSpan}; |
2119 | /// |
2120 | /// // A printer can be created in a const context. |
2121 | /// const PRINTER: SpanPrinter = SpanPrinter::new(); |
2122 | /// |
2123 | /// let span = 48.minutes(); |
2124 | /// let mut buf = vec![]; |
2125 | /// // Printing to a `Vec<u8>` can never fail. |
2126 | /// PRINTER.print_span(&span, &mut buf).unwrap(); |
2127 | /// assert_eq!(buf, "PT48M" .as_bytes()); |
2128 | /// ``` |
2129 | /// |
2130 | /// # Example: using adapters with `std::io::Write` and `std::fmt::Write` |
2131 | /// |
2132 | /// By using the [`StdIoWrite`](super::StdIoWrite) and |
2133 | /// [`StdFmtWrite`](super::StdFmtWrite) adapters, one can print spans |
2134 | /// directly to implementations of `std::io::Write` and `std::fmt::Write`, |
2135 | /// respectively. The example below demonstrates writing to anything |
2136 | /// that implements `std::io::Write`. Similar code can be written for |
2137 | /// `std::fmt::Write`. |
2138 | /// |
2139 | /// ```no_run |
2140 | /// use std::{fs::File, io::{BufWriter, Write}, path::Path}; |
2141 | /// |
2142 | /// use jiff::{fmt::{StdIoWrite, temporal::SpanPrinter}, ToSpan}; |
2143 | /// |
2144 | /// let span = 48.minutes(); |
2145 | /// |
2146 | /// let path = Path::new("/tmp/output" ); |
2147 | /// let mut file = BufWriter::new(File::create(path)?); |
2148 | /// SpanPrinter::new().print_span(&span, StdIoWrite(&mut file)).unwrap(); |
2149 | /// file.flush()?; |
2150 | /// assert_eq!(std::fs::read_to_string(path)?, "PT48m" ); |
2151 | /// |
2152 | /// # Ok::<(), Box<dyn std::error::Error>>(()) |
2153 | /// ``` |
2154 | #[derive (Debug)] |
2155 | pub struct SpanPrinter { |
2156 | p: printer::SpanPrinter, |
2157 | } |
2158 | |
2159 | impl SpanPrinter { |
2160 | /// Create a new Temporal span printer with the default configuration. |
2161 | #[inline ] |
2162 | pub const fn new() -> SpanPrinter { |
2163 | SpanPrinter { p: printer::SpanPrinter::new() } |
2164 | } |
2165 | |
2166 | /// Use lowercase for unit designator labels. |
2167 | /// |
2168 | /// By default, unit designator labels are written in uppercase. |
2169 | /// |
2170 | /// # Example |
2171 | /// |
2172 | /// This shows the difference between the default (uppercase) and enabling |
2173 | /// lowercase. Lowercase unit designator labels tend to be easier to read |
2174 | /// (in this author's opinion), but they aren't as broadly supported since |
2175 | /// they are an extension to ISO 8601. |
2176 | /// |
2177 | /// ``` |
2178 | /// use jiff::{fmt::temporal::SpanPrinter, ToSpan}; |
2179 | /// |
2180 | /// let span = 5.years().days(10).hours(1); |
2181 | /// let printer = SpanPrinter::new(); |
2182 | /// assert_eq!(printer.span_to_string(&span), "P5Y10DT1H" ); |
2183 | /// assert_eq!(printer.lowercase(true).span_to_string(&span), "P5y10dT1h" ); |
2184 | /// ``` |
2185 | #[inline ] |
2186 | pub const fn lowercase(self, yes: bool) -> SpanPrinter { |
2187 | SpanPrinter { p: self.p.lowercase(yes) } |
2188 | } |
2189 | |
2190 | /// Format a `Span` into a string. |
2191 | /// |
2192 | /// This is a convenience routine for [`SpanPrinter::print_span`] with |
2193 | /// a `String`. |
2194 | /// |
2195 | /// # Example |
2196 | /// |
2197 | /// ``` |
2198 | /// use jiff::{fmt::temporal::SpanPrinter, ToSpan}; |
2199 | /// |
2200 | /// const PRINTER: SpanPrinter = SpanPrinter::new(); |
2201 | /// |
2202 | /// let span = 3.years().months(5); |
2203 | /// assert_eq!(PRINTER.span_to_string(&span), "P3Y5M" ); |
2204 | /// |
2205 | /// # Ok::<(), Box<dyn std::error::Error>>(()) |
2206 | /// ``` |
2207 | #[cfg (feature = "alloc" )] |
2208 | pub fn span_to_string(&self, span: &Span) -> alloc::string::String { |
2209 | let mut buf = alloc::string::String::with_capacity(4); |
2210 | // OK because writing to `String` never fails. |
2211 | self.print_span(span, &mut buf).unwrap(); |
2212 | buf |
2213 | } |
2214 | |
2215 | /// Format a `SignedDuration` into a string. |
2216 | /// |
2217 | /// This balances the units of the duration up to at most hours |
2218 | /// automatically. |
2219 | /// |
2220 | /// This is a convenience routine for [`SpanPrinter::print_duration`] with |
2221 | /// a `String`. |
2222 | /// |
2223 | /// # Example |
2224 | /// |
2225 | /// ``` |
2226 | /// use jiff::{fmt::temporal::SpanPrinter, SignedDuration}; |
2227 | /// |
2228 | /// const PRINTER: SpanPrinter = SpanPrinter::new(); |
2229 | /// |
2230 | /// let dur = SignedDuration::new(86_525, 123_000_789); |
2231 | /// assert_eq!(PRINTER.duration_to_string(&dur), "PT24H2M5.123000789S" ); |
2232 | /// assert_eq!(PRINTER.duration_to_string(&-dur), "-PT24H2M5.123000789S" ); |
2233 | /// |
2234 | /// # Ok::<(), Box<dyn std::error::Error>>(()) |
2235 | /// ``` |
2236 | #[cfg (feature = "alloc" )] |
2237 | pub fn duration_to_string( |
2238 | &self, |
2239 | duration: &SignedDuration, |
2240 | ) -> alloc::string::String { |
2241 | let mut buf = alloc::string::String::with_capacity(4); |
2242 | // OK because writing to `String` never fails. |
2243 | self.print_duration(duration, &mut buf).unwrap(); |
2244 | buf |
2245 | } |
2246 | |
2247 | /// Print a `Span` to the given writer. |
2248 | /// |
2249 | /// # Errors |
2250 | /// |
2251 | /// This only returns an error when writing to the given [`Write`] |
2252 | /// implementation would fail. Some such implementations, like for `String` |
2253 | /// and `Vec<u8>`, never fail (unless memory allocation fails). In such |
2254 | /// cases, it would be appropriate to call `unwrap()` on the result. |
2255 | /// |
2256 | /// # Example |
2257 | /// |
2258 | /// ``` |
2259 | /// use jiff::{fmt::temporal::SpanPrinter, ToSpan}; |
2260 | /// |
2261 | /// const PRINTER: SpanPrinter = SpanPrinter::new(); |
2262 | /// |
2263 | /// let span = 3.years().months(5); |
2264 | /// |
2265 | /// let mut buf = String::new(); |
2266 | /// // Printing to a `String` can never fail. |
2267 | /// PRINTER.print_span(&span, &mut buf).unwrap(); |
2268 | /// assert_eq!(buf, "P3Y5M" ); |
2269 | /// |
2270 | /// # Ok::<(), Box<dyn std::error::Error>>(()) |
2271 | /// ``` |
2272 | pub fn print_span<W: Write>( |
2273 | &self, |
2274 | span: &Span, |
2275 | wtr: W, |
2276 | ) -> Result<(), Error> { |
2277 | self.p.print_span(span, wtr) |
2278 | } |
2279 | |
2280 | /// Print a `SignedDuration` to the given writer. |
2281 | /// |
2282 | /// This balances the units of the duration up to at most hours |
2283 | /// automatically. |
2284 | /// |
2285 | /// # Errors |
2286 | /// |
2287 | /// This only returns an error when writing to the given [`Write`] |
2288 | /// implementation would fail. Some such implementations, like for `String` |
2289 | /// and `Vec<u8>`, never fail (unless memory allocation fails). In such |
2290 | /// cases, it would be appropriate to call `unwrap()` on the result. |
2291 | /// |
2292 | /// # Example |
2293 | /// |
2294 | /// ``` |
2295 | /// use jiff::{fmt::temporal::SpanPrinter, SignedDuration}; |
2296 | /// |
2297 | /// const PRINTER: SpanPrinter = SpanPrinter::new(); |
2298 | /// |
2299 | /// let dur = SignedDuration::new(86_525, 123_000_789); |
2300 | /// |
2301 | /// let mut buf = String::new(); |
2302 | /// // Printing to a `String` can never fail. |
2303 | /// PRINTER.print_duration(&dur, &mut buf).unwrap(); |
2304 | /// assert_eq!(buf, "PT24H2M5.123000789S" ); |
2305 | /// |
2306 | /// // Negative durations are supported. |
2307 | /// buf.clear(); |
2308 | /// PRINTER.print_duration(&-dur, &mut buf).unwrap(); |
2309 | /// assert_eq!(buf, "-PT24H2M5.123000789S" ); |
2310 | /// |
2311 | /// # Ok::<(), Box<dyn std::error::Error>>(()) |
2312 | /// ``` |
2313 | pub fn print_duration<W: Write>( |
2314 | &self, |
2315 | duration: &SignedDuration, |
2316 | wtr: W, |
2317 | ) -> Result<(), Error> { |
2318 | self.p.print_duration(duration, wtr) |
2319 | } |
2320 | } |
2321 | |
2322 | #[cfg (test)] |
2323 | mod tests { |
2324 | use alloc::string::ToString; |
2325 | |
2326 | use crate::Unit; |
2327 | |
2328 | use super::*; |
2329 | |
2330 | // This test ensures that strings like `2024-07-15+02` fail to parse. |
2331 | // Note though that `2024-07-15[America/New_York]` is okay! |
2332 | #[test ] |
2333 | fn err_temporal_datetime_offset() { |
2334 | insta::assert_snapshot!( |
2335 | DateTimeParser::new().parse_date(b"2024-07-15+02" ).unwrap_err(), |
2336 | @r###"parsed value '2024-07-15', but unparsed input "+02" remains (expected no unparsed input)"### , |
2337 | ); |
2338 | insta::assert_snapshot!( |
2339 | DateTimeParser::new().parse_date(b"2024-07-15-02" ).unwrap_err(), |
2340 | @r###"parsed value '2024-07-15', but unparsed input "-02" remains (expected no unparsed input)"### , |
2341 | ); |
2342 | } |
2343 | |
2344 | #[test ] |
2345 | fn year_zero() { |
2346 | insta::assert_snapshot!( |
2347 | DateTimeParser::new().parse_date("0000-01-01" ).unwrap(), |
2348 | @"0000-01-01" , |
2349 | ); |
2350 | insta::assert_snapshot!( |
2351 | DateTimeParser::new().parse_date("+000000-01-01" ).unwrap(), |
2352 | @"0000-01-01" , |
2353 | ); |
2354 | insta::assert_snapshot!( |
2355 | DateTimeParser::new().parse_date("-000000-01-01" ).unwrap_err(), |
2356 | @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"### , |
2357 | ); |
2358 | } |
2359 | |
2360 | // Regression test for: https://github.com/BurntSushi/jiff/issues/59 |
2361 | #[test ] |
2362 | fn fractional_duration_roundtrip() { |
2363 | let span1: Span = "Pt843517081,1H" .parse().unwrap(); |
2364 | let span2: Span = span1.to_string().parse().unwrap(); |
2365 | assert_eq!( |
2366 | span1.total(Unit::Hour).unwrap(), |
2367 | span2.total(Unit::Hour).unwrap() |
2368 | ); |
2369 | } |
2370 | } |
2371 | |