jiff/tz/
ambiguous.rs

1use crate::{
2    civil::DateTime,
3    error::{err, 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::{self, AmbiguousOffset}};
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(err!(
659                    "the datetime {dt} is ambiguous since it falls into \
660                     a gap between offsets {before} and {after}",
661                    dt = self.dt,
662                ));
663            }
664            AmbiguousOffset::Fold { before, after } => {
665                return Err(err!(
666                    "the datetime {dt} is ambiguous since it falls into \
667                     a fold between offsets {before} and {after}",
668                    dt = self.dt,
669                ));
670            }
671        };
672        offset.to_timestamp(self.dt)
673    }
674
675    /// Disambiguates this (possibly ambiguous) timestamp into a specific
676    /// timestamp.
677    ///
678    /// This is the same as calling one of the disambiguation methods, but
679    /// the method chosen is indicated by the option given. This is useful
680    /// when the disambiguation option needs to be chosen at runtime.
681    ///
682    /// # Errors
683    ///
684    /// This returns an error if this would have returned a timestamp
685    /// outside of its minimum and maximum values.
686    ///
687    /// This can also return an error when using the [`Disambiguation::Reject`]
688    /// strategy. Namely, when using the `Reject` strategy, any ambiguous
689    /// timestamp always results in an error.
690    ///
691    /// # Example
692    ///
693    /// This example shows the various disambiguation modes when given a
694    /// datetime that falls in a "fold" (i.e., a backwards DST transition).
695    ///
696    /// ```
697    /// use jiff::{civil::date, tz::{self, Disambiguation}};
698    ///
699    /// let newyork = tz::db().get("America/New_York")?;
700    /// let dt = date(2024, 11, 3).at(1, 30, 0, 0);
701    /// let ambiguous = newyork.to_ambiguous_timestamp(dt);
702    ///
703    /// // In compatible mode, backward transitions select the earlier
704    /// // time. In the EDT->EST transition, that's the -04 (EDT) offset.
705    /// let ts = ambiguous.clone().disambiguate(Disambiguation::Compatible)?;
706    /// assert_eq!(ts.to_string(), "2024-11-03T05:30:00Z");
707    ///
708    /// // The earlier time in the EDT->EST transition is the -04 (EDT) offset.
709    /// let ts = ambiguous.clone().disambiguate(Disambiguation::Earlier)?;
710    /// assert_eq!(ts.to_string(), "2024-11-03T05:30:00Z");
711    ///
712    /// // The later time in the EDT->EST transition is the -05 (EST) offset.
713    /// let ts = ambiguous.clone().disambiguate(Disambiguation::Later)?;
714    /// assert_eq!(ts.to_string(), "2024-11-03T06:30:00Z");
715    ///
716    /// // Since our datetime is ambiguous, the 'reject' strategy errors.
717    /// assert!(ambiguous.disambiguate(Disambiguation::Reject).is_err());
718    ///
719    /// # Ok::<(), Box<dyn std::error::Error>>(())
720    /// ```
721    #[inline]
722    pub fn disambiguate(
723        self,
724        option: Disambiguation,
725    ) -> Result<Timestamp, Error> {
726        match option {
727            Disambiguation::Compatible => self.compatible(),
728            Disambiguation::Earlier => self.earlier(),
729            Disambiguation::Later => self.later(),
730            Disambiguation::Reject => self.unambiguous(),
731        }
732    }
733
734    /// Convert this ambiguous timestamp into an ambiguous zoned date time by
735    /// attaching a time zone.
736    ///
737    /// This is useful when you have a [`civil::DateTime`], [`TimeZone`] and
738    /// want to convert it to an instant while applying a particular
739    /// disambiguation strategy without an extra clone of the `TimeZone`.
740    ///
741    /// This isn't currently exposed because I believe use cases for crate
742    /// users can be satisfied via [`TimeZone::into_ambiguous_zoned`] (which
743    /// is implemented via this routine).
744    #[inline]
745    pub(crate) fn into_ambiguous_zoned(self, tz: TimeZone) -> AmbiguousZoned {
746        AmbiguousZoned::new(self, tz)
747    }
748}
749
750/// A possibly ambiguous [`Zoned`], created by
751/// [`TimeZone::to_ambiguous_zoned`].
752///
753/// While this is called an ambiguous zoned datetime, the thing that is
754/// actually ambiguous is the offset. That is, an ambiguous zoned datetime
755/// is actually a triple of a [`civil::DateTime`](crate::civil::DateTime), a
756/// [`TimeZone`] and an [`AmbiguousOffset`].
757///
758/// When the offset is ambiguous, it either represents a gap (civil time is
759/// skipped) or a fold (civil time is repeated). In both cases, there are, by
760/// construction, two different offsets to choose from: the offset from before
761/// the transition and the offset from after the transition.
762///
763/// The purpose of this type is to represent that ambiguity (when it occurs)
764/// and enable callers to make a choice about how to resolve that ambiguity.
765/// In some cases, you might want to reject ambiguity altogether, which is
766/// supported by the [`AmbiguousZoned::unambiguous`] routine.
767///
768/// This type provides four different out-of-the-box disambiguation strategies:
769///
770/// * [`AmbiguousZoned::compatible`] implements the
771/// [`Disambiguation::Compatible`] strategy. In the case of a gap, the offset
772/// after the gap is selected. In the case of a fold, the offset before the
773/// fold occurs is selected.
774/// * [`AmbiguousZoned::earlier`] implements the
775/// [`Disambiguation::Earlier`] strategy. This always selects the "earlier"
776/// offset.
777/// * [`AmbiguousZoned::later`] implements the
778/// [`Disambiguation::Later`] strategy. This always selects the "later"
779/// offset.
780/// * [`AmbiguousZoned::unambiguous`] implements the
781/// [`Disambiguation::Reject`] strategy. It acts as an assertion that the
782/// offset is unambiguous. If it is ambiguous, then an appropriate error is
783/// returned.
784///
785/// The [`AmbiguousZoned::disambiguate`] method can be used with the
786/// [`Disambiguation`] enum when the disambiguation strategy isn't known until
787/// runtime.
788///
789/// Note also that these aren't the only disambiguation strategies. The
790/// [`AmbiguousOffset`] type, accessible via [`AmbiguousZoned::offset`],
791/// exposes the full details of the ambiguity. So any strategy can be
792/// implemented.
793///
794/// # Example
795///
796/// This example shows how the "compatible" disambiguation strategy is
797/// implemented. Recall that the "compatible" strategy chooses the offset
798/// corresponding to the civil datetime after a gap, and the offset
799/// corresponding to the civil datetime before a gap.
800///
801/// ```
802/// use jiff::{civil::date, tz::{self, AmbiguousOffset}};
803///
804/// let tz = tz::db().get("America/New_York")?;
805/// let dt = date(2024, 3, 10).at(2, 30, 0, 0);
806/// let ambiguous = tz.to_ambiguous_zoned(dt);
807/// let offset = match ambiguous.offset() {
808///     AmbiguousOffset::Unambiguous { offset } => offset,
809///     // This is counter-intuitive, but in order to get the civil datetime
810///     // *after* the gap, we need to select the offset from *before* the
811///     // gap.
812///     AmbiguousOffset::Gap { before, .. } => before,
813///     AmbiguousOffset::Fold { before, .. } => before,
814/// };
815/// let zdt = offset.to_timestamp(dt)?.to_zoned(ambiguous.into_time_zone());
816/// assert_eq!(zdt.to_string(), "2024-03-10T03:30:00-04:00[America/New_York]");
817///
818/// # Ok::<(), Box<dyn std::error::Error>>(())
819/// ```
820#[derive(Clone, Debug, Eq, PartialEq)]
821pub struct AmbiguousZoned {
822    ts: AmbiguousTimestamp,
823    tz: TimeZone,
824}
825
826impl AmbiguousZoned {
827    #[inline]
828    fn new(ts: AmbiguousTimestamp, tz: TimeZone) -> AmbiguousZoned {
829        AmbiguousZoned { ts, tz }
830    }
831
832    /// Returns a reference to the time zone that was used to create this
833    /// ambiguous zoned datetime.
834    ///
835    /// # Example
836    ///
837    /// ```
838    /// use jiff::{civil::date, tz};
839    ///
840    /// let tz = tz::db().get("America/New_York")?;
841    /// let dt = date(2024, 7, 10).at(17, 15, 0, 0);
842    /// let zdt = tz.to_ambiguous_zoned(dt);
843    /// assert_eq!(&tz, zdt.time_zone());
844    ///
845    /// # Ok::<(), Box<dyn std::error::Error>>(())
846    /// ```
847    #[inline]
848    pub fn time_zone(&self) -> &TimeZone {
849        &self.tz
850    }
851
852    /// Consumes this ambiguous zoned datetime and returns the underlying
853    /// `TimeZone`. This is useful if you no longer need the ambiguous zoned
854    /// datetime and want its `TimeZone` without cloning it. (Cloning a
855    /// `TimeZone` is cheap but not free.)
856    ///
857    /// # Example
858    ///
859    /// ```
860    /// use jiff::{civil::date, tz};
861    ///
862    /// let tz = tz::db().get("America/New_York")?;
863    /// let dt = date(2024, 7, 10).at(17, 15, 0, 0);
864    /// let zdt = tz.to_ambiguous_zoned(dt);
865    /// assert_eq!(tz, zdt.into_time_zone());
866    ///
867    /// # Ok::<(), Box<dyn std::error::Error>>(())
868    /// ```
869    #[inline]
870    pub fn into_time_zone(self) -> TimeZone {
871        self.tz
872    }
873
874    /// Returns the civil datetime that was used to create this ambiguous
875    /// zoned datetime.
876    ///
877    /// # Example
878    ///
879    /// ```
880    /// use jiff::{civil::date, tz};
881    ///
882    /// let tz = tz::db().get("America/New_York")?;
883    /// let dt = date(2024, 7, 10).at(17, 15, 0, 0);
884    /// let zdt = tz.to_ambiguous_zoned(dt);
885    /// assert_eq!(zdt.datetime(), dt);
886    ///
887    /// # Ok::<(), Box<dyn std::error::Error>>(())
888    /// ```
889    #[inline]
890    pub fn datetime(&self) -> DateTime {
891        self.ts.datetime()
892    }
893
894    /// Returns the possibly ambiguous offset that is the ultimate source of
895    /// ambiguity.
896    ///
897    /// Most civil datetimes are not ambiguous, and thus, the offset will not
898    /// be ambiguous either. In this case, the offset returned will be the
899    /// [`AmbiguousOffset::Unambiguous`] variant.
900    ///
901    /// But, not all civil datetimes are unambiguous. There are exactly two
902    /// cases where a civil datetime can be ambiguous: when a civil datetime
903    /// does not exist (a gap) or when a civil datetime is repeated (a fold).
904    /// In both such cases, the _offset_ is the thing that is ambiguous as
905    /// there are two possible choices for the offset in both cases: the offset
906    /// before the transition (whether it's a gap or a fold) or the offset
907    /// after the transition.
908    ///
909    /// This type captures the fact that computing an offset from a civil
910    /// datetime in a particular time zone is in one of three possible states:
911    ///
912    /// 1. It is unambiguous.
913    /// 2. It is ambiguous because there is a gap in time.
914    /// 3. It is ambiguous because there is a fold in time.
915    ///
916    /// # Example
917    ///
918    /// ```
919    /// use jiff::{civil::date, tz::{self, AmbiguousOffset}};
920    ///
921    /// let tz = tz::db().get("America/New_York")?;
922    ///
923    /// // Not ambiguous.
924    /// let dt = date(2024, 7, 15).at(17, 30, 0, 0);
925    /// let zdt = tz.to_ambiguous_zoned(dt);
926    /// assert_eq!(zdt.offset(), AmbiguousOffset::Unambiguous {
927    ///     offset: tz::offset(-4),
928    /// });
929    ///
930    /// // Ambiguous because of a gap.
931    /// let dt = date(2024, 3, 10).at(2, 30, 0, 0);
932    /// let zdt = tz.to_ambiguous_zoned(dt);
933    /// assert_eq!(zdt.offset(), AmbiguousOffset::Gap {
934    ///     before: tz::offset(-5),
935    ///     after: tz::offset(-4),
936    /// });
937    ///
938    /// // Ambiguous because of a fold.
939    /// let dt = date(2024, 11, 3).at(1, 30, 0, 0);
940    /// let zdt = tz.to_ambiguous_zoned(dt);
941    /// assert_eq!(zdt.offset(), AmbiguousOffset::Fold {
942    ///     before: tz::offset(-4),
943    ///     after: tz::offset(-5),
944    /// });
945    ///
946    /// # Ok::<(), Box<dyn std::error::Error>>(())
947    /// ```
948    #[inline]
949    pub fn offset(&self) -> AmbiguousOffset {
950        self.ts.offset
951    }
952
953    /// Returns true if and only if this possibly ambiguous zoned datetime is
954    /// actually ambiguous.
955    ///
956    /// This occurs precisely in cases when the offset is _not_
957    /// [`AmbiguousOffset::Unambiguous`].
958    ///
959    /// # Example
960    ///
961    /// ```
962    /// use jiff::{civil::date, tz::{self, AmbiguousOffset}};
963    ///
964    /// let tz = tz::db().get("America/New_York")?;
965    ///
966    /// // Not ambiguous.
967    /// let dt = date(2024, 7, 15).at(17, 30, 0, 0);
968    /// let zdt = tz.to_ambiguous_zoned(dt);
969    /// assert!(!zdt.is_ambiguous());
970    ///
971    /// // Ambiguous because of a gap.
972    /// let dt = date(2024, 3, 10).at(2, 30, 0, 0);
973    /// let zdt = tz.to_ambiguous_zoned(dt);
974    /// assert!(zdt.is_ambiguous());
975    ///
976    /// // Ambiguous because of a fold.
977    /// let dt = date(2024, 11, 3).at(1, 30, 0, 0);
978    /// let zdt = tz.to_ambiguous_zoned(dt);
979    /// assert!(zdt.is_ambiguous());
980    ///
981    /// # Ok::<(), Box<dyn std::error::Error>>(())
982    /// ```
983    #[inline]
984    pub fn is_ambiguous(&self) -> bool {
985        !matches!(self.offset(), AmbiguousOffset::Unambiguous { .. })
986    }
987
988    /// Disambiguates this zoned datetime according to the
989    /// [`Disambiguation::Compatible`] strategy.
990    ///
991    /// If this zoned datetime is unambiguous, then this is a no-op.
992    ///
993    /// The "compatible" strategy selects the offset corresponding to the civil
994    /// time after a gap, and the offset corresponding to the civil time before
995    /// a fold. This is what is specified in [RFC 5545].
996    ///
997    /// [RFC 5545]: https://datatracker.ietf.org/doc/html/rfc5545
998    ///
999    /// # Errors
1000    ///
1001    /// This returns an error when the combination of the civil datetime
1002    /// and offset would lead to a `Zoned` with a timestamp outside of the
1003    /// [`Timestamp::MIN`] and [`Timestamp::MAX`] limits. This only occurs
1004    /// when the civil datetime is "close" to its own [`DateTime::MIN`]
1005    /// and [`DateTime::MAX`] limits.
1006    ///
1007    /// # Example
1008    ///
1009    /// ```
1010    /// use jiff::{civil::date, tz};
1011    ///
1012    /// let tz = tz::db().get("America/New_York")?;
1013    ///
1014    /// // Not ambiguous.
1015    /// let dt = date(2024, 7, 15).at(17, 30, 0, 0);
1016    /// let zdt = tz.to_ambiguous_zoned(dt);
1017    /// assert_eq!(
1018    ///     zdt.compatible()?.to_string(),
1019    ///     "2024-07-15T17:30:00-04:00[America/New_York]",
1020    /// );
1021    ///
1022    /// // Ambiguous because of a gap.
1023    /// let dt = date(2024, 3, 10).at(2, 30, 0, 0);
1024    /// let zdt = tz.to_ambiguous_zoned(dt);
1025    /// assert_eq!(
1026    ///     zdt.compatible()?.to_string(),
1027    ///     "2024-03-10T03:30:00-04:00[America/New_York]",
1028    /// );
1029    ///
1030    /// // Ambiguous because of a fold.
1031    /// let dt = date(2024, 11, 3).at(1, 30, 0, 0);
1032    /// let zdt = tz.to_ambiguous_zoned(dt);
1033    /// assert_eq!(
1034    ///     zdt.compatible()?.to_string(),
1035    ///     "2024-11-03T01:30:00-04:00[America/New_York]",
1036    /// );
1037    ///
1038    /// # Ok::<(), Box<dyn std::error::Error>>(())
1039    /// ```
1040    #[inline]
1041    pub fn compatible(self) -> Result<Zoned, Error> {
1042        let ts = self.ts.compatible().with_context(|| {
1043            err!(
1044                "error converting datetime {dt} to instant in time zone {tz}",
1045                dt = self.datetime(),
1046                tz = self.time_zone().diagnostic_name(),
1047            )
1048        })?;
1049        Ok(ts.to_zoned(self.tz))
1050    }
1051
1052    /// Disambiguates this zoned datetime according to the
1053    /// [`Disambiguation::Earlier`] strategy.
1054    ///
1055    /// If this zoned datetime is unambiguous, then this is a no-op.
1056    ///
1057    /// The "earlier" strategy selects the offset corresponding to the civil
1058    /// time before a gap, and the offset corresponding to the civil time
1059    /// before a fold.
1060    ///
1061    /// # Errors
1062    ///
1063    /// This returns an error when the combination of the civil datetime
1064    /// and offset would lead to a `Zoned` with a timestamp outside of the
1065    /// [`Timestamp::MIN`] and [`Timestamp::MAX`] limits. This only occurs
1066    /// when the civil datetime is "close" to its own [`DateTime::MIN`]
1067    /// and [`DateTime::MAX`] limits.
1068    ///
1069    /// # Example
1070    ///
1071    /// ```
1072    /// use jiff::{civil::date, tz};
1073    ///
1074    /// let tz = tz::db().get("America/New_York")?;
1075    ///
1076    /// // Not ambiguous.
1077    /// let dt = date(2024, 7, 15).at(17, 30, 0, 0);
1078    /// let zdt = tz.to_ambiguous_zoned(dt);
1079    /// assert_eq!(
1080    ///     zdt.earlier()?.to_string(),
1081    ///     "2024-07-15T17:30:00-04:00[America/New_York]",
1082    /// );
1083    ///
1084    /// // Ambiguous because of a gap.
1085    /// let dt = date(2024, 3, 10).at(2, 30, 0, 0);
1086    /// let zdt = tz.to_ambiguous_zoned(dt);
1087    /// assert_eq!(
1088    ///     zdt.earlier()?.to_string(),
1089    ///     "2024-03-10T01:30:00-05:00[America/New_York]",
1090    /// );
1091    ///
1092    /// // Ambiguous because of a fold.
1093    /// let dt = date(2024, 11, 3).at(1, 30, 0, 0);
1094    /// let zdt = tz.to_ambiguous_zoned(dt);
1095    /// assert_eq!(
1096    ///     zdt.earlier()?.to_string(),
1097    ///     "2024-11-03T01:30:00-04:00[America/New_York]",
1098    /// );
1099    ///
1100    /// # Ok::<(), Box<dyn std::error::Error>>(())
1101    /// ```
1102    #[inline]
1103    pub fn earlier(self) -> Result<Zoned, Error> {
1104        let ts = self.ts.earlier().with_context(|| {
1105            err!(
1106                "error converting datetime {dt} to instant in time zone {tz}",
1107                dt = self.datetime(),
1108                tz = self.time_zone().diagnostic_name(),
1109            )
1110        })?;
1111        Ok(ts.to_zoned(self.tz))
1112    }
1113
1114    /// Disambiguates this zoned datetime according to the
1115    /// [`Disambiguation::Later`] strategy.
1116    ///
1117    /// If this zoned datetime is unambiguous, then this is a no-op.
1118    ///
1119    /// The "later" strategy selects the offset corresponding to the civil
1120    /// time after a gap, and the offset corresponding to the civil time
1121    /// after a fold.
1122    ///
1123    /// # Errors
1124    ///
1125    /// This returns an error when the combination of the civil datetime
1126    /// and offset would lead to a `Zoned` with a timestamp outside of the
1127    /// [`Timestamp::MIN`] and [`Timestamp::MAX`] limits. This only occurs
1128    /// when the civil datetime is "close" to its own [`DateTime::MIN`]
1129    /// and [`DateTime::MAX`] limits.
1130    ///
1131    /// # Example
1132    ///
1133    /// ```
1134    /// use jiff::{civil::date, tz};
1135    ///
1136    /// let tz = tz::db().get("America/New_York")?;
1137    ///
1138    /// // Not ambiguous.
1139    /// let dt = date(2024, 7, 15).at(17, 30, 0, 0);
1140    /// let zdt = tz.to_ambiguous_zoned(dt);
1141    /// assert_eq!(
1142    ///     zdt.later()?.to_string(),
1143    ///     "2024-07-15T17:30:00-04:00[America/New_York]",
1144    /// );
1145    ///
1146    /// // Ambiguous because of a gap.
1147    /// let dt = date(2024, 3, 10).at(2, 30, 0, 0);
1148    /// let zdt = tz.to_ambiguous_zoned(dt);
1149    /// assert_eq!(
1150    ///     zdt.later()?.to_string(),
1151    ///     "2024-03-10T03:30:00-04:00[America/New_York]",
1152    /// );
1153    ///
1154    /// // Ambiguous because of a fold.
1155    /// let dt = date(2024, 11, 3).at(1, 30, 0, 0);
1156    /// let zdt = tz.to_ambiguous_zoned(dt);
1157    /// assert_eq!(
1158    ///     zdt.later()?.to_string(),
1159    ///     "2024-11-03T01:30:00-05:00[America/New_York]",
1160    /// );
1161    ///
1162    /// # Ok::<(), Box<dyn std::error::Error>>(())
1163    /// ```
1164    #[inline]
1165    pub fn later(self) -> Result<Zoned, Error> {
1166        let ts = self.ts.later().with_context(|| {
1167            err!(
1168                "error converting datetime {dt} to instant in time zone {tz}",
1169                dt = self.datetime(),
1170                tz = self.time_zone().diagnostic_name(),
1171            )
1172        })?;
1173        Ok(ts.to_zoned(self.tz))
1174    }
1175
1176    /// Disambiguates this zoned datetime according to the
1177    /// [`Disambiguation::Reject`] strategy.
1178    ///
1179    /// If this zoned datetime is unambiguous, then this is a no-op.
1180    ///
1181    /// The "reject" strategy always returns an error when the zoned datetime
1182    /// is ambiguous.
1183    ///
1184    /// # Errors
1185    ///
1186    /// This returns an error when the combination of the civil datetime
1187    /// and offset would lead to a `Zoned` with a timestamp outside of the
1188    /// [`Timestamp::MIN`] and [`Timestamp::MAX`] limits. This only occurs
1189    /// when the civil datetime is "close" to its own [`DateTime::MIN`]
1190    /// and [`DateTime::MAX`] limits.
1191    ///
1192    /// This also returns an error when the timestamp is ambiguous.
1193    ///
1194    /// # Example
1195    ///
1196    /// ```
1197    /// use jiff::{civil::date, tz};
1198    ///
1199    /// let tz = tz::db().get("America/New_York")?;
1200    ///
1201    /// // Not ambiguous.
1202    /// let dt = date(2024, 7, 15).at(17, 30, 0, 0);
1203    /// let zdt = tz.to_ambiguous_zoned(dt);
1204    /// assert_eq!(
1205    ///     zdt.later()?.to_string(),
1206    ///     "2024-07-15T17:30:00-04:00[America/New_York]",
1207    /// );
1208    ///
1209    /// // Ambiguous because of a gap.
1210    /// let dt = date(2024, 3, 10).at(2, 30, 0, 0);
1211    /// let zdt = tz.to_ambiguous_zoned(dt);
1212    /// assert!(zdt.unambiguous().is_err());
1213    ///
1214    /// // Ambiguous because of a fold.
1215    /// let dt = date(2024, 11, 3).at(1, 30, 0, 0);
1216    /// let zdt = tz.to_ambiguous_zoned(dt);
1217    /// assert!(zdt.unambiguous().is_err());
1218    ///
1219    /// # Ok::<(), Box<dyn std::error::Error>>(())
1220    /// ```
1221    #[inline]
1222    pub fn unambiguous(self) -> Result<Zoned, Error> {
1223        let ts = self.ts.unambiguous().with_context(|| {
1224            err!(
1225                "error converting datetime {dt} to instant in time zone {tz}",
1226                dt = self.datetime(),
1227                tz = self.time_zone().diagnostic_name(),
1228            )
1229        })?;
1230        Ok(ts.to_zoned(self.tz))
1231    }
1232
1233    /// Disambiguates this (possibly ambiguous) timestamp into a concrete
1234    /// time zone aware timestamp.
1235    ///
1236    /// This is the same as calling one of the disambiguation methods, but
1237    /// the method chosen is indicated by the option given. This is useful
1238    /// when the disambiguation option needs to be chosen at runtime.
1239    ///
1240    /// # Errors
1241    ///
1242    /// This returns an error if this would have returned a zoned datetime
1243    /// outside of its minimum and maximum values.
1244    ///
1245    /// This can also return an error when using the [`Disambiguation::Reject`]
1246    /// strategy. Namely, when using the `Reject` strategy, any ambiguous
1247    /// timestamp always results in an error.
1248    ///
1249    /// # Example
1250    ///
1251    /// This example shows the various disambiguation modes when given a
1252    /// datetime that falls in a "fold" (i.e., a backwards DST transition).
1253    ///
1254    /// ```
1255    /// use jiff::{civil::date, tz::{self, Disambiguation}};
1256    ///
1257    /// let newyork = tz::db().get("America/New_York")?;
1258    /// let dt = date(2024, 11, 3).at(1, 30, 0, 0);
1259    /// let ambiguous = newyork.to_ambiguous_zoned(dt);
1260    ///
1261    /// // In compatible mode, backward transitions select the earlier
1262    /// // time. In the EDT->EST transition, that's the -04 (EDT) offset.
1263    /// let zdt = ambiguous.clone().disambiguate(Disambiguation::Compatible)?;
1264    /// assert_eq!(
1265    ///     zdt.to_string(),
1266    ///     "2024-11-03T01:30:00-04:00[America/New_York]",
1267    /// );
1268    ///
1269    /// // The earlier time in the EDT->EST transition is the -04 (EDT) offset.
1270    /// let zdt = ambiguous.clone().disambiguate(Disambiguation::Earlier)?;
1271    /// assert_eq!(
1272    ///     zdt.to_string(),
1273    ///     "2024-11-03T01:30:00-04:00[America/New_York]",
1274    /// );
1275    ///
1276    /// // The later time in the EDT->EST transition is the -05 (EST) offset.
1277    /// let zdt = ambiguous.clone().disambiguate(Disambiguation::Later)?;
1278    /// assert_eq!(
1279    ///     zdt.to_string(),
1280    ///     "2024-11-03T01:30:00-05:00[America/New_York]",
1281    /// );
1282    ///
1283    /// // Since our datetime is ambiguous, the 'reject' strategy errors.
1284    /// assert!(ambiguous.disambiguate(Disambiguation::Reject).is_err());
1285    ///
1286    /// # Ok::<(), Box<dyn std::error::Error>>(())
1287    /// ```
1288    #[inline]
1289    pub fn disambiguate(self, option: Disambiguation) -> Result<Zoned, Error> {
1290        match option {
1291            Disambiguation::Compatible => self.compatible(),
1292            Disambiguation::Earlier => self.earlier(),
1293            Disambiguation::Later => self.later(),
1294            Disambiguation::Reject => self.unambiguous(),
1295        }
1296    }
1297}