Skip to main content

jiff/tz/
ambiguous.rs

1use crate::{
2    civil::DateTime,
3    error::{tz::ambiguous::Error as E, Error, ErrorContext},
4    shared::util::itime::IAmbiguousOffset,
5    tz::{Offset, TimeZone},
6    Timestamp, Zoned,
7};
8
9/// Configuration for resolving ambiguous datetimes in a particular time zone.
10///
11/// This is useful for specifying how to disambiguate ambiguous datetimes at
12/// runtime. For example, as configuration for parsing [`Zoned`] values via
13/// [`fmt::temporal::DateTimeParser::disambiguation`](crate::fmt::temporal::DateTimeParser::disambiguation).
14///
15/// Note that there is no difference in using
16/// `Disambiguation::Compatible.disambiguate(ambiguous_timestamp)` and
17/// `ambiguous_timestamp.compatible()`. They are equivalent. The purpose of
18/// this enum is to expose the disambiguation strategy as a runtime value for
19/// configuration purposes.
20///
21/// The default value is `Disambiguation::Compatible`, which matches the
22/// behavior specified in [RFC 5545 (iCalendar)]. Namely, when an ambiguous
23/// datetime is found in a fold (the clocks are rolled back), then the earlier
24/// time is selected. And when an ambiguous datetime is found in a gap (the
25/// clocks are skipped forward), then the later time is selected.
26///
27/// This enum is non-exhaustive so that other forms of disambiguation may be
28/// added in semver compatible releases.
29///
30/// [RFC 5545 (iCalendar)]: https://datatracker.ietf.org/doc/html/rfc5545
31///
32/// # Example
33///
34/// This example shows the default disambiguation mode ("compatible") when
35/// given a datetime that falls in a "gap" (i.e., a forwards DST transition).
36///
37/// ```
38/// use jiff::{civil::date, tz};
39///
40/// let newyork = tz::db().get("America/New_York")?;
41/// let ambiguous = newyork.to_ambiguous_zoned(date(2024, 3, 10).at(2, 30, 0, 0));
42///
43/// // NOTE: This is identical to `ambiguous.compatible()`.
44/// let zdt = ambiguous.disambiguate(tz::Disambiguation::Compatible)?;
45/// assert_eq!(zdt.datetime(), date(2024, 3, 10).at(3, 30, 0, 0));
46/// // In compatible mode, forward transitions select the later
47/// // time. In the EST->EDT transition, that's the -04 (EDT) offset.
48/// assert_eq!(zdt.offset(), tz::offset(-4));
49///
50/// # Ok::<(), Box<dyn std::error::Error>>(())
51/// ```
52///
53/// # Example: parsing
54///
55/// This example shows how to set the disambiguation configuration while
56/// parsing a [`Zoned`] datetime. In this example, we always prefer the earlier
57/// time.
58///
59/// ```
60/// use jiff::{civil::date, fmt::temporal::DateTimeParser, tz};
61///
62/// static PARSER: DateTimeParser = DateTimeParser::new()
63///     .disambiguation(tz::Disambiguation::Earlier);
64///
65/// let zdt = PARSER.parse_zoned("2024-03-10T02:30[America/New_York]")?;
66/// // In earlier mode, forward transitions select the earlier time, unlike
67/// // in compatible mode. In this case, that's the pre-DST offset of -05.
68/// assert_eq!(zdt.datetime(), date(2024, 3, 10).at(1, 30, 0, 0));
69/// assert_eq!(zdt.offset(), tz::offset(-5));
70///
71/// # Ok::<(), Box<dyn std::error::Error>>(())
72/// ```
73#[derive(Clone, Copy, Debug, Default)]
74#[non_exhaustive]
75pub enum Disambiguation {
76    /// In a backward transition, the earlier time is selected. In forward
77    /// transition, the later time is selected.
78    ///
79    /// This is equivalent to [`AmbiguousTimestamp::compatible`] and
80    /// [`AmbiguousZoned::compatible`].
81    #[default]
82    Compatible,
83    /// The earlier time is always selected.
84    ///
85    /// This is equivalent to [`AmbiguousTimestamp::earlier`] and
86    /// [`AmbiguousZoned::earlier`].
87    Earlier,
88    /// The later time is always selected.
89    ///
90    /// This is equivalent to [`AmbiguousTimestamp::later`] and
91    /// [`AmbiguousZoned::later`].
92    Later,
93    /// When an ambiguous datetime is encountered, this strategy will always
94    /// result in an error. This is useful if you need to require datetimes
95    /// from users to unambiguously refer to a specific instant.
96    ///
97    /// This is equivalent to [`AmbiguousTimestamp::unambiguous`] and
98    /// [`AmbiguousZoned::unambiguous`].
99    Reject,
100}
101
102/// A possibly ambiguous [`Offset`].
103///
104/// An `AmbiguousOffset` is part of both [`AmbiguousTimestamp`] and
105/// [`AmbiguousZoned`], which are created by
106/// [`TimeZone::to_ambiguous_timestamp`] and
107/// [`TimeZone::to_ambiguous_zoned`], respectively.
108///
109/// When converting a civil datetime in a particular time zone to a precise
110/// instant in time (that is, either `Timestamp` or `Zoned`), then the primary
111/// thing needed to form a precise instant in time is an [`Offset`]. The
112/// problem is that some civil datetimes are ambiguous. That is, some do not
113/// exist (because they fall into a gap, where some civil time is skipped),
114/// or some are repeated (because they fall into a fold, where some civil time
115/// is repeated).
116///
117/// The purpose of this type is to represent that ambiguity when it occurs.
118/// The ambiguity is manifest through the offset choice: it is either the
119/// offset _before_ the transition or the offset _after_ the transition. This
120/// is true regardless of whether the ambiguity occurs as a result of a gap
121/// or a fold.
122///
123/// It is generally considered very rare to need to inspect values of this
124/// type directly. Instead, higher level routines like
125/// [`AmbiguousZoned::compatible`] or [`AmbiguousZoned::unambiguous`] will
126/// implement a strategy for you.
127///
128/// # Example
129///
130/// This example shows how the "compatible" disambiguation strategy is
131/// implemented. Recall that the "compatible" strategy chooses the offset
132/// corresponding to the civil datetime after a gap, and the offset
133/// corresponding to the civil datetime before a gap.
134///
135/// ```
136/// use jiff::{civil::date, tz::{self, AmbiguousOffset}};
137///
138/// let tz = tz::db().get("America/New_York")?;
139/// let dt = date(2024, 3, 10).at(2, 30, 0, 0);
140/// let offset = match tz.to_ambiguous_timestamp(dt).offset() {
141///     AmbiguousOffset::Unambiguous { offset } => offset,
142///     // This is counter-intuitive, but in order to get the civil datetime
143///     // *after* the gap, we need to select the offset from *before* the
144///     // gap.
145///     AmbiguousOffset::Gap { before, .. } => before,
146///     AmbiguousOffset::Fold { before, .. } => before,
147/// };
148/// assert_eq!(offset.to_timestamp(dt)?.to_string(), "2024-03-10T07:30:00Z");
149///
150/// # Ok::<(), Box<dyn std::error::Error>>(())
151/// ```
152#[derive(Clone, Copy, Debug, Eq, PartialEq)]
153pub enum AmbiguousOffset {
154    /// The offset for a particular civil datetime and time zone is
155    /// unambiguous.
156    ///
157    /// This is the overwhelmingly common case. In general, the only time this
158    /// case does not occur is when there is a transition to a different time
159    /// zone (rare) or to/from daylight saving time (occurs for 1 hour twice
160    /// in year in many geographic locations).
161    Unambiguous {
162        /// The offset from UTC for the corresponding civil datetime given. The
163        /// offset is determined via the relevant time zone data, and in this
164        /// case, there is only one possible offset that could be applied to
165        /// the given civil datetime.
166        offset: Offset,
167    },
168    /// The offset for a particular civil datetime and time zone is ambiguous
169    /// because there is a gap.
170    ///
171    /// This most commonly occurs when a civil datetime corresponds to an hour
172    /// that was "skipped" in a jump to DST (daylight saving time).
173    Gap {
174        /// The offset corresponding to the time before a gap.
175        ///
176        /// For example, given a time zone of `America/Los_Angeles`, the offset
177        /// for time immediately preceding `2020-03-08T02:00:00` is `-08`.
178        before: Offset,
179        /// The offset corresponding to the later time in a gap.
180        ///
181        /// For example, given a time zone of `America/Los_Angeles`, the offset
182        /// for time immediately following `2020-03-08T02:59:59` is `-07`.
183        after: Offset,
184    },
185    /// The offset for a particular civil datetime and time zone is ambiguous
186    /// because there is a fold.
187    ///
188    /// This most commonly occurs when a civil datetime corresponds to an hour
189    /// that was "repeated" in a jump to standard time from DST (daylight
190    /// saving time).
191    Fold {
192        /// The offset corresponding to the earlier time in a fold.
193        ///
194        /// For example, given a time zone of `America/Los_Angeles`, the offset
195        /// for time on the first `2020-11-01T01:00:00` is `-07`.
196        before: Offset,
197        /// The offset corresponding to the earlier time in a fold.
198        ///
199        /// For example, given a time zone of `America/Los_Angeles`, the offset
200        /// for time on the second `2020-11-01T01:00:00` is `-08`.
201        after: Offset,
202    },
203}
204
205impl AmbiguousOffset {
206    #[inline]
207    pub(crate) const fn from_iambiguous_offset_const(
208        iaoff: IAmbiguousOffset,
209    ) -> AmbiguousOffset {
210        match iaoff {
211            IAmbiguousOffset::Unambiguous { offset } => {
212                let offset = Offset::from_ioffset_const(offset);
213                AmbiguousOffset::Unambiguous { offset }
214            }
215            IAmbiguousOffset::Gap { before, after } => {
216                let before = Offset::from_ioffset_const(before);
217                let after = Offset::from_ioffset_const(after);
218                AmbiguousOffset::Gap { before, after }
219            }
220            IAmbiguousOffset::Fold { before, after } => {
221                let before = Offset::from_ioffset_const(before);
222                let after = Offset::from_ioffset_const(after);
223                AmbiguousOffset::Fold { before, after }
224            }
225        }
226    }
227}
228
229/// A possibly ambiguous [`Timestamp`], created by
230/// [`TimeZone::to_ambiguous_timestamp`].
231///
232/// While this is called an ambiguous _timestamp_, the thing that is
233/// actually ambiguous is the offset. That is, an ambiguous timestamp is
234/// actually a pair of a [`civil::DateTime`](crate::civil::DateTime) and an
235/// [`AmbiguousOffset`].
236///
237/// When the offset is ambiguous, it either represents a gap (civil time is
238/// skipped) or a fold (civil time is repeated). In both cases, there are, by
239/// construction, two different offsets to choose from: the offset from before
240/// the transition and the offset from after the transition.
241///
242/// The purpose of this type is to represent that ambiguity (when it occurs)
243/// and enable callers to make a choice about how to resolve that ambiguity.
244/// In some cases, you might want to reject ambiguity altogether, which is
245/// supported by the [`AmbiguousTimestamp::unambiguous`] routine.
246///
247/// This type provides four different out-of-the-box disambiguation strategies:
248///
249/// * [`AmbiguousTimestamp::compatible`] implements the
250/// [`Disambiguation::Compatible`] strategy. In the case of a gap, the offset
251/// after the gap is selected. In the case of a fold, the offset before the
252/// fold occurs is selected.
253/// * [`AmbiguousTimestamp::earlier`] implements the
254/// [`Disambiguation::Earlier`] strategy. This always selects the "earlier"
255/// offset.
256/// * [`AmbiguousTimestamp::later`] implements the
257/// [`Disambiguation::Later`] strategy. This always selects the "later"
258/// offset.
259/// * [`AmbiguousTimestamp::unambiguous`] implements the
260/// [`Disambiguation::Reject`] strategy. It acts as an assertion that the
261/// offset is unambiguous. If it is ambiguous, then an appropriate error is
262/// returned.
263///
264/// The [`AmbiguousTimestamp::disambiguate`] method can be used with the
265/// [`Disambiguation`] enum when the disambiguation strategy isn't known until
266/// runtime.
267///
268/// Note also that these aren't the only disambiguation strategies. The
269/// [`AmbiguousOffset`] type, accessible via [`AmbiguousTimestamp::offset`],
270/// exposes the full details of the ambiguity. So any strategy can be
271/// implemented.
272///
273/// # Example
274///
275/// This example shows how the "compatible" disambiguation strategy is
276/// implemented. Recall that the "compatible" strategy chooses the offset
277/// corresponding to the civil datetime after a gap, and the offset
278/// corresponding to the civil datetime before a gap.
279///
280/// ```
281/// use jiff::{civil::date, tz::{self, AmbiguousOffset}};
282///
283/// let tz = tz::db().get("America/New_York")?;
284/// let dt = date(2024, 3, 10).at(2, 30, 0, 0);
285/// let offset = match tz.to_ambiguous_timestamp(dt).offset() {
286///     AmbiguousOffset::Unambiguous { offset } => offset,
287///     // This is counter-intuitive, but in order to get the civil datetime
288///     // *after* the gap, we need to select the offset from *before* the
289///     // gap.
290///     AmbiguousOffset::Gap { before, .. } => before,
291///     AmbiguousOffset::Fold { before, .. } => before,
292/// };
293/// assert_eq!(offset.to_timestamp(dt)?.to_string(), "2024-03-10T07:30:00Z");
294///
295/// # Ok::<(), Box<dyn std::error::Error>>(())
296/// ```
297#[derive(Clone, Copy, Debug, Eq, PartialEq)]
298pub struct AmbiguousTimestamp {
299    dt: DateTime,
300    offset: AmbiguousOffset,
301}
302
303impl AmbiguousTimestamp {
304    #[inline]
305    pub(crate) fn new(
306        dt: DateTime,
307        kind: AmbiguousOffset,
308    ) -> AmbiguousTimestamp {
309        AmbiguousTimestamp { dt, offset: kind }
310    }
311
312    /// Returns the civil datetime that was used to create this ambiguous
313    /// timestamp.
314    ///
315    /// # Example
316    ///
317    /// ```
318    /// use jiff::{civil::date, tz};
319    ///
320    /// let tz = tz::db().get("America/New_York")?;
321    /// let dt = date(2024, 7, 10).at(17, 15, 0, 0);
322    /// let ts = tz.to_ambiguous_timestamp(dt);
323    /// assert_eq!(ts.datetime(), dt);
324    ///
325    /// # Ok::<(), Box<dyn std::error::Error>>(())
326    /// ```
327    #[inline]
328    pub fn datetime(&self) -> DateTime {
329        self.dt
330    }
331
332    /// Returns the possibly ambiguous offset that is the ultimate source of
333    /// ambiguity.
334    ///
335    /// Most civil datetimes are not ambiguous, and thus, the offset will not
336    /// be ambiguous either. In this case, the offset returned will be the
337    /// [`AmbiguousOffset::Unambiguous`] variant.
338    ///
339    /// But, not all civil datetimes are unambiguous. There are exactly two
340    /// cases where a civil datetime can be ambiguous: when a civil datetime
341    /// does not exist (a gap) or when a civil datetime is repeated (a fold).
342    /// In both such cases, the _offset_ is the thing that is ambiguous as
343    /// there are two possible choices for the offset in both cases: the offset
344    /// before the transition (whether it's a gap or a fold) or the offset
345    /// after the transition.
346    ///
347    /// This type captures the fact that computing an offset from a civil
348    /// datetime in a particular time zone is in one of three possible states:
349    ///
350    /// 1. It is unambiguous.
351    /// 2. It is ambiguous because there is a gap in time.
352    /// 3. It is ambiguous because there is a fold in time.
353    ///
354    /// # Example
355    ///
356    /// ```
357    /// use jiff::{civil::date, tz::{self, AmbiguousOffset}};
358    ///
359    /// let tz = tz::db().get("America/New_York")?;
360    ///
361    /// // Not ambiguous.
362    /// let dt = date(2024, 7, 15).at(17, 30, 0, 0);
363    /// let ts = tz.to_ambiguous_timestamp(dt);
364    /// assert_eq!(ts.offset(), AmbiguousOffset::Unambiguous {
365    ///     offset: tz::offset(-4),
366    /// });
367    ///
368    /// // Ambiguous because of a gap.
369    /// let dt = date(2024, 3, 10).at(2, 30, 0, 0);
370    /// let ts = tz.to_ambiguous_timestamp(dt);
371    /// assert_eq!(ts.offset(), AmbiguousOffset::Gap {
372    ///     before: tz::offset(-5),
373    ///     after: tz::offset(-4),
374    /// });
375    ///
376    /// // Ambiguous because of a fold.
377    /// let dt = date(2024, 11, 3).at(1, 30, 0, 0);
378    /// let ts = tz.to_ambiguous_timestamp(dt);
379    /// assert_eq!(ts.offset(), AmbiguousOffset::Fold {
380    ///     before: tz::offset(-4),
381    ///     after: tz::offset(-5),
382    /// });
383    ///
384    /// # Ok::<(), Box<dyn std::error::Error>>(())
385    /// ```
386    #[inline]
387    pub fn offset(&self) -> AmbiguousOffset {
388        self.offset
389    }
390
391    /// Returns true if and only if this possibly ambiguous timestamp is
392    /// actually ambiguous.
393    ///
394    /// This occurs precisely in cases when the offset is _not_
395    /// [`AmbiguousOffset::Unambiguous`].
396    ///
397    /// # Example
398    ///
399    /// ```
400    /// use jiff::{civil::date, tz};
401    ///
402    /// let tz = tz::db().get("America/New_York")?;
403    ///
404    /// // Not ambiguous.
405    /// let dt = date(2024, 7, 15).at(17, 30, 0, 0);
406    /// let ts = tz.to_ambiguous_timestamp(dt);
407    /// assert!(!ts.is_ambiguous());
408    ///
409    /// // Ambiguous because of a gap.
410    /// let dt = date(2024, 3, 10).at(2, 30, 0, 0);
411    /// let ts = tz.to_ambiguous_timestamp(dt);
412    /// assert!(ts.is_ambiguous());
413    ///
414    /// // Ambiguous because of a fold.
415    /// let dt = date(2024, 11, 3).at(1, 30, 0, 0);
416    /// let ts = tz.to_ambiguous_timestamp(dt);
417    /// assert!(ts.is_ambiguous());
418    ///
419    /// # Ok::<(), Box<dyn std::error::Error>>(())
420    /// ```
421    #[inline]
422    pub fn is_ambiguous(&self) -> bool {
423        !matches!(self.offset(), AmbiguousOffset::Unambiguous { .. })
424    }
425
426    /// Disambiguates this timestamp according to the
427    /// [`Disambiguation::Compatible`] strategy.
428    ///
429    /// If this timestamp is unambiguous, then this is a no-op.
430    ///
431    /// The "compatible" strategy selects the offset corresponding to the civil
432    /// time after a gap, and the offset corresponding to the civil time before
433    /// a fold. This is what is specified in [RFC 5545].
434    ///
435    /// [RFC 5545]: https://datatracker.ietf.org/doc/html/rfc5545
436    ///
437    /// # Errors
438    ///
439    /// This returns an error when the combination of the civil datetime
440    /// and offset would lead to a `Timestamp` outside of the
441    /// [`Timestamp::MIN`] and [`Timestamp::MAX`] limits. This only occurs
442    /// when the civil datetime is "close" to its own [`DateTime::MIN`]
443    /// and [`DateTime::MAX`] limits.
444    ///
445    /// # Example
446    ///
447    /// ```
448    /// use jiff::{civil::date, tz};
449    ///
450    /// let tz = tz::db().get("America/New_York")?;
451    ///
452    /// // Not ambiguous.
453    /// let dt = date(2024, 7, 15).at(17, 30, 0, 0);
454    /// let ts = tz.to_ambiguous_timestamp(dt);
455    /// assert_eq!(
456    ///     ts.compatible()?.to_string(),
457    ///     "2024-07-15T21:30:00Z",
458    /// );
459    ///
460    /// // Ambiguous because of a gap.
461    /// let dt = date(2024, 3, 10).at(2, 30, 0, 0);
462    /// let ts = tz.to_ambiguous_timestamp(dt);
463    /// assert_eq!(
464    ///     ts.compatible()?.to_string(),
465    ///     "2024-03-10T07:30:00Z",
466    /// );
467    ///
468    /// // Ambiguous because of a fold.
469    /// let dt = date(2024, 11, 3).at(1, 30, 0, 0);
470    /// let ts = tz.to_ambiguous_timestamp(dt);
471    /// assert_eq!(
472    ///     ts.compatible()?.to_string(),
473    ///     "2024-11-03T05:30:00Z",
474    /// );
475    ///
476    /// # Ok::<(), Box<dyn std::error::Error>>(())
477    /// ```
478    #[inline]
479    pub fn compatible(self) -> Result<Timestamp, Error> {
480        let offset = match self.offset() {
481            AmbiguousOffset::Unambiguous { offset } => offset,
482            AmbiguousOffset::Gap { before, .. } => before,
483            AmbiguousOffset::Fold { before, .. } => before,
484        };
485        offset.to_timestamp(self.dt)
486    }
487
488    /// Disambiguates this timestamp according to the
489    /// [`Disambiguation::Earlier`] strategy.
490    ///
491    /// If this timestamp is unambiguous, then this is a no-op.
492    ///
493    /// The "earlier" strategy selects the offset corresponding to the civil
494    /// time before a gap, and the offset corresponding to the civil time
495    /// before a fold.
496    ///
497    /// # Errors
498    ///
499    /// This returns an error when the combination of the civil datetime
500    /// and offset would lead to a `Timestamp` outside of the
501    /// [`Timestamp::MIN`] and [`Timestamp::MAX`] limits. This only occurs
502    /// when the civil datetime is "close" to its own [`DateTime::MIN`]
503    /// and [`DateTime::MAX`] limits.
504    ///
505    /// # Example
506    ///
507    /// ```
508    /// use jiff::{civil::date, tz};
509    ///
510    /// let tz = tz::db().get("America/New_York")?;
511    ///
512    /// // Not ambiguous.
513    /// let dt = date(2024, 7, 15).at(17, 30, 0, 0);
514    /// let ts = tz.to_ambiguous_timestamp(dt);
515    /// assert_eq!(
516    ///     ts.earlier()?.to_string(),
517    ///     "2024-07-15T21:30:00Z",
518    /// );
519    ///
520    /// // Ambiguous because of a gap.
521    /// let dt = date(2024, 3, 10).at(2, 30, 0, 0);
522    /// let ts = tz.to_ambiguous_timestamp(dt);
523    /// assert_eq!(
524    ///     ts.earlier()?.to_string(),
525    ///     "2024-03-10T06:30:00Z",
526    /// );
527    ///
528    /// // Ambiguous because of a fold.
529    /// let dt = date(2024, 11, 3).at(1, 30, 0, 0);
530    /// let ts = tz.to_ambiguous_timestamp(dt);
531    /// assert_eq!(
532    ///     ts.earlier()?.to_string(),
533    ///     "2024-11-03T05:30:00Z",
534    /// );
535    ///
536    /// # Ok::<(), Box<dyn std::error::Error>>(())
537    /// ```
538    #[inline]
539    pub fn earlier(self) -> Result<Timestamp, Error> {
540        let offset = match self.offset() {
541            AmbiguousOffset::Unambiguous { offset } => offset,
542            AmbiguousOffset::Gap { after, .. } => after,
543            AmbiguousOffset::Fold { before, .. } => before,
544        };
545        offset.to_timestamp(self.dt)
546    }
547
548    /// Disambiguates this timestamp according to the
549    /// [`Disambiguation::Later`] strategy.
550    ///
551    /// If this timestamp is unambiguous, then this is a no-op.
552    ///
553    /// The "later" strategy selects the offset corresponding to the civil
554    /// time after a gap, and the offset corresponding to the civil time
555    /// after a fold.
556    ///
557    /// # Errors
558    ///
559    /// This returns an error when the combination of the civil datetime
560    /// and offset would lead to a `Timestamp` outside of the
561    /// [`Timestamp::MIN`] and [`Timestamp::MAX`] limits. This only occurs
562    /// when the civil datetime is "close" to its own [`DateTime::MIN`]
563    /// and [`DateTime::MAX`] limits.
564    ///
565    /// # Example
566    ///
567    /// ```
568    /// use jiff::{civil::date, tz};
569    ///
570    /// let tz = tz::db().get("America/New_York")?;
571    ///
572    /// // Not ambiguous.
573    /// let dt = date(2024, 7, 15).at(17, 30, 0, 0);
574    /// let ts = tz.to_ambiguous_timestamp(dt);
575    /// assert_eq!(
576    ///     ts.later()?.to_string(),
577    ///     "2024-07-15T21:30:00Z",
578    /// );
579    ///
580    /// // Ambiguous because of a gap.
581    /// let dt = date(2024, 3, 10).at(2, 30, 0, 0);
582    /// let ts = tz.to_ambiguous_timestamp(dt);
583    /// assert_eq!(
584    ///     ts.later()?.to_string(),
585    ///     "2024-03-10T07:30:00Z",
586    /// );
587    ///
588    /// // Ambiguous because of a fold.
589    /// let dt = date(2024, 11, 3).at(1, 30, 0, 0);
590    /// let ts = tz.to_ambiguous_timestamp(dt);
591    /// assert_eq!(
592    ///     ts.later()?.to_string(),
593    ///     "2024-11-03T06:30:00Z",
594    /// );
595    ///
596    /// # Ok::<(), Box<dyn std::error::Error>>(())
597    /// ```
598    #[inline]
599    pub fn later(self) -> Result<Timestamp, Error> {
600        let offset = match self.offset() {
601            AmbiguousOffset::Unambiguous { offset } => offset,
602            AmbiguousOffset::Gap { before, .. } => before,
603            AmbiguousOffset::Fold { after, .. } => after,
604        };
605        offset.to_timestamp(self.dt)
606    }
607
608    /// Disambiguates this timestamp according to the
609    /// [`Disambiguation::Reject`] strategy.
610    ///
611    /// If this timestamp is unambiguous, then this is a no-op.
612    ///
613    /// The "reject" strategy always returns an error when the timestamp
614    /// is ambiguous.
615    ///
616    /// # Errors
617    ///
618    /// This returns an error when the combination of the civil datetime
619    /// and offset would lead to a `Timestamp` outside of the
620    /// [`Timestamp::MIN`] and [`Timestamp::MAX`] limits. This only occurs
621    /// when the civil datetime is "close" to its own [`DateTime::MIN`]
622    /// and [`DateTime::MAX`] limits.
623    ///
624    /// This also returns an error when the timestamp is ambiguous.
625    ///
626    /// # Example
627    ///
628    /// ```
629    /// use jiff::{civil::date, tz};
630    ///
631    /// let tz = tz::db().get("America/New_York")?;
632    ///
633    /// // Not ambiguous.
634    /// let dt = date(2024, 7, 15).at(17, 30, 0, 0);
635    /// let ts = tz.to_ambiguous_timestamp(dt);
636    /// assert_eq!(
637    ///     ts.later()?.to_string(),
638    ///     "2024-07-15T21:30:00Z",
639    /// );
640    ///
641    /// // Ambiguous because of a gap.
642    /// let dt = date(2024, 3, 10).at(2, 30, 0, 0);
643    /// let ts = tz.to_ambiguous_timestamp(dt);
644    /// assert!(ts.unambiguous().is_err());
645    ///
646    /// // Ambiguous because of a fold.
647    /// let dt = date(2024, 11, 3).at(1, 30, 0, 0);
648    /// let ts = tz.to_ambiguous_timestamp(dt);
649    /// assert!(ts.unambiguous().is_err());
650    ///
651    /// # Ok::<(), Box<dyn std::error::Error>>(())
652    /// ```
653    #[inline]
654    pub fn unambiguous(self) -> Result<Timestamp, Error> {
655        let offset = match self.offset() {
656            AmbiguousOffset::Unambiguous { offset } => offset,
657            AmbiguousOffset::Gap { before, after } => {
658                return Err(Error::from(E::BecauseGap { before, after }));
659            }
660            AmbiguousOffset::Fold { before, after } => {
661                return Err(Error::from(E::BecauseFold { before, after }));
662            }
663        };
664        offset.to_timestamp(self.dt)
665    }
666
667    /// Disambiguates this (possibly ambiguous) timestamp into a specific
668    /// timestamp.
669    ///
670    /// This is the same as calling one of the disambiguation methods, but
671    /// the method chosen is indicated by the option given. This is useful
672    /// when the disambiguation option needs to be chosen at runtime.
673    ///
674    /// # Errors
675    ///
676    /// This returns an error if this would have returned a timestamp
677    /// outside of its minimum and maximum values.
678    ///
679    /// This can also return an error when using the [`Disambiguation::Reject`]
680    /// strategy. Namely, when using the `Reject` strategy, any ambiguous
681    /// timestamp always results in an error.
682    ///
683    /// # Example
684    ///
685    /// This example shows the various disambiguation modes when given a
686    /// datetime that falls in a "fold" (i.e., a backwards DST transition).
687    ///
688    /// ```
689    /// use jiff::{civil::date, tz::{self, Disambiguation}};
690    ///
691    /// let newyork = tz::db().get("America/New_York")?;
692    /// let dt = date(2024, 11, 3).at(1, 30, 0, 0);
693    /// let ambiguous = newyork.to_ambiguous_timestamp(dt);
694    ///
695    /// // In compatible mode, backward transitions select the earlier
696    /// // time. In the EDT->EST transition, that's the -04 (EDT) offset.
697    /// let ts = ambiguous.clone().disambiguate(Disambiguation::Compatible)?;
698    /// assert_eq!(ts.to_string(), "2024-11-03T05:30:00Z");
699    ///
700    /// // The earlier time in the EDT->EST transition is the -04 (EDT) offset.
701    /// let ts = ambiguous.clone().disambiguate(Disambiguation::Earlier)?;
702    /// assert_eq!(ts.to_string(), "2024-11-03T05:30:00Z");
703    ///
704    /// // The later time in the EDT->EST transition is the -05 (EST) offset.
705    /// let ts = ambiguous.clone().disambiguate(Disambiguation::Later)?;
706    /// assert_eq!(ts.to_string(), "2024-11-03T06:30:00Z");
707    ///
708    /// // Since our datetime is ambiguous, the 'reject' strategy errors.
709    /// assert!(ambiguous.disambiguate(Disambiguation::Reject).is_err());
710    ///
711    /// # Ok::<(), Box<dyn std::error::Error>>(())
712    /// ```
713    #[inline]
714    pub fn disambiguate(
715        self,
716        option: Disambiguation,
717    ) -> Result<Timestamp, Error> {
718        match option {
719            Disambiguation::Compatible => self.compatible(),
720            Disambiguation::Earlier => self.earlier(),
721            Disambiguation::Later => self.later(),
722            Disambiguation::Reject => self.unambiguous(),
723        }
724    }
725
726    /// Convert this ambiguous timestamp into an ambiguous zoned date time by
727    /// attaching a time zone.
728    ///
729    /// This is useful when you have a [`civil::DateTime`], [`TimeZone`] and
730    /// want to convert it to an instant while applying a particular
731    /// disambiguation strategy without an extra clone of the `TimeZone`.
732    ///
733    /// This isn't currently exposed because I believe use cases for crate
734    /// users can be satisfied via [`TimeZone::into_ambiguous_zoned`] (which
735    /// is implemented via this routine).
736    #[inline]
737    pub(crate) fn into_ambiguous_zoned(self, tz: TimeZone) -> AmbiguousZoned {
738        AmbiguousZoned::new(self, tz)
739    }
740}
741
742/// A possibly ambiguous [`Zoned`], created by
743/// [`TimeZone::to_ambiguous_zoned`].
744///
745/// While this is called an ambiguous zoned datetime, the thing that is
746/// actually ambiguous is the offset. That is, an ambiguous zoned datetime
747/// is actually a triple of a [`civil::DateTime`](crate::civil::DateTime), a
748/// [`TimeZone`] and an [`AmbiguousOffset`].
749///
750/// When the offset is ambiguous, it either represents a gap (civil time is
751/// skipped) or a fold (civil time is repeated). In both cases, there are, by
752/// construction, two different offsets to choose from: the offset from before
753/// the transition and the offset from after the transition.
754///
755/// The purpose of this type is to represent that ambiguity (when it occurs)
756/// and enable callers to make a choice about how to resolve that ambiguity.
757/// In some cases, you might want to reject ambiguity altogether, which is
758/// supported by the [`AmbiguousZoned::unambiguous`] routine.
759///
760/// This type provides four different out-of-the-box disambiguation strategies:
761///
762/// * [`AmbiguousZoned::compatible`] implements the
763/// [`Disambiguation::Compatible`] strategy. In the case of a gap, the offset
764/// after the gap is selected. In the case of a fold, the offset before the
765/// fold occurs is selected.
766/// * [`AmbiguousZoned::earlier`] implements the
767/// [`Disambiguation::Earlier`] strategy. This always selects the "earlier"
768/// offset.
769/// * [`AmbiguousZoned::later`] implements the
770/// [`Disambiguation::Later`] strategy. This always selects the "later"
771/// offset.
772/// * [`AmbiguousZoned::unambiguous`] implements the
773/// [`Disambiguation::Reject`] strategy. It acts as an assertion that the
774/// offset is unambiguous. If it is ambiguous, then an appropriate error is
775/// returned.
776///
777/// The [`AmbiguousZoned::disambiguate`] method can be used with the
778/// [`Disambiguation`] enum when the disambiguation strategy isn't known until
779/// runtime.
780///
781/// Note also that these aren't the only disambiguation strategies. The
782/// [`AmbiguousOffset`] type, accessible via [`AmbiguousZoned::offset`],
783/// exposes the full details of the ambiguity. So any strategy can be
784/// implemented.
785///
786/// # Example
787///
788/// This example shows how the "compatible" disambiguation strategy is
789/// implemented. Recall that the "compatible" strategy chooses the offset
790/// corresponding to the civil datetime after a gap, and the offset
791/// corresponding to the civil datetime before a gap.
792///
793/// ```
794/// use jiff::{civil::date, tz::{self, AmbiguousOffset}};
795///
796/// let tz = tz::db().get("America/New_York")?;
797/// let dt = date(2024, 3, 10).at(2, 30, 0, 0);
798/// let ambiguous = tz.to_ambiguous_zoned(dt);
799/// let offset = match ambiguous.offset() {
800///     AmbiguousOffset::Unambiguous { offset } => offset,
801///     // This is counter-intuitive, but in order to get the civil datetime
802///     // *after* the gap, we need to select the offset from *before* the
803///     // gap.
804///     AmbiguousOffset::Gap { before, .. } => before,
805///     AmbiguousOffset::Fold { before, .. } => before,
806/// };
807/// let zdt = offset.to_timestamp(dt)?.to_zoned(ambiguous.into_time_zone());
808/// assert_eq!(zdt.to_string(), "2024-03-10T03:30:00-04:00[America/New_York]");
809///
810/// # Ok::<(), Box<dyn std::error::Error>>(())
811/// ```
812#[derive(Clone, Debug, Eq, PartialEq)]
813pub struct AmbiguousZoned {
814    ts: AmbiguousTimestamp,
815    tz: TimeZone,
816}
817
818impl AmbiguousZoned {
819    #[inline]
820    fn new(ts: AmbiguousTimestamp, tz: TimeZone) -> AmbiguousZoned {
821        AmbiguousZoned { ts, tz }
822    }
823
824    /// Returns a reference to the time zone that was used to create this
825    /// ambiguous zoned datetime.
826    ///
827    /// # Example
828    ///
829    /// ```
830    /// use jiff::{civil::date, tz};
831    ///
832    /// let tz = tz::db().get("America/New_York")?;
833    /// let dt = date(2024, 7, 10).at(17, 15, 0, 0);
834    /// let zdt = tz.to_ambiguous_zoned(dt);
835    /// assert_eq!(&tz, zdt.time_zone());
836    ///
837    /// # Ok::<(), Box<dyn std::error::Error>>(())
838    /// ```
839    #[inline]
840    pub fn time_zone(&self) -> &TimeZone {
841        &self.tz
842    }
843
844    /// Consumes this ambiguous zoned datetime and returns the underlying
845    /// `TimeZone`. This is useful if you no longer need the ambiguous zoned
846    /// datetime and want its `TimeZone` without cloning it. (Cloning a
847    /// `TimeZone` is cheap but not free.)
848    ///
849    /// # Example
850    ///
851    /// ```
852    /// use jiff::{civil::date, tz};
853    ///
854    /// let tz = tz::db().get("America/New_York")?;
855    /// let dt = date(2024, 7, 10).at(17, 15, 0, 0);
856    /// let zdt = tz.to_ambiguous_zoned(dt);
857    /// assert_eq!(tz, zdt.into_time_zone());
858    ///
859    /// # Ok::<(), Box<dyn std::error::Error>>(())
860    /// ```
861    #[inline]
862    pub fn into_time_zone(self) -> TimeZone {
863        self.tz
864    }
865
866    /// Returns the civil datetime that was used to create this ambiguous
867    /// zoned datetime.
868    ///
869    /// # Example
870    ///
871    /// ```
872    /// use jiff::{civil::date, tz};
873    ///
874    /// let tz = tz::db().get("America/New_York")?;
875    /// let dt = date(2024, 7, 10).at(17, 15, 0, 0);
876    /// let zdt = tz.to_ambiguous_zoned(dt);
877    /// assert_eq!(zdt.datetime(), dt);
878    ///
879    /// # Ok::<(), Box<dyn std::error::Error>>(())
880    /// ```
881    #[inline]
882    pub fn datetime(&self) -> DateTime {
883        self.ts.datetime()
884    }
885
886    /// Returns the possibly ambiguous offset that is the ultimate source of
887    /// ambiguity.
888    ///
889    /// Most civil datetimes are not ambiguous, and thus, the offset will not
890    /// be ambiguous either. In this case, the offset returned will be the
891    /// [`AmbiguousOffset::Unambiguous`] variant.
892    ///
893    /// But, not all civil datetimes are unambiguous. There are exactly two
894    /// cases where a civil datetime can be ambiguous: when a civil datetime
895    /// does not exist (a gap) or when a civil datetime is repeated (a fold).
896    /// In both such cases, the _offset_ is the thing that is ambiguous as
897    /// there are two possible choices for the offset in both cases: the offset
898    /// before the transition (whether it's a gap or a fold) or the offset
899    /// after the transition.
900    ///
901    /// This type captures the fact that computing an offset from a civil
902    /// datetime in a particular time zone is in one of three possible states:
903    ///
904    /// 1. It is unambiguous.
905    /// 2. It is ambiguous because there is a gap in time.
906    /// 3. It is ambiguous because there is a fold in time.
907    ///
908    /// # Example
909    ///
910    /// ```
911    /// use jiff::{civil::date, tz::{self, AmbiguousOffset}};
912    ///
913    /// let tz = tz::db().get("America/New_York")?;
914    ///
915    /// // Not ambiguous.
916    /// let dt = date(2024, 7, 15).at(17, 30, 0, 0);
917    /// let zdt = tz.to_ambiguous_zoned(dt);
918    /// assert_eq!(zdt.offset(), AmbiguousOffset::Unambiguous {
919    ///     offset: tz::offset(-4),
920    /// });
921    ///
922    /// // Ambiguous because of a gap.
923    /// let dt = date(2024, 3, 10).at(2, 30, 0, 0);
924    /// let zdt = tz.to_ambiguous_zoned(dt);
925    /// assert_eq!(zdt.offset(), AmbiguousOffset::Gap {
926    ///     before: tz::offset(-5),
927    ///     after: tz::offset(-4),
928    /// });
929    ///
930    /// // Ambiguous because of a fold.
931    /// let dt = date(2024, 11, 3).at(1, 30, 0, 0);
932    /// let zdt = tz.to_ambiguous_zoned(dt);
933    /// assert_eq!(zdt.offset(), AmbiguousOffset::Fold {
934    ///     before: tz::offset(-4),
935    ///     after: tz::offset(-5),
936    /// });
937    ///
938    /// # Ok::<(), Box<dyn std::error::Error>>(())
939    /// ```
940    #[inline]
941    pub fn offset(&self) -> AmbiguousOffset {
942        self.ts.offset
943    }
944
945    /// Returns true if and only if this possibly ambiguous zoned datetime is
946    /// actually ambiguous.
947    ///
948    /// This occurs precisely in cases when the offset is _not_
949    /// [`AmbiguousOffset::Unambiguous`].
950    ///
951    /// # Example
952    ///
953    /// ```
954    /// use jiff::{civil::date, tz};
955    ///
956    /// let tz = tz::db().get("America/New_York")?;
957    ///
958    /// // Not ambiguous.
959    /// let dt = date(2024, 7, 15).at(17, 30, 0, 0);
960    /// let zdt = tz.to_ambiguous_zoned(dt);
961    /// assert!(!zdt.is_ambiguous());
962    ///
963    /// // Ambiguous because of a gap.
964    /// let dt = date(2024, 3, 10).at(2, 30, 0, 0);
965    /// let zdt = tz.to_ambiguous_zoned(dt);
966    /// assert!(zdt.is_ambiguous());
967    ///
968    /// // Ambiguous because of a fold.
969    /// let dt = date(2024, 11, 3).at(1, 30, 0, 0);
970    /// let zdt = tz.to_ambiguous_zoned(dt);
971    /// assert!(zdt.is_ambiguous());
972    ///
973    /// # Ok::<(), Box<dyn std::error::Error>>(())
974    /// ```
975    #[inline]
976    pub fn is_ambiguous(&self) -> bool {
977        !matches!(self.offset(), AmbiguousOffset::Unambiguous { .. })
978    }
979
980    /// Disambiguates this zoned datetime according to the
981    /// [`Disambiguation::Compatible`] strategy.
982    ///
983    /// If this zoned datetime is unambiguous, then this is a no-op.
984    ///
985    /// The "compatible" strategy selects the offset corresponding to the civil
986    /// time after a gap, and the offset corresponding to the civil time before
987    /// a fold. This is what is specified in [RFC 5545].
988    ///
989    /// [RFC 5545]: https://datatracker.ietf.org/doc/html/rfc5545
990    ///
991    /// # Errors
992    ///
993    /// This returns an error when the combination of the civil datetime
994    /// and offset would lead to a `Zoned` with a timestamp outside of the
995    /// [`Timestamp::MIN`] and [`Timestamp::MAX`] limits. This only occurs
996    /// when the civil datetime is "close" to its own [`DateTime::MIN`]
997    /// and [`DateTime::MAX`] limits.
998    ///
999    /// # Example
1000    ///
1001    /// ```
1002    /// use jiff::{civil::date, tz};
1003    ///
1004    /// let tz = tz::db().get("America/New_York")?;
1005    ///
1006    /// // Not ambiguous.
1007    /// let dt = date(2024, 7, 15).at(17, 30, 0, 0);
1008    /// let zdt = tz.to_ambiguous_zoned(dt);
1009    /// assert_eq!(
1010    ///     zdt.compatible()?.to_string(),
1011    ///     "2024-07-15T17:30:00-04:00[America/New_York]",
1012    /// );
1013    ///
1014    /// // Ambiguous because of a gap.
1015    /// let dt = date(2024, 3, 10).at(2, 30, 0, 0);
1016    /// let zdt = tz.to_ambiguous_zoned(dt);
1017    /// assert_eq!(
1018    ///     zdt.compatible()?.to_string(),
1019    ///     "2024-03-10T03:30:00-04:00[America/New_York]",
1020    /// );
1021    ///
1022    /// // Ambiguous because of a fold.
1023    /// let dt = date(2024, 11, 3).at(1, 30, 0, 0);
1024    /// let zdt = tz.to_ambiguous_zoned(dt);
1025    /// assert_eq!(
1026    ///     zdt.compatible()?.to_string(),
1027    ///     "2024-11-03T01:30:00-04:00[America/New_York]",
1028    /// );
1029    ///
1030    /// # Ok::<(), Box<dyn std::error::Error>>(())
1031    /// ```
1032    #[inline]
1033    pub fn compatible(self) -> Result<Zoned, Error> {
1034        let ts = self
1035            .ts
1036            .compatible()
1037            .with_context(|| E::InTimeZone { tz: self.time_zone().clone() })?;
1038        Ok(ts.to_zoned(self.tz))
1039    }
1040
1041    /// Disambiguates this zoned datetime according to the
1042    /// [`Disambiguation::Earlier`] strategy.
1043    ///
1044    /// If this zoned datetime is unambiguous, then this is a no-op.
1045    ///
1046    /// The "earlier" strategy selects the offset corresponding to the civil
1047    /// time before a gap, and the offset corresponding to the civil time
1048    /// before a fold.
1049    ///
1050    /// # Errors
1051    ///
1052    /// This returns an error when the combination of the civil datetime
1053    /// and offset would lead to a `Zoned` with a timestamp outside of the
1054    /// [`Timestamp::MIN`] and [`Timestamp::MAX`] limits. This only occurs
1055    /// when the civil datetime is "close" to its own [`DateTime::MIN`]
1056    /// and [`DateTime::MAX`] limits.
1057    ///
1058    /// # Example
1059    ///
1060    /// ```
1061    /// use jiff::{civil::date, tz};
1062    ///
1063    /// let tz = tz::db().get("America/New_York")?;
1064    ///
1065    /// // Not ambiguous.
1066    /// let dt = date(2024, 7, 15).at(17, 30, 0, 0);
1067    /// let zdt = tz.to_ambiguous_zoned(dt);
1068    /// assert_eq!(
1069    ///     zdt.earlier()?.to_string(),
1070    ///     "2024-07-15T17:30:00-04:00[America/New_York]",
1071    /// );
1072    ///
1073    /// // Ambiguous because of a gap.
1074    /// let dt = date(2024, 3, 10).at(2, 30, 0, 0);
1075    /// let zdt = tz.to_ambiguous_zoned(dt);
1076    /// assert_eq!(
1077    ///     zdt.earlier()?.to_string(),
1078    ///     "2024-03-10T01:30:00-05:00[America/New_York]",
1079    /// );
1080    ///
1081    /// // Ambiguous because of a fold.
1082    /// let dt = date(2024, 11, 3).at(1, 30, 0, 0);
1083    /// let zdt = tz.to_ambiguous_zoned(dt);
1084    /// assert_eq!(
1085    ///     zdt.earlier()?.to_string(),
1086    ///     "2024-11-03T01:30:00-04:00[America/New_York]",
1087    /// );
1088    ///
1089    /// # Ok::<(), Box<dyn std::error::Error>>(())
1090    /// ```
1091    #[inline]
1092    pub fn earlier(self) -> Result<Zoned, Error> {
1093        let ts = self
1094            .ts
1095            .earlier()
1096            .with_context(|| E::InTimeZone { tz: self.time_zone().clone() })?;
1097        Ok(ts.to_zoned(self.tz))
1098    }
1099
1100    /// Disambiguates this zoned datetime according to the
1101    /// [`Disambiguation::Later`] strategy.
1102    ///
1103    /// If this zoned datetime is unambiguous, then this is a no-op.
1104    ///
1105    /// The "later" strategy selects the offset corresponding to the civil
1106    /// time after a gap, and the offset corresponding to the civil time
1107    /// after a fold.
1108    ///
1109    /// # Errors
1110    ///
1111    /// This returns an error when the combination of the civil datetime
1112    /// and offset would lead to a `Zoned` with a timestamp outside of the
1113    /// [`Timestamp::MIN`] and [`Timestamp::MAX`] limits. This only occurs
1114    /// when the civil datetime is "close" to its own [`DateTime::MIN`]
1115    /// and [`DateTime::MAX`] limits.
1116    ///
1117    /// # Example
1118    ///
1119    /// ```
1120    /// use jiff::{civil::date, tz};
1121    ///
1122    /// let tz = tz::db().get("America/New_York")?;
1123    ///
1124    /// // Not ambiguous.
1125    /// let dt = date(2024, 7, 15).at(17, 30, 0, 0);
1126    /// let zdt = tz.to_ambiguous_zoned(dt);
1127    /// assert_eq!(
1128    ///     zdt.later()?.to_string(),
1129    ///     "2024-07-15T17:30:00-04:00[America/New_York]",
1130    /// );
1131    ///
1132    /// // Ambiguous because of a gap.
1133    /// let dt = date(2024, 3, 10).at(2, 30, 0, 0);
1134    /// let zdt = tz.to_ambiguous_zoned(dt);
1135    /// assert_eq!(
1136    ///     zdt.later()?.to_string(),
1137    ///     "2024-03-10T03:30:00-04:00[America/New_York]",
1138    /// );
1139    ///
1140    /// // Ambiguous because of a fold.
1141    /// let dt = date(2024, 11, 3).at(1, 30, 0, 0);
1142    /// let zdt = tz.to_ambiguous_zoned(dt);
1143    /// assert_eq!(
1144    ///     zdt.later()?.to_string(),
1145    ///     "2024-11-03T01:30:00-05:00[America/New_York]",
1146    /// );
1147    ///
1148    /// # Ok::<(), Box<dyn std::error::Error>>(())
1149    /// ```
1150    #[inline]
1151    pub fn later(self) -> Result<Zoned, Error> {
1152        let ts = self
1153            .ts
1154            .later()
1155            .with_context(|| E::InTimeZone { tz: self.time_zone().clone() })?;
1156        Ok(ts.to_zoned(self.tz))
1157    }
1158
1159    /// Disambiguates this zoned datetime according to the
1160    /// [`Disambiguation::Reject`] strategy.
1161    ///
1162    /// If this zoned datetime is unambiguous, then this is a no-op.
1163    ///
1164    /// The "reject" strategy always returns an error when the zoned datetime
1165    /// is ambiguous.
1166    ///
1167    /// # Errors
1168    ///
1169    /// This returns an error when the combination of the civil datetime
1170    /// and offset would lead to a `Zoned` with a timestamp outside of the
1171    /// [`Timestamp::MIN`] and [`Timestamp::MAX`] limits. This only occurs
1172    /// when the civil datetime is "close" to its own [`DateTime::MIN`]
1173    /// and [`DateTime::MAX`] limits.
1174    ///
1175    /// This also returns an error when the timestamp is ambiguous.
1176    ///
1177    /// # Example
1178    ///
1179    /// ```
1180    /// use jiff::{civil::date, tz};
1181    ///
1182    /// let tz = tz::db().get("America/New_York")?;
1183    ///
1184    /// // Not ambiguous.
1185    /// let dt = date(2024, 7, 15).at(17, 30, 0, 0);
1186    /// let zdt = tz.to_ambiguous_zoned(dt);
1187    /// assert_eq!(
1188    ///     zdt.later()?.to_string(),
1189    ///     "2024-07-15T17:30:00-04:00[America/New_York]",
1190    /// );
1191    ///
1192    /// // Ambiguous because of a gap.
1193    /// let dt = date(2024, 3, 10).at(2, 30, 0, 0);
1194    /// let zdt = tz.to_ambiguous_zoned(dt);
1195    /// assert!(zdt.unambiguous().is_err());
1196    ///
1197    /// // Ambiguous because of a fold.
1198    /// let dt = date(2024, 11, 3).at(1, 30, 0, 0);
1199    /// let zdt = tz.to_ambiguous_zoned(dt);
1200    /// assert!(zdt.unambiguous().is_err());
1201    ///
1202    /// # Ok::<(), Box<dyn std::error::Error>>(())
1203    /// ```
1204    #[inline]
1205    pub fn unambiguous(self) -> Result<Zoned, Error> {
1206        let ts = self
1207            .ts
1208            .unambiguous()
1209            .with_context(|| E::InTimeZone { tz: self.time_zone().clone() })?;
1210        Ok(ts.to_zoned(self.tz))
1211    }
1212
1213    /// Disambiguates this (possibly ambiguous) timestamp into a concrete
1214    /// time zone aware timestamp.
1215    ///
1216    /// This is the same as calling one of the disambiguation methods, but
1217    /// the method chosen is indicated by the option given. This is useful
1218    /// when the disambiguation option needs to be chosen at runtime.
1219    ///
1220    /// # Errors
1221    ///
1222    /// This returns an error if this would have returned a zoned datetime
1223    /// outside of its minimum and maximum values.
1224    ///
1225    /// This can also return an error when using the [`Disambiguation::Reject`]
1226    /// strategy. Namely, when using the `Reject` strategy, any ambiguous
1227    /// timestamp always results in an error.
1228    ///
1229    /// # Example
1230    ///
1231    /// This example shows the various disambiguation modes when given a
1232    /// datetime that falls in a "fold" (i.e., a backwards DST transition).
1233    ///
1234    /// ```
1235    /// use jiff::{civil::date, tz::{self, Disambiguation}};
1236    ///
1237    /// let newyork = tz::db().get("America/New_York")?;
1238    /// let dt = date(2024, 11, 3).at(1, 30, 0, 0);
1239    /// let ambiguous = newyork.to_ambiguous_zoned(dt);
1240    ///
1241    /// // In compatible mode, backward transitions select the earlier
1242    /// // time. In the EDT->EST transition, that's the -04 (EDT) offset.
1243    /// let zdt = ambiguous.clone().disambiguate(Disambiguation::Compatible)?;
1244    /// assert_eq!(
1245    ///     zdt.to_string(),
1246    ///     "2024-11-03T01:30:00-04:00[America/New_York]",
1247    /// );
1248    ///
1249    /// // The earlier time in the EDT->EST transition is the -04 (EDT) offset.
1250    /// let zdt = ambiguous.clone().disambiguate(Disambiguation::Earlier)?;
1251    /// assert_eq!(
1252    ///     zdt.to_string(),
1253    ///     "2024-11-03T01:30:00-04:00[America/New_York]",
1254    /// );
1255    ///
1256    /// // The later time in the EDT->EST transition is the -05 (EST) offset.
1257    /// let zdt = ambiguous.clone().disambiguate(Disambiguation::Later)?;
1258    /// assert_eq!(
1259    ///     zdt.to_string(),
1260    ///     "2024-11-03T01:30:00-05:00[America/New_York]",
1261    /// );
1262    ///
1263    /// // Since our datetime is ambiguous, the 'reject' strategy errors.
1264    /// assert!(ambiguous.disambiguate(Disambiguation::Reject).is_err());
1265    ///
1266    /// # Ok::<(), Box<dyn std::error::Error>>(())
1267    /// ```
1268    #[inline]
1269    pub fn disambiguate(self, option: Disambiguation) -> Result<Zoned, Error> {
1270        match option {
1271            Disambiguation::Compatible => self.compatible(),
1272            Disambiguation::Earlier => self.earlier(),
1273            Disambiguation::Later => self.later(),
1274            Disambiguation::Reject => self.unambiguous(),
1275        }
1276    }
1277}