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