Coverage Report

Created: 2026-06-28 08:04

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/jiff-0.2.28/src/tz/ambiguous.rs
Line
Count
Source
1
use crate::{
2
    civil::DateTime,
3
    error::{tz::ambiguous::Error as E, Error, ErrorContext},
4
    shared::util::itime::IAmbiguousOffset,
5
    tz::{Offset, TimeZone},
6
    Timestamp, Zoned,
7
};
8
9
/// Configuration for resolving ambiguous datetimes in a particular time zone.
10
///
11
/// This is useful for specifying how to disambiguate ambiguous datetimes at
12
/// runtime. For example, as configuration for parsing [`Zoned`] values via
13
/// [`fmt::temporal::DateTimeParser::disambiguation`](crate::fmt::temporal::DateTimeParser::disambiguation).
14
///
15
/// Note that there is no difference in using
16
/// `Disambiguation::Compatible.disambiguate(ambiguous_timestamp)` and
17
/// `ambiguous_timestamp.compatible()`. They are equivalent. The purpose of
18
/// this enum is to expose the disambiguation strategy as a runtime value for
19
/// configuration purposes.
20
///
21
/// The default value is `Disambiguation::Compatible`, which matches the
22
/// behavior specified in [RFC 5545 (iCalendar)]. Namely, when an ambiguous
23
/// datetime is found in a fold (the clocks are rolled back), then the earlier
24
/// time is selected. And when an ambiguous datetime is found in a gap (the
25
/// clocks are skipped forward), then the later time is selected.
26
///
27
/// This enum is non-exhaustive so that other forms of disambiguation may be
28
/// added in semver compatible releases.
29
///
30
/// [RFC 5545 (iCalendar)]: https://datatracker.ietf.org/doc/html/rfc5545
31
///
32
/// # Example
33
///
34
/// This example shows the default disambiguation mode ("compatible") when
35
/// given a datetime that falls in a "gap" (i.e., a forwards DST transition).
36
///
37
/// ```
38
/// use jiff::{civil::date, tz};
39
///
40
/// let newyork = tz::db().get("America/New_York")?;
41
/// let ambiguous = newyork.to_ambiguous_zoned(date(2024, 3, 10).at(2, 30, 0, 0));
42
///
43
/// // NOTE: This is identical to `ambiguous.compatible()`.
44
/// let zdt = ambiguous.disambiguate(tz::Disambiguation::Compatible)?;
45
/// assert_eq!(zdt.datetime(), date(2024, 3, 10).at(3, 30, 0, 0));
46
/// // In compatible mode, forward transitions select the later
47
/// // time. In the EST->EDT transition, that's the -04 (EDT) offset.
48
/// assert_eq!(zdt.offset(), tz::offset(-4));
49
///
50
/// # Ok::<(), Box<dyn std::error::Error>>(())
51
/// ```
52
///
53
/// # Example: parsing
54
///
55
/// This example shows how to set the disambiguation configuration while
56
/// parsing a [`Zoned`] datetime. In this example, we always prefer the earlier
57
/// time.
58
///
59
/// ```
60
/// use jiff::{civil::date, fmt::temporal::DateTimeParser, tz};
61
///
62
/// static PARSER: DateTimeParser = DateTimeParser::new()
63
///     .disambiguation(tz::Disambiguation::Earlier);
64
///
65
/// let zdt = PARSER.parse_zoned("2024-03-10T02:30[America/New_York]")?;
66
/// // In earlier mode, forward transitions select the earlier time, unlike
67
/// // in compatible mode. In this case, that's the pre-DST offset of -05.
68
/// assert_eq!(zdt.datetime(), date(2024, 3, 10).at(1, 30, 0, 0));
69
/// assert_eq!(zdt.offset(), tz::offset(-5));
70
///
71
/// # Ok::<(), Box<dyn std::error::Error>>(())
72
/// ```
73
#[derive(Clone, Copy, Debug, Default)]
74
#[non_exhaustive]
75
pub 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)]
153
pub 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
205
impl AmbiguousOffset {
206
    #[inline]
207
0
    pub(crate) const fn from_iambiguous_offset_const(
208
0
        iaoff: IAmbiguousOffset,
209
0
    ) -> AmbiguousOffset {
210
0
        match iaoff {
211
0
            IAmbiguousOffset::Unambiguous { offset } => {
212
0
                let offset = Offset::from_ioffset_const(offset);
213
0
                AmbiguousOffset::Unambiguous { offset }
214
            }
215
0
            IAmbiguousOffset::Gap { before, after } => {
216
0
                let before = Offset::from_ioffset_const(before);
217
0
                let after = Offset::from_ioffset_const(after);
218
0
                AmbiguousOffset::Gap { before, after }
219
            }
220
0
            IAmbiguousOffset::Fold { before, after } => {
221
0
                let before = Offset::from_ioffset_const(before);
222
0
                let after = Offset::from_ioffset_const(after);
223
0
                AmbiguousOffset::Fold { before, after }
224
            }
225
        }
226
0
    }
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)]
298
pub struct AmbiguousTimestamp {
299
    dt: DateTime,
300
    offset: AmbiguousOffset,
301
}
302
303
impl AmbiguousTimestamp {
304
    #[inline]
305
0
    pub(crate) fn new(
306
0
        dt: DateTime,
307
0
        kind: AmbiguousOffset,
308
0
    ) -> AmbiguousTimestamp {
309
0
        AmbiguousTimestamp { dt, offset: kind }
310
0
    }
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
0
    pub fn datetime(&self) -> DateTime {
329
0
        self.dt
330
0
    }
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
0
    pub fn offset(&self) -> AmbiguousOffset {
388
0
        self.offset
389
0
    }
390
391
    /// Returns true if and only if this possibly ambiguous timestamp is
392
    /// actually ambiguous.
393
    ///
394
    /// This occurs precisely in cases when the offset is _not_
395
    /// [`AmbiguousOffset::Unambiguous`].
396
    ///
397
    /// # Example
398
    ///
399
    /// ```
400
    /// use jiff::{civil::date, tz};
401
    ///
402
    /// let tz = tz::db().get("America/New_York")?;
403
    ///
404
    /// // Not ambiguous.
405
    /// let dt = date(2024, 7, 15).at(17, 30, 0, 0);
406
    /// let ts = tz.to_ambiguous_timestamp(dt);
407
    /// assert!(!ts.is_ambiguous());
408
    ///
409
    /// // Ambiguous because of a gap.
410
    /// let dt = date(2024, 3, 10).at(2, 30, 0, 0);
411
    /// let ts = tz.to_ambiguous_timestamp(dt);
412
    /// assert!(ts.is_ambiguous());
413
    ///
414
    /// // Ambiguous because of a fold.
415
    /// let dt = date(2024, 11, 3).at(1, 30, 0, 0);
416
    /// let ts = tz.to_ambiguous_timestamp(dt);
417
    /// assert!(ts.is_ambiguous());
418
    ///
419
    /// # Ok::<(), Box<dyn std::error::Error>>(())
420
    /// ```
421
    #[inline]
422
0
    pub fn is_ambiguous(&self) -> bool {
423
0
        !matches!(self.offset(), AmbiguousOffset::Unambiguous { .. })
424
0
    }
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
0
    pub fn compatible(self) -> Result<Timestamp, Error> {
480
0
        let offset = match self.offset() {
481
0
            AmbiguousOffset::Unambiguous { offset } => offset,
482
0
            AmbiguousOffset::Gap { before, .. } => before,
483
0
            AmbiguousOffset::Fold { before, .. } => before,
484
        };
485
0
        offset.to_timestamp(self.dt)
486
0
    }
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
0
    pub fn earlier(self) -> Result<Timestamp, Error> {
540
0
        let offset = match self.offset() {
541
0
            AmbiguousOffset::Unambiguous { offset } => offset,
542
0
            AmbiguousOffset::Gap { after, .. } => after,
543
0
            AmbiguousOffset::Fold { before, .. } => before,
544
        };
545
0
        offset.to_timestamp(self.dt)
546
0
    }
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
0
    pub fn later(self) -> Result<Timestamp, Error> {
600
0
        let offset = match self.offset() {
601
0
            AmbiguousOffset::Unambiguous { offset } => offset,
602
0
            AmbiguousOffset::Gap { before, .. } => before,
603
0
            AmbiguousOffset::Fold { after, .. } => after,
604
        };
605
0
        offset.to_timestamp(self.dt)
606
0
    }
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
0
    pub fn unambiguous(self) -> Result<Timestamp, Error> {
655
0
        let offset = match self.offset() {
656
0
            AmbiguousOffset::Unambiguous { offset } => offset,
657
0
            AmbiguousOffset::Gap { before, after } => {
658
0
                return Err(Error::from(E::BecauseGap { before, after }));
659
            }
660
0
            AmbiguousOffset::Fold { before, after } => {
661
0
                return Err(Error::from(E::BecauseFold { before, after }));
662
            }
663
        };
664
0
        offset.to_timestamp(self.dt)
665
0
    }
666
667
    /// Disambiguates this (possibly ambiguous) timestamp into a specific
668
    /// timestamp.
669
    ///
670
    /// This is the same as calling one of the disambiguation methods, but
671
    /// the method chosen is indicated by the option given. This is useful
672
    /// when the disambiguation option needs to be chosen at runtime.
673
    ///
674
    /// # Errors
675
    ///
676
    /// This returns an error if this would have returned a timestamp
677
    /// outside of its minimum and maximum values.
678
    ///
679
    /// This can also return an error when using the [`Disambiguation::Reject`]
680
    /// strategy. Namely, when using the `Reject` strategy, any ambiguous
681
    /// timestamp always results in an error.
682
    ///
683
    /// # Example
684
    ///
685
    /// This example shows the various disambiguation modes when given a
686
    /// datetime that falls in a "fold" (i.e., a backwards DST transition).
687
    ///
688
    /// ```
689
    /// use jiff::{civil::date, tz::{self, Disambiguation}};
690
    ///
691
    /// let newyork = tz::db().get("America/New_York")?;
692
    /// let dt = date(2024, 11, 3).at(1, 30, 0, 0);
693
    /// let ambiguous = newyork.to_ambiguous_timestamp(dt);
694
    ///
695
    /// // In compatible mode, backward transitions select the earlier
696
    /// // time. In the EDT->EST transition, that's the -04 (EDT) offset.
697
    /// let ts = ambiguous.clone().disambiguate(Disambiguation::Compatible)?;
698
    /// assert_eq!(ts.to_string(), "2024-11-03T05:30:00Z");
699
    ///
700
    /// // The earlier time in the EDT->EST transition is the -04 (EDT) offset.
701
    /// let ts = ambiguous.clone().disambiguate(Disambiguation::Earlier)?;
702
    /// assert_eq!(ts.to_string(), "2024-11-03T05:30:00Z");
703
    ///
704
    /// // The later time in the EDT->EST transition is the -05 (EST) offset.
705
    /// let ts = ambiguous.clone().disambiguate(Disambiguation::Later)?;
706
    /// assert_eq!(ts.to_string(), "2024-11-03T06:30:00Z");
707
    ///
708
    /// // Since our datetime is ambiguous, the 'reject' strategy errors.
709
    /// assert!(ambiguous.disambiguate(Disambiguation::Reject).is_err());
710
    ///
711
    /// # Ok::<(), Box<dyn std::error::Error>>(())
712
    /// ```
713
    #[inline]
714
0
    pub fn disambiguate(
715
0
        self,
716
0
        option: Disambiguation,
717
0
    ) -> Result<Timestamp, Error> {
718
0
        match option {
719
0
            Disambiguation::Compatible => self.compatible(),
720
0
            Disambiguation::Earlier => self.earlier(),
721
0
            Disambiguation::Later => self.later(),
722
0
            Disambiguation::Reject => self.unambiguous(),
723
        }
724
0
    }
725
726
    /// Convert this ambiguous timestamp into an ambiguous zoned date time by
727
    /// attaching a time zone.
728
    ///
729
    /// This is useful when you have a [`civil::DateTime`], [`TimeZone`] and
730
    /// want to convert it to an instant while applying a particular
731
    /// disambiguation strategy without an extra clone of the `TimeZone`.
732
    ///
733
    /// This isn't currently exposed because I believe use cases for crate
734
    /// users can be satisfied via [`TimeZone::into_ambiguous_zoned`] (which
735
    /// is implemented via this routine).
736
    #[inline]
737
0
    pub(crate) fn into_ambiguous_zoned(self, tz: TimeZone) -> AmbiguousZoned {
738
0
        AmbiguousZoned::new(self, tz)
739
0
    }
740
}
741
742
/// A possibly ambiguous [`Zoned`], created by
743
/// [`TimeZone::to_ambiguous_zoned`].
744
///
745
/// While this is called an ambiguous zoned datetime, the thing that is
746
/// actually ambiguous is the offset. That is, an ambiguous zoned datetime
747
/// is actually a triple of a [`civil::DateTime`](crate::civil::DateTime), a
748
/// [`TimeZone`] and an [`AmbiguousOffset`].
749
///
750
/// When the offset is ambiguous, it either represents a gap (civil time is
751
/// skipped) or a fold (civil time is repeated). In both cases, there are, by
752
/// construction, two different offsets to choose from: the offset from before
753
/// the transition and the offset from after the transition.
754
///
755
/// The purpose of this type is to represent that ambiguity (when it occurs)
756
/// and enable callers to make a choice about how to resolve that ambiguity.
757
/// In some cases, you might want to reject ambiguity altogether, which is
758
/// supported by the [`AmbiguousZoned::unambiguous`] routine.
759
///
760
/// This type provides four different out-of-the-box disambiguation strategies:
761
///
762
/// * [`AmbiguousZoned::compatible`] implements the
763
/// [`Disambiguation::Compatible`] strategy. In the case of a gap, the offset
764
/// after the gap is selected. In the case of a fold, the offset before the
765
/// fold occurs is selected.
766
/// * [`AmbiguousZoned::earlier`] implements the
767
/// [`Disambiguation::Earlier`] strategy. This always selects the "earlier"
768
/// offset.
769
/// * [`AmbiguousZoned::later`] implements the
770
/// [`Disambiguation::Later`] strategy. This always selects the "later"
771
/// offset.
772
/// * [`AmbiguousZoned::unambiguous`] implements the
773
/// [`Disambiguation::Reject`] strategy. It acts as an assertion that the
774
/// offset is unambiguous. If it is ambiguous, then an appropriate error is
775
/// returned.
776
///
777
/// The [`AmbiguousZoned::disambiguate`] method can be used with the
778
/// [`Disambiguation`] enum when the disambiguation strategy isn't known until
779
/// runtime.
780
///
781
/// Note also that these aren't the only disambiguation strategies. The
782
/// [`AmbiguousOffset`] type, accessible via [`AmbiguousZoned::offset`],
783
/// exposes the full details of the ambiguity. So any strategy can be
784
/// implemented.
785
///
786
/// # Example
787
///
788
/// This example shows how the "compatible" disambiguation strategy is
789
/// implemented. Recall that the "compatible" strategy chooses the offset
790
/// corresponding to the civil datetime after a gap, and the offset
791
/// corresponding to the civil datetime before a gap.
792
///
793
/// ```
794
/// use jiff::{civil::date, tz::{self, AmbiguousOffset}};
795
///
796
/// let tz = tz::db().get("America/New_York")?;
797
/// let dt = date(2024, 3, 10).at(2, 30, 0, 0);
798
/// let ambiguous = tz.to_ambiguous_zoned(dt);
799
/// let offset = match ambiguous.offset() {
800
///     AmbiguousOffset::Unambiguous { offset } => offset,
801
///     // This is counter-intuitive, but in order to get the civil datetime
802
///     // *after* the gap, we need to select the offset from *before* the
803
///     // gap.
804
///     AmbiguousOffset::Gap { before, .. } => before,
805
///     AmbiguousOffset::Fold { before, .. } => before,
806
/// };
807
/// let zdt = offset.to_timestamp(dt)?.to_zoned(ambiguous.into_time_zone());
808
/// assert_eq!(zdt.to_string(), "2024-03-10T03:30:00-04:00[America/New_York]");
809
///
810
/// # Ok::<(), Box<dyn std::error::Error>>(())
811
/// ```
812
#[derive(Clone, Debug, Eq, PartialEq)]
813
pub struct AmbiguousZoned {
814
    ts: AmbiguousTimestamp,
815
    tz: TimeZone,
816
}
817
818
impl AmbiguousZoned {
819
    #[inline]
820
0
    fn new(ts: AmbiguousTimestamp, tz: TimeZone) -> AmbiguousZoned {
821
0
        AmbiguousZoned { ts, tz }
822
0
    }
823
824
    /// Returns a reference to the time zone that was used to create this
825
    /// ambiguous zoned datetime.
826
    ///
827
    /// # Example
828
    ///
829
    /// ```
830
    /// use jiff::{civil::date, tz};
831
    ///
832
    /// let tz = tz::db().get("America/New_York")?;
833
    /// let dt = date(2024, 7, 10).at(17, 15, 0, 0);
834
    /// let zdt = tz.to_ambiguous_zoned(dt);
835
    /// assert_eq!(&tz, zdt.time_zone());
836
    ///
837
    /// # Ok::<(), Box<dyn std::error::Error>>(())
838
    /// ```
839
    #[inline]
840
0
    pub fn time_zone(&self) -> &TimeZone {
841
0
        &self.tz
842
0
    }
843
844
    /// Consumes this ambiguous zoned datetime and returns the underlying
845
    /// `TimeZone`. This is useful if you no longer need the ambiguous zoned
846
    /// datetime and want its `TimeZone` without cloning it. (Cloning a
847
    /// `TimeZone` is cheap but not free.)
848
    ///
849
    /// # Example
850
    ///
851
    /// ```
852
    /// use jiff::{civil::date, tz};
853
    ///
854
    /// let tz = tz::db().get("America/New_York")?;
855
    /// let dt = date(2024, 7, 10).at(17, 15, 0, 0);
856
    /// let zdt = tz.to_ambiguous_zoned(dt);
857
    /// assert_eq!(tz, zdt.into_time_zone());
858
    ///
859
    /// # Ok::<(), Box<dyn std::error::Error>>(())
860
    /// ```
861
    #[inline]
862
0
    pub fn into_time_zone(self) -> TimeZone {
863
0
        self.tz
864
0
    }
865
866
    /// Returns the civil datetime that was used to create this ambiguous
867
    /// zoned datetime.
868
    ///
869
    /// # Example
870
    ///
871
    /// ```
872
    /// use jiff::{civil::date, tz};
873
    ///
874
    /// let tz = tz::db().get("America/New_York")?;
875
    /// let dt = date(2024, 7, 10).at(17, 15, 0, 0);
876
    /// let zdt = tz.to_ambiguous_zoned(dt);
877
    /// assert_eq!(zdt.datetime(), dt);
878
    ///
879
    /// # Ok::<(), Box<dyn std::error::Error>>(())
880
    /// ```
881
    #[inline]
882
0
    pub fn datetime(&self) -> DateTime {
883
0
        self.ts.datetime()
884
0
    }
885
886
    /// Returns the possibly ambiguous offset that is the ultimate source of
887
    /// ambiguity.
888
    ///
889
    /// Most civil datetimes are not ambiguous, and thus, the offset will not
890
    /// be ambiguous either. In this case, the offset returned will be the
891
    /// [`AmbiguousOffset::Unambiguous`] variant.
892
    ///
893
    /// But, not all civil datetimes are unambiguous. There are exactly two
894
    /// cases where a civil datetime can be ambiguous: when a civil datetime
895
    /// does not exist (a gap) or when a civil datetime is repeated (a fold).
896
    /// In both such cases, the _offset_ is the thing that is ambiguous as
897
    /// there are two possible choices for the offset in both cases: the offset
898
    /// before the transition (whether it's a gap or a fold) or the offset
899
    /// after the transition.
900
    ///
901
    /// This type captures the fact that computing an offset from a civil
902
    /// datetime in a particular time zone is in one of three possible states:
903
    ///
904
    /// 1. It is unambiguous.
905
    /// 2. It is ambiguous because there is a gap in time.
906
    /// 3. It is ambiguous because there is a fold in time.
907
    ///
908
    /// # Example
909
    ///
910
    /// ```
911
    /// use jiff::{civil::date, tz::{self, AmbiguousOffset}};
912
    ///
913
    /// let tz = tz::db().get("America/New_York")?;
914
    ///
915
    /// // Not ambiguous.
916
    /// let dt = date(2024, 7, 15).at(17, 30, 0, 0);
917
    /// let zdt = tz.to_ambiguous_zoned(dt);
918
    /// assert_eq!(zdt.offset(), AmbiguousOffset::Unambiguous {
919
    ///     offset: tz::offset(-4),
920
    /// });
921
    ///
922
    /// // Ambiguous because of a gap.
923
    /// let dt = date(2024, 3, 10).at(2, 30, 0, 0);
924
    /// let zdt = tz.to_ambiguous_zoned(dt);
925
    /// assert_eq!(zdt.offset(), AmbiguousOffset::Gap {
926
    ///     before: tz::offset(-5),
927
    ///     after: tz::offset(-4),
928
    /// });
929
    ///
930
    /// // Ambiguous because of a fold.
931
    /// let dt = date(2024, 11, 3).at(1, 30, 0, 0);
932
    /// let zdt = tz.to_ambiguous_zoned(dt);
933
    /// assert_eq!(zdt.offset(), AmbiguousOffset::Fold {
934
    ///     before: tz::offset(-4),
935
    ///     after: tz::offset(-5),
936
    /// });
937
    ///
938
    /// # Ok::<(), Box<dyn std::error::Error>>(())
939
    /// ```
940
    #[inline]
941
0
    pub fn offset(&self) -> AmbiguousOffset {
942
0
        self.ts.offset
943
0
    }
944
945
    /// Returns true if and only if this possibly ambiguous zoned datetime is
946
    /// actually ambiguous.
947
    ///
948
    /// This occurs precisely in cases when the offset is _not_
949
    /// [`AmbiguousOffset::Unambiguous`].
950
    ///
951
    /// # Example
952
    ///
953
    /// ```
954
    /// use jiff::{civil::date, tz};
955
    ///
956
    /// let tz = tz::db().get("America/New_York")?;
957
    ///
958
    /// // Not ambiguous.
959
    /// let dt = date(2024, 7, 15).at(17, 30, 0, 0);
960
    /// let zdt = tz.to_ambiguous_zoned(dt);
961
    /// assert!(!zdt.is_ambiguous());
962
    ///
963
    /// // Ambiguous because of a gap.
964
    /// let dt = date(2024, 3, 10).at(2, 30, 0, 0);
965
    /// let zdt = tz.to_ambiguous_zoned(dt);
966
    /// assert!(zdt.is_ambiguous());
967
    ///
968
    /// // Ambiguous because of a fold.
969
    /// let dt = date(2024, 11, 3).at(1, 30, 0, 0);
970
    /// let zdt = tz.to_ambiguous_zoned(dt);
971
    /// assert!(zdt.is_ambiguous());
972
    ///
973
    /// # Ok::<(), Box<dyn std::error::Error>>(())
974
    /// ```
975
    #[inline]
976
0
    pub fn is_ambiguous(&self) -> bool {
977
0
        !matches!(self.offset(), AmbiguousOffset::Unambiguous { .. })
978
0
    }
979
980
    /// Disambiguates this zoned datetime according to the
981
    /// [`Disambiguation::Compatible`] strategy.
982
    ///
983
    /// If this zoned datetime is unambiguous, then this is a no-op.
984
    ///
985
    /// The "compatible" strategy selects the offset corresponding to the civil
986
    /// time after a gap, and the offset corresponding to the civil time before
987
    /// a fold. This is what is specified in [RFC 5545].
988
    ///
989
    /// [RFC 5545]: https://datatracker.ietf.org/doc/html/rfc5545
990
    ///
991
    /// # Errors
992
    ///
993
    /// This returns an error when the combination of the civil datetime
994
    /// and offset would lead to a `Zoned` with a timestamp outside of the
995
    /// [`Timestamp::MIN`] and [`Timestamp::MAX`] limits. This only occurs
996
    /// when the civil datetime is "close" to its own [`DateTime::MIN`]
997
    /// and [`DateTime::MAX`] limits.
998
    ///
999
    /// # Example
1000
    ///
1001
    /// ```
1002
    /// use jiff::{civil::date, tz};
1003
    ///
1004
    /// let tz = tz::db().get("America/New_York")?;
1005
    ///
1006
    /// // Not ambiguous.
1007
    /// let dt = date(2024, 7, 15).at(17, 30, 0, 0);
1008
    /// let zdt = tz.to_ambiguous_zoned(dt);
1009
    /// assert_eq!(
1010
    ///     zdt.compatible()?.to_string(),
1011
    ///     "2024-07-15T17:30:00-04:00[America/New_York]",
1012
    /// );
1013
    ///
1014
    /// // Ambiguous because of a gap.
1015
    /// let dt = date(2024, 3, 10).at(2, 30, 0, 0);
1016
    /// let zdt = tz.to_ambiguous_zoned(dt);
1017
    /// assert_eq!(
1018
    ///     zdt.compatible()?.to_string(),
1019
    ///     "2024-03-10T03:30:00-04:00[America/New_York]",
1020
    /// );
1021
    ///
1022
    /// // Ambiguous because of a fold.
1023
    /// let dt = date(2024, 11, 3).at(1, 30, 0, 0);
1024
    /// let zdt = tz.to_ambiguous_zoned(dt);
1025
    /// assert_eq!(
1026
    ///     zdt.compatible()?.to_string(),
1027
    ///     "2024-11-03T01:30:00-04:00[America/New_York]",
1028
    /// );
1029
    ///
1030
    /// # Ok::<(), Box<dyn std::error::Error>>(())
1031
    /// ```
1032
    #[inline]
1033
0
    pub fn compatible(self) -> Result<Zoned, Error> {
1034
0
        let ts = self
1035
0
            .ts
1036
0
            .compatible()
1037
0
            .with_context(|| E::InTimeZone { tz: self.time_zone().clone() })?;
1038
0
        Ok(ts.to_zoned(self.tz))
1039
0
    }
1040
1041
    /// Disambiguates this zoned datetime according to the
1042
    /// [`Disambiguation::Earlier`] strategy.
1043
    ///
1044
    /// If this zoned datetime is unambiguous, then this is a no-op.
1045
    ///
1046
    /// The "earlier" strategy selects the offset corresponding to the civil
1047
    /// time before a gap, and the offset corresponding to the civil time
1048
    /// before a fold.
1049
    ///
1050
    /// # Errors
1051
    ///
1052
    /// This returns an error when the combination of the civil datetime
1053
    /// and offset would lead to a `Zoned` with a timestamp outside of the
1054
    /// [`Timestamp::MIN`] and [`Timestamp::MAX`] limits. This only occurs
1055
    /// when the civil datetime is "close" to its own [`DateTime::MIN`]
1056
    /// and [`DateTime::MAX`] limits.
1057
    ///
1058
    /// # Example
1059
    ///
1060
    /// ```
1061
    /// use jiff::{civil::date, tz};
1062
    ///
1063
    /// let tz = tz::db().get("America/New_York")?;
1064
    ///
1065
    /// // Not ambiguous.
1066
    /// let dt = date(2024, 7, 15).at(17, 30, 0, 0);
1067
    /// let zdt = tz.to_ambiguous_zoned(dt);
1068
    /// assert_eq!(
1069
    ///     zdt.earlier()?.to_string(),
1070
    ///     "2024-07-15T17:30:00-04:00[America/New_York]",
1071
    /// );
1072
    ///
1073
    /// // Ambiguous because of a gap.
1074
    /// let dt = date(2024, 3, 10).at(2, 30, 0, 0);
1075
    /// let zdt = tz.to_ambiguous_zoned(dt);
1076
    /// assert_eq!(
1077
    ///     zdt.earlier()?.to_string(),
1078
    ///     "2024-03-10T01:30:00-05:00[America/New_York]",
1079
    /// );
1080
    ///
1081
    /// // Ambiguous because of a fold.
1082
    /// let dt = date(2024, 11, 3).at(1, 30, 0, 0);
1083
    /// let zdt = tz.to_ambiguous_zoned(dt);
1084
    /// assert_eq!(
1085
    ///     zdt.earlier()?.to_string(),
1086
    ///     "2024-11-03T01:30:00-04:00[America/New_York]",
1087
    /// );
1088
    ///
1089
    /// # Ok::<(), Box<dyn std::error::Error>>(())
1090
    /// ```
1091
    #[inline]
1092
0
    pub fn earlier(self) -> Result<Zoned, Error> {
1093
0
        let ts = self
1094
0
            .ts
1095
0
            .earlier()
1096
0
            .with_context(|| E::InTimeZone { tz: self.time_zone().clone() })?;
1097
0
        Ok(ts.to_zoned(self.tz))
1098
0
    }
1099
1100
    /// Disambiguates this zoned datetime according to the
1101
    /// [`Disambiguation::Later`] strategy.
1102
    ///
1103
    /// If this zoned datetime is unambiguous, then this is a no-op.
1104
    ///
1105
    /// The "later" strategy selects the offset corresponding to the civil
1106
    /// time after a gap, and the offset corresponding to the civil time
1107
    /// after a fold.
1108
    ///
1109
    /// # Errors
1110
    ///
1111
    /// This returns an error when the combination of the civil datetime
1112
    /// and offset would lead to a `Zoned` with a timestamp outside of the
1113
    /// [`Timestamp::MIN`] and [`Timestamp::MAX`] limits. This only occurs
1114
    /// when the civil datetime is "close" to its own [`DateTime::MIN`]
1115
    /// and [`DateTime::MAX`] limits.
1116
    ///
1117
    /// # Example
1118
    ///
1119
    /// ```
1120
    /// use jiff::{civil::date, tz};
1121
    ///
1122
    /// let tz = tz::db().get("America/New_York")?;
1123
    ///
1124
    /// // Not ambiguous.
1125
    /// let dt = date(2024, 7, 15).at(17, 30, 0, 0);
1126
    /// let zdt = tz.to_ambiguous_zoned(dt);
1127
    /// assert_eq!(
1128
    ///     zdt.later()?.to_string(),
1129
    ///     "2024-07-15T17:30:00-04:00[America/New_York]",
1130
    /// );
1131
    ///
1132
    /// // Ambiguous because of a gap.
1133
    /// let dt = date(2024, 3, 10).at(2, 30, 0, 0);
1134
    /// let zdt = tz.to_ambiguous_zoned(dt);
1135
    /// assert_eq!(
1136
    ///     zdt.later()?.to_string(),
1137
    ///     "2024-03-10T03:30:00-04:00[America/New_York]",
1138
    /// );
1139
    ///
1140
    /// // Ambiguous because of a fold.
1141
    /// let dt = date(2024, 11, 3).at(1, 30, 0, 0);
1142
    /// let zdt = tz.to_ambiguous_zoned(dt);
1143
    /// assert_eq!(
1144
    ///     zdt.later()?.to_string(),
1145
    ///     "2024-11-03T01:30:00-05:00[America/New_York]",
1146
    /// );
1147
    ///
1148
    /// # Ok::<(), Box<dyn std::error::Error>>(())
1149
    /// ```
1150
    #[inline]
1151
0
    pub fn later(self) -> Result<Zoned, Error> {
1152
0
        let ts = self
1153
0
            .ts
1154
0
            .later()
1155
0
            .with_context(|| E::InTimeZone { tz: self.time_zone().clone() })?;
1156
0
        Ok(ts.to_zoned(self.tz))
1157
0
    }
1158
1159
    /// Disambiguates this zoned datetime according to the
1160
    /// [`Disambiguation::Reject`] strategy.
1161
    ///
1162
    /// If this zoned datetime is unambiguous, then this is a no-op.
1163
    ///
1164
    /// The "reject" strategy always returns an error when the zoned datetime
1165
    /// is ambiguous.
1166
    ///
1167
    /// # Errors
1168
    ///
1169
    /// This returns an error when the combination of the civil datetime
1170
    /// and offset would lead to a `Zoned` with a timestamp outside of the
1171
    /// [`Timestamp::MIN`] and [`Timestamp::MAX`] limits. This only occurs
1172
    /// when the civil datetime is "close" to its own [`DateTime::MIN`]
1173
    /// and [`DateTime::MAX`] limits.
1174
    ///
1175
    /// This also returns an error when the timestamp is ambiguous.
1176
    ///
1177
    /// # Example
1178
    ///
1179
    /// ```
1180
    /// use jiff::{civil::date, tz};
1181
    ///
1182
    /// let tz = tz::db().get("America/New_York")?;
1183
    ///
1184
    /// // Not ambiguous.
1185
    /// let dt = date(2024, 7, 15).at(17, 30, 0, 0);
1186
    /// let zdt = tz.to_ambiguous_zoned(dt);
1187
    /// assert_eq!(
1188
    ///     zdt.later()?.to_string(),
1189
    ///     "2024-07-15T17:30:00-04:00[America/New_York]",
1190
    /// );
1191
    ///
1192
    /// // Ambiguous because of a gap.
1193
    /// let dt = date(2024, 3, 10).at(2, 30, 0, 0);
1194
    /// let zdt = tz.to_ambiguous_zoned(dt);
1195
    /// assert!(zdt.unambiguous().is_err());
1196
    ///
1197
    /// // Ambiguous because of a fold.
1198
    /// let dt = date(2024, 11, 3).at(1, 30, 0, 0);
1199
    /// let zdt = tz.to_ambiguous_zoned(dt);
1200
    /// assert!(zdt.unambiguous().is_err());
1201
    ///
1202
    /// # Ok::<(), Box<dyn std::error::Error>>(())
1203
    /// ```
1204
    #[inline]
1205
0
    pub fn unambiguous(self) -> Result<Zoned, Error> {
1206
0
        let ts = self
1207
0
            .ts
1208
0
            .unambiguous()
1209
0
            .with_context(|| E::InTimeZone { tz: self.time_zone().clone() })?;
1210
0
        Ok(ts.to_zoned(self.tz))
1211
0
    }
1212
1213
    /// Disambiguates this (possibly ambiguous) timestamp into a concrete
1214
    /// time zone aware timestamp.
1215
    ///
1216
    /// This is the same as calling one of the disambiguation methods, but
1217
    /// the method chosen is indicated by the option given. This is useful
1218
    /// when the disambiguation option needs to be chosen at runtime.
1219
    ///
1220
    /// # Errors
1221
    ///
1222
    /// This returns an error if this would have returned a zoned datetime
1223
    /// outside of its minimum and maximum values.
1224
    ///
1225
    /// This can also return an error when using the [`Disambiguation::Reject`]
1226
    /// strategy. Namely, when using the `Reject` strategy, any ambiguous
1227
    /// timestamp always results in an error.
1228
    ///
1229
    /// # Example
1230
    ///
1231
    /// This example shows the various disambiguation modes when given a
1232
    /// datetime that falls in a "fold" (i.e., a backwards DST transition).
1233
    ///
1234
    /// ```
1235
    /// use jiff::{civil::date, tz::{self, Disambiguation}};
1236
    ///
1237
    /// let newyork = tz::db().get("America/New_York")?;
1238
    /// let dt = date(2024, 11, 3).at(1, 30, 0, 0);
1239
    /// let ambiguous = newyork.to_ambiguous_zoned(dt);
1240
    ///
1241
    /// // In compatible mode, backward transitions select the earlier
1242
    /// // time. In the EDT->EST transition, that's the -04 (EDT) offset.
1243
    /// let zdt = ambiguous.clone().disambiguate(Disambiguation::Compatible)?;
1244
    /// assert_eq!(
1245
    ///     zdt.to_string(),
1246
    ///     "2024-11-03T01:30:00-04:00[America/New_York]",
1247
    /// );
1248
    ///
1249
    /// // The earlier time in the EDT->EST transition is the -04 (EDT) offset.
1250
    /// let zdt = ambiguous.clone().disambiguate(Disambiguation::Earlier)?;
1251
    /// assert_eq!(
1252
    ///     zdt.to_string(),
1253
    ///     "2024-11-03T01:30:00-04:00[America/New_York]",
1254
    /// );
1255
    ///
1256
    /// // The later time in the EDT->EST transition is the -05 (EST) offset.
1257
    /// let zdt = ambiguous.clone().disambiguate(Disambiguation::Later)?;
1258
    /// assert_eq!(
1259
    ///     zdt.to_string(),
1260
    ///     "2024-11-03T01:30:00-05:00[America/New_York]",
1261
    /// );
1262
    ///
1263
    /// // Since our datetime is ambiguous, the 'reject' strategy errors.
1264
    /// assert!(ambiguous.disambiguate(Disambiguation::Reject).is_err());
1265
    ///
1266
    /// # Ok::<(), Box<dyn std::error::Error>>(())
1267
    /// ```
1268
    #[inline]
1269
0
    pub fn disambiguate(self, option: Disambiguation) -> Result<Zoned, Error> {
1270
0
        match option {
1271
0
            Disambiguation::Compatible => self.compatible(),
1272
0
            Disambiguation::Earlier => self.earlier(),
1273
0
            Disambiguation::Later => self.later(),
1274
0
            Disambiguation::Reject => self.unambiguous(),
1275
        }
1276
0
    }
1277
}