Skip to main content

jiff/fmt/temporal/
pieces.rs

1use crate::{
2    civil::{Date, DateTime, Time},
3    error::Error,
4    tz::{Offset, TimeZone, TimeZoneDatabase},
5    util::borrow::StringCow,
6    Timestamp, Zoned,
7};
8
9/// A low level representation of a parsed Temporal ISO 8601 datetime string.
10///
11/// Most users should not need to use or care about this type. Its purpose is
12/// to represent the individual components of a datetime string for more
13/// flexible parsing when use cases call for it.
14///
15/// One can parse into `Pieces` via [`Pieces::parse`]. Its date, time
16/// (optional), offset (optional) and time zone annotation (optional) can be
17/// queried independently. Each component corresponds to the following in a
18/// datetime string:
19///
20/// ```text
21/// {date}T{time}{offset}[{time-zone-annotation}]
22/// ```
23///
24/// For example:
25///
26/// ```text
27/// 2025-01-03T19:54-05[America/New_York]
28/// ```
29///
30/// A date is the only required component.
31///
32/// A `Pieces` can also be constructed from structured values via its `From`
33/// trait implementations. The `From` trait has the following implementations
34/// available:
35///
36/// * `From<Date>` creates a `Pieces` with just a civil [`Date`]. All other
37/// components are left empty.
38/// * `From<DateTime>` creates a `Pieces` with a civil [`Date`] and [`Time`].
39/// The offset and time zone annotation are left empty.
40/// * `From<Timestamp>` creates a `Pieces` from a [`Timestamp`] using
41/// a Zulu offset. This signifies that the precise instant is known, but the
42/// local time's offset from UTC is unknown. The [`Date`] and [`Time`] are
43/// determined via `Offset::UTC.to_datetime(timestamp)`. The time zone
44/// annotation is left empty.
45/// * `From<(Timestamp, Offset)>` creates a `Pieces` from a [`Timestamp`] and
46/// an [`Offset`]. The [`Date`] and [`Time`] are determined via
47/// `offset.to_datetime(timestamp)`. The time zone annotation is left empty.
48/// * `From<&Zoned>` creates a `Pieces` from a [`Zoned`]. This populates all
49/// fields of a `Pieces`.
50///
51/// A `Pieces` can be converted to a Temporal ISO 8601 string via its `Display`
52/// trait implementation.
53///
54/// # Example: distinguishing between `Z`, `+00:00` and `-00:00`
55///
56/// With `Pieces`, it's possible to parse a datetime string and inspect the
57/// "type" of its offset when it is zero. This makes use of the
58/// [`PiecesOffset`] and [`PiecesNumericOffset`] auxiliary types.
59///
60/// ```
61/// use jiff::{
62///     fmt::temporal::{Pieces, PiecesNumericOffset, PiecesOffset},
63///     tz::Offset,
64/// };
65///
66/// let pieces = Pieces::parse("1970-01-01T00:00:00Z")?;
67/// let off = pieces.offset().unwrap();
68/// // Parsed as Zulu.
69/// assert_eq!(off, PiecesOffset::Zulu);
70/// // Gets converted from Zulu to UTC, i.e., just zero.
71/// assert_eq!(off.to_numeric_offset(), Offset::UTC);
72///
73/// let pieces = Pieces::parse("1970-01-01T00:00:00-00:00")?;
74/// let off = pieces.offset().unwrap();
75/// // Parsed as a negative zero.
76/// assert_eq!(off, PiecesOffset::from(
77///     PiecesNumericOffset::from(Offset::UTC).with_negative_zero(),
78/// ));
79/// // Gets converted from -00:00 to UTC, i.e., just zero.
80/// assert_eq!(off.to_numeric_offset(), Offset::UTC);
81///
82/// let pieces = Pieces::parse("1970-01-01T00:00:00+00:00")?;
83/// let off = pieces.offset().unwrap();
84/// // Parsed as a positive zero.
85/// assert_eq!(off, PiecesOffset::from(
86///     PiecesNumericOffset::from(Offset::UTC),
87/// ));
88/// // Gets converted from -00:00 to UTC, i.e., just zero.
89/// assert_eq!(off.to_numeric_offset(), Offset::UTC);
90///
91/// # Ok::<(), Box<dyn std::error::Error>>(())
92/// ```
93///
94/// It's rare to need to care about these differences, but the above example
95/// demonstrates that `Pieces` doesn't try to do any automatic translation for
96/// you.
97///
98/// # Example: it is very easy to misuse `Pieces`
99///
100/// This example shows how easily you can shoot yourself in the foot with
101/// `Pieces`:
102///
103/// ```
104/// use jiff::{fmt::temporal::{Pieces, TimeZoneAnnotation}, tz};
105///
106/// let mut pieces = Pieces::parse("2025-01-03T07:55+02[Africa/Cairo]")?;
107/// pieces = pieces.with_offset(tz::offset(-10));
108/// // This is nonsense because the offset isn't compatible with the time zone!
109/// // Moreover, the actual instant that this timestamp represents has changed.
110/// assert_eq!(pieces.to_string(), "2025-01-03T07:55:00-10:00[Africa/Cairo]");
111///
112/// # Ok::<(), Box<dyn std::error::Error>>(())
113/// ```
114///
115/// In the above example, we take a parsed `Pieces`, change its offset and
116/// then format it back into a string. There are no speed bumps or errors.
117/// A `Pieces` will just blindly follow your instruction, even if it produces
118/// a nonsense result. Nonsense results are still parsable back into `Pieces`:
119///
120/// ```
121/// use jiff::{civil, fmt::temporal::Pieces, tz::{TimeZone, offset}};
122///
123/// let pieces = Pieces::parse("2025-01-03T07:55:00-10:00[Africa/Cairo]")?;
124/// assert_eq!(pieces.date(), civil::date(2025, 1, 3));
125/// assert_eq!(pieces.time(), Some(civil::time(7, 55, 0, 0)));
126/// assert_eq!(pieces.to_numeric_offset(), Some(offset(-10)));
127/// assert_eq!(pieces.to_time_zone()?, Some(TimeZone::get("Africa/Cairo")?));
128///
129/// # Ok::<(), Box<dyn std::error::Error>>(())
130/// ```
131///
132/// This exemplifies that `Pieces` is a mostly "dumb" type that passes
133/// through the data it contains, even if it doesn't make sense.
134///
135/// # Case study: how to parse `2025-01-03T17:28-05` into `Zoned`
136///
137/// One thing in particular that `Pieces` enables callers to do is side-step
138/// some of the stricter requirements placed on the higher level parsing
139/// functions (such as `Zoned`'s `FromStr` trait implementation). For example,
140/// parsing a datetime string into a `Zoned` _requires_ that the string contain
141/// a time zone annotation. Namely, parsing `2025-01-03T17:28-05` into a
142/// `Zoned` will fail:
143///
144/// ```
145/// use jiff::Zoned;
146///
147/// assert_eq!(
148///     "2025-01-03T17:28-05".parse::<Zoned>().unwrap_err().to_string(),
149///     "failed to find time zone annotation in square brackets, \
150///      which is required for parsing a zoned datetime",
151/// );
152/// ```
153///
154/// The above fails because an RFC 3339 timestamp only contains an offset,
155/// not a time zone, and thus the resulting `Zoned` could never do time zone
156/// aware arithmetic.
157///
158/// However, in some cases, you might want to bypass these protections and
159/// creat a `Zoned` value with a fixed offset time zone anyway. For example,
160/// perhaps your use cases don't need time zone aware arithmetic, but want to
161/// preserve the offset anyway. This can be accomplished with `Pieces`:
162///
163/// ```
164/// use jiff::{fmt::temporal::Pieces, tz::TimeZone};
165///
166/// let pieces = Pieces::parse("2025-01-03T17:28-05")?;
167/// let time = pieces.time().unwrap_or_else(jiff::civil::Time::midnight);
168/// let dt = pieces.date().to_datetime(time);
169/// let Some(offset) = pieces.to_numeric_offset() else {
170///     let msg = format!(
171///         "datetime string has no offset, \
172///          and thus cannot be parsed into an instant",
173///     );
174///     return Err(msg.into());
175/// };
176/// let zdt = TimeZone::fixed(offset).to_zoned(dt)?;
177/// assert_eq!(zdt.to_string(), "2025-01-03T17:28:00-05:00[-05:00]");
178///
179/// # Ok::<(), Box<dyn std::error::Error>>(())
180/// ```
181///
182/// One problem with the above code snippet is that it completely ignores if
183/// a time zone annotation is present. If it is, it probably makes sense to use
184/// it, but "fall back" to a fixed offset time zone if it isn't (which the
185/// higher level `Zoned` parsing function won't do for you):
186///
187/// ```
188/// use jiff::{fmt::temporal::Pieces, tz::TimeZone};
189///
190/// let timestamp = "2025-01-02T15:13-05";
191///
192/// let pieces = Pieces::parse(timestamp)?;
193/// let time = pieces.time().unwrap_or_else(jiff::civil::Time::midnight);
194/// let dt = pieces.date().to_datetime(time);
195/// let tz = match pieces.to_time_zone()? {
196///     Some(tz) => tz,
197///     None => {
198///         let Some(offset) = pieces.to_numeric_offset() else {
199///             let msg = format!(
200///                 "timestamp `{timestamp}` has no time zone \
201///                  or offset, and thus cannot be parsed into \
202///                  an instant",
203///             );
204///             return Err(msg.into());
205///         };
206///         TimeZone::fixed(offset)
207///     }
208/// };
209/// // We don't bother with offset conflict resolution. And note that
210/// // this uses automatic "compatible" disambiguation in the case of
211/// // discontinuities. Of course, this is all moot if `TimeZone` is
212/// // fixed. The above code handles the case where it isn't!
213/// let zdt = tz.to_zoned(dt)?;
214/// assert_eq!(zdt.to_string(), "2025-01-02T15:13:00-05:00[-05:00]");
215///
216/// # Ok::<(), Box<dyn std::error::Error>>(())
217/// ```
218///
219/// This is mostly the same as above, but if an annotation is present, we use
220/// a `TimeZone` derived from that over the offset present.
221///
222/// However, this still doesn't quite capture what happens when parsing into a
223/// `Zoned` value. In particular, parsing into a `Zoned` is _also_ doing offset
224/// conflict resolution for you. An offset conflict occurs when there is a
225/// mismatch between the offset in an RFC 3339 timestamp and the time zone in
226/// an RFC 9557 time zone annotation.
227///
228/// For example, `2024-06-14T17:30-05[America/New_York]` has a mismatch
229/// since the date is in daylight saving time, but the offset, `-05`, is the
230/// offset for standard time in `America/New_York`. If this datetime were
231/// fed to the above code, then the `-05` offset would be completely ignored
232/// and `America/New_York` would resolve the datetime based on its rules. In
233/// this case, you'd get `2024-06-14T17:30-04`, which is a different instant
234/// than the original datetime!
235///
236/// You can either implement your own conflict resolution or use
237/// [`tz::OffsetConflict`](crate::tz::OffsetConflict) to do it for you.
238///
239/// ```
240/// use jiff::{fmt::temporal::Pieces, tz::{OffsetConflict, TimeZone}};
241///
242/// let timestamp = "2024-06-14T17:30-05[America/New_York]";
243/// // The default for conflict resolution when parsing into a `Zoned` is
244/// // actually `Reject`, but we use `AlwaysOffset` here to show a different
245/// // strategy. You'll want to pick the conflict resolution that suits your
246/// // needs. The `Reject` strategy is what you should pick if you aren't
247/// // sure.
248/// let conflict_resolution = OffsetConflict::AlwaysOffset;
249///
250/// let pieces = Pieces::parse(timestamp)?;
251/// let time = pieces.time().unwrap_or_else(jiff::civil::Time::midnight);
252/// let dt = pieces.date().to_datetime(time);
253/// let ambiguous_zdt = match pieces.to_time_zone()? {
254///     Some(tz) => {
255///         match pieces.to_numeric_offset() {
256///             None => tz.into_ambiguous_zoned(dt),
257///             Some(offset) => {
258///                 conflict_resolution.resolve(dt, offset, tz)?
259///             }
260///         }
261///     }
262///     None => {
263///         let Some(offset) = pieces.to_numeric_offset() else {
264///             let msg = format!(
265///                 "timestamp `{timestamp}` has no time zone \
266///                  or offset, and thus cannot be parsed into \
267///                  an instant",
268///             );
269///             return Err(msg.into());
270///         };
271///         // Won't even be ambiguous, but gets us the same
272///         // type as the branch above.
273///         TimeZone::fixed(offset).into_ambiguous_zoned(dt)
274///     }
275/// };
276/// // We do compatible disambiguation here like we do in the previous
277/// // examples, but you could choose any strategy. As with offset conflict
278/// // resolution, if you aren't sure what to pick, a safe choice here would
279/// // be `ambiguous_zdt.unambiguous()`, which will return an error if the
280/// // datetime is ambiguous in any way. Then, if you ever hit an error, you
281/// // can examine the case to see if it should be handled in a different way.
282/// let zdt = ambiguous_zdt.compatible()?;
283/// // Notice that we now have a different civil time and offset, but the
284/// // instant it corresponds to is the same as the one we started with.
285/// assert_eq!(zdt.to_string(), "2024-06-14T18:30:00-04:00[America/New_York]");
286///
287/// # Ok::<(), Box<dyn std::error::Error>>(())
288/// ```
289///
290/// The above has effectively completely rebuilt the higher level `Zoned`
291/// parsing routine, but with a fallback to a fixed time zone when a time zone
292/// annotation is not present.
293///
294/// # Case study: inferring the time zone of RFC 3339 timestamps
295///
296/// As [one real world use case details][infer-time-zone], it might be
297/// desirable to try and infer the time zone of RFC 3339 timestamps with
298/// varying offsets. This might be applicable when:
299///
300/// * You have out-of-band information, possibly contextual, that indicates
301/// the timestamps have to come from a fixed set of time zones.
302/// * The time zones have different standard offsets.
303/// * You have a specific desire or need to use a [`Zoned`] value for its
304/// ergonomics and time zone aware handling. After all, in this case, you
305/// believe the timestamps to actually be generated from a specific time zone,
306/// but the interchange format doesn't support carrying that information. Or
307/// the source data simply omits it.
308///
309/// In other words, you might be trying to make the best of a bad situation.
310///
311/// A `Pieces` can help you accomplish this because it gives you access to each
312/// component of a parsed datetime, and thus lets you implement arbitrary logic
313/// for how to translate that into a `Zoned`. In this case, there is
314/// contextual information that Jiff can't possibly know about.
315///
316/// The general approach we take here is to make use of
317/// [`tz::OffsetConflict`](crate::tz::OffsetConflict) to query whether a
318/// timestamp has a fixed offset compatible with a particular time zone. And if
319/// so, we can _probably_ assume it comes from that time zone. One hitch is
320/// that it's possible for the timestamp to be valid for multiple time zones,
321/// so we check that as well.
322///
323/// In the use case linked above, we have fixed offset timestamps from
324/// `America/Chicago` and `America/New_York`. So let's try implementing the
325/// above strategy. Note that we assume our inputs are RFC 3339 fixed offset
326/// timestamps and error otherwise. This is just to keep things simple. To
327/// handle data that is more varied, see the previous case study where we
328/// respect a time zone annotation if it's present, and fall back to a fixed
329/// offset time zone if it isn't.
330///
331/// ```
332/// use jiff::{fmt::temporal::Pieces, tz::{OffsetConflict, TimeZone}, Zoned};
333///
334/// // The time zones we're allowed to choose from.
335/// let tzs = &[
336///     TimeZone::get("America/New_York")?,
337///     TimeZone::get("America/Chicago")?,
338/// ];
339///
340/// // Here's our data that lacks time zones. The task is to assign a time zone
341/// // from `tzs` to each below and convert it to a `Zoned`. If we fail on any
342/// // one, then we substitute `None`.
343/// let data = &[
344///     "2024-01-13T10:33-05",
345///     "2024-01-25T12:15-06",
346///     "2024-03-10T02:30-05",
347///     "2024-06-08T14:01-05",
348///     "2024-06-12T11:46-04",
349///     "2024-11-03T01:30-05",
350/// ];
351/// // Our answers.
352/// let mut zdts: Vec<Option<Zoned>> = vec![];
353/// for string in data {
354///     // Parse and gather up the data that we can from the input.
355///     // In this case, that's a civil datetime and an offset from UTC.
356///     let pieces = Pieces::parse(string)?;
357///     let time = pieces.time().unwrap_or_else(jiff::civil::Time::midnight);
358///     let dt = pieces.date().to_datetime(time);
359///     let Some(offset) = pieces.to_numeric_offset() else {
360///         // A robust implementation should use a TZ annotation if present.
361///         return Err("missing offset".into());
362///     };
363///     // Now collect all time zones that are valid for this timestamp.
364///     let mut candidates = vec![];
365///     for tz in tzs {
366///         let result = OffsetConflict::Reject.resolve(dt, offset, tz.clone());
367///         // The parsed offset isn't valid for this time zone, so reject it.
368///         let Ok(ambiguous_zdt) = result else { continue };
369///         // This can never fail because we used the "reject" conflict
370///         // resolution strategy. It will never return an ambiguous
371///         // `Zoned` since we always have a valid offset that does
372///         // disambiguation for us.
373///         let zdt = ambiguous_zdt.unambiguous().unwrap();
374///         candidates.push(zdt);
375///     }
376///     if candidates.len() == 1 {
377///         zdts.push(Some(candidates.pop().unwrap()));
378///     } else {
379///         zdts.push(None);
380///     }
381/// }
382/// assert_eq!(zdts, vec![
383///     Some("2024-01-13T10:33-05[America/New_York]".parse()?),
384///     Some("2024-01-25T12:15-06[America/Chicago]".parse()?),
385///     // Failed because the clock time falls in a gap in the
386///     // transition to daylight saving time, and it could be
387///     // valid for either America/New_York or America/Chicago.
388///     None,
389///     Some("2024-06-08T14:01-05[America/Chicago]".parse()?),
390///     Some("2024-06-12T11:46-04[America/New_York]".parse()?),
391///     // Failed because the clock time falls in a fold in the
392///     // transition out of daylight saving time, and it could be
393///     // valid for either America/New_York or America/Chicago.
394///     None,
395/// ]);
396///
397/// # Ok::<(), Box<dyn std::error::Error>>(())
398/// ```
399///
400/// The one hitch here is that if the time zones are close to each
401/// geographically and both have daylight saving time, then there are some
402/// RFC 3339 timestamps that are truly ambiguous. For example,
403/// `2024-11-03T01:30-05` is perfectly valid for both `America/New_York` and
404/// `America/Chicago`. In this case, there is no way to tell which time zone
405/// the timestamp belongs to. It might be reasonable to return an error in
406/// this case or omit the timestamp. It depends on what you need to do.
407///
408/// With more effort, it would also be possible to optimize the above routine
409/// by utilizing [`TimeZone::preceding`] and [`TimeZone::following`] to get
410/// the exact boundaries of each time zone transition. Then you could use an
411/// offset lookup table for each range to determine the appropriate time zone.
412///
413/// [infer-time-zone]: https://github.com/BurntSushi/jiff/discussions/181#discussioncomment-11729435
414#[derive(Clone, Debug, Eq, Hash, PartialEq)]
415pub struct Pieces<'n> {
416    date: Date,
417    time: Option<Time>,
418    offset: Option<PiecesOffset>,
419    time_zone_annotation: Option<TimeZoneAnnotation<'n>>,
420}
421
422impl<'n> Pieces<'n> {
423    /// Parses a Temporal ISO 8601 datetime string into a `Pieces`.
424    ///
425    /// This is a convenience routine for
426    /// [`DateTimeParser::parses_pieces`](crate::fmt::temporal::DateTimeParser::parse_pieces).
427    ///
428    /// Note that the `Pieces` returned is parameterized by the lifetime of
429    /// `input`. This is because it might borrow a sub-slice of `input` for
430    /// a time zone annotation name. For example,
431    /// `Canada/Yukon` in `2025-01-03T16:42-07[Canada/Yukon]`.
432    ///
433    /// # Example
434    ///
435    /// ```
436    /// use jiff::{civil, fmt::temporal::Pieces, tz::TimeZone};
437    ///
438    /// let pieces = Pieces::parse("2025-01-03T16:42[Canada/Yukon]")?;
439    /// assert_eq!(pieces.date(), civil::date(2025, 1, 3));
440    /// assert_eq!(pieces.time(), Some(civil::time(16, 42, 0, 0)));
441    /// assert_eq!(pieces.to_numeric_offset(), None);
442    /// assert_eq!(pieces.to_time_zone()?, Some(TimeZone::get("Canada/Yukon")?));
443    ///
444    /// # Ok::<(), Box<dyn std::error::Error>>(())
445    /// ```
446    #[inline]
447    pub fn parse<I: ?Sized + AsRef<[u8]> + 'n>(
448        input: &'n I,
449    ) -> Result<Pieces<'n>, Error> {
450        let input = input.as_ref();
451        super::DEFAULT_DATETIME_PARSER.parse_pieces(input)
452    }
453
454    /// Returns the civil date in this `Pieces`.
455    ///
456    /// Note that every `Pieces` value is guaranteed to have a `Date`.
457    ///
458    /// # Example
459    ///
460    /// ```
461    /// use jiff::{civil, fmt::temporal::Pieces};
462    ///
463    /// let pieces = Pieces::parse("2025-01-03")?;
464    /// assert_eq!(pieces.date(), civil::date(2025, 1, 3));
465    ///
466    /// # Ok::<(), Box<dyn std::error::Error>>(())
467    /// ```
468    #[inline]
469    pub fn date(&self) -> Date {
470        self.date
471    }
472
473    /// Returns the civil time in this `Pieces`.
474    ///
475    /// The time component is optional. In
476    /// [`DateTimeParser`](crate::fmt::temporal::DateTimeParser), parsing
477    /// into types that require a time (like [`DateTime`]) when a time is
478    /// missing automatically set the time to midnight. (Or, more precisely,
479    /// the first instant of the day.)
480    ///
481    /// # Example
482    ///
483    /// ```
484    /// use jiff::{civil, fmt::temporal::Pieces, Zoned};
485    ///
486    /// let pieces = Pieces::parse("2025-01-03T14:49:01")?;
487    /// assert_eq!(pieces.date(), civil::date(2025, 1, 3));
488    /// assert_eq!(pieces.time(), Some(civil::time(14, 49, 1, 0)));
489    ///
490    /// // tricksy tricksy, the first instant of 2015-10-18 in Sao Paulo is
491    /// // not midnight!
492    /// let pieces = Pieces::parse("2015-10-18[America/Sao_Paulo]")?;
493    /// // Parsing into pieces just gives us the component parts, so no time:
494    /// assert_eq!(pieces.time(), None);
495    ///
496    /// // But if this uses higher level routines to parse into a `Zoned`,
497    /// // then we can see that the missing time implies the first instant
498    /// // of the day:
499    /// let zdt: Zoned = "2015-10-18[America/Sao_Paulo]".parse()?;
500    /// assert_eq!(zdt.time(), jiff::civil::time(1, 0, 0, 0));
501    ///
502    /// # Ok::<(), Box<dyn std::error::Error>>(())
503    /// ```
504    #[inline]
505    pub fn time(&self) -> Option<Time> {
506        self.time
507    }
508
509    /// Returns the offset in this `Pieces`.
510    ///
511    /// The offset returned can be infallibly converted to a numeric offset,
512    /// i.e., [`Offset`]. But it also includes extra data to indicate whether
513    /// a `Z` or a `-00:00` was parsed. (Neither of which are representable by
514    /// an `Offset`, which doesn't distinguish between Zulu and UTC and doesn't
515    /// represent negative and positive zero differently.)
516    ///
517    /// # Example
518    ///
519    /// This example shows how different flavors of `Offset::UTC` can be parsed
520    /// and inspected.
521    ///
522    /// ```
523    /// use jiff::{
524    ///     fmt::temporal::{Pieces, PiecesNumericOffset, PiecesOffset},
525    ///     tz::Offset,
526    /// };
527    ///
528    /// let pieces = Pieces::parse("1970-01-01T00:00:00Z")?;
529    /// let off = pieces.offset().unwrap();
530    /// // Parsed as Zulu.
531    /// assert_eq!(off, PiecesOffset::Zulu);
532    /// // Gets converted from Zulu to UTC, i.e., just zero.
533    /// assert_eq!(off.to_numeric_offset(), Offset::UTC);
534    ///
535    /// let pieces = Pieces::parse("1970-01-01T00:00:00-00:00")?;
536    /// let off = pieces.offset().unwrap();
537    /// // Parsed as a negative zero.
538    /// assert_eq!(off, PiecesOffset::from(
539    ///     PiecesNumericOffset::from(Offset::UTC).with_negative_zero(),
540    /// ));
541    /// // Gets converted from -00:00 to UTC, i.e., just zero.
542    /// assert_eq!(off.to_numeric_offset(), Offset::UTC);
543    ///
544    /// let pieces = Pieces::parse("1970-01-01T00:00:00+00:00")?;
545    /// let off = pieces.offset().unwrap();
546    /// // Parsed as a positive zero.
547    /// assert_eq!(off, PiecesOffset::from(
548    ///     PiecesNumericOffset::from(Offset::UTC),
549    /// ));
550    /// // Gets converted from -00:00 to UTC, i.e., just zero.
551    /// assert_eq!(off.to_numeric_offset(), Offset::UTC);
552    ///
553    /// # Ok::<(), Box<dyn std::error::Error>>(())
554    /// ```
555    #[inline]
556    pub fn offset(&self) -> Option<PiecesOffset> {
557        self.offset
558    }
559
560    /// Returns the time zone annotation in this `Pieces`.
561    ///
562    /// A time zone annotation is optional. The higher level
563    /// [`DateTimeParser`](crate::fmt::temporal::DateTimeParser)
564    /// requires a time zone annotation when parsing into a [`Zoned`].
565    ///
566    /// A time zone annotation is either an offset, or more commonly, an IANA
567    /// time zone identifier.
568    ///
569    /// # Example
570    ///
571    /// ```
572    /// use jiff::{fmt::temporal::{Pieces, TimeZoneAnnotation}, tz::offset};
573    ///
574    /// // A time zone annotation from a name:
575    /// let pieces = Pieces::parse("2025-01-02T16:47-05[America/New_York]")?;
576    /// assert_eq!(
577    ///     pieces.time_zone_annotation().unwrap(),
578    ///     &TimeZoneAnnotation::from("America/New_York"),
579    /// );
580    ///
581    /// // A time zone annotation from an offset:
582    /// let pieces = Pieces::parse("2025-01-02T16:47-05[-05:00]")?;
583    /// assert_eq!(
584    ///     pieces.time_zone_annotation().unwrap(),
585    ///     &TimeZoneAnnotation::from(offset(-5)),
586    /// );
587    ///
588    /// # Ok::<(), Box<dyn std::error::Error>>(())
589    /// ```
590    #[inline]
591    pub fn time_zone_annotation(&self) -> Option<&TimeZoneAnnotation<'n>> {
592        self.time_zone_annotation.as_ref()
593    }
594
595    /// A convenience routine for converting an offset on this `Pieces`,
596    /// if present, to a numeric [`Offset`].
597    ///
598    /// This collapses the offsets `Z`, `-00:00` and `+00:00` all to
599    /// [`Offset::UTC`]. If you need to distinguish between them, then use
600    /// [`Pieces::offset`].
601    ///
602    /// # Example
603    ///
604    /// This example shows how `Z`, `-00:00` and `+00:00` all map to the same
605    /// [`Offset`] value:
606    ///
607    /// ```
608    /// use jiff::{fmt::temporal::Pieces, tz::Offset};
609    ///
610    /// let pieces = Pieces::parse("1970-01-01T00:00:00Z")?;
611    /// assert_eq!(pieces.to_numeric_offset(), Some(Offset::UTC));
612    ///
613    /// let pieces = Pieces::parse("1970-01-01T00:00:00-00:00")?;
614    /// assert_eq!(pieces.to_numeric_offset(), Some(Offset::UTC));
615    ///
616    /// let pieces = Pieces::parse("1970-01-01T00:00:00+00:00")?;
617    /// assert_eq!(pieces.to_numeric_offset(), Some(Offset::UTC));
618    ///
619    /// # Ok::<(), Box<dyn std::error::Error>>(())
620    /// ```
621    #[inline]
622    pub fn to_numeric_offset(&self) -> Option<Offset> {
623        self.offset().map(|poffset| poffset.to_numeric_offset())
624    }
625
626    /// A convenience routine for converting a time zone annotation, if
627    /// present, into a [`TimeZone`].
628    ///
629    /// If no annotation is on this `Pieces`, then this returns `Ok(None)`.
630    ///
631    /// This may return an error if the time zone annotation is a name and it
632    /// couldn't be found in Jiff's global time zone database.
633    ///
634    /// # Example
635    ///
636    /// ```
637    /// use jiff::{fmt::temporal::Pieces, tz::{TimeZone, offset}};
638    ///
639    /// // No time zone annotations means you get `Ok(None)`:
640    /// let pieces = Pieces::parse("2025-01-03T17:13-05")?;
641    /// assert_eq!(pieces.to_time_zone()?, None);
642    ///
643    /// // An offset time zone annotation gets you a fixed offset `TimeZone`:
644    /// let pieces = Pieces::parse("2025-01-03T17:13-05[-05]")?;
645    /// assert_eq!(pieces.to_time_zone()?, Some(TimeZone::fixed(offset(-5))));
646    ///
647    /// // A time zone annotation name gets you a IANA time zone:
648    /// let pieces = Pieces::parse("2025-01-03T17:13-05[America/New_York]")?;
649    /// assert_eq!(pieces.to_time_zone()?, Some(TimeZone::get("America/New_York")?));
650    ///
651    /// // A time zone annotation name that doesn't exist gives you an error:
652    /// let pieces = Pieces::parse("2025-01-03T17:13-05[Australia/Bluey]")?;
653    /// assert_eq!(
654    ///     pieces.to_time_zone().unwrap_err().to_string(),
655    ///     "failed to find time zone `Australia/Bluey` in time zone database",
656    /// );
657    ///
658    /// # Ok::<(), Box<dyn std::error::Error>>(())
659    /// ```
660    #[inline]
661    pub fn to_time_zone(&self) -> Result<Option<TimeZone>, Error> {
662        self.to_time_zone_with(crate::tz::db())
663    }
664
665    /// A convenience routine for converting a time zone annotation, if
666    /// present, into a [`TimeZone`] using the given [`TimeZoneDatabase`].
667    ///
668    /// If no annotation is on this `Pieces`, then this returns `Ok(None)`.
669    ///
670    /// This may return an error if the time zone annotation is a name and it
671    /// couldn't be found in Jiff's global time zone database.
672    ///
673    /// # Example
674    ///
675    /// ```
676    /// use jiff::{fmt::temporal::Pieces, tz::TimeZone};
677    ///
678    /// // A time zone annotation name gets you a IANA time zone:
679    /// let pieces = Pieces::parse("2025-01-03T17:13-05[America/New_York]")?;
680    /// assert_eq!(
681    ///     pieces.to_time_zone_with(jiff::tz::db())?,
682    ///     Some(TimeZone::get("America/New_York")?),
683    /// );
684    ///
685    /// # Ok::<(), Box<dyn std::error::Error>>(())
686    /// ```
687    #[inline]
688    pub fn to_time_zone_with(
689        &self,
690        db: &TimeZoneDatabase,
691    ) -> Result<Option<TimeZone>, Error> {
692        let Some(ann) = self.time_zone_annotation() else { return Ok(None) };
693        ann.to_time_zone_with(db).map(Some)
694    }
695
696    /// Set the date on this `Pieces` to the one given.
697    ///
698    /// A `Date` is the minimal piece of information necessary to create a
699    /// `Pieces`. This method will override any previous setting.
700    ///
701    /// # Example
702    ///
703    /// ```
704    /// use jiff::{civil, fmt::temporal::Pieces, Timestamp};
705    ///
706    /// let pieces = Pieces::from(civil::date(2025, 1, 3));
707    /// assert_eq!(pieces.to_string(), "2025-01-03");
708    ///
709    /// // Alternatively, build a `Pieces` from another data type, and the
710    /// // date field will be automatically populated.
711    /// let pieces = Pieces::from(Timestamp::from_second(1735930208)?);
712    /// assert_eq!(pieces.date(), civil::date(2025, 1, 3));
713    /// assert_eq!(pieces.to_string(), "2025-01-03T18:50:08Z");
714    ///
715    /// # Ok::<(), Box<dyn std::error::Error>>(())
716    /// ```
717    #[inline]
718    pub fn with_date(self, date: Date) -> Pieces<'n> {
719        Pieces { date, ..self }
720    }
721
722    /// Set the time on this `Pieces` to the one given.
723    ///
724    /// Setting a [`Time`] on `Pieces` is optional. When formatting a
725    /// `Pieces` to a string, a missing `Time` may be omitted from the datetime
726    /// string in some cases. See [`Pieces::with_offset`] for more details.
727    ///
728    /// # Example
729    ///
730    /// ```
731    /// use jiff::{civil, fmt::temporal::Pieces};
732    ///
733    /// let pieces = Pieces::from(civil::date(2025, 1, 3))
734    ///     .with_time(civil::time(13, 48, 0, 0));
735    /// assert_eq!(pieces.to_string(), "2025-01-03T13:48:00");
736    /// // Alternatively, build a `Pieces` from a `DateTime` directly:
737    /// let pieces = Pieces::from(civil::date(2025, 1, 3).at(13, 48, 0, 0));
738    /// assert_eq!(pieces.to_string(), "2025-01-03T13:48:00");
739    ///
740    /// # Ok::<(), Box<dyn std::error::Error>>(())
741    /// ```
742    #[inline]
743    pub fn with_time(self, time: Time) -> Pieces<'n> {
744        Pieces { time: Some(time), ..self }
745    }
746
747    /// Set the offset on this `Pieces` to the one given.
748    ///
749    /// Setting the offset on `Pieces` is optional.
750    ///
751    /// The type of offset is polymorphic, and includes anything that can be
752    /// infallibly converted into a [`PiecesOffset`]. This includes an
753    /// [`Offset`].
754    ///
755    /// This refers to the offset in the [RFC 3339] component of a Temporal
756    /// ISO 8601 datetime string.
757    ///
758    /// Since a string like `2025-01-03+11` is not valid, if a `Pieces` has
759    /// an offset set but no [`Time`] set, then formatting the `Pieces` will
760    /// write an explicit `Time` set to midnight.
761    ///
762    /// Note that this is distinct from [`Pieces::with_time_zone_offset`].
763    /// This routine sets the offset on the datetime, while
764    /// `Pieces::with_time_zone_offset` sets the offset inside the time zone
765    /// annotation. When the timestamp offset and the time zone annotation
766    /// offset are both present, then they must be equivalent or else the
767    /// datetime string is not a valid Temporal ISO 8601 string. However, a
768    /// `Pieces` will let you format a string with mismatching offsets.
769    ///
770    /// # Example
771    ///
772    /// This example shows how easily you can shoot yourself in the foot with
773    /// this routine:
774    ///
775    /// ```
776    /// use jiff::{fmt::temporal::{Pieces, TimeZoneAnnotation}, tz};
777    ///
778    /// let mut pieces = Pieces::parse("2025-01-03T07:55+02[+02]")?;
779    /// pieces = pieces.with_offset(tz::offset(-10));
780    /// // This is nonsense because the offsets don't match!
781    /// // And notice also that the instant that this timestamp refers to has
782    /// // changed.
783    /// assert_eq!(pieces.to_string(), "2025-01-03T07:55:00-10:00[+02:00]");
784    ///
785    /// # Ok::<(), Box<dyn std::error::Error>>(())
786    /// ```
787    ///
788    /// This exemplifies that `Pieces` is a mostly "dumb" type that passes
789    /// through the data it contains, even if it doesn't make sense.
790    ///
791    /// # Example: changing the offset can change the instant
792    ///
793    /// Consider this case where a `Pieces` is created directly from a
794    /// `Timestamp`, and then the offset is changed.
795    ///
796    /// ```
797    /// use jiff::{fmt::temporal::Pieces, tz, Timestamp};
798    ///
799    /// let pieces = Pieces::from(Timestamp::UNIX_EPOCH)
800    ///     .with_offset(tz::offset(-5));
801    /// assert_eq!(pieces.to_string(), "1970-01-01T00:00:00-05:00");
802    /// ```
803    ///
804    /// You might do this naively as a way of printing the timestamp of the
805    /// Unix epoch with an offset of `-05` from UTC. But the above does not
806    /// correspond to the Unix epoch:
807    ///
808    /// ```
809    /// use jiff::{Timestamp, ToSpan, Unit};
810    ///
811    /// let ts: Timestamp = "1970-01-01T00:00:00-05:00".parse()?;
812    /// assert_eq!(
813    ///     ts.since((Unit::Hour, Timestamp::UNIX_EPOCH))?,
814    ///     5.hours().fieldwise(),
815    /// );
816    ///
817    /// # Ok::<(), Box<dyn std::error::Error>>(())
818    /// ```
819    ///
820    /// This further exemplifies how `Pieces` is just a "dumb" type that
821    /// passes through the data it contains.
822    ///
823    /// This specific example is also why `Pieces` has a `From` trait
824    /// implementation for `(Timestamp, Offset)`, which correspond more to
825    /// what you want:
826    ///
827    /// ```
828    /// use jiff::{fmt::temporal::Pieces, tz, Timestamp};
829    ///
830    /// let pieces = Pieces::from((Timestamp::UNIX_EPOCH, tz::offset(-5)));
831    /// assert_eq!(pieces.to_string(), "1969-12-31T19:00:00-05:00");
832    /// ```
833    ///
834    /// A decent mental model of `Pieces` is that setting fields on `Pieces`
835    /// can't change the values in memory of other fields.
836    ///
837    /// # Example: setting an offset forces a time to be written
838    ///
839    /// Consider these cases where formatting a `Pieces` won't write a
840    /// [`Time`]:
841    ///
842    /// ```
843    /// use jiff::fmt::temporal::Pieces;
844    ///
845    /// let pieces = Pieces::from(jiff::civil::date(2025, 1, 3));
846    /// assert_eq!(pieces.to_string(), "2025-01-03");
847    ///
848    /// let pieces = Pieces::from(jiff::civil::date(2025, 1, 3))
849    ///     .with_time_zone_name("Africa/Cairo");
850    /// assert_eq!(pieces.to_string(), "2025-01-03[Africa/Cairo]");
851    /// ```
852    ///
853    /// This works because the resulting strings are valid. In particular, when
854    /// one parses a `2025-01-03[Africa/Cairo]` into a `Zoned`, it results in a
855    /// time component of midnight automatically (or more precisely, the first
856    /// instead of the corresponding day):
857    ///
858    /// ```
859    /// use jiff::{civil::Time, Zoned};
860    ///
861    /// let zdt: Zoned = "2025-01-03[Africa/Cairo]".parse()?;
862    /// assert_eq!(zdt.time(), Time::midnight());
863    ///
864    /// // tricksy tricksy, the first instant of 2015-10-18 in Sao Paulo is
865    /// // not midnight!
866    /// let zdt: Zoned = "2015-10-18[America/Sao_Paulo]".parse()?;
867    /// assert_eq!(zdt.time(), jiff::civil::time(1, 0, 0, 0));
868    /// // This happens because midnight didn't appear on the clocks in
869    /// // Sao Paulo on 2015-10-18. So if you try to parse a datetime with
870    /// // midnight, automatic disambiguation kicks in and chooses the time
871    /// // after the gap automatically:
872    /// let zdt: Zoned = "2015-10-18T00:00:00[America/Sao_Paulo]".parse()?;
873    /// assert_eq!(zdt.time(), jiff::civil::time(1, 0, 0, 0));
874    ///
875    /// # Ok::<(), Box<dyn std::error::Error>>(())
876    /// ```
877    ///
878    /// However, if you have a date and an offset, then since things like
879    /// `2025-01-03+10` aren't valid Temporal ISO 8601 datetime strings, the
880    /// default midnight time is automatically written:
881    ///
882    /// ```
883    /// use jiff::{fmt::temporal::Pieces, tz};
884    ///
885    /// let pieces = Pieces::from(jiff::civil::date(2025, 1, 3))
886    ///     .with_offset(tz::offset(-5));
887    /// assert_eq!(pieces.to_string(), "2025-01-03T00:00:00-05:00");
888    ///
889    /// let pieces = Pieces::from(jiff::civil::date(2025, 1, 3))
890    ///     .with_offset(tz::offset(2))
891    ///     .with_time_zone_name("Africa/Cairo");
892    /// assert_eq!(pieces.to_string(), "2025-01-03T00:00:00+02:00[Africa/Cairo]");
893    /// ```
894    ///
895    /// # Example: formatting a Zulu or `-00:00` offset
896    ///
897    /// A [`PiecesOffset`] encapsulates not just a numeric offset, but also
898    /// whether a `Z` or a signed zero are used. While it's uncommon to need
899    /// this, this permits one to format a `Pieces` using either of these
900    /// constructs:
901    ///
902    /// ```
903    /// use jiff::{
904    ///     civil,
905    ///     fmt::temporal::{Pieces, PiecesNumericOffset, PiecesOffset},
906    ///     tz::Offset,
907    /// };
908    ///
909    /// let pieces = Pieces::from(civil::date(1970, 1, 1).at(0, 0, 0, 0))
910    ///     .with_offset(Offset::UTC);
911    /// assert_eq!(pieces.to_string(), "1970-01-01T00:00:00+00:00");
912    ///
913    /// let pieces = Pieces::from(civil::date(1970, 1, 1).at(0, 0, 0, 0))
914    ///     .with_offset(PiecesOffset::Zulu);
915    /// assert_eq!(pieces.to_string(), "1970-01-01T00:00:00Z");
916    ///
917    /// let pieces = Pieces::from(civil::date(1970, 1, 1).at(0, 0, 0, 0))
918    ///     .with_offset(PiecesNumericOffset::from(Offset::UTC).with_negative_zero());
919    /// assert_eq!(pieces.to_string(), "1970-01-01T00:00:00-00:00");
920    /// ```
921    ///
922    /// [RFC 3339]: https://www.rfc-editor.org/rfc/rfc3339
923    #[inline]
924    pub fn with_offset<T: Into<PiecesOffset>>(self, offset: T) -> Pieces<'n> {
925        Pieces { offset: Some(offset.into()), ..self }
926    }
927
928    /// Sets the time zone annotation on this `Pieces` to the given time zone
929    /// name.
930    ///
931    /// Setting a time zone annotation on `Pieces` is optional.
932    ///
933    /// This is a convenience routine for using
934    /// [`Pieces::with_time_zone_annotation`] with an explicitly constructed
935    /// [`TimeZoneAnnotation`] for a time zone name.
936    ///
937    /// # Example
938    ///
939    /// This example shows how easily you can shoot yourself in the foot with
940    /// this routine:
941    ///
942    /// ```
943    /// use jiff::fmt::temporal::{Pieces, TimeZoneAnnotation};
944    ///
945    /// let mut pieces = Pieces::parse("2025-01-03T07:55+02[Africa/Cairo]")?;
946    /// pieces = pieces.with_time_zone_name("Australia/Bluey");
947    /// // This is nonsense because `Australia/Bluey` isn't a valid time zone!
948    /// assert_eq!(pieces.to_string(), "2025-01-03T07:55:00+02:00[Australia/Bluey]");
949    ///
950    /// # Ok::<(), Box<dyn std::error::Error>>(())
951    /// ```
952    ///
953    /// This exemplifies that `Pieces` is a mostly "dumb" type that passes
954    /// through the data it contains, even if it doesn't make sense.
955    #[inline]
956    pub fn with_time_zone_name<'a>(self, name: &'a str) -> Pieces<'a> {
957        self.with_time_zone_annotation(TimeZoneAnnotation::from(name))
958    }
959
960    /// Sets the time zone annotation on this `Pieces` to the given offset.
961    ///
962    /// Setting a time zone annotation on `Pieces` is optional.
963    ///
964    /// This is a convenience routine for using
965    /// [`Pieces::with_time_zone_annotation`] with an explicitly constructed
966    /// [`TimeZoneAnnotation`] for a time zone offset.
967    ///
968    /// Note that this is distinct from [`Pieces::with_offset`]. This
969    /// routine sets the offset inside the time zone annotation, while
970    /// `Pieces::with_offset` sets the offset on the timestamp itself. When the
971    /// timestamp offset and the time zone annotation offset are both present,
972    /// then they must be equivalent or else the datetime string is not a valid
973    /// Temporal ISO 8601 string. However, a `Pieces` will let you format a
974    /// string with mismatching offsets.
975    ///
976    /// # Example
977    ///
978    /// This example shows how easily you can shoot yourself in the foot with
979    /// this routine:
980    ///
981    /// ```
982    /// use jiff::{fmt::temporal::{Pieces, TimeZoneAnnotation}, tz};
983    ///
984    /// let mut pieces = Pieces::parse("2025-01-03T07:55+02[Africa/Cairo]")?;
985    /// pieces = pieces.with_time_zone_offset(tz::offset(-7));
986    /// // This is nonsense because the offset `+02` does not match `-07`.
987    /// assert_eq!(pieces.to_string(), "2025-01-03T07:55:00+02:00[-07:00]");
988    ///
989    /// # Ok::<(), Box<dyn std::error::Error>>(())
990    /// ```
991    ///
992    /// This exemplifies that `Pieces` is a mostly "dumb" type that passes
993    /// through the data it contains, even if it doesn't make sense.
994    #[inline]
995    pub fn with_time_zone_offset(self, offset: Offset) -> Pieces<'static> {
996        self.with_time_zone_annotation(TimeZoneAnnotation::from(offset))
997    }
998
999    /// Returns a new `Pieces` with the given time zone annotation.
1000    ///
1001    /// Setting a time zone annotation on `Pieces` is optional.
1002    ///
1003    /// You may find it more convenient to use
1004    /// [`Pieces::with_time_zone_name`] or [`Pieces::with_time_zone_offset`].
1005    ///
1006    /// # Example
1007    ///
1008    /// This example shows how easily you can shoot yourself in the foot with
1009    /// this routine:
1010    ///
1011    /// ```
1012    /// use jiff::fmt::temporal::{Pieces, TimeZoneAnnotation};
1013    ///
1014    /// let mut pieces = Pieces::parse("2025-01-03T07:55+02[Africa/Cairo]")?;
1015    /// pieces = pieces.with_time_zone_annotation(
1016    ///     TimeZoneAnnotation::from("Canada/Yukon"),
1017    /// );
1018    /// // This is nonsense because the offset `+02` is never valid for the
1019    /// // `Canada/Yukon` time zone.
1020    /// assert_eq!(pieces.to_string(), "2025-01-03T07:55:00+02:00[Canada/Yukon]");
1021    ///
1022    /// # Ok::<(), Box<dyn std::error::Error>>(())
1023    /// ```
1024    ///
1025    /// This exemplifies that `Pieces` is a mostly "dumb" type that passes
1026    /// through the data it contains, even if it doesn't make sense.
1027    #[inline]
1028    pub fn with_time_zone_annotation<'a>(
1029        self,
1030        ann: TimeZoneAnnotation<'a>,
1031    ) -> Pieces<'a> {
1032        Pieces { time_zone_annotation: Some(ann), ..self }
1033    }
1034
1035    /// Converts this `Pieces` into an "owned" value whose lifetime is
1036    /// `'static`.
1037    ///
1038    /// The "owned" value in this context refers to the time zone annotation
1039    /// name, if present. For example, `Canada/Yukon` in
1040    /// `2025-01-03T07:55-07[Canada/Yukon]`. When parsing into a `Pieces`,
1041    /// the time zone annotation name is borrowed. But callers may find it more
1042    /// convenient to work with an owned value. By calling this method, the
1043    /// borrowed string internally will be copied into a new string heap
1044    /// allocation.
1045    ///
1046    /// If `Pieces` doesn't have a time zone annotation, is already owned or
1047    /// the time zone annotation is an offset, then this is a no-op.
1048    #[cfg(feature = "alloc")]
1049    #[inline]
1050    pub fn into_owned(self) -> Pieces<'static> {
1051        Pieces {
1052            date: self.date,
1053            time: self.time,
1054            offset: self.offset,
1055            time_zone_annotation: self
1056                .time_zone_annotation
1057                .map(|ann| ann.into_owned()),
1058        }
1059    }
1060}
1061
1062impl From<Date> for Pieces<'static> {
1063    #[inline]
1064    fn from(date: Date) -> Pieces<'static> {
1065        Pieces { date, time: None, offset: None, time_zone_annotation: None }
1066    }
1067}
1068
1069impl From<DateTime> for Pieces<'static> {
1070    #[inline]
1071    fn from(dt: DateTime) -> Pieces<'static> {
1072        Pieces::from(dt.date()).with_time(dt.time())
1073    }
1074}
1075
1076impl From<Timestamp> for Pieces<'static> {
1077    #[inline]
1078    fn from(ts: Timestamp) -> Pieces<'static> {
1079        let dt = Offset::UTC.to_datetime(ts);
1080        Pieces::from(dt).with_offset(PiecesOffset::Zulu)
1081    }
1082}
1083
1084impl From<(Timestamp, Offset)> for Pieces<'static> {
1085    #[inline]
1086    fn from((ts, offset): (Timestamp, Offset)) -> Pieces<'static> {
1087        Pieces::from(offset.to_datetime(ts)).with_offset(offset)
1088    }
1089}
1090
1091impl<'a> From<&'a Zoned> for Pieces<'a> {
1092    #[inline]
1093    fn from(zdt: &'a Zoned) -> Pieces<'a> {
1094        let mut pieces =
1095            Pieces::from(zdt.datetime()).with_offset(zdt.offset());
1096        if let Some(name) = zdt.time_zone().iana_name() {
1097            pieces = pieces.with_time_zone_name(name);
1098        } else {
1099            pieces = pieces.with_time_zone_offset(zdt.offset());
1100        }
1101        pieces
1102    }
1103}
1104
1105impl<'n> core::fmt::Display for Pieces<'n> {
1106    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1107        use crate::fmt::StdFmtWrite;
1108
1109        let precision =
1110            f.precision().map(|p| u8::try_from(p).unwrap_or(u8::MAX));
1111        super::DateTimePrinter::new()
1112            .precision(precision)
1113            .print_pieces(self, StdFmtWrite(f))
1114            .map_err(|_| core::fmt::Error)
1115    }
1116}
1117
1118/// An offset parsed from a Temporal ISO 8601 datetime string, for use with
1119/// [`Pieces`].
1120///
1121/// One can almost think of this as effectively equivalent to an `Offset`. And
1122/// indeed, all `PiecesOffset` values can be convert to an `Offset`. However,
1123/// some offsets in a datetime string have a different connotation that can't
1124/// be captured by an `Offset`.
1125///
1126/// For example, the offsets `Z`, `-00:00` and `+00:00` all map to
1127/// [`Offset::UTC`] after parsing. However, `Z` and `-00:00` generally
1128/// indicate that the offset from local time is unknown, where as `+00:00`
1129/// indicates that the offset from local is _known_ and is zero. This type
1130/// permits callers to inspect what offset was actually written.
1131///
1132/// # Example
1133///
1134/// This example shows how one can create Temporal ISO 8601 datetime strings
1135/// with `+00:00`, `-00:00` or `Z` offsets.
1136///
1137/// ```
1138/// use jiff::{
1139///     fmt::temporal::{Pieces, PiecesNumericOffset},
1140///     tz::Offset,
1141///     Timestamp,
1142/// };
1143///
1144/// // If you create a `Pieces` from a `Timestamp` with a UTC offset,
1145/// // then this is interpreted as "the offset from UTC is known and is
1146/// // zero."
1147/// let pieces = Pieces::from((Timestamp::UNIX_EPOCH, Offset::UTC));
1148/// assert_eq!(pieces.to_string(), "1970-01-01T00:00:00+00:00");
1149///
1150/// // Otherwise, if you create a `Pieces` from just a `Timestamp` with
1151/// // no offset, then it is interpreted as "the offset from UTC is not
1152/// // known." Typically, this is rendered with `Z` for "Zulu":
1153/// let pieces = Pieces::from(Timestamp::UNIX_EPOCH);
1154/// assert_eq!(pieces.to_string(), "1970-01-01T00:00:00Z");
1155///
1156/// // But it might be the case that you want to use `-00:00` instead,
1157/// // perhaps to conform to some existing convention or legacy
1158/// // applications that require it:
1159/// let pieces = Pieces::from(Timestamp::UNIX_EPOCH)
1160///     .with_offset(
1161///         PiecesNumericOffset::from(Offset::UTC).with_negative_zero(),
1162///     );
1163/// assert_eq!(pieces.to_string(), "1970-01-01T00:00:00-00:00");
1164/// ```
1165///
1166/// Without `Pieces`, it's not otherwise possible to emit a `-00:00` offset.
1167/// For example,
1168/// [`DateTimePrinter::print_timestamp`](crate::fmt::temporal::DateTimePrinter::print_timestamp)
1169/// will always emit `Z`, which is consider semantically identical to `-00:00`
1170/// by [RFC 9557]. There's no specific use case where it's expected that you
1171/// should need to write `-00:00` instead of `Z`, but it's conceivable legacy
1172/// or otherwise inflexible applications might want it. Or perhaps, in some
1173/// systems, there is a distinction to draw between `Z` and `-00:00`.
1174///
1175/// [RFC 9557]: https://www.rfc-editor.org/rfc/rfc9557.html
1176#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
1177#[non_exhaustive]
1178pub enum PiecesOffset {
1179    /// The "Zulu" offset, corresponding to UTC in a context where the offset
1180    /// for civil time is unknown or unavailable.
1181    ///
1182    /// [RFC 9557] defines this as equivalent in semantic meaning to `-00:00`:
1183    ///
1184    /// > If the time in UTC is known, but the offset to local time is unknown,
1185    /// > this can be represented with an offset of `Z`. (The original version
1186    /// > of this specification provided `-00:00` for this purpose, which is
1187    /// > not allowed by ISO-8601:2000 and therefore is less interoperable;
1188    /// > Section 3.3 of RFC 5322 describes a related convention for email,
1189    /// > which does not have this problem). This differs semantically from an
1190    /// > offset of `+00:00`, which implies that UTC is the preferred reference
1191    /// > point for the specified time.
1192    ///
1193    /// [RFC 9557]: https://www.rfc-editor.org/rfc/rfc9557
1194    Zulu,
1195    /// A specific numeric offset, including whether the parsed sign is
1196    /// negative.
1197    ///
1198    /// The sign is usually redundant, since an `Offset` is itself signed. But
1199    /// it can be used to distinguish between `+00:00` (`+00` is the preferred
1200    /// offset) and `-00:00` (`+00` is what should be used, but only because
1201    /// the offset to local time is not known). Generally speaking, one should
1202    /// regard `-00:00` as equivalent to `Z`, per RFC 9557.
1203    Numeric(PiecesNumericOffset),
1204}
1205
1206impl PiecesOffset {
1207    /// Converts this offset to a concrete numeric offset in all cases.
1208    ///
1209    /// If this was a `Z` or a `-00:00` offset, then `Offset::UTC` is returned.
1210    /// In all other cases, the underlying numeric offset is returned as-is.
1211    ///
1212    /// # Example
1213    ///
1214    /// ```
1215    /// use jiff::{
1216    ///     fmt::temporal::{Pieces, PiecesNumericOffset, PiecesOffset},
1217    ///     tz::Offset,
1218    /// };
1219    ///
1220    /// let pieces = Pieces::parse("1970-01-01T00:00:00Z")?;
1221    /// let off = pieces.offset().unwrap();
1222    /// // Parsed as Zulu.
1223    /// assert_eq!(off, PiecesOffset::Zulu);
1224    /// // Gets converted from Zulu to UTC, i.e., just zero.
1225    /// assert_eq!(off.to_numeric_offset(), Offset::UTC);
1226    ///
1227    /// let pieces = Pieces::parse("1970-01-01T00:00:00-00:00")?;
1228    /// let off = pieces.offset().unwrap();
1229    /// // Parsed as a negative zero.
1230    /// assert_eq!(off, PiecesOffset::from(
1231    ///     PiecesNumericOffset::from(Offset::UTC).with_negative_zero(),
1232    /// ));
1233    /// // Gets converted from -00:00 to UTC, i.e., just zero.
1234    /// assert_eq!(off.to_numeric_offset(), Offset::UTC);
1235    ///
1236    /// let pieces = Pieces::parse("1970-01-01T00:00:00+00:00")?;
1237    /// let off = pieces.offset().unwrap();
1238    /// // Parsed as a positive zero.
1239    /// assert_eq!(off, PiecesOffset::from(
1240    ///     PiecesNumericOffset::from(Offset::UTC),
1241    /// ));
1242    /// // Gets converted from -00:00 to UTC, i.e., just zero.
1243    /// assert_eq!(off.to_numeric_offset(), Offset::UTC);
1244    ///
1245    /// # Ok::<(), Box<dyn std::error::Error>>(())
1246    /// ```
1247    #[inline]
1248    pub fn to_numeric_offset(&self) -> Offset {
1249        match *self {
1250            PiecesOffset::Zulu => Offset::UTC,
1251            // -00:00 and +00:00 both collapse to zero here.
1252            PiecesOffset::Numeric(ref noffset) => noffset.offset(),
1253        }
1254    }
1255}
1256
1257impl From<Offset> for PiecesOffset {
1258    #[inline]
1259    fn from(offset: Offset) -> PiecesOffset {
1260        PiecesOffset::from(PiecesNumericOffset::from(offset))
1261    }
1262}
1263
1264impl From<PiecesNumericOffset> for PiecesOffset {
1265    #[inline]
1266    fn from(offset: PiecesNumericOffset) -> PiecesOffset {
1267        PiecesOffset::Numeric(offset)
1268    }
1269}
1270
1271/// A specific numeric offset, including the sign of the offset, for use with
1272/// [`Pieces`].
1273///
1274/// # Signedness
1275///
1276/// The sign attached to this type is usually redundant, since the underlying
1277/// [`Offset`] is itself signed. But it can be used to distinguish between
1278/// `+00:00` (`+00` is the preferred offset) and `-00:00` (`+00` is what should
1279/// be used, but only because the offset to local time is not known). Generally
1280/// speaking, one should regard `-00:00` as equivalent to `Z`, per [RFC 9557].
1281///
1282/// [RFC 9557]: https://www.rfc-editor.org/rfc/rfc9557
1283#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
1284pub struct PiecesNumericOffset {
1285    offset: Offset,
1286    is_negative: bool,
1287}
1288
1289impl PiecesNumericOffset {
1290    /// Returns the numeric offset.
1291    ///
1292    /// # Example
1293    ///
1294    /// ```
1295    /// use jiff::{
1296    ///     fmt::temporal::{Pieces, PiecesOffset},
1297    ///     tz::Offset,
1298    /// };
1299    ///
1300    /// let pieces = Pieces::parse("1970-01-01T00:00:00-05:30")?;
1301    /// let off = match pieces.offset().unwrap() {
1302    ///     PiecesOffset::Numeric(off) => off,
1303    ///     _ => unreachable!(),
1304    /// };
1305    /// // This is really only useful if you care that an actual
1306    /// // numeric offset was written and not, e.g., `Z`. Otherwise,
1307    /// // you could just use `PiecesOffset::to_numeric_offset`.
1308    /// assert_eq!(
1309    ///     off.offset(),
1310    ///     Offset::from_seconds(-5 * 60 * 60 - 30 * 60).unwrap(),
1311    /// );
1312    ///
1313    /// # Ok::<(), Box<dyn std::error::Error>>(())
1314    /// ```
1315    #[inline]
1316    pub fn offset(&self) -> Offset {
1317        self.offset
1318    }
1319
1320    /// Returns whether the sign of the offset is negative or not.
1321    ///
1322    /// When formatting a [`Pieces`] to a string, this is _only_ used to
1323    /// determine the rendered sign when the [`Offset`] is itself zero. In
1324    /// all other cases, the sign rendered matches the sign of the `Offset`.
1325    ///
1326    /// Since `Offset` does not keep track of a sign when its value is zero,
1327    /// when using the `From<Offset>` trait implementation for this type,
1328    /// `is_negative` is always set to `false` when the offset is zero.
1329    ///
1330    /// # Example
1331    ///
1332    /// ```
1333    /// use jiff::{
1334    ///     fmt::temporal::{Pieces, PiecesOffset},
1335    ///     tz::Offset,
1336    /// };
1337    ///
1338    /// let pieces = Pieces::parse("1970-01-01T00:00:00-00:00")?;
1339    /// let off = match pieces.offset().unwrap() {
1340    ///     PiecesOffset::Numeric(off) => off,
1341    ///     _ => unreachable!(),
1342    /// };
1343    /// // The numeric offset component in this case is
1344    /// // indistiguisable from `Offset::UTC`. This is
1345    /// // because an `Offset` does not use different
1346    /// // representations for negative and positive zero.
1347    /// assert_eq!(off.offset(), Offset::UTC);
1348    /// // This is where `is_negative` comes in handy:
1349    /// assert_eq!(off.is_negative(), true);
1350    ///
1351    /// # Ok::<(), Box<dyn std::error::Error>>(())
1352    /// ```
1353    #[inline]
1354    pub fn is_negative(&self) -> bool {
1355        self.is_negative
1356    }
1357
1358    /// Sets this numeric offset to use `-00:00` if and only if the offset
1359    /// is zero.
1360    ///
1361    /// # Example
1362    ///
1363    /// ```
1364    /// use jiff::{
1365    ///     fmt::temporal::{Pieces, PiecesNumericOffset},
1366    ///     tz::Offset,
1367    ///     Timestamp,
1368    /// };
1369    ///
1370    /// // If you create a `Pieces` from a `Timestamp` with a UTC offset,
1371    /// // then this is interpreted as "the offset from UTC is known and is
1372    /// // zero."
1373    /// let pieces = Pieces::from((Timestamp::UNIX_EPOCH, Offset::UTC));
1374    /// assert_eq!(pieces.to_string(), "1970-01-01T00:00:00+00:00");
1375    ///
1376    /// // Otherwise, if you create a `Pieces` from just a `Timestamp` with
1377    /// // no offset, then it is interpreted as "the offset from UTC is not
1378    /// // known." Typically, this is rendered with `Z` for "Zulu":
1379    /// let pieces = Pieces::from(Timestamp::UNIX_EPOCH);
1380    /// assert_eq!(pieces.to_string(), "1970-01-01T00:00:00Z");
1381    ///
1382    /// // But it might be the case that you want to use `-00:00` instead,
1383    /// // perhaps to conform to some existing convention or legacy
1384    /// // applications that require it:
1385    /// let pieces = Pieces::from(Timestamp::UNIX_EPOCH)
1386    ///     .with_offset(
1387    ///         PiecesNumericOffset::from(Offset::UTC).with_negative_zero(),
1388    ///     );
1389    /// assert_eq!(pieces.to_string(), "1970-01-01T00:00:00-00:00");
1390    /// ```
1391    #[inline]
1392    pub fn with_negative_zero(self) -> PiecesNumericOffset {
1393        PiecesNumericOffset { is_negative: true, ..self }
1394    }
1395}
1396
1397impl From<Offset> for PiecesNumericOffset {
1398    #[inline]
1399    fn from(offset: Offset) -> PiecesNumericOffset {
1400        // This can of course never return a -00:00 offset, only +00:00.
1401        PiecesNumericOffset { offset, is_negative: offset.is_negative() }
1402    }
1403}
1404
1405/// An [RFC 9557] time zone annotation, for use with [`Pieces`].
1406///
1407/// A time zone annotation is either a time zone name (typically an IANA time
1408/// zone identifier) like `America/New_York`, or an offset like `-05:00`. This
1409/// is normally an implementation detail of parsing into a [`Zoned`], but the
1410/// raw annotation can be accessed via [`Pieces::time_zone_annotation`] after
1411/// parsing into a [`Pieces`].
1412///
1413/// The lifetime parameter refers to the lifetime of the time zone
1414/// name. The lifetime is static when the time zone annotation is
1415/// offset or if the name is owned. An owned value can be produced via
1416/// [`TimeZoneAnnotation::into_owned`] when the `alloc` crate feature is
1417/// enabled.
1418///
1419/// # Construction
1420///
1421/// If you're using [`Pieces`], then its [`Pieces::with_time_zone_name`] and
1422/// [`Pieces::with_time_zone_offset`] methods should absolve you of needing to
1423/// build values of this type explicitly. But if the need arises, there are
1424/// `From` impls for `&str` (time zone annotation name) and [`Offset`] (time
1425/// zone annotation offset) for this type.
1426///
1427/// # Example
1428///
1429/// ```
1430/// use jiff::{fmt::temporal::{Pieces, TimeZoneAnnotation}, tz::offset};
1431///
1432/// // A time zone annotation from a name:
1433/// let pieces = Pieces::parse("2025-01-02T16:47-05[America/New_York]")?;
1434/// assert_eq!(
1435///     pieces.time_zone_annotation().unwrap(),
1436///     &TimeZoneAnnotation::from("America/New_York"),
1437/// );
1438///
1439/// // A time zone annotation from an offset:
1440/// let pieces = Pieces::parse("2025-01-02T16:47-05[-05:00]")?;
1441/// assert_eq!(
1442///     pieces.time_zone_annotation().unwrap(),
1443///     &TimeZoneAnnotation::from(offset(-5)),
1444/// );
1445///
1446/// # Ok::<(), Box<dyn std::error::Error>>(())
1447/// ```
1448///
1449/// [RFC 9557]: https://www.rfc-editor.org/rfc/rfc9557.html
1450#[derive(Clone, Debug, Eq, Hash, PartialEq)]
1451pub struct TimeZoneAnnotation<'n> {
1452    pub(crate) kind: TimeZoneAnnotationKind<'n>,
1453    /// Whether the annotation is marked as "critical," i.e., with a
1454    /// `!` prefix. When enabled, it's supposed to make the annotation
1455    /// un-ignorable.
1456    ///
1457    /// This is basically unused. And there's no way for callers to flip this
1458    /// switch currently. But it can be queried after parsing. Jiff also
1459    /// doesn't alter its behavior based on this flag. In particular, Jiff
1460    /// basically always behaves as if `critical` is true.
1461    pub(crate) critical: bool,
1462}
1463
1464impl<'n> TimeZoneAnnotation<'n> {
1465    /// Returns the "kind" of this annotation. The kind is either a name or an
1466    /// offset.
1467    ///
1468    /// # Example
1469    ///
1470    /// ```
1471    /// use jiff::fmt::temporal::{Pieces, TimeZoneAnnotation};
1472    ///
1473    /// // A time zone annotation from a name, which doesn't necessarily have
1474    /// // to point to a valid IANA time zone.
1475    /// let pieces = Pieces::parse("2025-01-02T16:47-05[Australia/Bluey]")?;
1476    /// assert_eq!(
1477    ///     pieces.time_zone_annotation().unwrap(),
1478    ///     &TimeZoneAnnotation::from("Australia/Bluey"),
1479    /// );
1480    ///
1481    /// # Ok::<(), Box<dyn std::error::Error>>(())
1482    /// ```
1483    #[inline]
1484    pub fn kind(&self) -> &TimeZoneAnnotationKind<'n> {
1485        &self.kind
1486    }
1487
1488    /// Returns true when this time zone is marked as "critical." This occurs
1489    /// when the time zone annotation is preceded by a `!`. It is meant to
1490    /// signify that, basically, implementations should error if the annotation
1491    /// is invalid in some way. And when it's absent, it's left up to the
1492    /// implementation's discretion about what to do (including silently
1493    /// ignoring the invalid annotation).
1494    ///
1495    /// Generally speaking, Jiff ignores this altogether for time zone
1496    /// annotations and behaves as if it's always true. But it's exposed here
1497    /// for callers to query in case it's useful.
1498    ///
1499    /// # Example
1500    ///
1501    /// ```
1502    /// use jiff::fmt::temporal::{Pieces, TimeZoneAnnotation};
1503    ///
1504    /// // not critical
1505    /// let pieces = Pieces::parse("2025-01-02T16:47-05[Australia/Bluey]")?;
1506    /// assert_eq!(
1507    ///     Some(false),
1508    ///     pieces.time_zone_annotation().map(|a| a.is_critical()),
1509    /// );
1510    ///
1511    /// // critical
1512    /// let pieces = Pieces::parse("2025-01-02T16:47-05[!Australia/Bluey]")?;
1513    /// assert_eq!(
1514    ///     Some(true),
1515    ///     pieces.time_zone_annotation().map(|a| a.is_critical()),
1516    /// );
1517    ///
1518    /// # Ok::<(), Box<dyn std::error::Error>>(())
1519    /// ```
1520    #[inline]
1521    pub fn is_critical(&self) -> bool {
1522        self.critical
1523    }
1524
1525    /// A convenience routine for converting this annotation into a time zone.
1526    ///
1527    /// This can fail if the annotation contains a name that couldn't be found
1528    /// in the global time zone database. If you need to use something other
1529    /// than the global time zone database, then use
1530    /// [`TimeZoneAnnotation::to_time_zone_with`].
1531    ///
1532    /// Note that it may be more convenient to use
1533    /// [`Pieces::to_time_zone`].
1534    ///
1535    /// # Example
1536    ///
1537    /// ```
1538    /// use jiff::{fmt::temporal::Pieces, tz::TimeZone};
1539    ///
1540    /// let pieces = Pieces::parse("2025-01-02T16:47-05[Australia/Tasmania]")?;
1541    /// let ann = pieces.time_zone_annotation().unwrap();
1542    /// assert_eq!(
1543    ///     ann.to_time_zone().unwrap(),
1544    ///     TimeZone::get("Australia/Tasmania").unwrap(),
1545    /// );
1546    ///
1547    /// let pieces = Pieces::parse("2025-01-02T16:47-05[Australia/Bluey]")?;
1548    /// let ann = pieces.time_zone_annotation().unwrap();
1549    /// assert_eq!(
1550    ///     ann.to_time_zone().unwrap_err().to_string(),
1551    ///     "failed to find time zone `Australia/Bluey` in time zone database",
1552    /// );
1553    ///
1554    /// # Ok::<(), Box<dyn std::error::Error>>(())
1555    /// ```
1556    #[inline]
1557    pub fn to_time_zone(&self) -> Result<TimeZone, Error> {
1558        self.to_time_zone_with(crate::tz::db())
1559    }
1560
1561    /// This is like [`TimeZoneAnnotation::to_time_zone`], but permits the
1562    /// caller to pass in their own time zone database.
1563    ///
1564    /// This can fail if the annotation contains a name that couldn't be found
1565    /// in the global time zone database. If you need to use something other
1566    /// than the global time zone database, then use
1567    /// [`TimeZoneAnnotation::to_time_zone_with`].
1568    ///
1569    /// Note that it may be more convenient to use
1570    /// [`Pieces::to_time_zone_with`].
1571    ///
1572    /// # Example
1573    ///
1574    /// ```
1575    /// use jiff::{fmt::temporal::Pieces, tz::TimeZone};
1576    ///
1577    /// let pieces = Pieces::parse("2025-01-02T16:47-05[Australia/Tasmania]")?;
1578    /// let ann = pieces.time_zone_annotation().unwrap();
1579    /// assert_eq!(
1580    ///     ann.to_time_zone_with(jiff::tz::db()).unwrap(),
1581    ///     TimeZone::get("Australia/Tasmania").unwrap(),
1582    /// );
1583    ///
1584    /// # Ok::<(), Box<dyn std::error::Error>>(())
1585    /// ```
1586    #[inline]
1587    pub fn to_time_zone_with(
1588        &self,
1589        db: &TimeZoneDatabase,
1590    ) -> Result<TimeZone, Error> {
1591        // NOTE: We don't currently utilize the critical flag here. Temporal
1592        // seems to ignore it. It's not quite clear what else we'd do with it,
1593        // particularly given that we provide a way to do conflict resolution
1594        // between offsets and time zones.
1595        let tz = match *self.kind() {
1596            TimeZoneAnnotationKind::Named(ref name) => {
1597                db.get(name.as_str())?
1598            }
1599            TimeZoneAnnotationKind::Offset(offset) => TimeZone::fixed(offset),
1600        };
1601        Ok(tz)
1602    }
1603
1604    /// Converts this time zone annotation into an "owned" value whose lifetime
1605    /// is `'static`.
1606    ///
1607    /// If this was already an "owned" value or a time zone annotation offset,
1608    /// then this is a no-op.
1609    #[cfg(feature = "alloc")]
1610    #[inline]
1611    pub fn into_owned(self) -> TimeZoneAnnotation<'static> {
1612        TimeZoneAnnotation {
1613            kind: self.kind.into_owned(),
1614            critical: self.critical,
1615        }
1616    }
1617}
1618
1619impl<'n> From<&'n str> for TimeZoneAnnotation<'n> {
1620    fn from(string: &'n str) -> TimeZoneAnnotation<'n> {
1621        let kind = TimeZoneAnnotationKind::from(string);
1622        TimeZoneAnnotation { kind, critical: false }
1623    }
1624}
1625
1626impl From<Offset> for TimeZoneAnnotation<'static> {
1627    fn from(offset: Offset) -> TimeZoneAnnotation<'static> {
1628        let kind = TimeZoneAnnotationKind::from(offset);
1629        TimeZoneAnnotation { kind, critical: false }
1630    }
1631}
1632
1633/// The kind of time zone found in an [RFC 9557] timestamp, for use with
1634/// [`Pieces`].
1635///
1636/// The lifetime parameter refers to the lifetime of the time zone
1637/// name. The lifetime is static when the time zone annotation is
1638/// offset or if the name is owned. An owned value can be produced via
1639/// [`TimeZoneAnnotation::into_owned`] when the `alloc` crate feature is
1640/// enabled.
1641///
1642/// [RFC 9557]: https://www.rfc-editor.org/rfc/rfc9557.html
1643#[derive(Clone, Debug, Eq, Hash, PartialEq)]
1644#[non_exhaustive]
1645pub enum TimeZoneAnnotationKind<'n> {
1646    /// The time zone annotation is a name, usually an IANA time zone
1647    /// identifier. For example, `America/New_York`.
1648    Named(TimeZoneAnnotationName<'n>),
1649    /// The time zone annotation is an offset. For example, `-05:00`.
1650    Offset(Offset),
1651}
1652
1653impl<'n> TimeZoneAnnotationKind<'n> {
1654    /// Converts this time zone annotation kind into an "owned" value whose
1655    /// lifetime is `'static`.
1656    ///
1657    /// If this was already an "owned" value or a time zone annotation offset,
1658    /// then this is a no-op.
1659    #[cfg(feature = "alloc")]
1660    #[inline]
1661    pub fn into_owned(self) -> TimeZoneAnnotationKind<'static> {
1662        match self {
1663            TimeZoneAnnotationKind::Named(named) => {
1664                TimeZoneAnnotationKind::Named(named.into_owned())
1665            }
1666            TimeZoneAnnotationKind::Offset(offset) => {
1667                TimeZoneAnnotationKind::Offset(offset)
1668            }
1669        }
1670    }
1671}
1672
1673impl<'n> From<&'n str> for TimeZoneAnnotationKind<'n> {
1674    fn from(string: &'n str) -> TimeZoneAnnotationKind<'n> {
1675        let name = TimeZoneAnnotationName::from(string);
1676        TimeZoneAnnotationKind::Named(name)
1677    }
1678}
1679
1680impl From<Offset> for TimeZoneAnnotationKind<'static> {
1681    fn from(offset: Offset) -> TimeZoneAnnotationKind<'static> {
1682        TimeZoneAnnotationKind::Offset(offset)
1683    }
1684}
1685
1686/// A time zone annotation parsed from a datetime string.
1687///
1688/// By default, a time zone annotation name borrows its name from the
1689/// input it was parsed from. When the `alloc` feature is enabled,
1690/// callers can de-couple the annotation from the parsed input with
1691/// [`TimeZoneAnnotationName::into_owned`].
1692///
1693/// A value of this type is usually found via [`Pieces::time_zone_annotation`],
1694/// but callers can also construct one via this type's `From<&str>` trait
1695/// implementation if necessary.
1696#[derive(Clone, Debug, Eq, Hash, PartialEq)]
1697pub struct TimeZoneAnnotationName<'n> {
1698    name: StringCow<'n>,
1699}
1700
1701impl<'n> TimeZoneAnnotationName<'n> {
1702    /// Returns the name of this time zone annotation as a string slice.
1703    ///
1704    /// Note that the lifetime of the string slice returned is tied to the
1705    /// lifetime of this time zone annotation. This may be shorter than the
1706    /// lifetime of the string, `'n`, in this annotation.
1707    #[inline]
1708    pub fn as_str<'a>(&'a self) -> &'a str {
1709        self.name.as_str()
1710    }
1711
1712    /// Converts this time zone annotation name into an "owned" value whose
1713    /// lifetime is `'static`.
1714    ///
1715    /// If this was already an "owned" value, then this is a no-op.
1716    #[cfg(feature = "alloc")]
1717    #[inline]
1718    pub fn into_owned(self) -> TimeZoneAnnotationName<'static> {
1719        TimeZoneAnnotationName { name: self.name.into_owned() }
1720    }
1721}
1722
1723impl<'n> From<&'n str> for TimeZoneAnnotationName<'n> {
1724    fn from(string: &'n str) -> TimeZoneAnnotationName<'n> {
1725        TimeZoneAnnotationName { name: StringCow::from(string) }
1726    }
1727}