Skip to main content

jiff/tz/
ambiguous.rs

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