Coverage Report

Created: 2026-01-16 06:52

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/jiff-0.2.18/src/tz/timezone.rs
Line
Count
Source
1
use crate::{
2
    civil::DateTime,
3
    error::{tz::timezone::Error as E, Error},
4
    tz::{
5
        ambiguous::{AmbiguousOffset, AmbiguousTimestamp, AmbiguousZoned},
6
        offset::{Dst, Offset},
7
    },
8
    util::{array_str::ArrayStr, sync::Arc},
9
    Timestamp, Zoned,
10
};
11
12
use crate::tz::posix::PosixTimeZoneOwned;
13
14
use self::repr::Repr;
15
16
/// A representation of a [time zone].
17
///
18
/// A time zone is a set of rules for determining the civil time, via an offset
19
/// from UTC, in a particular geographic region. In many cases, the offset
20
/// in a particular time zone can vary over the course of a year through
21
/// transitions into and out of [daylight saving time].
22
///
23
/// A `TimeZone` can be one of three possible representations:
24
///
25
/// * An identifier from the [IANA Time Zone Database] and the rules associated
26
/// with that identifier.
27
/// * A fixed offset where there are never any time zone transitions.
28
/// * A [POSIX TZ] string that specifies a standard offset and an optional
29
/// daylight saving time offset along with a rule for when DST is in effect.
30
/// The rule applies for every year. Since POSIX TZ strings cannot capture the
31
/// full complexity of time zone rules, they generally should not be used.
32
///
33
/// The most practical and useful representation is an IANA time zone. Namely,
34
/// it enjoys broad support and its database is regularly updated to reflect
35
/// real changes in time zone rules throughout the world. On Unix systems,
36
/// the time zone database is typically found at `/usr/share/zoneinfo`. For
37
/// more information on how Jiff interacts with The Time Zone Database, see
38
/// [`TimeZoneDatabase`](crate::tz::TimeZoneDatabase).
39
///
40
/// In typical usage, users of Jiff shouldn't need to reference a `TimeZone`
41
/// directly. Instead, there are convenience APIs on datetime types that accept
42
/// IANA time zone identifiers and do automatic database lookups for you. For
43
/// example, to convert a timestamp to a zone aware datetime:
44
///
45
/// ```
46
/// use jiff::Timestamp;
47
///
48
/// let ts = Timestamp::from_second(1_456_789_123)?;
49
/// let zdt = ts.in_tz("America/New_York")?;
50
/// assert_eq!(zdt.to_string(), "2016-02-29T18:38:43-05:00[America/New_York]");
51
///
52
/// # Ok::<(), Box<dyn std::error::Error>>(())
53
/// ```
54
///
55
/// Or to convert a civil datetime to a zoned datetime corresponding to a
56
/// precise instant in time:
57
///
58
/// ```
59
/// use jiff::civil::date;
60
///
61
/// let dt = date(2024, 7, 15).at(21, 27, 0, 0);
62
/// let zdt = dt.in_tz("America/New_York")?;
63
/// assert_eq!(zdt.to_string(), "2024-07-15T21:27:00-04:00[America/New_York]");
64
///
65
/// # Ok::<(), Box<dyn std::error::Error>>(())
66
/// ```
67
///
68
/// Or even converted a zoned datetime from one time zone to another:
69
///
70
/// ```
71
/// use jiff::civil::date;
72
///
73
/// let dt = date(2024, 7, 15).at(21, 27, 0, 0);
74
/// let zdt1 = dt.in_tz("America/New_York")?;
75
/// let zdt2 = zdt1.in_tz("Israel")?;
76
/// assert_eq!(zdt2.to_string(), "2024-07-16T04:27:00+03:00[Israel]");
77
///
78
/// # Ok::<(), Box<dyn std::error::Error>>(())
79
/// ```
80
///
81
/// # The system time zone
82
///
83
/// The system time zone can be retrieved via [`TimeZone::system`]. If it
84
/// couldn't be detected or if the `tz-system` crate feature is not enabled,
85
/// then [`TimeZone::unknown`] is returned. `TimeZone::system` is what's used
86
/// internally for retrieving the current zoned datetime via [`Zoned::now`].
87
///
88
/// While there is no platform independent way to detect your system's
89
/// "default" time zone, Jiff employs best-effort heuristics to determine it.
90
/// (For example, by examining `/etc/localtime` on Unix systems or the `TZ`
91
/// environment variable.) When the heuristics fail, Jiff will emit a `WARN`
92
/// level log. It can be viewed by installing a `log` compatible logger, such
93
/// as [`env_logger`].
94
///
95
/// # Custom time zones
96
///
97
/// At present, Jiff doesn't provide any APIs for manually constructing a
98
/// custom time zone. However, [`TimeZone::tzif`] is provided for reading
99
/// any valid TZif formatted data, as specified by [RFC 8536]. This provides
100
/// an interoperable way of utilizing custom time zone rules.
101
///
102
/// # A `TimeZone` is immutable
103
///
104
/// Once a `TimeZone` is created, it is immutable. That is, its underlying
105
/// time zone transition rules will never change. This is true for system time
106
/// zones or even if the IANA Time Zone Database it was loaded from changes on
107
/// disk. The only way such changes can be observed is by re-requesting the
108
/// `TimeZone` from a `TimeZoneDatabase`. (Or, in the case of the system time
109
/// zone, by calling `TimeZone::system`.)
110
///
111
/// # A `TimeZone` is cheap to clone
112
///
113
/// A `TimeZone` can be cheaply cloned. It uses automatic reference counting
114
/// internally. When `alloc` is disabled, cloning a `TimeZone` is still cheap
115
/// because POSIX time zones and TZif time zones are unsupported. Therefore,
116
/// cloning a time zone does a deep copy (since automatic reference counting is
117
/// not available), but the data being copied is small.
118
///
119
/// # Time zone equality
120
///
121
/// `TimeZone` provides an imperfect notion of equality. That is, when two time
122
/// zones are equal, then it is guaranteed for them to have the same rules.
123
/// However, two time zones may compare unequal and yet still have the same
124
/// rules.
125
///
126
/// The equality semantics are as follows:
127
///
128
/// * Two fixed offset time zones are equal when their offsets are equal.
129
/// * Two POSIX time zones are equal when their original rule strings are
130
/// byte-for-byte identical.
131
/// * Two IANA time zones are equal when their identifiers are equal _and_
132
/// checksums of their rules are equal.
133
/// * In all other cases, time zones are unequal.
134
///
135
/// Time zone equality is, for example, used in APIs like [`Zoned::since`]
136
/// when asking for spans with calendar units. Namely, since days can be of
137
/// different lengths in different time zones, `Zoned::since` will return an
138
/// error when the two zoned datetimes are in different time zones and when
139
/// the caller requests units greater than hours.
140
///
141
/// # Dealing with ambiguity
142
///
143
/// The principal job of a `TimeZone` is to provide two different
144
/// transformations:
145
///
146
/// * A conversion from a [`Timestamp`] to a civil time (also known as local,
147
/// naive or plain time). This conversion is always unambiguous. That is,
148
/// there is always precisely one representation of civil time for any
149
/// particular instant in time for a particular time zone.
150
/// * A conversion from a [`civil::DateTime`](crate::civil::DateTime) to an
151
/// instant in time. This conversion is sometimes ambiguous in that a civil
152
/// time might have either never appear on the clocks in a particular
153
/// time zone (a gap), or in that the civil time may have been repeated on the
154
/// clocks in a particular time zone (a fold). Typically, a transition to
155
/// daylight saving time is a gap, while a transition out of daylight saving
156
/// time is a fold.
157
///
158
/// The timestamp-to-civil time conversion is done via
159
/// [`TimeZone::to_datetime`], or its lower level counterpart,
160
/// [`TimeZone::to_offset`]. The civil time-to-timestamp conversion is done
161
/// via one of the following routines:
162
///
163
/// * [`TimeZone::to_zoned`] conveniently returns a [`Zoned`] and automatically
164
/// uses the
165
/// [`Disambiguation::Compatible`](crate::tz::Disambiguation::Compatible)
166
/// strategy if the given civil datetime is ambiguous in the time zone.
167
/// * [`TimeZone::to_ambiguous_zoned`] returns a potentially ambiguous
168
/// zoned datetime, [`AmbiguousZoned`], and provides fine-grained control over
169
/// how to resolve ambiguity, if it occurs.
170
/// * [`TimeZone::to_timestamp`] is like `TimeZone::to_zoned`, but returns
171
/// a [`Timestamp`] instead.
172
/// * [`TimeZone::to_ambiguous_timestamp`] is like
173
/// `TimeZone::to_ambiguous_zoned`, but returns an [`AmbiguousTimestamp`]
174
/// instead.
175
///
176
/// Here is an example where we explore the different disambiguation strategies
177
/// for a fold in time, where in this case, the 1 o'clock hour is repeated:
178
///
179
/// ```
180
/// use jiff::{civil::date, tz::TimeZone};
181
///
182
/// let tz = TimeZone::get("America/New_York")?;
183
/// let dt = date(2024, 11, 3).at(1, 30, 0, 0);
184
/// // It's ambiguous, so asking for an unambiguous instant presents an error!
185
/// assert!(tz.to_ambiguous_zoned(dt).unambiguous().is_err());
186
/// // Gives you the earlier time in a fold, i.e., before DST ends:
187
/// assert_eq!(
188
///     tz.to_ambiguous_zoned(dt).earlier()?.to_string(),
189
///     "2024-11-03T01:30:00-04:00[America/New_York]",
190
/// );
191
/// // Gives you the later time in a fold, i.e., after DST ends.
192
/// // Notice the offset change from the previous example!
193
/// assert_eq!(
194
///     tz.to_ambiguous_zoned(dt).later()?.to_string(),
195
///     "2024-11-03T01:30:00-05:00[America/New_York]",
196
/// );
197
/// // "Just give me something reasonable"
198
/// assert_eq!(
199
///     tz.to_ambiguous_zoned(dt).compatible()?.to_string(),
200
///     "2024-11-03T01:30:00-04:00[America/New_York]",
201
/// );
202
///
203
/// # Ok::<(), Box<dyn std::error::Error>>(())
204
/// ```
205
///
206
/// # Serde integration
207
///
208
/// At present, a `TimeZone` does not implement Serde's `Serialize` or
209
/// `Deserialize` traits directly. Nor does it implement `std::fmt::Display`
210
/// or `std::str::FromStr`. The reason for this is that it's not totally
211
/// clear if there is one single obvious behavior. Moreover, some `TimeZone`
212
/// values do not have an obvious succinct serialized representation. (For
213
/// example, when `/etc/localtime` on a Unix system is your system's time zone,
214
/// and it isn't a symlink to a TZif file in `/usr/share/zoneinfo`. In which
215
/// case, an IANA time zone identifier cannot easily be deduced by Jiff.)
216
///
217
/// Instead, Jiff offers helpers for use with Serde's [`with` attribute] via
218
/// the [`fmt::serde`](crate::fmt::serde) module:
219
///
220
/// ```
221
/// use jiff::tz::TimeZone;
222
///
223
/// #[derive(Debug, serde::Deserialize, serde::Serialize)]
224
/// struct Record {
225
///     #[serde(with = "jiff::fmt::serde::tz::optional")]
226
///     tz: Option<TimeZone>,
227
/// }
228
///
229
/// let json = r#"{"tz":"America/Nuuk"}"#;
230
/// let got: Record = serde_json::from_str(&json)?;
231
/// assert_eq!(got.tz, Some(TimeZone::get("America/Nuuk")?));
232
/// assert_eq!(serde_json::to_string(&got)?, json);
233
///
234
/// # Ok::<(), Box<dyn std::error::Error>>(())
235
/// ```
236
///
237
/// Alternatively, you may use the
238
/// [`fmt::temporal::DateTimeParser::parse_time_zone`](crate::fmt::temporal::DateTimeParser::parse_time_zone)
239
/// or
240
/// [`fmt::temporal::DateTimePrinter::print_time_zone`](crate::fmt::temporal::DateTimePrinter::print_time_zone)
241
/// routines to parse or print `TimeZone` values without using Serde.
242
///
243
/// [time zone]: https://en.wikipedia.org/wiki/Time_zone
244
/// [daylight saving time]: https://en.wikipedia.org/wiki/Daylight_saving_time
245
/// [IANA Time Zone Database]: https://en.wikipedia.org/wiki/Tz_database
246
/// [POSIX TZ]: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html
247
/// [`env_logger`]: https://docs.rs/env_logger
248
/// [RFC 8536]: https://datatracker.ietf.org/doc/html/rfc8536
249
/// [`with` attribute]: https://serde.rs/field-attrs.html#with
250
#[derive(Clone, Eq, PartialEq)]
251
pub struct TimeZone {
252
    repr: Repr,
253
}
254
255
impl TimeZone {
256
    /// The UTC time zone.
257
    ///
258
    /// The offset of this time is `0` and never has any transitions.
259
    pub const UTC: TimeZone = TimeZone { repr: Repr::utc() };
260
261
    /// Returns the system configured time zone, if available.
262
    ///
263
    /// Detection of a system's default time zone is generally heuristic
264
    /// based and platform specific.
265
    ///
266
    /// If callers need to know whether discovery of the system time zone
267
    /// failed, then use [`TimeZone::try_system`].
268
    ///
269
    /// # Fallback behavior
270
    ///
271
    /// If the system's default time zone could not be determined, or if
272
    /// the `tz-system` crate feature is not enabled, then this returns
273
    /// [`TimeZone::unknown`]. A `WARN` level log will also be emitted with
274
    /// a message explaining why time zone detection failed. The fallback to
275
    /// an unknown time zone is a practical trade-off, is what most other
276
    /// systems tend to do and is also recommended by [relevant standards such
277
    /// as freedesktop.org][freedesktop-org-localtime].
278
    ///
279
    /// An unknown time zone _behaves_ like [`TimeZone::UTC`], but will
280
    /// print as `Etc/Unknown` when converting a `Zoned` to a string.
281
    ///
282
    /// If you would like to fall back to UTC instead of
283
    /// the special "unknown" time zone, then you can do
284
    /// `TimeZone::try_system().unwrap_or(TimeZone::UTC)`.
285
    ///
286
    /// # Platform behavior
287
    ///
288
    /// This section is a "best effort" explanation of how the time zone is
289
    /// detected on supported platforms. The behavior is subject to change.
290
    ///
291
    /// On all platforms, the `TZ` environment variable overrides any other
292
    /// heuristic, and provides a way for end users to set the time zone for
293
    /// specific use cases. In general, Jiff respects the [POSIX TZ] rules.
294
    /// Here are some examples:
295
    ///
296
    /// * `TZ=America/New_York` for setting a time zone via an IANA Time Zone
297
    /// Database Identifier.
298
    /// * `TZ=/usr/share/zoneinfo/America/New_York` for setting a time zone
299
    /// by providing a file path to a TZif file directly.
300
    /// * `TZ=EST5EDT,M3.2.0,M11.1.0` for setting a time zone via a daylight
301
    /// saving time transition rule.
302
    ///
303
    /// When `TZ` is set to an invalid value, Jiff uses the fallback behavior
304
    /// described above.
305
    ///
306
    /// Otherwise, when `TZ` isn't set, then:
307
    ///
308
    /// On Unix non-Android systems, this inspects `/etc/localtime`. If it's
309
    /// a symbolic link to an entry in `/usr/share/zoneinfo`, then the suffix
310
    /// is considered an IANA Time Zone Database identifier. Otherwise,
311
    /// `/etc/localtime` is read as a TZif file directly.
312
    ///
313
    /// On Android systems, this inspects the `persist.sys.timezone` property.
314
    ///
315
    /// On Windows, the system time zone is determined via
316
    /// [`GetDynamicTimeZoneInformation`]. The result is then mapped to an
317
    /// IANA Time Zone Database identifier via Unicode's
318
    /// [CLDR XML data].
319
    ///
320
    /// [freedesktop-org-localtime]: https://www.freedesktop.org/software/systemd/man/latest/localtime.html
321
    /// [POSIX TZ]: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html
322
    /// [`GetDynamicTimeZoneInformation`]: https://learn.microsoft.com/en-us/windows/win32/api/timezoneapi/nf-timezoneapi-getdynamictimezoneinformation
323
    /// [CLDR XML data]: https://github.com/unicode-org/cldr/raw/main/common/supplemental/windowsZones.xml
324
    #[inline]
325
0
    pub fn system() -> TimeZone {
326
0
        match TimeZone::try_system() {
327
0
            Ok(tz) => tz,
328
0
            Err(_err) => {
329
                warn!(
330
                    "failed to get system time zone, \
331
                     falling back to `Etc/Unknown` \
332
                     (which behaves like UTC): {_err}",
333
                );
334
0
                TimeZone::unknown()
335
            }
336
        }
337
0
    }
338
339
    /// Returns the system configured time zone, if available.
340
    ///
341
    /// If the system's default time zone could not be determined, or if the
342
    /// `tz-system` crate feature is not enabled, then this returns an error.
343
    ///
344
    /// Detection of a system's default time zone is generally heuristic
345
    /// based and platform specific.
346
    ///
347
    /// Note that callers should generally prefer using [`TimeZone::system`].
348
    /// If a system time zone could not be found, then it falls
349
    /// back to [`TimeZone::UTC`] automatically. This is often
350
    /// what is recommended by [relevant standards such as
351
    /// freedesktop.org][freedesktop-org-localtime]. Conversely, this routine
352
    /// is useful if detection of a system's default time zone is critical.
353
    ///
354
    /// # Platform behavior
355
    ///
356
    /// This section is a "best effort" explanation of how the time zone is
357
    /// detected on supported platforms. The behavior is subject to change.
358
    ///
359
    /// On all platforms, the `TZ` environment variable overrides any other
360
    /// heuristic, and provides a way for end users to set the time zone for
361
    /// specific use cases. In general, Jiff respects the [POSIX TZ] rules.
362
    /// Here are some examples:
363
    ///
364
    /// * `TZ=America/New_York` for setting a time zone via an IANA Time Zone
365
    /// Database Identifier.
366
    /// * `TZ=/usr/share/zoneinfo/America/New_York` for setting a time zone
367
    /// by providing a file path to a TZif file directly.
368
    /// * `TZ=EST5EDT,M3.2.0,M11.1.0` for setting a time zone via a daylight
369
    /// saving time transition rule.
370
    ///
371
    /// When `TZ` is set to an invalid value, then this routine returns an
372
    /// error.
373
    ///
374
    /// Otherwise, when `TZ` isn't set, then:
375
    ///
376
    /// On Unix systems, this inspects `/etc/localtime`. If it's a symbolic
377
    /// link to an entry in `/usr/share/zoneinfo`, then the suffix is
378
    /// considered an IANA Time Zone Database identifier. Otherwise,
379
    /// `/etc/localtime` is read as a TZif file directly.
380
    ///
381
    /// On Windows, the system time zone is determined via
382
    /// [`GetDynamicTimeZoneInformation`]. The result is then mapped to an
383
    /// IANA Time Zone Database identifier via Unicode's
384
    /// [CLDR XML data].
385
    ///
386
    /// [freedesktop-org-localtime]: https://www.freedesktop.org/software/systemd/man/latest/localtime.html
387
    /// [POSIX TZ]: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html
388
    /// [`GetDynamicTimeZoneInformation`]: https://learn.microsoft.com/en-us/windows/win32/api/timezoneapi/nf-timezoneapi-getdynamictimezoneinformation
389
    /// [CLDR XML data]: https://github.com/unicode-org/cldr/raw/main/common/supplemental/windowsZones.xml
390
    #[inline]
391
0
    pub fn try_system() -> Result<TimeZone, Error> {
392
        #[cfg(not(feature = "tz-system"))]
393
        {
394
            Err(Error::from(crate::error::CrateFeatureError::TzSystem)
395
                .context(E::FailedSystem))
396
        }
397
        #[cfg(feature = "tz-system")]
398
        {
399
0
            crate::tz::system::get(crate::tz::db())
400
        }
401
0
    }
402
403
    /// A convenience function for performing a time zone database lookup for
404
    /// the given time zone identifier. It uses the default global time zone
405
    /// database via [`tz::db()`](crate::tz::db()).
406
    ///
407
    /// It is guaranteed that if the given time zone name is case insensitively
408
    /// equivalent to `UTC`, then the time zone returned will be equivalent to
409
    /// `TimeZone::UTC`. Similarly for `Etc/Unknown` and `TimeZone::unknown()`.
410
    ///
411
    /// # Errors
412
    ///
413
    /// This returns an error if the given time zone identifier could not be
414
    /// found in the default [`TimeZoneDatabase`](crate::tz::TimeZoneDatabase).
415
    ///
416
    /// # Example
417
    ///
418
    /// ```
419
    /// use jiff::{tz::TimeZone, Timestamp};
420
    ///
421
    /// let tz = TimeZone::get("Japan")?;
422
    /// assert_eq!(
423
    ///     tz.to_datetime(Timestamp::UNIX_EPOCH).to_string(),
424
    ///     "1970-01-01T09:00:00",
425
    /// );
426
    ///
427
    /// # Ok::<(), Box<dyn std::error::Error>>(())
428
    /// ```
429
    #[inline]
430
0
    pub fn get(time_zone_name: &str) -> Result<TimeZone, Error> {
431
0
        crate::tz::db().get(time_zone_name)
432
0
    }
433
434
    /// Returns a time zone with a fixed offset.
435
    ///
436
    /// A fixed offset will never have any transitions and won't follow any
437
    /// particular time zone rules. In general, one should avoid using fixed
438
    /// offset time zones unless you have a specific need for them. Otherwise,
439
    /// IANA time zones via [`TimeZone::get`] should be preferred, as they
440
    /// more accurately model the actual time zone transitions rules used in
441
    /// practice.
442
    ///
443
    /// # Example
444
    ///
445
    /// ```
446
    /// use jiff::{tz::{self, TimeZone}, Timestamp};
447
    ///
448
    /// let tz = TimeZone::fixed(tz::offset(10));
449
    /// assert_eq!(
450
    ///     tz.to_datetime(Timestamp::UNIX_EPOCH).to_string(),
451
    ///     "1970-01-01T10:00:00",
452
    /// );
453
    ///
454
    /// # Ok::<(), Box<dyn std::error::Error>>(())
455
    /// ```
456
    #[inline]
457
2.34k
    pub const fn fixed(offset: Offset) -> TimeZone {
458
        // Not doing `offset == Offset::UTC` because of `const`.
459
2.34k
        if offset.seconds_ranged().get_unchecked() == 0 {
460
1.34k
            return TimeZone::UTC;
461
993
        }
462
993
        let repr = Repr::fixed(offset);
463
993
        TimeZone { repr }
464
2.34k
    }
465
466
    /// Creates a time zone from a [POSIX TZ] rule string.
467
    ///
468
    /// A POSIX time zone provides a way to tersely define a single daylight
469
    /// saving time transition rule (or none at all) that applies for all
470
    /// years.
471
    ///
472
    /// Users should avoid using this kind of time zone unless there is a
473
    /// specific need for it. Namely, POSIX time zones cannot capture the full
474
    /// complexity of time zone transition rules in the real world. (See the
475
    /// example below.)
476
    ///
477
    /// [POSIX TZ]: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html
478
    ///
479
    /// # Errors
480
    ///
481
    /// This returns an error if the given POSIX time zone string is invalid.
482
    ///
483
    /// # Example
484
    ///
485
    /// This example demonstrates how a POSIX time zone may be historically
486
    /// inaccurate:
487
    ///
488
    /// ```
489
    /// use jiff::{civil::date, tz::TimeZone};
490
    ///
491
    /// // The tzdb entry for America/New_York.
492
    /// let iana = TimeZone::get("America/New_York")?;
493
    /// // The POSIX TZ string for New York DST that went into effect in 2007.
494
    /// let posix = TimeZone::posix("EST5EDT,M3.2.0,M11.1.0")?;
495
    ///
496
    /// // New York entered DST on April 2, 2006 at 2am:
497
    /// let dt = date(2006, 4, 2).at(2, 0, 0, 0);
498
    /// // The IANA tzdb entry correctly reports it as ambiguous:
499
    /// assert!(iana.to_ambiguous_timestamp(dt).is_ambiguous());
500
    /// // But the POSIX time zone does not:
501
    /// assert!(!posix.to_ambiguous_timestamp(dt).is_ambiguous());
502
    ///
503
    /// # Ok::<(), Box<dyn std::error::Error>>(())
504
    /// ```
505
    #[cfg(feature = "alloc")]
506
0
    pub fn posix(posix_tz_string: &str) -> Result<TimeZone, Error> {
507
0
        let posix_tz = PosixTimeZoneOwned::parse(posix_tz_string)?;
508
0
        Ok(TimeZone::from_posix_tz(posix_tz))
509
0
    }
510
511
    /// Creates a time zone from a POSIX tz. Expose so that other parts of Jiff
512
    /// can create a `TimeZone` from a POSIX tz. (Kinda sloppy to be honest.)
513
    #[cfg(feature = "alloc")]
514
0
    pub(crate) fn from_posix_tz(posix: PosixTimeZoneOwned) -> TimeZone {
515
0
        let repr = Repr::arc_posix(Arc::new(posix));
516
0
        TimeZone { repr }
517
0
    }
518
519
    /// Creates a time zone from TZif binary data, whose format is specified
520
    /// in [RFC 8536]. All versions of TZif (up through version 4) are
521
    /// supported.
522
    ///
523
    /// This constructor is typically not used, and instead, one should rely
524
    /// on time zone lookups via time zone identifiers with routines like
525
    /// [`TimeZone::get`]. However, this constructor does provide one way
526
    /// of using custom time zones with Jiff.
527
    ///
528
    /// The name given should be a IANA time zone database identifier.
529
    ///
530
    /// [RFC 8536]: https://datatracker.ietf.org/doc/html/rfc8536
531
    ///
532
    /// # Errors
533
    ///
534
    /// This returns an error if the given data was not recognized as valid
535
    /// TZif.
536
    #[cfg(feature = "alloc")]
537
0
    pub fn tzif(name: &str, data: &[u8]) -> Result<TimeZone, Error> {
538
        use alloc::string::ToString;
539
540
0
        let name = name.to_string();
541
0
        let tzif = crate::tz::tzif::Tzif::parse(Some(name), data)?;
542
0
        let repr = Repr::arc_tzif(Arc::new(tzif));
543
0
        Ok(TimeZone { repr })
544
0
    }
545
546
    /// Returns a `TimeZone` that is specifically marked as "unknown."
547
    ///
548
    /// This corresponds to the Unicode CLDR identifier `Etc/Unknown`, which
549
    /// is guaranteed to never be a valid IANA time zone identifier (as of
550
    /// the `2025a` release of tzdb).
551
    ///
552
    /// This type of `TimeZone` is used in circumstances where one wants to
553
    /// signal that discovering a time zone failed for some reason, but that
554
    /// execution can reasonably continue. For example, [`TimeZone::system`]
555
    /// returns this type of time zone when the system time zone could not be
556
    /// discovered.
557
    ///
558
    /// # Example
559
    ///
560
    /// Jiff permits an "unknown" time zone to losslessly be transmitted
561
    /// through serialization:
562
    ///
563
    /// ```
564
    /// use jiff::{civil::date, tz::TimeZone, Zoned};
565
    ///
566
    /// let tz = TimeZone::unknown();
567
    /// let zdt = date(2025, 2, 1).at(17, 0, 0, 0).to_zoned(tz)?;
568
    /// assert_eq!(zdt.to_string(), "2025-02-01T17:00:00Z[Etc/Unknown]");
569
    /// let got: Zoned = "2025-02-01T17:00:00Z[Etc/Unknown]".parse()?;
570
    /// assert_eq!(got, zdt);
571
    ///
572
    /// # Ok::<(), Box<dyn std::error::Error>>(())
573
    /// ```
574
    ///
575
    /// Note that not all systems support this. Some systems will reject
576
    /// `Etc/Unknown` because it is not a valid IANA time zone identifier and
577
    /// does not have an entry in the IANA time zone database. However, Jiff
578
    /// takes this approach because it surfaces an error condition in detecting
579
    /// the end user's time zone. Callers not wanting an "unknown" time zone
580
    /// can use `TimeZone::try_system().unwrap_or(TimeZone::UTC)` instead of
581
    /// `TimeZone::system`. (Where the latter falls back to the "unknown" time
582
    /// zone when a system configured time zone could not be found.)
583
0
    pub const fn unknown() -> TimeZone {
584
0
        let repr = Repr::unknown();
585
0
        TimeZone { repr }
586
0
    }
587
588
    /// This creates an unnamed TZif-backed `TimeZone`.
589
    ///
590
    /// At present, the only way for an unnamed TZif-backed `TimeZone` to be
591
    /// created is when the system time zone has no identifiable name. For
592
    /// example, when `/etc/localtime` is hard-linked to a TZif file instead
593
    /// of being symlinked. In this case, there is no cheap and unambiguous
594
    /// way to determine the time zone name. So we just let it be unnamed.
595
    /// Since this is the only such case, and hopefully will only ever be the
596
    /// only such case, we consider such unnamed TZif-back `TimeZone` values
597
    /// as being the "system" time zone.
598
    ///
599
    /// When this is used to construct a `TimeZone`, the `TimeZone::name`
600
    /// method will be "Local". This is... pretty unfortunate. I'm not sure
601
    /// what else to do other than to make `TimeZone::name` return an
602
    /// `Option<&str>`. But... we use it in a bunch of places and it just
603
    /// seems bad for a time zone to not have a name.
604
    ///
605
    /// OK, because of the above, I renamed `TimeZone::name` to
606
    /// `TimeZone::diagnostic_name`. This should make it clearer that you can't
607
    /// really use the name to do anything interesting. This also makes more
608
    /// sense for POSIX TZ strings too.
609
    ///
610
    /// In any case, this routine stays unexported because I don't want TZif
611
    /// backed `TimeZone` values to proliferate. If you have a legitimate use
612
    /// case otherwise, please file an issue. It will require API design.
613
    ///
614
    /// # Errors
615
    ///
616
    /// This returns an error if the given TZif data is invalid.
617
    #[cfg(feature = "tz-system")]
618
0
    pub(crate) fn tzif_system(data: &[u8]) -> Result<TimeZone, Error> {
619
0
        let tzif = crate::tz::tzif::Tzif::parse(None, data)?;
620
0
        let repr = Repr::arc_tzif(Arc::new(tzif));
621
0
        Ok(TimeZone { repr })
622
0
    }
623
624
    #[inline]
625
0
    pub(crate) fn diagnostic_name(&self) -> DiagnosticName<'_> {
626
0
        DiagnosticName(self)
627
0
    }
628
629
    /// Returns true if and only if this `TimeZone` can be succinctly
630
    /// serialized.
631
    ///
632
    /// Basically, this is only `false` when this `TimeZone` was created from
633
    /// a `/etc/localtime` for which a valid IANA time zone identifier could
634
    /// not be extracted.
635
    #[cfg(feature = "serde")]
636
    #[inline]
637
    pub(crate) fn has_succinct_serialization(&self) -> bool {
638
        repr::each! {
639
            &self.repr,
640
            UTC => true,
641
            UNKNOWN => true,
642
            FIXED(_offset) => true,
643
            STATIC_TZIF(tzif) => tzif.name().is_some(),
644
            ARC_TZIF(tzif) => tzif.name().is_some(),
645
            ARC_POSIX(_posix) => true,
646
        }
647
    }
648
649
    /// When this time zone was loaded from an IANA time zone database entry,
650
    /// then this returns the canonicalized name for that time zone.
651
    ///
652
    /// # Example
653
    ///
654
    /// ```
655
    /// use jiff::tz::TimeZone;
656
    ///
657
    /// let tz = TimeZone::get("america/NEW_YORK")?;
658
    /// assert_eq!(tz.iana_name(), Some("America/New_York"));
659
    ///
660
    /// # Ok::<(), Box<dyn std::error::Error>>(())
661
    /// ```
662
    #[inline]
663
1.98k
    pub fn iana_name(&self) -> Option<&str> {
664
1.98k
        repr::each! {
665
1.98k
            &self.repr,
666
1.80k
            UTC => Some("UTC"),
667
            // Note that while `Etc/Unknown` looks like an IANA time zone
668
            // identifier, it is specifically and explicitly NOT an IANA time
669
            // zone identifier. So we do not return it here if we have an
670
            // unknown time zone identifier.
671
0
            UNKNOWN => None,
672
178
            FIXED(_offset) => None,
673
0
            STATIC_TZIF(tzif) => tzif.name(),
674
0
            ARC_TZIF(tzif) => tzif.name(),
675
0
            ARC_POSIX(_posix) => None,
676
        }
677
1.98k
    }
678
679
    /// Returns true if and only if this time zone is unknown.
680
    ///
681
    /// This has the special internal identifier of `Etc/Unknown`, and this
682
    /// is what will be used when converting a `Zoned` to a string.
683
    ///
684
    /// Note that while `Etc/Unknown` looks like an IANA time zone identifier,
685
    /// it is specifically and explicitly not one. It is reserved and is
686
    /// guaranteed to never be an IANA time zone identifier.
687
    ///
688
    /// An unknown time zone can be created via [`TimeZone::unknown`]. It is
689
    /// also returned by [`TimeZone::system`] when a system configured time
690
    /// zone could not be found.
691
    ///
692
    /// # Example
693
    ///
694
    /// ```
695
    /// use jiff::tz::TimeZone;
696
    ///
697
    /// let tz = TimeZone::unknown();
698
    /// assert_eq!(tz.iana_name(), None);
699
    /// assert!(tz.is_unknown());
700
    /// ```
701
    #[inline]
702
992
    pub fn is_unknown(&self) -> bool {
703
992
        self.repr.is_unknown()
704
992
    }
705
706
    /// When this time zone is a POSIX time zone, return it.
707
    ///
708
    /// This doesn't attempt to convert other time zones that are representable
709
    /// as POSIX time zones to POSIX time zones (e.g., fixed offset time
710
    /// zones). Instead, this only returns something when the actual
711
    /// representation of the time zone is a POSIX time zone.
712
    #[inline]
713
0
    pub(crate) fn posix_tz(&self) -> Option<&PosixTimeZoneOwned> {
714
0
        repr::each! {
715
0
            &self.repr,
716
0
            UTC => None,
717
0
            UNKNOWN => None,
718
0
            FIXED(_offset) => None,
719
0
            STATIC_TZIF(_tzif) => None,
720
0
            ARC_TZIF(_tzif) => None,
721
0
            ARC_POSIX(posix) => Some(posix),
722
        }
723
0
    }
724
725
    /// Returns the civil datetime corresponding to the given timestamp in this
726
    /// time zone.
727
    ///
728
    /// This operation is always unambiguous. That is, for any instant in time
729
    /// supported by Jiff (that is, a `Timestamp`), there is always precisely
730
    /// one civil datetime corresponding to that instant.
731
    ///
732
    /// Note that this is considered a lower level routine. Consider working
733
    /// with zoned datetimes instead, and use [`Zoned::datetime`] to get its
734
    /// civil time if necessary.
735
    ///
736
    /// # Example
737
    ///
738
    /// ```
739
    /// use jiff::{tz::TimeZone, Timestamp};
740
    ///
741
    /// let tz = TimeZone::get("Europe/Rome")?;
742
    /// assert_eq!(
743
    ///     tz.to_datetime(Timestamp::UNIX_EPOCH).to_string(),
744
    ///     "1970-01-01T01:00:00",
745
    /// );
746
    ///
747
    /// # Ok::<(), Box<dyn std::error::Error>>(())
748
    /// ```
749
    ///
750
    /// As mentioned above, consider using `Zoned` instead:
751
    ///
752
    /// ```
753
    /// use jiff::Timestamp;
754
    ///
755
    /// let zdt = Timestamp::UNIX_EPOCH.in_tz("Europe/Rome")?;
756
    /// assert_eq!(zdt.datetime().to_string(), "1970-01-01T01:00:00");
757
    ///
758
    /// # Ok::<(), Box<dyn std::error::Error>>(())
759
    /// ```
760
    #[inline]
761
0
    pub fn to_datetime(&self, timestamp: Timestamp) -> DateTime {
762
0
        self.to_offset(timestamp).to_datetime(timestamp)
763
0
    }
764
765
    /// Returns the offset corresponding to the given timestamp in this time
766
    /// zone.
767
    ///
768
    /// This operation is always unambiguous. That is, for any instant in time
769
    /// supported by Jiff (that is, a `Timestamp`), there is always precisely
770
    /// one offset corresponding to that instant.
771
    ///
772
    /// Given an offset, one can use APIs like [`Offset::to_datetime`] to
773
    /// create a civil datetime from a timestamp.
774
    ///
775
    /// This also returns whether this timestamp is considered to be in
776
    /// "daylight saving time," as well as the abbreviation for the time zone
777
    /// at this time.
778
    ///
779
    /// # Example
780
    ///
781
    /// ```
782
    /// use jiff::{tz::{self, TimeZone}, Timestamp};
783
    ///
784
    /// let tz = TimeZone::get("America/New_York")?;
785
    ///
786
    /// // A timestamp in DST in New York.
787
    /// let ts = Timestamp::from_second(1_720_493_204)?;
788
    /// let offset = tz.to_offset(ts);
789
    /// assert_eq!(offset, tz::offset(-4));
790
    /// assert_eq!(offset.to_datetime(ts).to_string(), "2024-07-08T22:46:44");
791
    ///
792
    /// // A timestamp *not* in DST in New York.
793
    /// let ts = Timestamp::from_second(1_704_941_204)?;
794
    /// let offset = tz.to_offset(ts);
795
    /// assert_eq!(offset, tz::offset(-5));
796
    /// assert_eq!(offset.to_datetime(ts).to_string(), "2024-01-10T21:46:44");
797
    ///
798
    /// # Ok::<(), Box<dyn std::error::Error>>(())
799
    /// ```
800
    #[inline]
801
4.36k
    pub fn to_offset(&self, timestamp: Timestamp) -> Offset {
802
4.36k
        repr::each! {
803
4.36k
            &self.repr,
804
3.55k
            UTC => Offset::UTC,
805
0
            UNKNOWN => Offset::UTC,
806
814
            FIXED(offset) => offset,
807
0
            STATIC_TZIF(tzif) => tzif.to_offset(timestamp),
808
0
            ARC_TZIF(tzif) => tzif.to_offset(timestamp),
809
0
            ARC_POSIX(posix) => posix.to_offset(timestamp),
810
        }
811
4.36k
    }
812
813
    /// Returns the offset information corresponding to the given timestamp in
814
    /// this time zone. This includes the offset along with daylight saving
815
    /// time status and a time zone abbreviation.
816
    ///
817
    /// This is like [`TimeZone::to_offset`], but returns the aforementioned
818
    /// extra data in addition to the offset. This data may, in some cases, be
819
    /// more expensive to compute.
820
    ///
821
    /// # Example
822
    ///
823
    /// ```
824
    /// use jiff::{tz::{self, Dst, TimeZone}, Timestamp};
825
    ///
826
    /// let tz = TimeZone::get("America/New_York")?;
827
    ///
828
    /// // A timestamp in DST in New York.
829
    /// let ts = Timestamp::from_second(1_720_493_204)?;
830
    /// let info = tz.to_offset_info(ts);
831
    /// assert_eq!(info.offset(), tz::offset(-4));
832
    /// assert_eq!(info.dst(), Dst::Yes);
833
    /// assert_eq!(info.abbreviation(), "EDT");
834
    /// assert_eq!(
835
    ///     info.offset().to_datetime(ts).to_string(),
836
    ///     "2024-07-08T22:46:44",
837
    /// );
838
    ///
839
    /// // A timestamp *not* in DST in New York.
840
    /// let ts = Timestamp::from_second(1_704_941_204)?;
841
    /// let info = tz.to_offset_info(ts);
842
    /// assert_eq!(info.offset(), tz::offset(-5));
843
    /// assert_eq!(info.dst(), Dst::No);
844
    /// assert_eq!(info.abbreviation(), "EST");
845
    /// assert_eq!(
846
    ///     info.offset().to_datetime(ts).to_string(),
847
    ///     "2024-01-10T21:46:44",
848
    /// );
849
    ///
850
    /// # Ok::<(), Box<dyn std::error::Error>>(())
851
    /// ```
852
    #[inline]
853
0
    pub fn to_offset_info<'t>(
854
0
        &'t self,
855
0
        timestamp: Timestamp,
856
0
    ) -> TimeZoneOffsetInfo<'t> {
857
0
        repr::each! {
858
0
            &self.repr,
859
0
            UTC => TimeZoneOffsetInfo {
860
0
                offset: Offset::UTC,
861
0
                dst: Dst::No,
862
0
                abbreviation: TimeZoneAbbreviation::Borrowed("UTC"),
863
0
            },
864
0
            UNKNOWN => TimeZoneOffsetInfo {
865
0
                offset: Offset::UTC,
866
0
                dst: Dst::No,
867
0
                // It'd be kinda nice if this were just `ERR` to
868
0
                // indicate an error, but I can't find any precedent
869
0
                // for that. And CLDR says `Etc/Unknown` should behave
870
0
                // like UTC, so... I guess we use UTC here.
871
0
                abbreviation: TimeZoneAbbreviation::Borrowed("UTC"),
872
0
            },
873
            FIXED(offset) => {
874
0
                let abbreviation =
875
0
                    TimeZoneAbbreviation::Owned(offset.to_array_str());
876
0
                TimeZoneOffsetInfo {
877
0
                    offset,
878
0
                    dst: Dst::No,
879
0
                    abbreviation,
880
0
                }
881
            },
882
0
            STATIC_TZIF(tzif) => tzif.to_offset_info(timestamp),
883
0
            ARC_TZIF(tzif) => tzif.to_offset_info(timestamp),
884
0
            ARC_POSIX(posix) => posix.to_offset_info(timestamp),
885
        }
886
0
    }
887
888
    /// If this time zone is a fixed offset, then this returns the offset.
889
    /// If this time zone is not a fixed offset, then an error is returned.
890
    ///
891
    /// If you just need an offset for a given timestamp, then you can use
892
    /// [`TimeZone::to_offset`]. Or, if you need an offset for a civil
893
    /// datetime, then you can use [`TimeZone::to_ambiguous_timestamp`] or
894
    /// [`TimeZone::to_ambiguous_zoned`], although the result may be ambiguous.
895
    ///
896
    /// Generally, this routine is useful when you need to know whether the
897
    /// time zone is fixed, and you want to get the offset without having to
898
    /// specify a timestamp. This is sometimes required for interoperating with
899
    /// other datetime systems that need to distinguish between time zones that
900
    /// are fixed and time zones that are based on rules such as those found in
901
    /// the IANA time zone database.
902
    ///
903
    /// # Example
904
    ///
905
    /// ```
906
    /// use jiff::tz::{Offset, TimeZone};
907
    ///
908
    /// let tz = TimeZone::get("America/New_York")?;
909
    /// // A named time zone is not a fixed offset
910
    /// // and so cannot be converted to an offset
911
    /// // without a timestamp or civil datetime.
912
    /// assert_eq!(
913
    ///     tz.to_fixed_offset().unwrap_err().to_string(),
914
    ///     "cannot convert non-fixed IANA time zone \
915
    ///      to offset without a timestamp or civil datetime",
916
    /// );
917
    ///
918
    /// let tz = TimeZone::UTC;
919
    /// // UTC is a fixed offset and so can be converted
920
    /// // without a timestamp.
921
    /// assert_eq!(tz.to_fixed_offset()?, Offset::UTC);
922
    ///
923
    /// // And of course, creating a time zone from a
924
    /// // fixed offset results in a fixed offset time
925
    /// // zone too:
926
    /// let tz = TimeZone::fixed(jiff::tz::offset(-10));
927
    /// assert_eq!(tz.to_fixed_offset()?, jiff::tz::offset(-10));
928
    ///
929
    /// # Ok::<(), Box<dyn std::error::Error>>(())
930
    /// ```
931
    #[inline]
932
0
    pub fn to_fixed_offset(&self) -> Result<Offset, Error> {
933
0
        let mkerr = || {
934
0
            Error::from(E::ConvertNonFixed { kind: self.kind_description() })
935
0
        };
936
0
        repr::each! {
937
0
            &self.repr,
938
0
            UTC => Ok(Offset::UTC),
939
0
            UNKNOWN => Ok(Offset::UTC),
940
0
            FIXED(offset) => Ok(offset),
941
0
            STATIC_TZIF(_tzif) => Err(mkerr()),
942
0
            ARC_TZIF(_tzif) => Err(mkerr()),
943
0
            ARC_POSIX(_posix) => Err(mkerr()),
944
        }
945
0
    }
946
947
    /// Converts a civil datetime to a [`Zoned`] in this time zone.
948
    ///
949
    /// The given civil datetime may be ambiguous in this time zone. A civil
950
    /// datetime is ambiguous when either of the following occurs:
951
    ///
952
    /// * When the civil datetime falls into a "gap." That is, when there is a
953
    /// jump forward in time where a span of time does not appear on the clocks
954
    /// in this time zone. This _typically_ manifests as a 1 hour jump forward
955
    /// into daylight saving time.
956
    /// * When the civil datetime falls into a "fold." That is, when there is
957
    /// a jump backward in time where a span of time is _repeated_ on the
958
    /// clocks in this time zone. This _typically_ manifests as a 1 hour jump
959
    /// backward out of daylight saving time.
960
    ///
961
    /// This routine automatically resolves both of the above ambiguities via
962
    /// the
963
    /// [`Disambiguation::Compatible`](crate::tz::Disambiguation::Compatible)
964
    /// strategy. That in, the case of a gap, the time after the gap is used.
965
    /// In the case of a fold, the first repetition of the clock time is used.
966
    ///
967
    /// # Example
968
    ///
969
    /// This example shows how disambiguation works:
970
    ///
971
    /// ```
972
    /// use jiff::{civil::date, tz::TimeZone};
973
    ///
974
    /// let tz = TimeZone::get("America/New_York")?;
975
    ///
976
    /// // This demonstrates disambiguation behavior for a gap.
977
    /// let zdt = tz.to_zoned(date(2024, 3, 10).at(2, 30, 0, 0))?;
978
    /// assert_eq!(zdt.to_string(), "2024-03-10T03:30:00-04:00[America/New_York]");
979
    /// // This demonstrates disambiguation behavior for a fold.
980
    /// // Notice the offset: the -04 corresponds to the time while
981
    /// // still in DST. The second repetition of the 1 o'clock hour
982
    /// // occurs outside of DST, in "standard" time, with the offset -5.
983
    /// let zdt = tz.to_zoned(date(2024, 11, 3).at(1, 30, 0, 0))?;
984
    /// assert_eq!(zdt.to_string(), "2024-11-03T01:30:00-04:00[America/New_York]");
985
    ///
986
    /// # Ok::<(), Box<dyn std::error::Error>>(())
987
    /// ```
988
    #[inline]
989
0
    pub fn to_zoned(&self, dt: DateTime) -> Result<Zoned, Error> {
990
0
        self.to_ambiguous_zoned(dt).compatible()
991
0
    }
992
993
    /// Converts a civil datetime to a possibly ambiguous zoned datetime in
994
    /// this time zone.
995
    ///
996
    /// The given civil datetime may be ambiguous in this time zone. A civil
997
    /// datetime is ambiguous when either of the following occurs:
998
    ///
999
    /// * When the civil datetime falls into a "gap." That is, when there is a
1000
    /// jump forward in time where a span of time does not appear on the clocks
1001
    /// in this time zone. This _typically_ manifests as a 1 hour jump forward
1002
    /// into daylight saving time.
1003
    /// * When the civil datetime falls into a "fold." That is, when there is
1004
    /// a jump backward in time where a span of time is _repeated_ on the
1005
    /// clocks in this time zone. This _typically_ manifests as a 1 hour jump
1006
    /// backward out of daylight saving time.
1007
    ///
1008
    /// Unlike [`TimeZone::to_zoned`], this method does not do any automatic
1009
    /// disambiguation. Instead, callers are expected to use the methods on
1010
    /// [`AmbiguousZoned`] to resolve any ambiguity, if it occurs.
1011
    ///
1012
    /// # Example
1013
    ///
1014
    /// This example shows how to return an error when the civil datetime given
1015
    /// is ambiguous:
1016
    ///
1017
    /// ```
1018
    /// use jiff::{civil::date, tz::TimeZone};
1019
    ///
1020
    /// let tz = TimeZone::get("America/New_York")?;
1021
    ///
1022
    /// // This is not ambiguous:
1023
    /// let dt = date(2024, 3, 10).at(1, 0, 0, 0);
1024
    /// assert_eq!(
1025
    ///     tz.to_ambiguous_zoned(dt).unambiguous()?.to_string(),
1026
    ///     "2024-03-10T01:00:00-05:00[America/New_York]",
1027
    /// );
1028
    /// // But this is a gap, and thus ambiguous! So an error is returned.
1029
    /// let dt = date(2024, 3, 10).at(2, 0, 0, 0);
1030
    /// assert!(tz.to_ambiguous_zoned(dt).unambiguous().is_err());
1031
    /// // And so is this, because it's a fold.
1032
    /// let dt = date(2024, 11, 3).at(1, 0, 0, 0);
1033
    /// assert!(tz.to_ambiguous_zoned(dt).unambiguous().is_err());
1034
    ///
1035
    /// # Ok::<(), Box<dyn std::error::Error>>(())
1036
    /// ```
1037
    #[inline]
1038
0
    pub fn to_ambiguous_zoned(&self, dt: DateTime) -> AmbiguousZoned {
1039
0
        self.clone().into_ambiguous_zoned(dt)
1040
0
    }
1041
1042
    /// Converts a civil datetime to a possibly ambiguous zoned datetime in
1043
    /// this time zone, and does so by assuming ownership of this `TimeZone`.
1044
    ///
1045
    /// This is identical to [`TimeZone::to_ambiguous_zoned`], but it avoids
1046
    /// a `TimeZone::clone()` call. (Which are cheap, but not completely free.)
1047
    ///
1048
    /// # Example
1049
    ///
1050
    /// This example shows how to create a `Zoned` value from a `TimeZone`
1051
    /// and a `DateTime` without cloning the `TimeZone`:
1052
    ///
1053
    /// ```
1054
    /// use jiff::{civil::date, tz::TimeZone};
1055
    ///
1056
    /// let tz = TimeZone::get("America/New_York")?;
1057
    /// let dt = date(2024, 3, 10).at(1, 0, 0, 0);
1058
    /// assert_eq!(
1059
    ///     tz.into_ambiguous_zoned(dt).unambiguous()?.to_string(),
1060
    ///     "2024-03-10T01:00:00-05:00[America/New_York]",
1061
    /// );
1062
    ///
1063
    /// # Ok::<(), Box<dyn std::error::Error>>(())
1064
    /// ```
1065
    #[inline]
1066
0
    pub fn into_ambiguous_zoned(self, dt: DateTime) -> AmbiguousZoned {
1067
0
        self.to_ambiguous_timestamp(dt).into_ambiguous_zoned(self)
1068
0
    }
1069
1070
    /// Converts a civil datetime to a [`Timestamp`] in this time zone.
1071
    ///
1072
    /// The given civil datetime may be ambiguous in this time zone. A civil
1073
    /// datetime is ambiguous when either of the following occurs:
1074
    ///
1075
    /// * When the civil datetime falls into a "gap." That is, when there is a
1076
    /// jump forward in time where a span of time does not appear on the clocks
1077
    /// in this time zone. This _typically_ manifests as a 1 hour jump forward
1078
    /// into daylight saving time.
1079
    /// * When the civil datetime falls into a "fold." That is, when there is
1080
    /// a jump backward in time where a span of time is _repeated_ on the
1081
    /// clocks in this time zone. This _typically_ manifests as a 1 hour jump
1082
    /// backward out of daylight saving time.
1083
    ///
1084
    /// This routine automatically resolves both of the above ambiguities via
1085
    /// the
1086
    /// [`Disambiguation::Compatible`](crate::tz::Disambiguation::Compatible)
1087
    /// strategy. That in, the case of a gap, the time after the gap is used.
1088
    /// In the case of a fold, the first repetition of the clock time is used.
1089
    ///
1090
    /// This routine is identical to [`TimeZone::to_zoned`], except it returns
1091
    /// a `Timestamp` instead of a zoned datetime. The benefit of this
1092
    /// method is that it never requires cloning or consuming ownership of a
1093
    /// `TimeZone`, and it doesn't require construction of `Zoned` which has
1094
    /// a small but non-zero cost. (This is partially because a `Zoned` value
1095
    /// contains a `TimeZone`, but of course, a `Timestamp` does not.)
1096
    ///
1097
    /// # Example
1098
    ///
1099
    /// This example shows how disambiguation works:
1100
    ///
1101
    /// ```
1102
    /// use jiff::{civil::date, tz::TimeZone};
1103
    ///
1104
    /// let tz = TimeZone::get("America/New_York")?;
1105
    ///
1106
    /// // This demonstrates disambiguation behavior for a gap.
1107
    /// let ts = tz.to_timestamp(date(2024, 3, 10).at(2, 30, 0, 0))?;
1108
    /// assert_eq!(ts.to_string(), "2024-03-10T07:30:00Z");
1109
    /// // This demonstrates disambiguation behavior for a fold.
1110
    /// // Notice the offset: the -04 corresponds to the time while
1111
    /// // still in DST. The second repetition of the 1 o'clock hour
1112
    /// // occurs outside of DST, in "standard" time, with the offset -5.
1113
    /// let ts = tz.to_timestamp(date(2024, 11, 3).at(1, 30, 0, 0))?;
1114
    /// assert_eq!(ts.to_string(), "2024-11-03T05:30:00Z");
1115
    ///
1116
    /// # Ok::<(), Box<dyn std::error::Error>>(())
1117
    /// ```
1118
    #[inline]
1119
0
    pub fn to_timestamp(&self, dt: DateTime) -> Result<Timestamp, Error> {
1120
0
        self.to_ambiguous_timestamp(dt).compatible()
1121
0
    }
1122
1123
    /// Converts a civil datetime to a possibly ambiguous timestamp in
1124
    /// this time zone.
1125
    ///
1126
    /// The given civil datetime may be ambiguous in this time zone. A civil
1127
    /// datetime is ambiguous when either of the following occurs:
1128
    ///
1129
    /// * When the civil datetime falls into a "gap." That is, when there is a
1130
    /// jump forward in time where a span of time does not appear on the clocks
1131
    /// in this time zone. This _typically_ manifests as a 1 hour jump forward
1132
    /// into daylight saving time.
1133
    /// * When the civil datetime falls into a "fold." That is, when there is
1134
    /// a jump backward in time where a span of time is _repeated_ on the
1135
    /// clocks in this time zone. This _typically_ manifests as a 1 hour jump
1136
    /// backward out of daylight saving time.
1137
    ///
1138
    /// Unlike [`TimeZone::to_timestamp`], this method does not do any
1139
    /// automatic disambiguation. Instead, callers are expected to use the
1140
    /// methods on [`AmbiguousTimestamp`] to resolve any ambiguity, if it
1141
    /// occurs.
1142
    ///
1143
    /// This routine is identical to [`TimeZone::to_ambiguous_zoned`], except
1144
    /// it returns an `AmbiguousTimestamp` instead of a `AmbiguousZoned`. The
1145
    /// benefit of this method is that it never requires cloning or consuming
1146
    /// ownership of a `TimeZone`, and it doesn't require construction of
1147
    /// `Zoned` which has a small but non-zero cost. (This is partially because
1148
    /// a `Zoned` value contains a `TimeZone`, but of course, a `Timestamp`
1149
    /// does not.)
1150
    ///
1151
    /// # Example
1152
    ///
1153
    /// This example shows how to return an error when the civil datetime given
1154
    /// is ambiguous:
1155
    ///
1156
    /// ```
1157
    /// use jiff::{civil::date, tz::TimeZone};
1158
    ///
1159
    /// let tz = TimeZone::get("America/New_York")?;
1160
    ///
1161
    /// // This is not ambiguous:
1162
    /// let dt = date(2024, 3, 10).at(1, 0, 0, 0);
1163
    /// assert_eq!(
1164
    ///     tz.to_ambiguous_timestamp(dt).unambiguous()?.to_string(),
1165
    ///     "2024-03-10T06:00:00Z",
1166
    /// );
1167
    /// // But this is a gap, and thus ambiguous! So an error is returned.
1168
    /// let dt = date(2024, 3, 10).at(2, 0, 0, 0);
1169
    /// assert!(tz.to_ambiguous_timestamp(dt).unambiguous().is_err());
1170
    /// // And so is this, because it's a fold.
1171
    /// let dt = date(2024, 11, 3).at(1, 0, 0, 0);
1172
    /// assert!(tz.to_ambiguous_timestamp(dt).unambiguous().is_err());
1173
    ///
1174
    /// # Ok::<(), Box<dyn std::error::Error>>(())
1175
    /// ```
1176
    #[inline]
1177
1.58k
    pub fn to_ambiguous_timestamp(&self, dt: DateTime) -> AmbiguousTimestamp {
1178
1.58k
        let ambiguous_kind = repr::each! {
1179
1.58k
            &self.repr,
1180
1.40k
            UTC => AmbiguousOffset::Unambiguous { offset: Offset::UTC },
1181
0
            UNKNOWN => AmbiguousOffset::Unambiguous { offset: Offset::UTC },
1182
179
            FIXED(offset) => AmbiguousOffset::Unambiguous { offset },
1183
0
            STATIC_TZIF(tzif) => tzif.to_ambiguous_kind(dt),
1184
0
            ARC_TZIF(tzif) => tzif.to_ambiguous_kind(dt),
1185
0
            ARC_POSIX(posix) => posix.to_ambiguous_kind(dt),
1186
        };
1187
1.58k
        AmbiguousTimestamp::new(dt, ambiguous_kind)
1188
1.58k
    }
1189
1190
    /// Returns an iterator of time zone transitions preceding the given
1191
    /// timestamp. The iterator returned yields [`TimeZoneTransition`]
1192
    /// elements.
1193
    ///
1194
    /// The order of the iterator returned moves backward through time. If
1195
    /// there is a previous transition, then the timestamp of that transition
1196
    /// is guaranteed to be strictly less than the timestamp given.
1197
    ///
1198
    /// This is a low level API that you generally shouldn't need. It's
1199
    /// useful in cases where you need to know something about the specific
1200
    /// instants at which time zone transitions occur. For example, an embedded
1201
    /// device might need to be explicitly programmed with daylight saving
1202
    /// time transitions. APIs like this enable callers to explore those
1203
    /// transitions.
1204
    ///
1205
    /// A time zone transition refers to a specific point in time when the
1206
    /// offset from UTC for a particular geographical region changes. This
1207
    /// is usually a result of daylight saving time, but it can also occur
1208
    /// when a geographic region changes its permanent offset from UTC.
1209
    ///
1210
    /// The iterator returned is not guaranteed to yield any elements. For
1211
    /// example, this occurs with a fixed offset time zone. Logically, it
1212
    /// would also be possible for the iterator to be infinite, except that
1213
    /// eventually the timestamp would overflow Jiff's minimum timestamp
1214
    /// value, at which point, iteration stops.
1215
    ///
1216
    /// # Example: time since the previous transition
1217
    ///
1218
    /// This example shows how much time has passed since the previous time
1219
    /// zone transition:
1220
    ///
1221
    /// ```
1222
    /// use jiff::{Unit, Zoned};
1223
    ///
1224
    /// let now: Zoned = "2024-12-31 18:25-05[US/Eastern]".parse()?;
1225
    /// let trans = now.time_zone().preceding(now.timestamp()).next().unwrap();
1226
    /// let prev_at = trans.timestamp().to_zoned(now.time_zone().clone());
1227
    /// let span = now.since((Unit::Year, &prev_at))?;
1228
    /// assert_eq!(format!("{span:#}"), "1mo 27d 17h 25m");
1229
    ///
1230
    /// # Ok::<(), Box<dyn std::error::Error>>(())
1231
    /// ```
1232
    ///
1233
    /// # Example: show the 5 previous time zone transitions
1234
    ///
1235
    /// This shows how to find the 5 preceding time zone transitions (from a
1236
    /// particular datetime) for a particular time zone:
1237
    ///
1238
    /// ```
1239
    /// use jiff::{tz::offset, Zoned};
1240
    ///
1241
    /// let now: Zoned = "2024-12-31 18:25-05[US/Eastern]".parse()?;
1242
    /// let transitions = now
1243
    ///     .time_zone()
1244
    ///     .preceding(now.timestamp())
1245
    ///     .take(5)
1246
    ///     .map(|t| (
1247
    ///         t.timestamp().to_zoned(now.time_zone().clone()),
1248
    ///         t.offset(),
1249
    ///         t.abbreviation().to_string(),
1250
    ///     ))
1251
    ///     .collect::<Vec<_>>();
1252
    /// assert_eq!(transitions, vec![
1253
    ///     ("2024-11-03 01:00-05[US/Eastern]".parse()?, offset(-5), "EST".to_string()),
1254
    ///     ("2024-03-10 03:00-04[US/Eastern]".parse()?, offset(-4), "EDT".to_string()),
1255
    ///     ("2023-11-05 01:00-05[US/Eastern]".parse()?, offset(-5), "EST".to_string()),
1256
    ///     ("2023-03-12 03:00-04[US/Eastern]".parse()?, offset(-4), "EDT".to_string()),
1257
    ///     ("2022-11-06 01:00-05[US/Eastern]".parse()?, offset(-5), "EST".to_string()),
1258
    /// ]);
1259
    ///
1260
    /// # Ok::<(), Box<dyn std::error::Error>>(())
1261
    /// ```
1262
    #[inline]
1263
0
    pub fn preceding<'t>(
1264
0
        &'t self,
1265
0
        timestamp: Timestamp,
1266
0
    ) -> TimeZonePrecedingTransitions<'t> {
1267
0
        TimeZonePrecedingTransitions { tz: self, cur: timestamp }
1268
0
    }
1269
1270
    /// Returns an iterator of time zone transitions following the given
1271
    /// timestamp. The iterator returned yields [`TimeZoneTransition`]
1272
    /// elements.
1273
    ///
1274
    /// The order of the iterator returned moves forward through time. If
1275
    /// there is a following transition, then the timestamp of that transition
1276
    /// is guaranteed to be strictly greater than the timestamp given.
1277
    ///
1278
    /// This is a low level API that you generally shouldn't need. It's
1279
    /// useful in cases where you need to know something about the specific
1280
    /// instants at which time zone transitions occur. For example, an embedded
1281
    /// device might need to be explicitly programmed with daylight saving
1282
    /// time transitions. APIs like this enable callers to explore those
1283
    /// transitions.
1284
    ///
1285
    /// A time zone transition refers to a specific point in time when the
1286
    /// offset from UTC for a particular geographical region changes. This
1287
    /// is usually a result of daylight saving time, but it can also occur
1288
    /// when a geographic region changes its permanent offset from UTC.
1289
    ///
1290
    /// The iterator returned is not guaranteed to yield any elements. For
1291
    /// example, this occurs with a fixed offset time zone. Logically, it
1292
    /// would also be possible for the iterator to be infinite, except that
1293
    /// eventually the timestamp would overflow Jiff's maximum timestamp
1294
    /// value, at which point, iteration stops.
1295
    ///
1296
    /// # Example: time until the next transition
1297
    ///
1298
    /// This example shows how much time is left until the next time zone
1299
    /// transition:
1300
    ///
1301
    /// ```
1302
    /// use jiff::{Unit, Zoned};
1303
    ///
1304
    /// let now: Zoned = "2024-12-31 18:25-05[US/Eastern]".parse()?;
1305
    /// let trans = now.time_zone().following(now.timestamp()).next().unwrap();
1306
    /// let next_at = trans.timestamp().to_zoned(now.time_zone().clone());
1307
    /// let span = now.until((Unit::Year, &next_at))?;
1308
    /// assert_eq!(format!("{span:#}"), "2mo 8d 7h 35m");
1309
    ///
1310
    /// # Ok::<(), Box<dyn std::error::Error>>(())
1311
    /// ```
1312
    ///
1313
    /// # Example: show the 5 next time zone transitions
1314
    ///
1315
    /// This shows how to find the 5 following time zone transitions (from a
1316
    /// particular datetime) for a particular time zone:
1317
    ///
1318
    /// ```
1319
    /// use jiff::{tz::offset, Zoned};
1320
    ///
1321
    /// let now: Zoned = "2024-12-31 18:25-05[US/Eastern]".parse()?;
1322
    /// let transitions = now
1323
    ///     .time_zone()
1324
    ///     .following(now.timestamp())
1325
    ///     .take(5)
1326
    ///     .map(|t| (
1327
    ///         t.timestamp().to_zoned(now.time_zone().clone()),
1328
    ///         t.offset(),
1329
    ///         t.abbreviation().to_string(),
1330
    ///     ))
1331
    ///     .collect::<Vec<_>>();
1332
    /// assert_eq!(transitions, vec![
1333
    ///     ("2025-03-09 03:00-04[US/Eastern]".parse()?, offset(-4), "EDT".to_string()),
1334
    ///     ("2025-11-02 01:00-05[US/Eastern]".parse()?, offset(-5), "EST".to_string()),
1335
    ///     ("2026-03-08 03:00-04[US/Eastern]".parse()?, offset(-4), "EDT".to_string()),
1336
    ///     ("2026-11-01 01:00-05[US/Eastern]".parse()?, offset(-5), "EST".to_string()),
1337
    ///     ("2027-03-14 03:00-04[US/Eastern]".parse()?, offset(-4), "EDT".to_string()),
1338
    /// ]);
1339
    ///
1340
    /// # Ok::<(), Box<dyn std::error::Error>>(())
1341
    /// ```
1342
    #[inline]
1343
0
    pub fn following<'t>(
1344
0
        &'t self,
1345
0
        timestamp: Timestamp,
1346
0
    ) -> TimeZoneFollowingTransitions<'t> {
1347
0
        TimeZoneFollowingTransitions { tz: self, cur: timestamp }
1348
0
    }
1349
1350
    /// Used by the "preceding transitions" iterator.
1351
    #[inline]
1352
0
    fn previous_transition<'t>(
1353
0
        &'t self,
1354
0
        timestamp: Timestamp,
1355
0
    ) -> Option<TimeZoneTransition<'t>> {
1356
0
        repr::each! {
1357
0
            &self.repr,
1358
0
            UTC => None,
1359
0
            UNKNOWN => None,
1360
0
            FIXED(_offset) => None,
1361
0
            STATIC_TZIF(tzif) => tzif.previous_transition(timestamp),
1362
0
            ARC_TZIF(tzif) => tzif.previous_transition(timestamp),
1363
0
            ARC_POSIX(posix) => posix.previous_transition(timestamp),
1364
        }
1365
0
    }
1366
1367
    /// Used by the "following transitions" iterator.
1368
    #[inline]
1369
0
    fn next_transition<'t>(
1370
0
        &'t self,
1371
0
        timestamp: Timestamp,
1372
0
    ) -> Option<TimeZoneTransition<'t>> {
1373
0
        repr::each! {
1374
0
            &self.repr,
1375
0
            UTC => None,
1376
0
            UNKNOWN => None,
1377
0
            FIXED(_offset) => None,
1378
0
            STATIC_TZIF(tzif) => tzif.next_transition(timestamp),
1379
0
            ARC_TZIF(tzif) => tzif.next_transition(timestamp),
1380
0
            ARC_POSIX(posix) => posix.next_transition(timestamp),
1381
        }
1382
0
    }
1383
1384
    /// Returns a short description about the kind of this time zone.
1385
    ///
1386
    /// This is useful in error messages.
1387
0
    fn kind_description(&self) -> &'static str {
1388
0
        repr::each! {
1389
0
            &self.repr,
1390
0
            UTC => "UTC",
1391
0
            UNKNOWN => "Etc/Unknown",
1392
0
            FIXED(_offset) => "fixed",
1393
0
            STATIC_TZIF(_tzif) => "IANA",
1394
0
            ARC_TZIF(_tzif) => "IANA",
1395
0
            ARC_POSIX(_posix) => "POSIX",
1396
        }
1397
0
    }
1398
}
1399
1400
// Exposed APIs for Jiff's time zone proc macro.
1401
//
1402
// These are NOT part of Jiff's public API. There are *zero* semver guarantees
1403
// for them.
1404
#[doc(hidden)]
1405
impl TimeZone {
1406
0
    pub const fn __internal_from_tzif(
1407
0
        tzif: &'static crate::tz::tzif::TzifStatic,
1408
0
    ) -> TimeZone {
1409
0
        let repr = Repr::static_tzif(tzif);
1410
0
        TimeZone { repr }
1411
0
    }
1412
1413
    /// Returns a dumb copy of this `TimeZone`.
1414
    ///
1415
    /// # Safety
1416
    ///
1417
    /// Callers must ensure that this time zone is UTC, unknown, a fixed
1418
    /// offset or created with `TimeZone::__internal_from_tzif`.
1419
    ///
1420
    /// Namely, this specifically does not increment the ref count for
1421
    /// the `Arc` pointers when the tag is `ARC_TZIF` or `ARC_POSIX`.
1422
    /// This means that incorrect usage of this routine can lead to
1423
    /// use-after-free.
1424
    #[inline]
1425
0
    pub const unsafe fn copy(&self) -> TimeZone {
1426
        // SAFETY: Requirements are forwarded to the caller.
1427
0
        unsafe { TimeZone { repr: self.repr.copy() } }
1428
0
    }
1429
}
1430
1431
impl core::fmt::Debug for TimeZone {
1432
    #[inline]
1433
0
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1434
0
        f.debug_tuple("TimeZone").field(&self.repr).finish()
1435
0
    }
1436
}
1437
1438
/// A representation a single time zone transition.
1439
///
1440
/// A time zone transition is an instant in time the marks the beginning of
1441
/// a change in the offset from UTC that civil time is computed from in a
1442
/// particular time zone. For example, when daylight saving time comes into
1443
/// effect (or goes away). Another example is when a geographic region changes
1444
/// its permanent offset from UTC.
1445
///
1446
/// This is a low level type that you generally shouldn't need. It's useful in
1447
/// cases where you need to know something about the specific instants at which
1448
/// time zone transitions occur. For example, an embedded device might need to
1449
/// be explicitly programmed with daylight saving time transitions. APIs like
1450
/// this enable callers to explore those transitions.
1451
///
1452
/// This type is yielded by the iterators
1453
/// [`TimeZonePrecedingTransitions`] and
1454
/// [`TimeZoneFollowingTransitions`]. The iterators are created by
1455
/// [`TimeZone::preceding`] and [`TimeZone::following`], respectively.
1456
///
1457
/// # Example
1458
///
1459
/// This shows a somewhat silly example that finds all of the unique civil
1460
/// (or "clock" or "local") times at which a time zone transition has occurred
1461
/// in a particular time zone:
1462
///
1463
/// ```
1464
/// use std::collections::BTreeSet;
1465
/// use jiff::{civil, tz::TimeZone};
1466
///
1467
/// let tz = TimeZone::get("America/New_York")?;
1468
/// let now = civil::date(2024, 12, 31).at(18, 25, 0, 0).to_zoned(tz.clone())?;
1469
/// let mut set = BTreeSet::new();
1470
/// for trans in tz.preceding(now.timestamp()) {
1471
///     let time = tz.to_datetime(trans.timestamp()).time();
1472
///     set.insert(time);
1473
/// }
1474
/// assert_eq!(Vec::from_iter(set), vec![
1475
///     civil::time(1, 0, 0, 0),  // typical transition out of DST
1476
///     civil::time(3, 0, 0, 0),  // typical transition into DST
1477
///     civil::time(12, 0, 0, 0), // from when IANA starts keeping track
1478
///     civil::time(19, 0, 0, 0), // from World War 2
1479
/// ]);
1480
///
1481
/// # Ok::<(), Box<dyn std::error::Error>>(())
1482
/// ```
1483
#[derive(Clone, Debug)]
1484
pub struct TimeZoneTransition<'t> {
1485
    // We don't currently do anything smart to make iterating over
1486
    // transitions faster. We could if we pushed the iterator impl down into
1487
    // the respective modules (`posix` and `tzif`), but it's not clear such
1488
    // optimization is really worth it. However, this API should permit that
1489
    // kind of optimization in the future.
1490
    pub(crate) timestamp: Timestamp,
1491
    pub(crate) offset: Offset,
1492
    pub(crate) abbrev: &'t str,
1493
    pub(crate) dst: Dst,
1494
}
1495
1496
impl<'t> TimeZoneTransition<'t> {
1497
    /// Returns the timestamp at which this transition began.
1498
    ///
1499
    /// # Example
1500
    ///
1501
    /// ```
1502
    /// use jiff::{civil, tz::TimeZone};
1503
    ///
1504
    /// let tz = TimeZone::get("US/Eastern")?;
1505
    /// // Look for the first time zone transition in `US/Eastern` following
1506
    /// // 2023-03-09 00:00:00.
1507
    /// let start = civil::date(2024, 3, 9).to_zoned(tz.clone())?.timestamp();
1508
    /// let next = tz.following(start).next().unwrap();
1509
    /// assert_eq!(
1510
    ///     next.timestamp().to_zoned(tz.clone()).to_string(),
1511
    ///     "2024-03-10T03:00:00-04:00[US/Eastern]",
1512
    /// );
1513
    ///
1514
    /// # Ok::<(), Box<dyn std::error::Error>>(())
1515
    /// ```
1516
    #[inline]
1517
0
    pub fn timestamp(&self) -> Timestamp {
1518
0
        self.timestamp
1519
0
    }
1520
1521
    /// Returns the offset corresponding to this time zone transition. All
1522
    /// instants at and following this transition's timestamp (and before the
1523
    /// next transition's timestamp) need to apply this offset from UTC to get
1524
    /// the civil or "local" time in the corresponding time zone.
1525
    ///
1526
    /// # Example
1527
    ///
1528
    /// ```
1529
    /// use jiff::{civil, tz::{TimeZone, offset}};
1530
    ///
1531
    /// let tz = TimeZone::get("US/Eastern")?;
1532
    /// // Get the offset of the next transition after
1533
    /// // 2023-03-09 00:00:00.
1534
    /// let start = civil::date(2024, 3, 9).to_zoned(tz.clone())?.timestamp();
1535
    /// let next = tz.following(start).next().unwrap();
1536
    /// assert_eq!(next.offset(), offset(-4));
1537
    /// // Or go backwards to find the previous transition.
1538
    /// let prev = tz.preceding(start).next().unwrap();
1539
    /// assert_eq!(prev.offset(), offset(-5));
1540
    ///
1541
    /// # Ok::<(), Box<dyn std::error::Error>>(())
1542
    /// ```
1543
    #[inline]
1544
0
    pub fn offset(&self) -> Offset {
1545
0
        self.offset
1546
0
    }
1547
1548
    /// Returns the time zone abbreviation corresponding to this time
1549
    /// zone transition. All instants at and following this transition's
1550
    /// timestamp (and before the next transition's timestamp) may use this
1551
    /// abbreviation when creating a human readable string. For example,
1552
    /// this is the abbreviation used with the `%Z` specifier with Jiff's
1553
    /// [`fmt::strtime`](crate::fmt::strtime) module.
1554
    ///
1555
    /// Note that abbreviations can to be ambiguous. For example, the
1556
    /// abbreviation `CST` can be used for the time zones `Asia/Shanghai`,
1557
    /// `America/Chicago` and `America/Havana`.
1558
    ///
1559
    /// The lifetime of the string returned is tied to this
1560
    /// `TimeZoneTransition`, which may be shorter than `'t` (the lifetime of
1561
    /// the time zone this transition was created from).
1562
    ///
1563
    /// # Example
1564
    ///
1565
    /// ```
1566
    /// use jiff::{civil, tz::TimeZone};
1567
    ///
1568
    /// let tz = TimeZone::get("US/Eastern")?;
1569
    /// // Get the abbreviation of the next transition after
1570
    /// // 2023-03-09 00:00:00.
1571
    /// let start = civil::date(2024, 3, 9).to_zoned(tz.clone())?.timestamp();
1572
    /// let next = tz.following(start).next().unwrap();
1573
    /// assert_eq!(next.abbreviation(), "EDT");
1574
    /// // Or go backwards to find the previous transition.
1575
    /// let prev = tz.preceding(start).next().unwrap();
1576
    /// assert_eq!(prev.abbreviation(), "EST");
1577
    ///
1578
    /// # Ok::<(), Box<dyn std::error::Error>>(())
1579
    /// ```
1580
    #[inline]
1581
0
    pub fn abbreviation<'a>(&'a self) -> &'a str {
1582
0
        self.abbrev
1583
0
    }
1584
1585
    /// Returns whether daylight saving time is enabled for this time zone
1586
    /// transition.
1587
    ///
1588
    /// Callers should generally treat this as informational only. In
1589
    /// particular, not all time zone transitions are related to daylight
1590
    /// saving time. For example, some transitions are a result of a region
1591
    /// permanently changing their offset from UTC.
1592
    ///
1593
    /// # Example
1594
    ///
1595
    /// ```
1596
    /// use jiff::{civil, tz::{Dst, TimeZone}};
1597
    ///
1598
    /// let tz = TimeZone::get("US/Eastern")?;
1599
    /// // Get the DST status of the next transition after
1600
    /// // 2023-03-09 00:00:00.
1601
    /// let start = civil::date(2024, 3, 9).to_zoned(tz.clone())?.timestamp();
1602
    /// let next = tz.following(start).next().unwrap();
1603
    /// assert_eq!(next.dst(), Dst::Yes);
1604
    /// // Or go backwards to find the previous transition.
1605
    /// let prev = tz.preceding(start).next().unwrap();
1606
    /// assert_eq!(prev.dst(), Dst::No);
1607
    ///
1608
    /// # Ok::<(), Box<dyn std::error::Error>>(())
1609
    /// ```
1610
    #[inline]
1611
0
    pub fn dst(&self) -> Dst {
1612
0
        self.dst
1613
0
    }
1614
}
1615
1616
/// An offset along with DST status and a time zone abbreviation.
1617
///
1618
/// This information can be computed from a [`TimeZone`] given a [`Timestamp`]
1619
/// via [`TimeZone::to_offset_info`].
1620
///
1621
/// Generally, the extra information associated with the offset is not commonly
1622
/// needed. And indeed, inspecting the daylight saving time status of a
1623
/// particular instant in a time zone _usually_ leads to bugs. For example, not
1624
/// all time zone transitions are the result of daylight saving time. Some are
1625
/// the result of permanent changes to the standard UTC offset of a region.
1626
///
1627
/// This information is available via an API distinct from
1628
/// [`TimeZone::to_offset`] because it is not commonly needed and because it
1629
/// can sometimes be more expensive to compute.
1630
///
1631
/// The main use case for daylight saving time status or time zone
1632
/// abbreviations is for formatting datetimes in an end user's locale. If you
1633
/// want this, consider using the [`icu`] crate via [`jiff-icu`].
1634
///
1635
/// The lifetime parameter `'t` corresponds to the lifetime of the `TimeZone`
1636
/// that this info was extracted from.
1637
///
1638
/// # Example
1639
///
1640
/// ```
1641
/// use jiff::{tz::{self, Dst, TimeZone}, Timestamp};
1642
///
1643
/// let tz = TimeZone::get("America/New_York")?;
1644
///
1645
/// // A timestamp in DST in New York.
1646
/// let ts = Timestamp::from_second(1_720_493_204)?;
1647
/// let info = tz.to_offset_info(ts);
1648
/// assert_eq!(info.offset(), tz::offset(-4));
1649
/// assert_eq!(info.dst(), Dst::Yes);
1650
/// assert_eq!(info.abbreviation(), "EDT");
1651
/// assert_eq!(
1652
///     info.offset().to_datetime(ts).to_string(),
1653
///     "2024-07-08T22:46:44",
1654
/// );
1655
///
1656
/// // A timestamp *not* in DST in New York.
1657
/// let ts = Timestamp::from_second(1_704_941_204)?;
1658
/// let info = tz.to_offset_info(ts);
1659
/// assert_eq!(info.offset(), tz::offset(-5));
1660
/// assert_eq!(info.dst(), Dst::No);
1661
/// assert_eq!(info.abbreviation(), "EST");
1662
/// assert_eq!(
1663
///     info.offset().to_datetime(ts).to_string(),
1664
///     "2024-01-10T21:46:44",
1665
/// );
1666
///
1667
/// # Ok::<(), Box<dyn std::error::Error>>(())
1668
/// ```
1669
///
1670
/// [`icu`]: https://docs.rs/icu
1671
/// [`jiff-icu`]: https://docs.rs/jiff-icu
1672
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
1673
pub struct TimeZoneOffsetInfo<'t> {
1674
    pub(crate) offset: Offset,
1675
    pub(crate) dst: Dst,
1676
    pub(crate) abbreviation: TimeZoneAbbreviation<'t>,
1677
}
1678
1679
impl<'t> TimeZoneOffsetInfo<'t> {
1680
    /// Returns the offset.
1681
    ///
1682
    /// The offset is duration, from UTC, that should be used to offset the
1683
    /// civil time in a particular location.
1684
    ///
1685
    /// # Example
1686
    ///
1687
    /// ```
1688
    /// use jiff::{civil, tz::{TimeZone, offset}};
1689
    ///
1690
    /// let tz = TimeZone::get("US/Eastern")?;
1691
    /// // Get the offset for 2023-03-10 00:00:00.
1692
    /// let start = civil::date(2024, 3, 10).to_zoned(tz.clone())?.timestamp();
1693
    /// let info = tz.to_offset_info(start);
1694
    /// assert_eq!(info.offset(), offset(-5));
1695
    /// // Go forward a day and notice the offset changes due to DST!
1696
    /// let start = civil::date(2024, 3, 11).to_zoned(tz.clone())?.timestamp();
1697
    /// let info = tz.to_offset_info(start);
1698
    /// assert_eq!(info.offset(), offset(-4));
1699
    ///
1700
    /// # Ok::<(), Box<dyn std::error::Error>>(())
1701
    /// ```
1702
    #[inline]
1703
0
    pub fn offset(&self) -> Offset {
1704
0
        self.offset
1705
0
    }
1706
1707
    /// Returns the time zone abbreviation corresponding to this offset info.
1708
    ///
1709
    /// Note that abbreviations can to be ambiguous. For example, the
1710
    /// abbreviation `CST` can be used for the time zones `Asia/Shanghai`,
1711
    /// `America/Chicago` and `America/Havana`.
1712
    ///
1713
    /// The lifetime of the string returned is tied to this
1714
    /// `TimeZoneOffsetInfo`, which may be shorter than `'t` (the lifetime of
1715
    /// the time zone this transition was created from).
1716
    ///
1717
    /// # Example
1718
    ///
1719
    /// ```
1720
    /// use jiff::{civil, tz::TimeZone};
1721
    ///
1722
    /// let tz = TimeZone::get("US/Eastern")?;
1723
    /// // Get the time zone abbreviation for 2023-03-10 00:00:00.
1724
    /// let start = civil::date(2024, 3, 10).to_zoned(tz.clone())?.timestamp();
1725
    /// let info = tz.to_offset_info(start);
1726
    /// assert_eq!(info.abbreviation(), "EST");
1727
    /// // Go forward a day and notice the abbreviation changes due to DST!
1728
    /// let start = civil::date(2024, 3, 11).to_zoned(tz.clone())?.timestamp();
1729
    /// let info = tz.to_offset_info(start);
1730
    /// assert_eq!(info.abbreviation(), "EDT");
1731
    ///
1732
    /// # Ok::<(), Box<dyn std::error::Error>>(())
1733
    /// ```
1734
    #[inline]
1735
0
    pub fn abbreviation(&self) -> &str {
1736
0
        self.abbreviation.as_str()
1737
0
    }
1738
1739
    /// Returns whether daylight saving time is enabled for this offset
1740
    /// info.
1741
    ///
1742
    /// Callers should generally treat this as informational only. In
1743
    /// particular, not all time zone transitions are related to daylight
1744
    /// saving time. For example, some transitions are a result of a region
1745
    /// permanently changing their offset from UTC.
1746
    ///
1747
    /// # Example
1748
    ///
1749
    /// ```
1750
    /// use jiff::{civil, tz::{Dst, TimeZone}};
1751
    ///
1752
    /// let tz = TimeZone::get("US/Eastern")?;
1753
    /// // Get the DST status of 2023-03-11 00:00:00.
1754
    /// let start = civil::date(2024, 3, 11).to_zoned(tz.clone())?.timestamp();
1755
    /// let info = tz.to_offset_info(start);
1756
    /// assert_eq!(info.dst(), Dst::Yes);
1757
    ///
1758
    /// # Ok::<(), Box<dyn std::error::Error>>(())
1759
    /// ```
1760
    #[inline]
1761
0
    pub fn dst(&self) -> Dst {
1762
0
        self.dst
1763
0
    }
1764
}
1765
1766
/// An iterator over time zone transitions going backward in time.
1767
///
1768
/// This iterator is created by [`TimeZone::preceding`].
1769
///
1770
/// # Example: show the 5 previous time zone transitions
1771
///
1772
/// This shows how to find the 5 preceding time zone transitions (from a
1773
/// particular datetime) for a particular time zone:
1774
///
1775
/// ```
1776
/// use jiff::{tz::offset, Zoned};
1777
///
1778
/// let now: Zoned = "2024-12-31 18:25-05[US/Eastern]".parse()?;
1779
/// let transitions = now
1780
///     .time_zone()
1781
///     .preceding(now.timestamp())
1782
///     .take(5)
1783
///     .map(|t| (
1784
///         t.timestamp().to_zoned(now.time_zone().clone()),
1785
///         t.offset(),
1786
///         t.abbreviation().to_string(),
1787
///     ))
1788
///     .collect::<Vec<_>>();
1789
/// assert_eq!(transitions, vec![
1790
///     ("2024-11-03 01:00-05[US/Eastern]".parse()?, offset(-5), "EST".to_string()),
1791
///     ("2024-03-10 03:00-04[US/Eastern]".parse()?, offset(-4), "EDT".to_string()),
1792
///     ("2023-11-05 01:00-05[US/Eastern]".parse()?, offset(-5), "EST".to_string()),
1793
///     ("2023-03-12 03:00-04[US/Eastern]".parse()?, offset(-4), "EDT".to_string()),
1794
///     ("2022-11-06 01:00-05[US/Eastern]".parse()?, offset(-5), "EST".to_string()),
1795
/// ]);
1796
///
1797
/// # Ok::<(), Box<dyn std::error::Error>>(())
1798
/// ```
1799
#[derive(Clone, Debug)]
1800
pub struct TimeZonePrecedingTransitions<'t> {
1801
    tz: &'t TimeZone,
1802
    cur: Timestamp,
1803
}
1804
1805
impl<'t> Iterator for TimeZonePrecedingTransitions<'t> {
1806
    type Item = TimeZoneTransition<'t>;
1807
1808
0
    fn next(&mut self) -> Option<TimeZoneTransition<'t>> {
1809
0
        let trans = self.tz.previous_transition(self.cur)?;
1810
0
        self.cur = trans.timestamp();
1811
0
        Some(trans)
1812
0
    }
1813
}
1814
1815
impl<'t> core::iter::FusedIterator for TimeZonePrecedingTransitions<'t> {}
1816
1817
/// An iterator over time zone transitions going forward in time.
1818
///
1819
/// This iterator is created by [`TimeZone::following`].
1820
///
1821
/// # Example: show the 5 next time zone transitions
1822
///
1823
/// This shows how to find the 5 following time zone transitions (from a
1824
/// particular datetime) for a particular time zone:
1825
///
1826
/// ```
1827
/// use jiff::{tz::offset, Zoned};
1828
///
1829
/// let now: Zoned = "2024-12-31 18:25-05[US/Eastern]".parse()?;
1830
/// let transitions = now
1831
///     .time_zone()
1832
///     .following(now.timestamp())
1833
///     .take(5)
1834
///     .map(|t| (
1835
///         t.timestamp().to_zoned(now.time_zone().clone()),
1836
///         t.offset(),
1837
///         t.abbreviation().to_string(),
1838
///     ))
1839
///     .collect::<Vec<_>>();
1840
/// assert_eq!(transitions, vec![
1841
///     ("2025-03-09 03:00-04[US/Eastern]".parse()?, offset(-4), "EDT".to_string()),
1842
///     ("2025-11-02 01:00-05[US/Eastern]".parse()?, offset(-5), "EST".to_string()),
1843
///     ("2026-03-08 03:00-04[US/Eastern]".parse()?, offset(-4), "EDT".to_string()),
1844
///     ("2026-11-01 01:00-05[US/Eastern]".parse()?, offset(-5), "EST".to_string()),
1845
///     ("2027-03-14 03:00-04[US/Eastern]".parse()?, offset(-4), "EDT".to_string()),
1846
/// ]);
1847
///
1848
/// # Ok::<(), Box<dyn std::error::Error>>(())
1849
/// ```
1850
#[derive(Clone, Debug)]
1851
pub struct TimeZoneFollowingTransitions<'t> {
1852
    tz: &'t TimeZone,
1853
    cur: Timestamp,
1854
}
1855
1856
impl<'t> Iterator for TimeZoneFollowingTransitions<'t> {
1857
    type Item = TimeZoneTransition<'t>;
1858
1859
0
    fn next(&mut self) -> Option<TimeZoneTransition<'t>> {
1860
0
        let trans = self.tz.next_transition(self.cur)?;
1861
0
        self.cur = trans.timestamp();
1862
0
        Some(trans)
1863
0
    }
1864
}
1865
1866
impl<'t> core::iter::FusedIterator for TimeZoneFollowingTransitions<'t> {}
1867
1868
/// A helper type for converting a `TimeZone` to a succinct human readable
1869
/// description.
1870
///
1871
/// This is principally used in error messages in various places.
1872
///
1873
/// A previous iteration of this was just an `as_str() -> &str` method on
1874
/// `TimeZone`, but that's difficult to do without relying on dynamic memory
1875
/// allocation (or chunky arrays).
1876
pub(crate) struct DiagnosticName<'a>(&'a TimeZone);
1877
1878
impl<'a> core::fmt::Display for DiagnosticName<'a> {
1879
0
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1880
0
        repr::each! {
1881
0
            &self.0.repr,
1882
0
            UTC => f.write_str("UTC"),
1883
0
            UNKNOWN => f.write_str("Etc/Unknown"),
1884
0
            FIXED(offset) => offset.fmt(f),
1885
0
            STATIC_TZIF(tzif) => f.write_str(tzif.name().unwrap_or("Local")),
1886
0
            ARC_TZIF(tzif) => f.write_str(tzif.name().unwrap_or("Local")),
1887
0
            ARC_POSIX(posix) => posix.fmt(f),
1888
        }
1889
0
    }
1890
}
1891
1892
/// A light abstraction over different representations of a time zone
1893
/// abbreviation.
1894
///
1895
/// The lifetime parameter `'t` corresponds to the lifetime of the time zone
1896
/// that produced this abbreviation.
1897
#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
1898
pub(crate) enum TimeZoneAbbreviation<'t> {
1899
    /// For when the abbreviation is borrowed directly from other data. For
1900
    /// example, from TZif or from POSIX TZ strings.
1901
    Borrowed(&'t str),
1902
    /// For when the abbreviation has to be derived from other data. For
1903
    /// example, from a fixed offset.
1904
    ///
1905
    /// The idea here is that a `TimeZone` shouldn't need to store the
1906
    /// string representation of a fixed offset. Particularly in core-only
1907
    /// environments, this is quite wasteful. So we make the string on-demand
1908
    /// only when it's requested.
1909
    ///
1910
    /// An alternative design is to just implement `Display` and reuse
1911
    /// `Offset`'s `Display` impl, but then we couldn't offer a `-> &str` API.
1912
    /// I feel like that's just a bit overkill, and really just comes from the
1913
    /// core-only straight-jacket.
1914
    Owned(ArrayStr<9>),
1915
}
1916
1917
impl<'t> TimeZoneAbbreviation<'t> {
1918
    /// Returns this abbreviation as a string borrowed from `self`.
1919
    ///
1920
    /// Notice that, like `Cow`, the lifetime of the string returned is
1921
    /// tied to `self` and thus may be shorter than `'t`.
1922
0
    fn as_str<'a>(&'a self) -> &'a str {
1923
0
        match *self {
1924
0
            TimeZoneAbbreviation::Borrowed(s) => s,
1925
0
            TimeZoneAbbreviation::Owned(ref s) => s.as_str(),
1926
        }
1927
0
    }
1928
}
1929
1930
/// This module defines the internal representation of a `TimeZone`.
1931
///
1932
/// This module exists to _encapsulate_ the representation rigorously and
1933
/// expose a safe and sound API.
1934
// To squash warnings on older versions of Rust. Our polyfill below should
1935
// match what std does on newer versions of Rust, so the confusability should
1936
// be fine. ---AG
1937
#[allow(unstable_name_collisions)]
1938
mod repr {
1939
    use core::mem::ManuallyDrop;
1940
1941
    use crate::{
1942
        tz::tzif::TzifStatic,
1943
        util::{constant::unwrap, t},
1944
    };
1945
    #[cfg(feature = "alloc")]
1946
    use crate::{
1947
        tz::{posix::PosixTimeZoneOwned, tzif::TzifOwned},
1948
        util::sync::Arc,
1949
    };
1950
1951
    use super::Offset;
1952
1953
    // On Rust 1.84+, `StrictProvenancePolyfill` isn't actually used.
1954
    #[allow(unused_imports)]
1955
    use self::polyfill::{without_provenance, StrictProvenancePolyfill};
1956
1957
    /// A macro for "matching" over the time zone representation variants.
1958
    ///
1959
    /// This macro is safe to use.
1960
    ///
1961
    /// Note that the `ARC_TZIF` and `ARC_POSIX` branches are automatically
1962
    /// removed when `alloc` isn't enabled. Users of this macro needn't handle
1963
    /// the `cfg` themselves.
1964
    macro_rules! each {
1965
        (
1966
            $repr:expr,
1967
            UTC => $utc:expr,
1968
            UNKNOWN => $unknown:expr,
1969
            FIXED($offset:ident) => $fixed:expr,
1970
            STATIC_TZIF($static_tzif:ident) => $static_tzif_block:expr,
1971
            ARC_TZIF($arc_tzif:ident) => $arc_tzif_block:expr,
1972
            ARC_POSIX($arc_posix:ident) => $arc_posix_block:expr,
1973
        ) => {{
1974
            let repr = $repr;
1975
            match repr.tag() {
1976
                Repr::UTC => $utc,
1977
                Repr::UNKNOWN => $unknown,
1978
                Repr::FIXED => {
1979
                    // SAFETY: We've ensured our pointer tag is correct.
1980
                    let $offset = unsafe { repr.get_fixed() };
1981
                    $fixed
1982
                }
1983
                Repr::STATIC_TZIF => {
1984
                    // SAFETY: We've ensured our pointer tag is correct.
1985
                    let $static_tzif = unsafe { repr.get_static_tzif() };
1986
                    $static_tzif_block
1987
                }
1988
                #[cfg(feature = "alloc")]
1989
                Repr::ARC_TZIF => {
1990
                    // SAFETY: We've ensured our pointer tag is correct.
1991
                    let $arc_tzif = unsafe { repr.get_arc_tzif() };
1992
                    $arc_tzif_block
1993
                }
1994
                #[cfg(feature = "alloc")]
1995
                Repr::ARC_POSIX => {
1996
                    // SAFETY: We've ensured our pointer tag is correct.
1997
                    let $arc_posix = unsafe { repr.get_arc_posix() };
1998
                    $arc_posix_block
1999
                }
2000
                _ => {
2001
                    debug_assert!(false, "each: invalid time zone repr tag!");
2002
                    // SAFETY: The constructors for `Repr` guarantee that the
2003
                    // tag is always one of the values matched above.
2004
                    unsafe {
2005
                        core::hint::unreachable_unchecked();
2006
                    }
2007
                }
2008
            }
2009
        }};
2010
    }
2011
    pub(super) use each;
2012
2013
    /// The internal representation of a `TimeZone`.
2014
    ///
2015
    /// It has 6 different possible variants: `UTC`, `Etc/Unknown`, fixed
2016
    /// offset, `static` TZif, `Arc` TZif or `Arc` POSIX time zone.
2017
    ///
2018
    /// This design uses pointer tagging so that:
2019
    ///
2020
    /// * The size of a `TimeZone` stays no bigger than a single word.
2021
    /// * In core-only environments, a `TimeZone` can be created from
2022
    ///   compile-time TZif data without allocating.
2023
    /// * UTC, unknown and fixed offset time zone does not require allocating.
2024
    /// * We can still alloc for TZif and POSIX time zones created at runtime.
2025
    ///   (Allocating for TZif at runtime is the intended common case, and
2026
    ///   corresponds to reading `/usr/share/zoneinfo` entries.)
2027
    ///
2028
    /// We achieve this through pointer tagging and careful use of a strict
2029
    /// provenance polyfill (because of MSRV). We use the lower 4 bits of a
2030
    /// pointer to indicate which variant we have. This is sound because we
2031
    /// require all types that we allocate for to have a minimum alignment of
2032
    /// 8 bytes.
2033
    pub(super) struct Repr {
2034
        ptr: *const u8,
2035
    }
2036
2037
    impl Repr {
2038
        const BITS: usize = 0b111;
2039
        pub(super) const UTC: usize = 1;
2040
        pub(super) const UNKNOWN: usize = 2;
2041
        pub(super) const FIXED: usize = 3;
2042
        pub(super) const STATIC_TZIF: usize = 0;
2043
        pub(super) const ARC_TZIF: usize = 4;
2044
        pub(super) const ARC_POSIX: usize = 5;
2045
2046
        // The minimum alignment required for any heap allocated time zone
2047
        // variants. This is related to the number of tags. We have 6 distinct
2048
        // values above, which means we need an alignment of at least 6. Since
2049
        // alignment must be a power of 2, the smallest possible alignment
2050
        // is 8.
2051
        const ALIGN: usize = 8;
2052
2053
        /// Creates a representation for a `UTC` time zone.
2054
        #[inline]
2055
0
        pub(super) const fn utc() -> Repr {
2056
0
            let ptr = without_provenance(Repr::UTC);
2057
0
            Repr { ptr }
2058
0
        }
2059
2060
        /// Creates a representation for a `Etc/Unknown` time zone.
2061
        #[inline]
2062
0
        pub(super) const fn unknown() -> Repr {
2063
0
            let ptr = without_provenance(Repr::UNKNOWN);
2064
0
            Repr { ptr }
2065
0
        }
2066
2067
        /// Creates a representation for a fixed offset time zone.
2068
        #[inline]
2069
993
        pub(super) const fn fixed(offset: Offset) -> Repr {
2070
993
            let seconds = offset.seconds_ranged().get_unchecked();
2071
            // OK because offset is in -93599..=93599.
2072
993
            let shifted = unwrap!(
2073
993
                seconds.checked_shl(4),
2074
                "offset small enough for left shift by 4 bits",
2075
            );
2076
993
            assert!(usize::MAX >= 4_294_967_295);
2077
            // usize cast is okay because Jiff requires 32-bit.
2078
993
            let ptr = without_provenance((shifted as usize) | Repr::FIXED);
2079
993
            Repr { ptr }
2080
993
        }
2081
2082
        /// Creates a representation for a created-at-compile-time TZif time
2083
        /// zone.
2084
        ///
2085
        /// This can only be correctly called by the `jiff-static` proc macro.
2086
        #[inline]
2087
0
        pub(super) const fn static_tzif(tzif: &'static TzifStatic) -> Repr {
2088
0
            assert!(core::mem::align_of::<TzifStatic>() >= Repr::ALIGN);
2089
0
            let tzif = (tzif as *const TzifStatic).cast::<u8>();
2090
            // We very specifically do no materialize the pointer address here
2091
            // because 1) it's UB and 2) the compiler generally prevents. This
2092
            // is because in a const context, the specific pointer address
2093
            // cannot be relied upon. Yet, we still want to do pointer tagging.
2094
            //
2095
            // Thankfully, this is the only variant that is a pointer that
2096
            // we want to create in a const context. So we just make this
2097
            // variant's tag `0`, and thus, no explicit pointer tagging is
2098
            // required. (Because we ensure the alignment is at least 4, and
2099
            // thus the least significant 3 bits are 0.)
2100
            //
2101
            // If this ends up not working out or if we need to support
2102
            // another `static` variant, then we could perhaps to pointer
2103
            // tagging with pointer arithmetic (like what the `tagged-pointer`
2104
            // crate does). I haven't tried it though and I'm unclear if it
2105
            // work.
2106
0
            Repr { ptr: tzif }
2107
0
        }
2108
2109
        /// Creates a representation for a TZif time zone.
2110
        #[cfg(feature = "alloc")]
2111
        #[inline]
2112
0
        pub(super) fn arc_tzif(tzif: Arc<TzifOwned>) -> Repr {
2113
0
            assert!(core::mem::align_of::<TzifOwned>() >= Repr::ALIGN);
2114
0
            let tzif = Arc::into_raw(tzif).cast::<u8>();
2115
0
            assert!(tzif.addr() % 4 == 0);
2116
0
            let ptr = tzif.map_addr(|addr| addr | Repr::ARC_TZIF);
2117
0
            Repr { ptr }
2118
0
        }
2119
2120
        /// Creates a representation for a POSIX time zone.
2121
        #[cfg(feature = "alloc")]
2122
        #[inline]
2123
0
        pub(super) fn arc_posix(posix_tz: Arc<PosixTimeZoneOwned>) -> Repr {
2124
0
            assert!(
2125
0
                core::mem::align_of::<PosixTimeZoneOwned>() >= Repr::ALIGN
2126
            );
2127
0
            let posix_tz = Arc::into_raw(posix_tz).cast::<u8>();
2128
0
            assert!(posix_tz.addr() % 4 == 0);
2129
0
            let ptr = posix_tz.map_addr(|addr| addr | Repr::ARC_POSIX);
2130
0
            Repr { ptr }
2131
0
        }
2132
2133
        /// Gets the offset representation.
2134
        ///
2135
        /// # Safety
2136
        ///
2137
        /// Callers must ensure that the pointer tag is `FIXED`.
2138
        #[inline]
2139
1.17k
        pub(super) unsafe fn get_fixed(&self) -> Offset {
2140
            #[allow(unstable_name_collisions)]
2141
1.17k
            let addr = self.ptr.addr();
2142
            // NOTE: Because of sign extension, we need to case to `i32`
2143
            // before shifting.
2144
1.17k
            let seconds = t::SpanZoneOffset::new_unchecked((addr as i32) >> 4);
2145
1.17k
            Offset::from_seconds_ranged(seconds)
2146
1.17k
        }
2147
2148
        /// Returns true if and only if this representation corresponds to the
2149
        /// `Etc/Unknown` time zone.
2150
        #[inline]
2151
992
        pub(super) fn is_unknown(&self) -> bool {
2152
992
            self.tag() == Repr::UNKNOWN
2153
992
        }
2154
2155
        /// Gets the static TZif representation.
2156
        ///
2157
        /// # Safety
2158
        ///
2159
        /// Callers must ensure that the pointer tag is `STATIC_TZIF`.
2160
        #[inline]
2161
0
        pub(super) unsafe fn get_static_tzif(&self) -> &'static TzifStatic {
2162
            #[allow(unstable_name_collisions)]
2163
0
            let ptr = self.ptr.map_addr(|addr| addr & !Repr::BITS);
Unexecuted instantiation: <jiff::tz::timezone::repr::Repr>::get_static_tzif::{closure#0}
Unexecuted instantiation: <jiff::tz::timezone::repr::Repr>::get_static_tzif::{closure#0}
Unexecuted instantiation: <jiff::tz::timezone::repr::Repr>::get_static_tzif::{closure#0}
Unexecuted instantiation: <jiff::tz::timezone::repr::Repr>::get_static_tzif::{closure#0}
Unexecuted instantiation: <jiff::tz::timezone::repr::Repr>::get_static_tzif::{closure#0}
2164
            // SAFETY: Getting a `STATIC_TZIF` tag is only possible when
2165
            // `self.ptr` was constructed from a valid and aligned (to at least
2166
            // 4 bytes) `&TzifStatic` borrow. Which must be guaranteed by the
2167
            // caller. We've also removed the tag bits above, so we must now
2168
            // have the original pointer.
2169
0
            unsafe { &*ptr.cast::<TzifStatic>() }
2170
0
        }
2171
2172
        /// Gets the `Arc` TZif representation.
2173
        ///
2174
        /// # Safety
2175
        ///
2176
        /// Callers must ensure that the pointer tag is `ARC_TZIF`.
2177
        #[cfg(feature = "alloc")]
2178
        #[inline]
2179
0
        pub(super) unsafe fn get_arc_tzif<'a>(&'a self) -> &'a TzifOwned {
2180
0
            let ptr = self.ptr.map_addr(|addr| addr & !Repr::BITS);
Unexecuted instantiation: <jiff::tz::timezone::repr::Repr>::get_arc_tzif::{closure#0}
Unexecuted instantiation: <jiff::tz::timezone::repr::Repr>::get_arc_tzif::{closure#0}
Unexecuted instantiation: <jiff::tz::timezone::repr::Repr>::get_arc_tzif::{closure#0}
Unexecuted instantiation: <jiff::tz::timezone::repr::Repr>::get_arc_tzif::{closure#0}
Unexecuted instantiation: <jiff::tz::timezone::repr::Repr>::get_arc_tzif::{closure#0}
2181
            // SAFETY: Getting a `ARC_TZIF` tag is only possible when
2182
            // `self.ptr` was constructed from a valid and aligned
2183
            // (to at least 4 bytes) `Arc<TzifOwned>`. We've removed
2184
            // the tag bits above, so we must now have the original
2185
            // pointer.
2186
0
            let arc = ManuallyDrop::new(unsafe {
2187
0
                Arc::from_raw(ptr.cast::<TzifOwned>())
2188
            });
2189
            // SAFETY: The lifetime of the pointer returned is always
2190
            // valid as long as the strong count on `arc` is at least
2191
            // 1. Since the lifetime is no longer than `Repr` itself,
2192
            // and a `Repr` being alive implies there is at least 1
2193
            // for the strong `Arc` count, it follows that the lifetime
2194
            // returned here is correct.
2195
0
            unsafe { &*Arc::as_ptr(&arc) }
2196
0
        }
2197
2198
        /// Gets the `Arc` POSIX time zone representation.
2199
        ///
2200
        /// # Safety
2201
        ///
2202
        /// Callers must ensure that the pointer tag is `ARC_POSIX`.
2203
        #[cfg(feature = "alloc")]
2204
        #[inline]
2205
0
        pub(super) unsafe fn get_arc_posix<'a>(
2206
0
            &'a self,
2207
0
        ) -> &'a PosixTimeZoneOwned {
2208
0
            let ptr = self.ptr.map_addr(|addr| addr & !Repr::BITS);
Unexecuted instantiation: <jiff::tz::timezone::repr::Repr>::get_arc_posix::{closure#0}
Unexecuted instantiation: <jiff::tz::timezone::repr::Repr>::get_arc_posix::{closure#0}
Unexecuted instantiation: <jiff::tz::timezone::repr::Repr>::get_arc_posix::{closure#0}
Unexecuted instantiation: <jiff::tz::timezone::repr::Repr>::get_arc_posix::{closure#0}
Unexecuted instantiation: <jiff::tz::timezone::repr::Repr>::get_arc_posix::{closure#0}
2209
            // SAFETY: Getting a `ARC_POSIX` tag is only possible when
2210
            // `self.ptr` was constructed from a valid and aligned (to at least
2211
            // 4 bytes) `Arc<PosixTimeZoneOwned>`. We've removed the tag
2212
            // bits above, so we must now have the original pointer.
2213
0
            let arc = ManuallyDrop::new(unsafe {
2214
0
                Arc::from_raw(ptr.cast::<PosixTimeZoneOwned>())
2215
            });
2216
            // SAFETY: The lifetime of the pointer returned is always
2217
            // valid as long as the strong count on `arc` is at least
2218
            // 1. Since the lifetime is no longer than `Repr` itself,
2219
            // and a `Repr` being alive implies there is at least 1
2220
            // for the strong `Arc` count, it follows that the lifetime
2221
            // returned here is correct.
2222
0
            unsafe { &*Arc::as_ptr(&arc) }
2223
0
        }
2224
2225
        /// Returns the tag on the representation's pointer.
2226
        ///
2227
        /// The value is guaranteed to be one of the constant tag values.
2228
        #[inline]
2229
15.5k
        pub(super) fn tag(&self) -> usize {
2230
            #[allow(unstable_name_collisions)]
2231
            {
2232
15.5k
                self.ptr.addr() & Repr::BITS
2233
            }
2234
15.5k
        }
2235
2236
        /// Returns a dumb copy of this representation.
2237
        ///
2238
        /// # Safety
2239
        ///
2240
        /// Callers must ensure that this representation's tag is UTC,
2241
        /// UNKNOWN, FIXED or STATIC_TZIF.
2242
        ///
2243
        /// Namely, this specifically does not increment the ref count for
2244
        /// the `Arc` pointers when the tag is `ARC_TZIF` or `ARC_POSIX`.
2245
        /// This means that incorrect usage of this routine can lead to
2246
        /// use-after-free.
2247
        ///
2248
        /// NOTE: It would be nice if we could make this `copy` routine safe,
2249
        /// or at least panic if it's misused. But to do that, you need to know
2250
        /// the time zone variant. And to know the time zone variant, you need
2251
        /// to "look" at the tag in the pointer. And looking at the address of
2252
        /// a pointer in a `const` context is precarious.
2253
        #[inline]
2254
0
        pub(super) const unsafe fn copy(&self) -> Repr {
2255
0
            Repr { ptr: self.ptr }
2256
0
        }
2257
    }
2258
2259
    // SAFETY: We use automatic reference counting.
2260
    unsafe impl Send for Repr {}
2261
    // SAFETY: We don't use an interior mutability and otherwise don't permit
2262
    // any kind of mutation (other than for an `Arc` managing its ref counts)
2263
    // of a `Repr`.
2264
    unsafe impl Sync for Repr {}
2265
2266
    impl core::fmt::Debug for Repr {
2267
0
        fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
2268
0
            each! {
2269
0
                self,
2270
0
                UTC => f.write_str("UTC"),
2271
0
                UNKNOWN => f.write_str("Etc/Unknown"),
2272
0
                FIXED(offset) => core::fmt::Debug::fmt(&offset, f),
2273
                STATIC_TZIF(tzif) => {
2274
                    // The full debug output is a bit much, so constrain it.
2275
0
                    let field = tzif.name().unwrap_or("Local");
2276
0
                    f.debug_tuple("TZif").field(&field).finish()
2277
                },
2278
                ARC_TZIF(tzif) => {
2279
                    // The full debug output is a bit much, so constrain it.
2280
0
                    let field = tzif.name().unwrap_or("Local");
2281
0
                    f.debug_tuple("TZif").field(&field).finish()
2282
                },
2283
                ARC_POSIX(posix) => {
2284
0
                    f.write_str("Posix(")?;
2285
0
                    core::fmt::Display::fmt(&posix, f)?;
2286
0
                    f.write_str(")")
2287
                },
2288
            }
2289
0
        }
2290
    }
2291
2292
    impl Clone for Repr {
2293
        #[inline]
2294
1.05k
        fn clone(&self) -> Repr {
2295
            // This `match` is written in an exhaustive fashion so that if
2296
            // a new tag is added, it should be explicitly considered here.
2297
1.05k
            match self.tag() {
2298
                // These are all `Copy` and can just be memcpy'd as-is.
2299
                Repr::UTC
2300
                | Repr::UNKNOWN
2301
                | Repr::FIXED
2302
1.05k
                | Repr::STATIC_TZIF => Repr { ptr: self.ptr },
2303
                #[cfg(feature = "alloc")]
2304
                Repr::ARC_TZIF => {
2305
0
                    let ptr = self.ptr.map_addr(|addr| addr & !Repr::BITS);
Unexecuted instantiation: <jiff::tz::timezone::repr::Repr as core::clone::Clone>::clone::{closure#0}
Unexecuted instantiation: <jiff::tz::timezone::repr::Repr as core::clone::Clone>::clone::{closure#0}
Unexecuted instantiation: <jiff::tz::timezone::repr::Repr as core::clone::Clone>::clone::{closure#0}
Unexecuted instantiation: <jiff::tz::timezone::repr::Repr as core::clone::Clone>::clone::{closure#0}
Unexecuted instantiation: <jiff::tz::timezone::repr::Repr as core::clone::Clone>::clone::{closure#0}
2306
                    // SAFETY: Getting a `ARC_TZIF` tag is only possible when
2307
                    // `self.ptr` was constructed from a valid and aligned
2308
                    // (to at least 4 bytes) `Arc<TzifOwned>`. We've removed
2309
                    // the tag bits above, so we must now have the original
2310
                    // pointer.
2311
0
                    unsafe {
2312
0
                        Arc::increment_strong_count(ptr.cast::<TzifOwned>());
2313
0
                    }
2314
0
                    Repr { ptr: self.ptr }
2315
                }
2316
                #[cfg(feature = "alloc")]
2317
                Repr::ARC_POSIX => {
2318
0
                    let ptr = self.ptr.map_addr(|addr| addr & !Repr::BITS);
Unexecuted instantiation: <jiff::tz::timezone::repr::Repr as core::clone::Clone>::clone::{closure#1}
Unexecuted instantiation: <jiff::tz::timezone::repr::Repr as core::clone::Clone>::clone::{closure#1}
Unexecuted instantiation: <jiff::tz::timezone::repr::Repr as core::clone::Clone>::clone::{closure#1}
Unexecuted instantiation: <jiff::tz::timezone::repr::Repr as core::clone::Clone>::clone::{closure#1}
Unexecuted instantiation: <jiff::tz::timezone::repr::Repr as core::clone::Clone>::clone::{closure#1}
2319
                    // SAFETY: Getting a `ARC_POSIX` tag is only possible when
2320
                    // `self.ptr` was constructed from a valid and aligned (to
2321
                    // at least 4 bytes) `Arc<PosixTimeZoneOwned>`. We've
2322
                    // removed the tag bits above, so we must now have the
2323
                    // original pointer.
2324
0
                    unsafe {
2325
0
                        Arc::increment_strong_count(
2326
0
                            ptr.cast::<PosixTimeZoneOwned>(),
2327
0
                        );
2328
0
                    }
2329
0
                    Repr { ptr: self.ptr }
2330
                }
2331
                _ => {
2332
0
                    debug_assert!(false, "clone: invalid time zone repr tag!");
2333
                    // SAFETY: The constructors for `Repr` guarantee that the
2334
                    // tag is always one of the values matched above.
2335
                    unsafe {
2336
0
                        core::hint::unreachable_unchecked();
2337
                    }
2338
                }
2339
            }
2340
1.05k
        }
2341
    }
2342
2343
    impl Drop for Repr {
2344
        #[inline]
2345
5.59k
        fn drop(&mut self) {
2346
            // This `match` is written in an exhaustive fashion so that if
2347
            // a new tag is added, it should be explicitly considered here.
2348
5.59k
            match self.tag() {
2349
                // These are all `Copy` and have no destructor.
2350
                Repr::UTC
2351
                | Repr::UNKNOWN
2352
                | Repr::FIXED
2353
5.59k
                | Repr::STATIC_TZIF => {}
2354
                #[cfg(feature = "alloc")]
2355
                Repr::ARC_TZIF => {
2356
0
                    let ptr = self.ptr.map_addr(|addr| addr & !Repr::BITS);
Unexecuted instantiation: <jiff::tz::timezone::repr::Repr as core::ops::drop::Drop>::drop::{closure#0}
Unexecuted instantiation: <jiff::tz::timezone::repr::Repr as core::ops::drop::Drop>::drop::{closure#0}
Unexecuted instantiation: <jiff::tz::timezone::repr::Repr as core::ops::drop::Drop>::drop::{closure#0}
Unexecuted instantiation: <jiff::tz::timezone::repr::Repr as core::ops::drop::Drop>::drop::{closure#0}
Unexecuted instantiation: <jiff::tz::timezone::repr::Repr as core::ops::drop::Drop>::drop::{closure#0}
2357
                    // SAFETY: Getting a `ARC_TZIF` tag is only possible when
2358
                    // `self.ptr` was constructed from a valid and aligned
2359
                    // (to at least 4 bytes) `Arc<TzifOwned>`. We've removed
2360
                    // the tag bits above, so we must now have the original
2361
                    // pointer.
2362
0
                    unsafe {
2363
0
                        Arc::decrement_strong_count(ptr.cast::<TzifOwned>());
2364
0
                    }
2365
                }
2366
                #[cfg(feature = "alloc")]
2367
                Repr::ARC_POSIX => {
2368
0
                    let ptr = self.ptr.map_addr(|addr| addr & !Repr::BITS);
Unexecuted instantiation: <jiff::tz::timezone::repr::Repr as core::ops::drop::Drop>::drop::{closure#1}
Unexecuted instantiation: <jiff::tz::timezone::repr::Repr as core::ops::drop::Drop>::drop::{closure#1}
Unexecuted instantiation: <jiff::tz::timezone::repr::Repr as core::ops::drop::Drop>::drop::{closure#1}
Unexecuted instantiation: <jiff::tz::timezone::repr::Repr as core::ops::drop::Drop>::drop::{closure#1}
Unexecuted instantiation: <jiff::tz::timezone::repr::Repr as core::ops::drop::Drop>::drop::{closure#1}
2369
                    // SAFETY: Getting a `ARC_POSIX` tag is only possible when
2370
                    // `self.ptr` was constructed from a valid and aligned (to
2371
                    // at least 4 bytes) `Arc<PosixTimeZoneOwned>`. We've
2372
                    // removed the tag bits above, so we must now have the
2373
                    // original pointer.
2374
0
                    unsafe {
2375
0
                        Arc::decrement_strong_count(
2376
0
                            ptr.cast::<PosixTimeZoneOwned>(),
2377
0
                        );
2378
0
                    }
2379
                }
2380
                _ => {
2381
0
                    debug_assert!(false, "drop: invalid time zone repr tag!");
2382
                    // SAFETY: The constructors for `Repr` guarantee that the
2383
                    // tag is always one of the values matched above.
2384
                    unsafe {
2385
0
                        core::hint::unreachable_unchecked();
2386
                    }
2387
                }
2388
            }
2389
5.59k
        }
2390
    }
2391
2392
    impl Eq for Repr {}
2393
2394
    impl PartialEq for Repr {
2395
0
        fn eq(&self, other: &Repr) -> bool {
2396
0
            if self.tag() != other.tag() {
2397
0
                return false;
2398
0
            }
2399
0
            each! {
2400
0
                self,
2401
0
                UTC => true,
2402
0
                UNKNOWN => true,
2403
                // SAFETY: OK, because we know the tags are equivalent and
2404
                // `self` has a `FIXED` tag.
2405
0
                FIXED(offset) => offset == unsafe { other.get_fixed() },
2406
                // SAFETY: OK, because we know the tags are equivalent and
2407
                // `self` has a `STATIC_TZIF` tag.
2408
0
                STATIC_TZIF(tzif) => tzif == unsafe { other.get_static_tzif() },
2409
                // SAFETY: OK, because we know the tags are equivalent and
2410
                // `self` has an `ARC_TZIF` tag.
2411
0
                ARC_TZIF(tzif) => tzif == unsafe { other.get_arc_tzif() },
2412
                // SAFETY: OK, because we know the tags are equivalent and
2413
                // `self` has an `ARC_POSIX` tag.
2414
0
                ARC_POSIX(posix) => posix == unsafe { other.get_arc_posix() },
2415
            }
2416
0
        }
2417
    }
2418
2419
    /// This is a polyfill for a small subset of std's strict provenance APIs.
2420
    ///
2421
    /// The strict provenance APIs in `core` were stabilized in Rust 1.84,
2422
    /// but it will likely be a while before Jiff can use them. (At time of
2423
    /// writing, 2025-02-24, Jiff's MSRV is Rust 1.70.)
2424
    mod polyfill {
2425
993
        pub(super) const fn without_provenance(addr: usize) -> *const u8 {
2426
            // SAFETY: Every valid `usize` is also a valid pointer (but not
2427
            // necessarily legal to dereference).
2428
            //
2429
            // MSRV(1.84): We *really* ought to be using
2430
            // `core::ptr::without_provenance` here, but Jiff's MSRV prevents
2431
            // us.
2432
            #[allow(integer_to_ptr_transmutes)]
2433
            unsafe {
2434
993
                core::mem::transmute(addr)
2435
            }
2436
993
        }
2437
2438
        // On Rust 1.84+, `StrictProvenancePolyfill` isn't actually used.
2439
        #[allow(dead_code)]
2440
        pub(super) trait StrictProvenancePolyfill:
2441
            Sized + Clone + Copy
2442
        {
2443
            fn addr(&self) -> usize;
2444
            fn with_addr(&self, addr: usize) -> Self;
2445
0
            fn map_addr(&self, map: impl FnOnce(usize) -> usize) -> Self {
2446
0
                self.with_addr(map(self.addr()))
2447
0
            }
2448
        }
2449
2450
        impl StrictProvenancePolyfill for *const u8 {
2451
0
            fn addr(&self) -> usize {
2452
                // SAFETY: Pointer-to-integer transmutes are valid (if you are
2453
                // okay with losing the provenance).
2454
                //
2455
                // The implementation in std says that this isn't guaranteed to
2456
                // be sound outside of std, but I'm not sure how else to do it.
2457
                // In practice, this seems likely fine?
2458
0
                unsafe { core::mem::transmute(self.cast::<()>()) }
2459
0
            }
2460
2461
0
            fn with_addr(&self, address: usize) -> Self {
2462
0
                let self_addr = self.addr() as isize;
2463
0
                let dest_addr = address as isize;
2464
0
                let offset = dest_addr.wrapping_sub(self_addr);
2465
0
                self.wrapping_offset(offset)
2466
0
            }
2467
        }
2468
    }
2469
}
2470
2471
#[cfg(test)]
2472
mod tests {
2473
    #[cfg(feature = "alloc")]
2474
    use crate::tz::testdata::TzifTestFile;
2475
    use crate::{civil::date, tz::offset};
2476
2477
    use super::*;
2478
2479
    fn unambiguous(offset_hours: i8) -> AmbiguousOffset {
2480
        let offset = offset(offset_hours);
2481
        o_unambiguous(offset)
2482
    }
2483
2484
    fn gap(
2485
        earlier_offset_hours: i8,
2486
        later_offset_hours: i8,
2487
    ) -> AmbiguousOffset {
2488
        let earlier = offset(earlier_offset_hours);
2489
        let later = offset(later_offset_hours);
2490
        o_gap(earlier, later)
2491
    }
2492
2493
    fn fold(
2494
        earlier_offset_hours: i8,
2495
        later_offset_hours: i8,
2496
    ) -> AmbiguousOffset {
2497
        let earlier = offset(earlier_offset_hours);
2498
        let later = offset(later_offset_hours);
2499
        o_fold(earlier, later)
2500
    }
2501
2502
    fn o_unambiguous(offset: Offset) -> AmbiguousOffset {
2503
        AmbiguousOffset::Unambiguous { offset }
2504
    }
2505
2506
    fn o_gap(earlier: Offset, later: Offset) -> AmbiguousOffset {
2507
        AmbiguousOffset::Gap { before: earlier, after: later }
2508
    }
2509
2510
    fn o_fold(earlier: Offset, later: Offset) -> AmbiguousOffset {
2511
        AmbiguousOffset::Fold { before: earlier, after: later }
2512
    }
2513
2514
    #[cfg(feature = "alloc")]
2515
    #[test]
2516
    fn time_zone_tzif_to_ambiguous_timestamp() {
2517
        let tests: &[(&str, &[_])] = &[
2518
            (
2519
                "America/New_York",
2520
                &[
2521
                    ((1969, 12, 31, 19, 0, 0, 0), unambiguous(-5)),
2522
                    ((2024, 3, 10, 1, 59, 59, 999_999_999), unambiguous(-5)),
2523
                    ((2024, 3, 10, 2, 0, 0, 0), gap(-5, -4)),
2524
                    ((2024, 3, 10, 2, 59, 59, 999_999_999), gap(-5, -4)),
2525
                    ((2024, 3, 10, 3, 0, 0, 0), unambiguous(-4)),
2526
                    ((2024, 11, 3, 0, 59, 59, 999_999_999), unambiguous(-4)),
2527
                    ((2024, 11, 3, 1, 0, 0, 0), fold(-4, -5)),
2528
                    ((2024, 11, 3, 1, 59, 59, 999_999_999), fold(-4, -5)),
2529
                    ((2024, 11, 3, 2, 0, 0, 0), unambiguous(-5)),
2530
                ],
2531
            ),
2532
            (
2533
                "Europe/Dublin",
2534
                &[
2535
                    ((1970, 1, 1, 0, 0, 0, 0), unambiguous(1)),
2536
                    ((2024, 3, 31, 0, 59, 59, 999_999_999), unambiguous(0)),
2537
                    ((2024, 3, 31, 1, 0, 0, 0), gap(0, 1)),
2538
                    ((2024, 3, 31, 1, 59, 59, 999_999_999), gap(0, 1)),
2539
                    ((2024, 3, 31, 2, 0, 0, 0), unambiguous(1)),
2540
                    ((2024, 10, 27, 0, 59, 59, 999_999_999), unambiguous(1)),
2541
                    ((2024, 10, 27, 1, 0, 0, 0), fold(1, 0)),
2542
                    ((2024, 10, 27, 1, 59, 59, 999_999_999), fold(1, 0)),
2543
                    ((2024, 10, 27, 2, 0, 0, 0), unambiguous(0)),
2544
                ],
2545
            ),
2546
            (
2547
                "Australia/Tasmania",
2548
                &[
2549
                    ((1970, 1, 1, 11, 0, 0, 0), unambiguous(11)),
2550
                    ((2024, 4, 7, 1, 59, 59, 999_999_999), unambiguous(11)),
2551
                    ((2024, 4, 7, 2, 0, 0, 0), fold(11, 10)),
2552
                    ((2024, 4, 7, 2, 59, 59, 999_999_999), fold(11, 10)),
2553
                    ((2024, 4, 7, 3, 0, 0, 0), unambiguous(10)),
2554
                    ((2024, 10, 6, 1, 59, 59, 999_999_999), unambiguous(10)),
2555
                    ((2024, 10, 6, 2, 0, 0, 0), gap(10, 11)),
2556
                    ((2024, 10, 6, 2, 59, 59, 999_999_999), gap(10, 11)),
2557
                    ((2024, 10, 6, 3, 0, 0, 0), unambiguous(11)),
2558
                ],
2559
            ),
2560
            (
2561
                "Antarctica/Troll",
2562
                &[
2563
                    ((1970, 1, 1, 0, 0, 0, 0), unambiguous(0)),
2564
                    // test the gap
2565
                    ((2024, 3, 31, 0, 59, 59, 999_999_999), unambiguous(0)),
2566
                    ((2024, 3, 31, 1, 0, 0, 0), gap(0, 2)),
2567
                    ((2024, 3, 31, 1, 59, 59, 999_999_999), gap(0, 2)),
2568
                    // still in the gap!
2569
                    ((2024, 3, 31, 2, 0, 0, 0), gap(0, 2)),
2570
                    ((2024, 3, 31, 2, 59, 59, 999_999_999), gap(0, 2)),
2571
                    // finally out
2572
                    ((2024, 3, 31, 3, 0, 0, 0), unambiguous(2)),
2573
                    // test the fold
2574
                    ((2024, 10, 27, 0, 59, 59, 999_999_999), unambiguous(2)),
2575
                    ((2024, 10, 27, 1, 0, 0, 0), fold(2, 0)),
2576
                    ((2024, 10, 27, 1, 59, 59, 999_999_999), fold(2, 0)),
2577
                    // still in the fold!
2578
                    ((2024, 10, 27, 2, 0, 0, 0), fold(2, 0)),
2579
                    ((2024, 10, 27, 2, 59, 59, 999_999_999), fold(2, 0)),
2580
                    // finally out
2581
                    ((2024, 10, 27, 3, 0, 0, 0), unambiguous(0)),
2582
                ],
2583
            ),
2584
            (
2585
                "America/St_Johns",
2586
                &[
2587
                    (
2588
                        (1969, 12, 31, 20, 30, 0, 0),
2589
                        o_unambiguous(-Offset::hms(3, 30, 0)),
2590
                    ),
2591
                    (
2592
                        (2024, 3, 10, 1, 59, 59, 999_999_999),
2593
                        o_unambiguous(-Offset::hms(3, 30, 0)),
2594
                    ),
2595
                    (
2596
                        (2024, 3, 10, 2, 0, 0, 0),
2597
                        o_gap(-Offset::hms(3, 30, 0), -Offset::hms(2, 30, 0)),
2598
                    ),
2599
                    (
2600
                        (2024, 3, 10, 2, 59, 59, 999_999_999),
2601
                        o_gap(-Offset::hms(3, 30, 0), -Offset::hms(2, 30, 0)),
2602
                    ),
2603
                    (
2604
                        (2024, 3, 10, 3, 0, 0, 0),
2605
                        o_unambiguous(-Offset::hms(2, 30, 0)),
2606
                    ),
2607
                    (
2608
                        (2024, 11, 3, 0, 59, 59, 999_999_999),
2609
                        o_unambiguous(-Offset::hms(2, 30, 0)),
2610
                    ),
2611
                    (
2612
                        (2024, 11, 3, 1, 0, 0, 0),
2613
                        o_fold(-Offset::hms(2, 30, 0), -Offset::hms(3, 30, 0)),
2614
                    ),
2615
                    (
2616
                        (2024, 11, 3, 1, 59, 59, 999_999_999),
2617
                        o_fold(-Offset::hms(2, 30, 0), -Offset::hms(3, 30, 0)),
2618
                    ),
2619
                    (
2620
                        (2024, 11, 3, 2, 0, 0, 0),
2621
                        o_unambiguous(-Offset::hms(3, 30, 0)),
2622
                    ),
2623
                ],
2624
            ),
2625
            // This time zone has an interesting transition where it jumps
2626
            // backwards a full day at 1867-10-19T15:30:00.
2627
            (
2628
                "America/Sitka",
2629
                &[
2630
                    ((1969, 12, 31, 16, 0, 0, 0), unambiguous(-8)),
2631
                    (
2632
                        (-9999, 1, 2, 16, 58, 46, 0),
2633
                        o_unambiguous(Offset::hms(14, 58, 47)),
2634
                    ),
2635
                    (
2636
                        (1867, 10, 18, 15, 29, 59, 0),
2637
                        o_unambiguous(Offset::hms(14, 58, 47)),
2638
                    ),
2639
                    (
2640
                        (1867, 10, 18, 15, 30, 0, 0),
2641
                        // A fold of 24 hours!!!
2642
                        o_fold(
2643
                            Offset::hms(14, 58, 47),
2644
                            -Offset::hms(9, 1, 13),
2645
                        ),
2646
                    ),
2647
                    (
2648
                        (1867, 10, 19, 15, 29, 59, 999_999_999),
2649
                        // Still in the fold...
2650
                        o_fold(
2651
                            Offset::hms(14, 58, 47),
2652
                            -Offset::hms(9, 1, 13),
2653
                        ),
2654
                    ),
2655
                    (
2656
                        (1867, 10, 19, 15, 30, 0, 0),
2657
                        // Finally out.
2658
                        o_unambiguous(-Offset::hms(9, 1, 13)),
2659
                    ),
2660
                ],
2661
            ),
2662
            // As with to_datetime, we test every possible transition
2663
            // point here since this time zone has a small number of them.
2664
            (
2665
                "Pacific/Honolulu",
2666
                &[
2667
                    (
2668
                        (1896, 1, 13, 11, 59, 59, 0),
2669
                        o_unambiguous(-Offset::hms(10, 31, 26)),
2670
                    ),
2671
                    (
2672
                        (1896, 1, 13, 12, 0, 0, 0),
2673
                        o_gap(
2674
                            -Offset::hms(10, 31, 26),
2675
                            -Offset::hms(10, 30, 0),
2676
                        ),
2677
                    ),
2678
                    (
2679
                        (1896, 1, 13, 12, 1, 25, 0),
2680
                        o_gap(
2681
                            -Offset::hms(10, 31, 26),
2682
                            -Offset::hms(10, 30, 0),
2683
                        ),
2684
                    ),
2685
                    (
2686
                        (1896, 1, 13, 12, 1, 26, 0),
2687
                        o_unambiguous(-Offset::hms(10, 30, 0)),
2688
                    ),
2689
                    (
2690
                        (1933, 4, 30, 1, 59, 59, 0),
2691
                        o_unambiguous(-Offset::hms(10, 30, 0)),
2692
                    ),
2693
                    (
2694
                        (1933, 4, 30, 2, 0, 0, 0),
2695
                        o_gap(-Offset::hms(10, 30, 0), -Offset::hms(9, 30, 0)),
2696
                    ),
2697
                    (
2698
                        (1933, 4, 30, 2, 59, 59, 0),
2699
                        o_gap(-Offset::hms(10, 30, 0), -Offset::hms(9, 30, 0)),
2700
                    ),
2701
                    (
2702
                        (1933, 4, 30, 3, 0, 0, 0),
2703
                        o_unambiguous(-Offset::hms(9, 30, 0)),
2704
                    ),
2705
                    (
2706
                        (1933, 5, 21, 10, 59, 59, 0),
2707
                        o_unambiguous(-Offset::hms(9, 30, 0)),
2708
                    ),
2709
                    (
2710
                        (1933, 5, 21, 11, 0, 0, 0),
2711
                        o_fold(
2712
                            -Offset::hms(9, 30, 0),
2713
                            -Offset::hms(10, 30, 0),
2714
                        ),
2715
                    ),
2716
                    (
2717
                        (1933, 5, 21, 11, 59, 59, 0),
2718
                        o_fold(
2719
                            -Offset::hms(9, 30, 0),
2720
                            -Offset::hms(10, 30, 0),
2721
                        ),
2722
                    ),
2723
                    (
2724
                        (1933, 5, 21, 12, 0, 0, 0),
2725
                        o_unambiguous(-Offset::hms(10, 30, 0)),
2726
                    ),
2727
                    (
2728
                        (1942, 2, 9, 1, 59, 59, 0),
2729
                        o_unambiguous(-Offset::hms(10, 30, 0)),
2730
                    ),
2731
                    (
2732
                        (1942, 2, 9, 2, 0, 0, 0),
2733
                        o_gap(-Offset::hms(10, 30, 0), -Offset::hms(9, 30, 0)),
2734
                    ),
2735
                    (
2736
                        (1942, 2, 9, 2, 59, 59, 0),
2737
                        o_gap(-Offset::hms(10, 30, 0), -Offset::hms(9, 30, 0)),
2738
                    ),
2739
                    (
2740
                        (1942, 2, 9, 3, 0, 0, 0),
2741
                        o_unambiguous(-Offset::hms(9, 30, 0)),
2742
                    ),
2743
                    (
2744
                        (1945, 8, 14, 13, 29, 59, 0),
2745
                        o_unambiguous(-Offset::hms(9, 30, 0)),
2746
                    ),
2747
                    (
2748
                        (1945, 8, 14, 13, 30, 0, 0),
2749
                        o_unambiguous(-Offset::hms(9, 30, 0)),
2750
                    ),
2751
                    (
2752
                        (1945, 8, 14, 13, 30, 1, 0),
2753
                        o_unambiguous(-Offset::hms(9, 30, 0)),
2754
                    ),
2755
                    (
2756
                        (1945, 9, 30, 0, 59, 59, 0),
2757
                        o_unambiguous(-Offset::hms(9, 30, 0)),
2758
                    ),
2759
                    (
2760
                        (1945, 9, 30, 1, 0, 0, 0),
2761
                        o_fold(
2762
                            -Offset::hms(9, 30, 0),
2763
                            -Offset::hms(10, 30, 0),
2764
                        ),
2765
                    ),
2766
                    (
2767
                        (1945, 9, 30, 1, 59, 59, 0),
2768
                        o_fold(
2769
                            -Offset::hms(9, 30, 0),
2770
                            -Offset::hms(10, 30, 0),
2771
                        ),
2772
                    ),
2773
                    (
2774
                        (1945, 9, 30, 2, 0, 0, 0),
2775
                        o_unambiguous(-Offset::hms(10, 30, 0)),
2776
                    ),
2777
                    (
2778
                        (1947, 6, 8, 1, 59, 59, 0),
2779
                        o_unambiguous(-Offset::hms(10, 30, 0)),
2780
                    ),
2781
                    (
2782
                        (1947, 6, 8, 2, 0, 0, 0),
2783
                        o_gap(-Offset::hms(10, 30, 0), -offset(10)),
2784
                    ),
2785
                    (
2786
                        (1947, 6, 8, 2, 29, 59, 0),
2787
                        o_gap(-Offset::hms(10, 30, 0), -offset(10)),
2788
                    ),
2789
                    ((1947, 6, 8, 2, 30, 0, 0), unambiguous(-10)),
2790
                ],
2791
            ),
2792
        ];
2793
        for &(tzname, datetimes_to_ambiguous) in tests {
2794
            let test_file = TzifTestFile::get(tzname);
2795
            let tz = TimeZone::tzif(test_file.name, test_file.data).unwrap();
2796
            for &(datetime, ambiguous_kind) in datetimes_to_ambiguous {
2797
                let (year, month, day, hour, min, sec, nano) = datetime;
2798
                let dt = date(year, month, day).at(hour, min, sec, nano);
2799
                let got = tz.to_ambiguous_zoned(dt);
2800
                assert_eq!(
2801
                    got.offset(),
2802
                    ambiguous_kind,
2803
                    "\nTZ: {tzname}\ndatetime: \
2804
                     {year:04}-{month:02}-{day:02}T\
2805
                     {hour:02}:{min:02}:{sec:02}.{nano:09}",
2806
                );
2807
            }
2808
        }
2809
    }
2810
2811
    #[cfg(feature = "alloc")]
2812
    #[test]
2813
    fn time_zone_tzif_to_datetime() {
2814
        let o = |hours| offset(hours);
2815
        let tests: &[(&str, &[_])] = &[
2816
            (
2817
                "America/New_York",
2818
                &[
2819
                    ((0, 0), o(-5), "EST", (1969, 12, 31, 19, 0, 0, 0)),
2820
                    (
2821
                        (1710052200, 0),
2822
                        o(-5),
2823
                        "EST",
2824
                        (2024, 3, 10, 1, 30, 0, 0),
2825
                    ),
2826
                    (
2827
                        (1710053999, 999_999_999),
2828
                        o(-5),
2829
                        "EST",
2830
                        (2024, 3, 10, 1, 59, 59, 999_999_999),
2831
                    ),
2832
                    ((1710054000, 0), o(-4), "EDT", (2024, 3, 10, 3, 0, 0, 0)),
2833
                    (
2834
                        (1710055800, 0),
2835
                        o(-4),
2836
                        "EDT",
2837
                        (2024, 3, 10, 3, 30, 0, 0),
2838
                    ),
2839
                    ((1730610000, 0), o(-4), "EDT", (2024, 11, 3, 1, 0, 0, 0)),
2840
                    (
2841
                        (1730611800, 0),
2842
                        o(-4),
2843
                        "EDT",
2844
                        (2024, 11, 3, 1, 30, 0, 0),
2845
                    ),
2846
                    (
2847
                        (1730613599, 999_999_999),
2848
                        o(-4),
2849
                        "EDT",
2850
                        (2024, 11, 3, 1, 59, 59, 999_999_999),
2851
                    ),
2852
                    ((1730613600, 0), o(-5), "EST", (2024, 11, 3, 1, 0, 0, 0)),
2853
                    (
2854
                        (1730615400, 0),
2855
                        o(-5),
2856
                        "EST",
2857
                        (2024, 11, 3, 1, 30, 0, 0),
2858
                    ),
2859
                ],
2860
            ),
2861
            (
2862
                "Australia/Tasmania",
2863
                &[
2864
                    ((0, 0), o(11), "AEDT", (1970, 1, 1, 11, 0, 0, 0)),
2865
                    (
2866
                        (1728142200, 0),
2867
                        o(10),
2868
                        "AEST",
2869
                        (2024, 10, 6, 1, 30, 0, 0),
2870
                    ),
2871
                    (
2872
                        (1728143999, 999_999_999),
2873
                        o(10),
2874
                        "AEST",
2875
                        (2024, 10, 6, 1, 59, 59, 999_999_999),
2876
                    ),
2877
                    (
2878
                        (1728144000, 0),
2879
                        o(11),
2880
                        "AEDT",
2881
                        (2024, 10, 6, 3, 0, 0, 0),
2882
                    ),
2883
                    (
2884
                        (1728145800, 0),
2885
                        o(11),
2886
                        "AEDT",
2887
                        (2024, 10, 6, 3, 30, 0, 0),
2888
                    ),
2889
                    ((1712415600, 0), o(11), "AEDT", (2024, 4, 7, 2, 0, 0, 0)),
2890
                    (
2891
                        (1712417400, 0),
2892
                        o(11),
2893
                        "AEDT",
2894
                        (2024, 4, 7, 2, 30, 0, 0),
2895
                    ),
2896
                    (
2897
                        (1712419199, 999_999_999),
2898
                        o(11),
2899
                        "AEDT",
2900
                        (2024, 4, 7, 2, 59, 59, 999_999_999),
2901
                    ),
2902
                    ((1712419200, 0), o(10), "AEST", (2024, 4, 7, 2, 0, 0, 0)),
2903
                    (
2904
                        (1712421000, 0),
2905
                        o(10),
2906
                        "AEST",
2907
                        (2024, 4, 7, 2, 30, 0, 0),
2908
                    ),
2909
                ],
2910
            ),
2911
            // Pacific/Honolulu is small eough that we just test every
2912
            // possible instant before, at and after each transition.
2913
            (
2914
                "Pacific/Honolulu",
2915
                &[
2916
                    (
2917
                        (-2334101315, 0),
2918
                        -Offset::hms(10, 31, 26),
2919
                        "LMT",
2920
                        (1896, 1, 13, 11, 59, 59, 0),
2921
                    ),
2922
                    (
2923
                        (-2334101314, 0),
2924
                        -Offset::hms(10, 30, 0),
2925
                        "HST",
2926
                        (1896, 1, 13, 12, 1, 26, 0),
2927
                    ),
2928
                    (
2929
                        (-2334101313, 0),
2930
                        -Offset::hms(10, 30, 0),
2931
                        "HST",
2932
                        (1896, 1, 13, 12, 1, 27, 0),
2933
                    ),
2934
                    (
2935
                        (-1157283001, 0),
2936
                        -Offset::hms(10, 30, 0),
2937
                        "HST",
2938
                        (1933, 4, 30, 1, 59, 59, 0),
2939
                    ),
2940
                    (
2941
                        (-1157283000, 0),
2942
                        -Offset::hms(9, 30, 0),
2943
                        "HDT",
2944
                        (1933, 4, 30, 3, 0, 0, 0),
2945
                    ),
2946
                    (
2947
                        (-1157282999, 0),
2948
                        -Offset::hms(9, 30, 0),
2949
                        "HDT",
2950
                        (1933, 4, 30, 3, 0, 1, 0),
2951
                    ),
2952
                    (
2953
                        (-1155436201, 0),
2954
                        -Offset::hms(9, 30, 0),
2955
                        "HDT",
2956
                        (1933, 5, 21, 11, 59, 59, 0),
2957
                    ),
2958
                    (
2959
                        (-1155436200, 0),
2960
                        -Offset::hms(10, 30, 0),
2961
                        "HST",
2962
                        (1933, 5, 21, 11, 0, 0, 0),
2963
                    ),
2964
                    (
2965
                        (-1155436199, 0),
2966
                        -Offset::hms(10, 30, 0),
2967
                        "HST",
2968
                        (1933, 5, 21, 11, 0, 1, 0),
2969
                    ),
2970
                    (
2971
                        (-880198201, 0),
2972
                        -Offset::hms(10, 30, 0),
2973
                        "HST",
2974
                        (1942, 2, 9, 1, 59, 59, 0),
2975
                    ),
2976
                    (
2977
                        (-880198200, 0),
2978
                        -Offset::hms(9, 30, 0),
2979
                        "HWT",
2980
                        (1942, 2, 9, 3, 0, 0, 0),
2981
                    ),
2982
                    (
2983
                        (-880198199, 0),
2984
                        -Offset::hms(9, 30, 0),
2985
                        "HWT",
2986
                        (1942, 2, 9, 3, 0, 1, 0),
2987
                    ),
2988
                    (
2989
                        (-769395601, 0),
2990
                        -Offset::hms(9, 30, 0),
2991
                        "HWT",
2992
                        (1945, 8, 14, 13, 29, 59, 0),
2993
                    ),
2994
                    (
2995
                        (-769395600, 0),
2996
                        -Offset::hms(9, 30, 0),
2997
                        "HPT",
2998
                        (1945, 8, 14, 13, 30, 0, 0),
2999
                    ),
3000
                    (
3001
                        (-769395599, 0),
3002
                        -Offset::hms(9, 30, 0),
3003
                        "HPT",
3004
                        (1945, 8, 14, 13, 30, 1, 0),
3005
                    ),
3006
                    (
3007
                        (-765376201, 0),
3008
                        -Offset::hms(9, 30, 0),
3009
                        "HPT",
3010
                        (1945, 9, 30, 1, 59, 59, 0),
3011
                    ),
3012
                    (
3013
                        (-765376200, 0),
3014
                        -Offset::hms(10, 30, 0),
3015
                        "HST",
3016
                        (1945, 9, 30, 1, 0, 0, 0),
3017
                    ),
3018
                    (
3019
                        (-765376199, 0),
3020
                        -Offset::hms(10, 30, 0),
3021
                        "HST",
3022
                        (1945, 9, 30, 1, 0, 1, 0),
3023
                    ),
3024
                    (
3025
                        (-712150201, 0),
3026
                        -Offset::hms(10, 30, 0),
3027
                        "HST",
3028
                        (1947, 6, 8, 1, 59, 59, 0),
3029
                    ),
3030
                    // At this point, we hit the last transition and the POSIX
3031
                    // TZ string takes over.
3032
                    (
3033
                        (-712150200, 0),
3034
                        -Offset::hms(10, 0, 0),
3035
                        "HST",
3036
                        (1947, 6, 8, 2, 30, 0, 0),
3037
                    ),
3038
                    (
3039
                        (-712150199, 0),
3040
                        -Offset::hms(10, 0, 0),
3041
                        "HST",
3042
                        (1947, 6, 8, 2, 30, 1, 0),
3043
                    ),
3044
                ],
3045
            ),
3046
            // This time zone has an interesting transition where it jumps
3047
            // backwards a full day at 1867-10-19T15:30:00.
3048
            (
3049
                "America/Sitka",
3050
                &[
3051
                    ((0, 0), o(-8), "PST", (1969, 12, 31, 16, 0, 0, 0)),
3052
                    (
3053
                        (-377705023201, 0),
3054
                        Offset::hms(14, 58, 47),
3055
                        "LMT",
3056
                        (-9999, 1, 2, 16, 58, 46, 0),
3057
                    ),
3058
                    (
3059
                        (-3225223728, 0),
3060
                        Offset::hms(14, 58, 47),
3061
                        "LMT",
3062
                        (1867, 10, 19, 15, 29, 59, 0),
3063
                    ),
3064
                    // Notice the 24 hour time jump backwards a whole day!
3065
                    (
3066
                        (-3225223727, 0),
3067
                        -Offset::hms(9, 1, 13),
3068
                        "LMT",
3069
                        (1867, 10, 18, 15, 30, 0, 0),
3070
                    ),
3071
                    (
3072
                        (-3225223726, 0),
3073
                        -Offset::hms(9, 1, 13),
3074
                        "LMT",
3075
                        (1867, 10, 18, 15, 30, 1, 0),
3076
                    ),
3077
                ],
3078
            ),
3079
        ];
3080
        for &(tzname, timestamps_to_datetimes) in tests {
3081
            let test_file = TzifTestFile::get(tzname);
3082
            let tz = TimeZone::tzif(test_file.name, test_file.data).unwrap();
3083
            for &((unix_sec, unix_nano), offset, abbrev, datetime) in
3084
                timestamps_to_datetimes
3085
            {
3086
                let (year, month, day, hour, min, sec, nano) = datetime;
3087
                let timestamp = Timestamp::new(unix_sec, unix_nano).unwrap();
3088
                let info = tz.to_offset_info(timestamp);
3089
                assert_eq!(
3090
                    info.offset(),
3091
                    offset,
3092
                    "\nTZ={tzname}, timestamp({unix_sec}, {unix_nano})",
3093
                );
3094
                assert_eq!(
3095
                    info.abbreviation(),
3096
                    abbrev,
3097
                    "\nTZ={tzname}, timestamp({unix_sec}, {unix_nano})",
3098
                );
3099
                assert_eq!(
3100
                    info.offset().to_datetime(timestamp),
3101
                    date(year, month, day).at(hour, min, sec, nano),
3102
                    "\nTZ={tzname}, timestamp({unix_sec}, {unix_nano})",
3103
                );
3104
            }
3105
        }
3106
    }
3107
3108
    #[cfg(feature = "alloc")]
3109
    #[test]
3110
    fn time_zone_posix_to_ambiguous_timestamp() {
3111
        let tests: &[(&str, &[_])] = &[
3112
            // America/New_York, but a utopia in which DST is abolished.
3113
            (
3114
                "EST5",
3115
                &[
3116
                    ((1969, 12, 31, 19, 0, 0, 0), unambiguous(-5)),
3117
                    ((2024, 3, 10, 2, 0, 0, 0), unambiguous(-5)),
3118
                ],
3119
            ),
3120
            // The standard DST rule for America/New_York.
3121
            (
3122
                "EST5EDT,M3.2.0,M11.1.0",
3123
                &[
3124
                    ((1969, 12, 31, 19, 0, 0, 0), unambiguous(-5)),
3125
                    ((2024, 3, 10, 1, 59, 59, 999_999_999), unambiguous(-5)),
3126
                    ((2024, 3, 10, 2, 0, 0, 0), gap(-5, -4)),
3127
                    ((2024, 3, 10, 2, 59, 59, 999_999_999), gap(-5, -4)),
3128
                    ((2024, 3, 10, 3, 0, 0, 0), unambiguous(-4)),
3129
                    ((2024, 11, 3, 0, 59, 59, 999_999_999), unambiguous(-4)),
3130
                    ((2024, 11, 3, 1, 0, 0, 0), fold(-4, -5)),
3131
                    ((2024, 11, 3, 1, 59, 59, 999_999_999), fold(-4, -5)),
3132
                    ((2024, 11, 3, 2, 0, 0, 0), unambiguous(-5)),
3133
                ],
3134
            ),
3135
            // A bit of a nonsensical America/New_York that has DST, but whose
3136
            // offset is equivalent to standard time. Having the same offset
3137
            // means there's never any ambiguity.
3138
            (
3139
                "EST5EDT5,M3.2.0,M11.1.0",
3140
                &[
3141
                    ((1969, 12, 31, 19, 0, 0, 0), unambiguous(-5)),
3142
                    ((2024, 3, 10, 1, 59, 59, 999_999_999), unambiguous(-5)),
3143
                    ((2024, 3, 10, 2, 0, 0, 0), unambiguous(-5)),
3144
                    ((2024, 3, 10, 2, 59, 59, 999_999_999), unambiguous(-5)),
3145
                    ((2024, 3, 10, 3, 0, 0, 0), unambiguous(-5)),
3146
                    ((2024, 11, 3, 0, 59, 59, 999_999_999), unambiguous(-5)),
3147
                    ((2024, 11, 3, 1, 0, 0, 0), unambiguous(-5)),
3148
                    ((2024, 11, 3, 1, 59, 59, 999_999_999), unambiguous(-5)),
3149
                    ((2024, 11, 3, 2, 0, 0, 0), unambiguous(-5)),
3150
                ],
3151
            ),
3152
            // This is Europe/Dublin's rule. It's interesting because its
3153
            // DST is an offset behind standard time. (DST is usually one hour
3154
            // ahead of standard time.)
3155
            (
3156
                "IST-1GMT0,M10.5.0,M3.5.0/1",
3157
                &[
3158
                    ((1970, 1, 1, 0, 0, 0, 0), unambiguous(0)),
3159
                    ((2024, 3, 31, 0, 59, 59, 999_999_999), unambiguous(0)),
3160
                    ((2024, 3, 31, 1, 0, 0, 0), gap(0, 1)),
3161
                    ((2024, 3, 31, 1, 59, 59, 999_999_999), gap(0, 1)),
3162
                    ((2024, 3, 31, 2, 0, 0, 0), unambiguous(1)),
3163
                    ((2024, 10, 27, 0, 59, 59, 999_999_999), unambiguous(1)),
3164
                    ((2024, 10, 27, 1, 0, 0, 0), fold(1, 0)),
3165
                    ((2024, 10, 27, 1, 59, 59, 999_999_999), fold(1, 0)),
3166
                    ((2024, 10, 27, 2, 0, 0, 0), unambiguous(0)),
3167
                ],
3168
            ),
3169
            // This is Australia/Tasmania's rule. We chose this because it's
3170
            // in the southern hemisphere where DST still skips ahead one hour,
3171
            // but it usually starts in the fall and ends in the spring.
3172
            (
3173
                "AEST-10AEDT,M10.1.0,M4.1.0/3",
3174
                &[
3175
                    ((1970, 1, 1, 11, 0, 0, 0), unambiguous(11)),
3176
                    ((2024, 4, 7, 1, 59, 59, 999_999_999), unambiguous(11)),
3177
                    ((2024, 4, 7, 2, 0, 0, 0), fold(11, 10)),
3178
                    ((2024, 4, 7, 2, 59, 59, 999_999_999), fold(11, 10)),
3179
                    ((2024, 4, 7, 3, 0, 0, 0), unambiguous(10)),
3180
                    ((2024, 10, 6, 1, 59, 59, 999_999_999), unambiguous(10)),
3181
                    ((2024, 10, 6, 2, 0, 0, 0), gap(10, 11)),
3182
                    ((2024, 10, 6, 2, 59, 59, 999_999_999), gap(10, 11)),
3183
                    ((2024, 10, 6, 3, 0, 0, 0), unambiguous(11)),
3184
                ],
3185
            ),
3186
            // This is Antarctica/Troll's rule. We chose this one because its
3187
            // DST transition is 2 hours instead of the standard 1 hour. This
3188
            // means gaps and folds are twice as long as they usually are. And
3189
            // it means there are 22 hour and 26 hour days, respectively. Wow!
3190
            (
3191
                "<+00>0<+02>-2,M3.5.0/1,M10.5.0/3",
3192
                &[
3193
                    ((1970, 1, 1, 0, 0, 0, 0), unambiguous(0)),
3194
                    // test the gap
3195
                    ((2024, 3, 31, 0, 59, 59, 999_999_999), unambiguous(0)),
3196
                    ((2024, 3, 31, 1, 0, 0, 0), gap(0, 2)),
3197
                    ((2024, 3, 31, 1, 59, 59, 999_999_999), gap(0, 2)),
3198
                    // still in the gap!
3199
                    ((2024, 3, 31, 2, 0, 0, 0), gap(0, 2)),
3200
                    ((2024, 3, 31, 2, 59, 59, 999_999_999), gap(0, 2)),
3201
                    // finally out
3202
                    ((2024, 3, 31, 3, 0, 0, 0), unambiguous(2)),
3203
                    // test the fold
3204
                    ((2024, 10, 27, 0, 59, 59, 999_999_999), unambiguous(2)),
3205
                    ((2024, 10, 27, 1, 0, 0, 0), fold(2, 0)),
3206
                    ((2024, 10, 27, 1, 59, 59, 999_999_999), fold(2, 0)),
3207
                    // still in the fold!
3208
                    ((2024, 10, 27, 2, 0, 0, 0), fold(2, 0)),
3209
                    ((2024, 10, 27, 2, 59, 59, 999_999_999), fold(2, 0)),
3210
                    // finally out
3211
                    ((2024, 10, 27, 3, 0, 0, 0), unambiguous(0)),
3212
                ],
3213
            ),
3214
            // This is America/St_Johns' rule, which has an offset with
3215
            // non-zero minutes *and* a DST transition rule. (Indian Standard
3216
            // Time is the one I'm more familiar with, but it turns out IST
3217
            // does not have DST!)
3218
            (
3219
                "NST3:30NDT,M3.2.0,M11.1.0",
3220
                &[
3221
                    (
3222
                        (1969, 12, 31, 20, 30, 0, 0),
3223
                        o_unambiguous(-Offset::hms(3, 30, 0)),
3224
                    ),
3225
                    (
3226
                        (2024, 3, 10, 1, 59, 59, 999_999_999),
3227
                        o_unambiguous(-Offset::hms(3, 30, 0)),
3228
                    ),
3229
                    (
3230
                        (2024, 3, 10, 2, 0, 0, 0),
3231
                        o_gap(-Offset::hms(3, 30, 0), -Offset::hms(2, 30, 0)),
3232
                    ),
3233
                    (
3234
                        (2024, 3, 10, 2, 59, 59, 999_999_999),
3235
                        o_gap(-Offset::hms(3, 30, 0), -Offset::hms(2, 30, 0)),
3236
                    ),
3237
                    (
3238
                        (2024, 3, 10, 3, 0, 0, 0),
3239
                        o_unambiguous(-Offset::hms(2, 30, 0)),
3240
                    ),
3241
                    (
3242
                        (2024, 11, 3, 0, 59, 59, 999_999_999),
3243
                        o_unambiguous(-Offset::hms(2, 30, 0)),
3244
                    ),
3245
                    (
3246
                        (2024, 11, 3, 1, 0, 0, 0),
3247
                        o_fold(-Offset::hms(2, 30, 0), -Offset::hms(3, 30, 0)),
3248
                    ),
3249
                    (
3250
                        (2024, 11, 3, 1, 59, 59, 999_999_999),
3251
                        o_fold(-Offset::hms(2, 30, 0), -Offset::hms(3, 30, 0)),
3252
                    ),
3253
                    (
3254
                        (2024, 11, 3, 2, 0, 0, 0),
3255
                        o_unambiguous(-Offset::hms(3, 30, 0)),
3256
                    ),
3257
                ],
3258
            ),
3259
        ];
3260
        for &(posix_tz, datetimes_to_ambiguous) in tests {
3261
            let tz = TimeZone::posix(posix_tz).unwrap();
3262
            for &(datetime, ambiguous_kind) in datetimes_to_ambiguous {
3263
                let (year, month, day, hour, min, sec, nano) = datetime;
3264
                let dt = date(year, month, day).at(hour, min, sec, nano);
3265
                let got = tz.to_ambiguous_zoned(dt);
3266
                assert_eq!(
3267
                    got.offset(),
3268
                    ambiguous_kind,
3269
                    "\nTZ: {posix_tz}\ndatetime: \
3270
                     {year:04}-{month:02}-{day:02}T\
3271
                     {hour:02}:{min:02}:{sec:02}.{nano:09}",
3272
                );
3273
            }
3274
        }
3275
    }
3276
3277
    #[cfg(feature = "alloc")]
3278
    #[test]
3279
    fn time_zone_posix_to_datetime() {
3280
        let o = |hours| offset(hours);
3281
        let tests: &[(&str, &[_])] = &[
3282
            ("EST5", &[((0, 0), o(-5), (1969, 12, 31, 19, 0, 0, 0))]),
3283
            (
3284
                // From America/New_York
3285
                "EST5EDT,M3.2.0,M11.1.0",
3286
                &[
3287
                    ((0, 0), o(-5), (1969, 12, 31, 19, 0, 0, 0)),
3288
                    ((1710052200, 0), o(-5), (2024, 3, 10, 1, 30, 0, 0)),
3289
                    (
3290
                        (1710053999, 999_999_999),
3291
                        o(-5),
3292
                        (2024, 3, 10, 1, 59, 59, 999_999_999),
3293
                    ),
3294
                    ((1710054000, 0), o(-4), (2024, 3, 10, 3, 0, 0, 0)),
3295
                    ((1710055800, 0), o(-4), (2024, 3, 10, 3, 30, 0, 0)),
3296
                    ((1730610000, 0), o(-4), (2024, 11, 3, 1, 0, 0, 0)),
3297
                    ((1730611800, 0), o(-4), (2024, 11, 3, 1, 30, 0, 0)),
3298
                    (
3299
                        (1730613599, 999_999_999),
3300
                        o(-4),
3301
                        (2024, 11, 3, 1, 59, 59, 999_999_999),
3302
                    ),
3303
                    ((1730613600, 0), o(-5), (2024, 11, 3, 1, 0, 0, 0)),
3304
                    ((1730615400, 0), o(-5), (2024, 11, 3, 1, 30, 0, 0)),
3305
                ],
3306
            ),
3307
            (
3308
                // From Australia/Tasmania
3309
                //
3310
                // We chose this because it's a time zone in the southern
3311
                // hemisphere with DST. Unlike the northern hemisphere, its DST
3312
                // starts in the fall and ends in the spring. In the northern
3313
                // hemisphere, we typically start DST in the spring and end it
3314
                // in the fall.
3315
                "AEST-10AEDT,M10.1.0,M4.1.0/3",
3316
                &[
3317
                    ((0, 0), o(11), (1970, 1, 1, 11, 0, 0, 0)),
3318
                    ((1728142200, 0), o(10), (2024, 10, 6, 1, 30, 0, 0)),
3319
                    (
3320
                        (1728143999, 999_999_999),
3321
                        o(10),
3322
                        (2024, 10, 6, 1, 59, 59, 999_999_999),
3323
                    ),
3324
                    ((1728144000, 0), o(11), (2024, 10, 6, 3, 0, 0, 0)),
3325
                    ((1728145800, 0), o(11), (2024, 10, 6, 3, 30, 0, 0)),
3326
                    ((1712415600, 0), o(11), (2024, 4, 7, 2, 0, 0, 0)),
3327
                    ((1712417400, 0), o(11), (2024, 4, 7, 2, 30, 0, 0)),
3328
                    (
3329
                        (1712419199, 999_999_999),
3330
                        o(11),
3331
                        (2024, 4, 7, 2, 59, 59, 999_999_999),
3332
                    ),
3333
                    ((1712419200, 0), o(10), (2024, 4, 7, 2, 0, 0, 0)),
3334
                    ((1712421000, 0), o(10), (2024, 4, 7, 2, 30, 0, 0)),
3335
                ],
3336
            ),
3337
            (
3338
                // Uses the maximum possible offset. A sloppy read of POSIX
3339
                // seems to indicate the maximum offset is 24:59:59, but since
3340
                // DST defaults to 1 hour ahead of standard time, it's possible
3341
                // to use 24:59:59 for standard time, omit the DST offset, and
3342
                // thus get a DST offset of 25:59:59.
3343
                "XXX-24:59:59YYY,M3.2.0,M11.1.0",
3344
                &[
3345
                    // 2024-01-05T00:00:00+00
3346
                    (
3347
                        (1704412800, 0),
3348
                        Offset::hms(24, 59, 59),
3349
                        (2024, 1, 6, 0, 59, 59, 0),
3350
                    ),
3351
                    // 2024-06-05T00:00:00+00 (DST)
3352
                    (
3353
                        (1717545600, 0),
3354
                        Offset::hms(25, 59, 59),
3355
                        (2024, 6, 6, 1, 59, 59, 0),
3356
                    ),
3357
                ],
3358
            ),
3359
        ];
3360
        for &(posix_tz, timestamps_to_datetimes) in tests {
3361
            let tz = TimeZone::posix(posix_tz).unwrap();
3362
            for &((unix_sec, unix_nano), offset, datetime) in
3363
                timestamps_to_datetimes
3364
            {
3365
                let (year, month, day, hour, min, sec, nano) = datetime;
3366
                let timestamp = Timestamp::new(unix_sec, unix_nano).unwrap();
3367
                assert_eq!(
3368
                    tz.to_offset(timestamp),
3369
                    offset,
3370
                    "\ntimestamp({unix_sec}, {unix_nano})",
3371
                );
3372
                assert_eq!(
3373
                    tz.to_datetime(timestamp),
3374
                    date(year, month, day).at(hour, min, sec, nano),
3375
                    "\ntimestamp({unix_sec}, {unix_nano})",
3376
                );
3377
            }
3378
        }
3379
    }
3380
3381
    #[test]
3382
    fn time_zone_fixed_to_datetime() {
3383
        let tz = offset(-5).to_time_zone();
3384
        let unix_epoch = Timestamp::new(0, 0).unwrap();
3385
        assert_eq!(
3386
            tz.to_datetime(unix_epoch),
3387
            date(1969, 12, 31).at(19, 0, 0, 0),
3388
        );
3389
3390
        let tz = Offset::from_seconds(93_599).unwrap().to_time_zone();
3391
        let timestamp = Timestamp::new(253402207200, 999_999_999).unwrap();
3392
        assert_eq!(
3393
            tz.to_datetime(timestamp),
3394
            date(9999, 12, 31).at(23, 59, 59, 999_999_999),
3395
        );
3396
3397
        let tz = Offset::from_seconds(-93_599).unwrap().to_time_zone();
3398
        let timestamp = Timestamp::new(-377705023201, 0).unwrap();
3399
        assert_eq!(
3400
            tz.to_datetime(timestamp),
3401
            date(-9999, 1, 1).at(0, 0, 0, 0),
3402
        );
3403
    }
3404
3405
    #[test]
3406
    fn time_zone_fixed_to_timestamp() {
3407
        let tz = offset(-5).to_time_zone();
3408
        let dt = date(1969, 12, 31).at(19, 0, 0, 0);
3409
        assert_eq!(
3410
            tz.to_zoned(dt).unwrap().timestamp(),
3411
            Timestamp::new(0, 0).unwrap()
3412
        );
3413
3414
        let tz = Offset::from_seconds(93_599).unwrap().to_time_zone();
3415
        let dt = date(9999, 12, 31).at(23, 59, 59, 999_999_999);
3416
        assert_eq!(
3417
            tz.to_zoned(dt).unwrap().timestamp(),
3418
            Timestamp::new(253402207200, 999_999_999).unwrap(),
3419
        );
3420
        let tz = Offset::from_seconds(93_598).unwrap().to_time_zone();
3421
        assert!(tz.to_zoned(dt).is_err());
3422
3423
        let tz = Offset::from_seconds(-93_599).unwrap().to_time_zone();
3424
        let dt = date(-9999, 1, 1).at(0, 0, 0, 0);
3425
        assert_eq!(
3426
            tz.to_zoned(dt).unwrap().timestamp(),
3427
            Timestamp::new(-377705023201, 0).unwrap(),
3428
        );
3429
        let tz = Offset::from_seconds(-93_598).unwrap().to_time_zone();
3430
        assert!(tz.to_zoned(dt).is_err());
3431
    }
3432
3433
    #[cfg(feature = "alloc")]
3434
    #[test]
3435
    fn time_zone_tzif_previous_transition() {
3436
        let tests: &[(&str, &[(&str, Option<&str>)])] = &[
3437
            (
3438
                "UTC",
3439
                &[
3440
                    ("1969-12-31T19Z", None),
3441
                    ("2024-03-10T02Z", None),
3442
                    ("-009999-12-01 00Z", None),
3443
                    ("9999-12-01 00Z", None),
3444
                ],
3445
            ),
3446
            (
3447
                "America/New_York",
3448
                &[
3449
                    ("2024-03-10 08Z", Some("2024-03-10 07Z")),
3450
                    ("2024-03-10 07:00:00.000000001Z", Some("2024-03-10 07Z")),
3451
                    ("2024-03-10 07Z", Some("2023-11-05 06Z")),
3452
                    ("2023-11-05 06Z", Some("2023-03-12 07Z")),
3453
                    ("-009999-01-31 00Z", None),
3454
                    ("9999-12-01 00Z", Some("9999-11-07 06Z")),
3455
                    // While at present we have "fat" TZif files for our
3456
                    // testdata, it's conceivable they could be swapped to
3457
                    // "slim." In which case, the tests above will mostly just
3458
                    // be testing POSIX TZ strings and not the TZif logic. So
3459
                    // below, we include times that will be in slim (i.e.,
3460
                    // historical times the precede the current DST rule).
3461
                    ("1969-12-31 19Z", Some("1969-10-26 06Z")),
3462
                    ("2000-04-02 08Z", Some("2000-04-02 07Z")),
3463
                    ("2000-04-02 07:00:00.000000001Z", Some("2000-04-02 07Z")),
3464
                    ("2000-04-02 07Z", Some("1999-10-31 06Z")),
3465
                    ("1999-10-31 06Z", Some("1999-04-04 07Z")),
3466
                ],
3467
            ),
3468
            (
3469
                "Australia/Tasmania",
3470
                &[
3471
                    ("2010-04-03 17Z", Some("2010-04-03 16Z")),
3472
                    ("2010-04-03 16:00:00.000000001Z", Some("2010-04-03 16Z")),
3473
                    ("2010-04-03 16Z", Some("2009-10-03 16Z")),
3474
                    ("2009-10-03 16Z", Some("2009-04-04 16Z")),
3475
                    ("-009999-01-31 00Z", None),
3476
                    ("9999-12-01 00Z", Some("9999-10-02 16Z")),
3477
                    // Tests for historical data from tzdb. No POSIX TZ.
3478
                    ("2000-03-25 17Z", Some("2000-03-25 16Z")),
3479
                    ("2000-03-25 16:00:00.000000001Z", Some("2000-03-25 16Z")),
3480
                    ("2000-03-25 16Z", Some("1999-10-02 16Z")),
3481
                    ("1999-10-02 16Z", Some("1999-03-27 16Z")),
3482
                ],
3483
            ),
3484
            // This is Europe/Dublin's rule. It's interesting because its
3485
            // DST is an offset behind standard time. (DST is usually one hour
3486
            // ahead of standard time.)
3487
            (
3488
                "Europe/Dublin",
3489
                &[
3490
                    ("2010-03-28 02Z", Some("2010-03-28 01Z")),
3491
                    ("2010-03-28 01:00:00.000000001Z", Some("2010-03-28 01Z")),
3492
                    ("2010-03-28 01Z", Some("2009-10-25 01Z")),
3493
                    ("2009-10-25 01Z", Some("2009-03-29 01Z")),
3494
                    ("-009999-01-31 00Z", None),
3495
                    ("9999-12-01 00Z", Some("9999-10-31 01Z")),
3496
                    // Tests for historical data from tzdb. No POSIX TZ.
3497
                    ("1990-03-25 02Z", Some("1990-03-25 01Z")),
3498
                    ("1990-03-25 01:00:00.000000001Z", Some("1990-03-25 01Z")),
3499
                    ("1990-03-25 01Z", Some("1989-10-29 01Z")),
3500
                    ("1989-10-25 01Z", Some("1989-03-26 01Z")),
3501
                ],
3502
            ),
3503
            (
3504
                // Sao Paulo eliminated DST in 2019, so the previous transition
3505
                // from 2024 is several years back.
3506
                "America/Sao_Paulo",
3507
                &[("2024-03-10 08Z", Some("2019-02-17 02Z"))],
3508
            ),
3509
        ];
3510
        for &(tzname, prev_trans) in tests {
3511
            if tzname != "America/Sao_Paulo" {
3512
                continue;
3513
            }
3514
            let test_file = TzifTestFile::get(tzname);
3515
            let tz = TimeZone::tzif(test_file.name, test_file.data).unwrap();
3516
            for (given, expected) in prev_trans {
3517
                let given: Timestamp = given.parse().unwrap();
3518
                let expected =
3519
                    expected.map(|s| s.parse::<Timestamp>().unwrap());
3520
                let got = tz.previous_transition(given).map(|t| t.timestamp());
3521
                assert_eq!(got, expected, "\nTZ: {tzname}\ngiven: {given}");
3522
            }
3523
        }
3524
    }
3525
3526
    #[cfg(feature = "alloc")]
3527
    #[test]
3528
    fn time_zone_tzif_next_transition() {
3529
        let tests: &[(&str, &[(&str, Option<&str>)])] = &[
3530
            (
3531
                "UTC",
3532
                &[
3533
                    ("1969-12-31T19Z", None),
3534
                    ("2024-03-10T02Z", None),
3535
                    ("-009999-12-01 00Z", None),
3536
                    ("9999-12-01 00Z", None),
3537
                ],
3538
            ),
3539
            (
3540
                "America/New_York",
3541
                &[
3542
                    ("2024-03-10 06Z", Some("2024-03-10 07Z")),
3543
                    ("2024-03-10 06:59:59.999999999Z", Some("2024-03-10 07Z")),
3544
                    ("2024-03-10 07Z", Some("2024-11-03 06Z")),
3545
                    ("2024-11-03 06Z", Some("2025-03-09 07Z")),
3546
                    ("-009999-12-01 00Z", Some("1883-11-18 17Z")),
3547
                    ("9999-12-01 00Z", None),
3548
                    // While at present we have "fat" TZif files for our
3549
                    // testdata, it's conceivable they could be swapped to
3550
                    // "slim." In which case, the tests above will mostly just
3551
                    // be testing POSIX TZ strings and not the TZif logic. So
3552
                    // below, we include times that will be in slim (i.e.,
3553
                    // historical times the precede the current DST rule).
3554
                    ("1969-12-31 19Z", Some("1970-04-26 07Z")),
3555
                    ("2000-04-02 06Z", Some("2000-04-02 07Z")),
3556
                    ("2000-04-02 06:59:59.999999999Z", Some("2000-04-02 07Z")),
3557
                    ("2000-04-02 07Z", Some("2000-10-29 06Z")),
3558
                    ("2000-10-29 06Z", Some("2001-04-01 07Z")),
3559
                ],
3560
            ),
3561
            (
3562
                "Australia/Tasmania",
3563
                &[
3564
                    ("2010-04-03 15Z", Some("2010-04-03 16Z")),
3565
                    ("2010-04-03 15:59:59.999999999Z", Some("2010-04-03 16Z")),
3566
                    ("2010-04-03 16Z", Some("2010-10-02 16Z")),
3567
                    ("2010-10-02 16Z", Some("2011-04-02 16Z")),
3568
                    ("-009999-12-01 00Z", Some("1895-08-31 14:10:44Z")),
3569
                    ("9999-12-01 00Z", None),
3570
                    // Tests for historical data from tzdb. No POSIX TZ.
3571
                    ("2000-03-25 15Z", Some("2000-03-25 16Z")),
3572
                    ("2000-03-25 15:59:59.999999999Z", Some("2000-03-25 16Z")),
3573
                    ("2000-03-25 16Z", Some("2000-08-26 16Z")),
3574
                    ("2000-08-26 16Z", Some("2001-03-24 16Z")),
3575
                ],
3576
            ),
3577
            (
3578
                "Europe/Dublin",
3579
                &[
3580
                    ("2010-03-28 00Z", Some("2010-03-28 01Z")),
3581
                    ("2010-03-28 00:59:59.999999999Z", Some("2010-03-28 01Z")),
3582
                    ("2010-03-28 01Z", Some("2010-10-31 01Z")),
3583
                    ("2010-10-31 01Z", Some("2011-03-27 01Z")),
3584
                    ("-009999-12-01 00Z", Some("1880-08-02 00:25:21Z")),
3585
                    ("9999-12-01 00Z", None),
3586
                    // Tests for historical data from tzdb. No POSIX TZ.
3587
                    ("1990-03-25 00Z", Some("1990-03-25 01Z")),
3588
                    ("1990-03-25 00:59:59.999999999Z", Some("1990-03-25 01Z")),
3589
                    ("1990-03-25 01Z", Some("1990-10-28 01Z")),
3590
                    ("1990-10-28 01Z", Some("1991-03-31 01Z")),
3591
                ],
3592
            ),
3593
            (
3594
                // Sao Paulo eliminated DST in 2019, so the next transition
3595
                // from 2024 no longer exists.
3596
                "America/Sao_Paulo",
3597
                &[("2024-03-10 08Z", None)],
3598
            ),
3599
        ];
3600
        for &(tzname, next_trans) in tests {
3601
            let test_file = TzifTestFile::get(tzname);
3602
            let tz = TimeZone::tzif(test_file.name, test_file.data).unwrap();
3603
            for (given, expected) in next_trans {
3604
                let given: Timestamp = given.parse().unwrap();
3605
                let expected =
3606
                    expected.map(|s| s.parse::<Timestamp>().unwrap());
3607
                let got = tz.next_transition(given).map(|t| t.timestamp());
3608
                assert_eq!(got, expected, "\nTZ: {tzname}\ngiven: {given}");
3609
            }
3610
        }
3611
    }
3612
3613
    #[cfg(feature = "alloc")]
3614
    #[test]
3615
    fn time_zone_posix_previous_transition() {
3616
        let tests: &[(&str, &[(&str, Option<&str>)])] = &[
3617
            // America/New_York, but a utopia in which DST is abolished. There
3618
            // are no time zone transitions, so next_transition always returns
3619
            // None.
3620
            (
3621
                "EST5",
3622
                &[
3623
                    ("1969-12-31T19Z", None),
3624
                    ("2024-03-10T02Z", None),
3625
                    ("-009999-12-01 00Z", None),
3626
                    ("9999-12-01 00Z", None),
3627
                ],
3628
            ),
3629
            // The standard DST rule for America/New_York.
3630
            (
3631
                "EST5EDT,M3.2.0,M11.1.0",
3632
                &[
3633
                    ("1969-12-31 19Z", Some("1969-11-02 06Z")),
3634
                    ("2024-03-10 08Z", Some("2024-03-10 07Z")),
3635
                    ("2024-03-10 07:00:00.000000001Z", Some("2024-03-10 07Z")),
3636
                    ("2024-03-10 07Z", Some("2023-11-05 06Z")),
3637
                    ("2023-11-05 06Z", Some("2023-03-12 07Z")),
3638
                    ("-009999-01-31 00Z", None),
3639
                    ("9999-12-01 00Z", Some("9999-11-07 06Z")),
3640
                ],
3641
            ),
3642
            (
3643
                // From Australia/Tasmania
3644
                "AEST-10AEDT,M10.1.0,M4.1.0/3",
3645
                &[
3646
                    ("2010-04-03 17Z", Some("2010-04-03 16Z")),
3647
                    ("2010-04-03 16:00:00.000000001Z", Some("2010-04-03 16Z")),
3648
                    ("2010-04-03 16Z", Some("2009-10-03 16Z")),
3649
                    ("2009-10-03 16Z", Some("2009-04-04 16Z")),
3650
                    ("-009999-01-31 00Z", None),
3651
                    ("9999-12-01 00Z", Some("9999-10-02 16Z")),
3652
                ],
3653
            ),
3654
            // This is Europe/Dublin's rule. It's interesting because its
3655
            // DST is an offset behind standard time. (DST is usually one hour
3656
            // ahead of standard time.)
3657
            (
3658
                "IST-1GMT0,M10.5.0,M3.5.0/1",
3659
                &[
3660
                    ("2010-03-28 02Z", Some("2010-03-28 01Z")),
3661
                    ("2010-03-28 01:00:00.000000001Z", Some("2010-03-28 01Z")),
3662
                    ("2010-03-28 01Z", Some("2009-10-25 01Z")),
3663
                    ("2009-10-25 01Z", Some("2009-03-29 01Z")),
3664
                    ("-009999-01-31 00Z", None),
3665
                    ("9999-12-01 00Z", Some("9999-10-31 01Z")),
3666
                ],
3667
            ),
3668
        ];
3669
        for &(posix_tz, prev_trans) in tests {
3670
            let tz = TimeZone::posix(posix_tz).unwrap();
3671
            for (given, expected) in prev_trans {
3672
                let given: Timestamp = given.parse().unwrap();
3673
                let expected =
3674
                    expected.map(|s| s.parse::<Timestamp>().unwrap());
3675
                let got = tz.previous_transition(given).map(|t| t.timestamp());
3676
                assert_eq!(got, expected, "\nTZ: {posix_tz}\ngiven: {given}");
3677
            }
3678
        }
3679
    }
3680
3681
    #[cfg(feature = "alloc")]
3682
    #[test]
3683
    fn time_zone_posix_next_transition() {
3684
        let tests: &[(&str, &[(&str, Option<&str>)])] = &[
3685
            // America/New_York, but a utopia in which DST is abolished. There
3686
            // are no time zone transitions, so next_transition always returns
3687
            // None.
3688
            (
3689
                "EST5",
3690
                &[
3691
                    ("1969-12-31T19Z", None),
3692
                    ("2024-03-10T02Z", None),
3693
                    ("-009999-12-01 00Z", None),
3694
                    ("9999-12-01 00Z", None),
3695
                ],
3696
            ),
3697
            // The standard DST rule for America/New_York.
3698
            (
3699
                "EST5EDT,M3.2.0,M11.1.0",
3700
                &[
3701
                    ("1969-12-31 19Z", Some("1970-03-08 07Z")),
3702
                    ("2024-03-10 06Z", Some("2024-03-10 07Z")),
3703
                    ("2024-03-10 06:59:59.999999999Z", Some("2024-03-10 07Z")),
3704
                    ("2024-03-10 07Z", Some("2024-11-03 06Z")),
3705
                    ("2024-11-03 06Z", Some("2025-03-09 07Z")),
3706
                    ("-009999-12-01 00Z", Some("-009998-03-10 07Z")),
3707
                    ("9999-12-01 00Z", None),
3708
                ],
3709
            ),
3710
            (
3711
                // From Australia/Tasmania
3712
                "AEST-10AEDT,M10.1.0,M4.1.0/3",
3713
                &[
3714
                    ("2010-04-03 15Z", Some("2010-04-03 16Z")),
3715
                    ("2010-04-03 15:59:59.999999999Z", Some("2010-04-03 16Z")),
3716
                    ("2010-04-03 16Z", Some("2010-10-02 16Z")),
3717
                    ("2010-10-02 16Z", Some("2011-04-02 16Z")),
3718
                    ("-009999-12-01 00Z", Some("-009998-04-06 16Z")),
3719
                    ("9999-12-01 00Z", None),
3720
                ],
3721
            ),
3722
            // This is Europe/Dublin's rule. It's interesting because its
3723
            // DST is an offset behind standard time. (DST is usually one hour
3724
            // ahead of standard time.)
3725
            (
3726
                "IST-1GMT0,M10.5.0,M3.5.0/1",
3727
                &[
3728
                    ("2010-03-28 00Z", Some("2010-03-28 01Z")),
3729
                    ("2010-03-28 00:59:59.999999999Z", Some("2010-03-28 01Z")),
3730
                    ("2010-03-28 01Z", Some("2010-10-31 01Z")),
3731
                    ("2010-10-31 01Z", Some("2011-03-27 01Z")),
3732
                    ("-009999-12-01 00Z", Some("-009998-03-31 01Z")),
3733
                    ("9999-12-01 00Z", None),
3734
                ],
3735
            ),
3736
        ];
3737
        for &(posix_tz, next_trans) in tests {
3738
            let tz = TimeZone::posix(posix_tz).unwrap();
3739
            for (given, expected) in next_trans {
3740
                let given: Timestamp = given.parse().unwrap();
3741
                let expected =
3742
                    expected.map(|s| s.parse::<Timestamp>().unwrap());
3743
                let got = tz.next_transition(given).map(|t| t.timestamp());
3744
                assert_eq!(got, expected, "\nTZ: {posix_tz}\ngiven: {given}");
3745
            }
3746
        }
3747
    }
3748
3749
    /// This tests that the size of a time zone is kept at a single word.
3750
    ///
3751
    /// This is important because every jiff::Zoned has a TimeZone inside of
3752
    /// it, and we want to keep its size as small as we can.
3753
    #[test]
3754
    fn time_zone_size() {
3755
        #[cfg(feature = "alloc")]
3756
        {
3757
            let word = core::mem::size_of::<usize>();
3758
            assert_eq!(word, core::mem::size_of::<TimeZone>());
3759
        }
3760
        #[cfg(all(target_pointer_width = "64", not(feature = "alloc")))]
3761
        {
3762
            #[cfg(debug_assertions)]
3763
            {
3764
                assert_eq!(8, core::mem::size_of::<TimeZone>());
3765
            }
3766
            #[cfg(not(debug_assertions))]
3767
            {
3768
                // This asserts the same value as the alloc value above, but
3769
                // it wasn't always this way, which is why it's written out
3770
                // separately. Moreover, in theory, I'd be open to regressing
3771
                // this value if it led to an improvement in alloc-mode. But
3772
                // more likely, it would be nice to decrease this size in
3773
                // non-alloc modes.
3774
                assert_eq!(8, core::mem::size_of::<TimeZone>());
3775
            }
3776
        }
3777
    }
3778
3779
    /// This tests a few other cases for `TimeZone::to_offset` that
3780
    /// probably aren't worth showing in doctest examples.
3781
    #[test]
3782
    fn time_zone_to_offset() {
3783
        let ts = Timestamp::from_second(123456789).unwrap();
3784
3785
        let tz = TimeZone::fixed(offset(-5));
3786
        let info = tz.to_offset_info(ts);
3787
        assert_eq!(info.offset(), offset(-5));
3788
        assert_eq!(info.dst(), Dst::No);
3789
        assert_eq!(info.abbreviation(), "-05");
3790
3791
        let tz = TimeZone::fixed(offset(5));
3792
        let info = tz.to_offset_info(ts);
3793
        assert_eq!(info.offset(), offset(5));
3794
        assert_eq!(info.dst(), Dst::No);
3795
        assert_eq!(info.abbreviation(), "+05");
3796
3797
        let tz = TimeZone::fixed(offset(-12));
3798
        let info = tz.to_offset_info(ts);
3799
        assert_eq!(info.offset(), offset(-12));
3800
        assert_eq!(info.dst(), Dst::No);
3801
        assert_eq!(info.abbreviation(), "-12");
3802
3803
        let tz = TimeZone::fixed(offset(12));
3804
        let info = tz.to_offset_info(ts);
3805
        assert_eq!(info.offset(), offset(12));
3806
        assert_eq!(info.dst(), Dst::No);
3807
        assert_eq!(info.abbreviation(), "+12");
3808
3809
        let tz = TimeZone::fixed(offset(0));
3810
        let info = tz.to_offset_info(ts);
3811
        assert_eq!(info.offset(), offset(0));
3812
        assert_eq!(info.dst(), Dst::No);
3813
        assert_eq!(info.abbreviation(), "UTC");
3814
    }
3815
3816
    /// This tests a few other cases for `TimeZone::to_fixed_offset` that
3817
    /// probably aren't worth showing in doctest examples.
3818
    #[test]
3819
    fn time_zone_to_fixed_offset() {
3820
        let tz = TimeZone::UTC;
3821
        assert_eq!(tz.to_fixed_offset().unwrap(), Offset::UTC);
3822
3823
        let offset = Offset::from_hours(1).unwrap();
3824
        let tz = TimeZone::fixed(offset);
3825
        assert_eq!(tz.to_fixed_offset().unwrap(), offset);
3826
3827
        #[cfg(feature = "alloc")]
3828
        {
3829
            let tz = TimeZone::posix("EST5").unwrap();
3830
            assert!(tz.to_fixed_offset().is_err());
3831
3832
            let test_file = TzifTestFile::get("America/New_York");
3833
            let tz = TimeZone::tzif(test_file.name, test_file.data).unwrap();
3834
            assert!(tz.to_fixed_offset().is_err());
3835
        }
3836
    }
3837
3838
    /// This tests that `TimeZone::following` correctly returns a final time
3839
    /// zone transition.
3840
    #[cfg(feature = "alloc")]
3841
    #[test]
3842
    fn time_zone_following_boa_vista() {
3843
        use alloc::{vec, vec::Vec};
3844
3845
        let test_file = TzifTestFile::get("America/Boa_Vista");
3846
        let tz = TimeZone::tzif(test_file.name, test_file.data).unwrap();
3847
        let last4: Vec<Timestamp> = vec![
3848
            "1999-10-03T04Z".parse().unwrap(),
3849
            "2000-02-27T03Z".parse().unwrap(),
3850
            "2000-10-08T04Z".parse().unwrap(),
3851
            "2000-10-15T03Z".parse().unwrap(),
3852
        ];
3853
3854
        let start: Timestamp = "2001-01-01T00Z".parse().unwrap();
3855
        let mut transitions: Vec<Timestamp> =
3856
            tz.preceding(start).take(4).map(|t| t.timestamp()).collect();
3857
        transitions.reverse();
3858
        assert_eq!(transitions, last4);
3859
3860
        let start: Timestamp = "1990-01-01T00Z".parse().unwrap();
3861
        let transitions: Vec<Timestamp> =
3862
            tz.following(start).map(|t| t.timestamp()).collect();
3863
        // The regression here was that the 2000-10-15 transition wasn't
3864
        // being found here, despite the fact that it existed and was found
3865
        // by `preceding`.
3866
        assert_eq!(transitions, last4);
3867
    }
3868
3869
    #[cfg(feature = "alloc")]
3870
    #[test]
3871
    fn regression_tzif_parse_panic() {
3872
        _ = TimeZone::tzif(
3873
            "",
3874
            &[
3875
                84, 90, 105, 102, 6, 0, 5, 35, 84, 10, 77, 0, 0, 0, 84, 82,
3876
                105, 102, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
3877
                0, 0, 0, 0, 2, 0, 0, 0, 5, 0, 0, 82, 28, 77, 0, 0, 90, 105,
3878
                78, 0, 0, 0, 0, 0, 0, 0, 84, 90, 105, 102, 0, 0, 5, 0, 84, 90,
3879
                105, 84, 77, 10, 0, 0, 0, 15, 93, 0, 0, 0, 0, 0, 0, 0, 0, 0,
3880
                0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 5, 0, 0, 0, 82, 0, 64, 1, 0,
3881
                0, 2, 0, 0, 0, 0, 0, 0, 126, 1, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0,
3882
                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 126, 0, 0, 0, 0, 0,
3883
                0, 160, 109, 1, 0, 90, 105, 102, 0, 0, 5, 0, 87, 90, 105, 84,
3884
                77, 10, 0, 0, 0, 0, 0, 122, 102, 105, 0, 0, 0, 0, 0, 0, 0, 0,
3885
                2, 0, 0, 0, 0, 0, 0, 5, 82, 0, 0, 0, 0, 0, 2, 0, 0, 90, 105,
3886
                102, 0, 0, 5, 0, 84, 90, 105, 84, 77, 10, 0, 0, 0, 102, 0, 0,
3887
                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 84, 90, 195, 190, 10, 84,
3888
                90, 77, 49, 84, 90, 105, 102, 49, 44, 74, 51, 44, 50, 10,
3889
            ],
3890
        );
3891
    }
3892
3893
    /// A regression test where a TZ lookup for the minimum civil datetime
3894
    /// resulted in a panic in the TZif handling.
3895
    #[cfg(feature = "alloc")]
3896
    #[test]
3897
    fn regression_tz_lookup_datetime_min() {
3898
        use alloc::string::ToString;
3899
3900
        let test_file = TzifTestFile::get("America/Boa_Vista");
3901
        let tz = TimeZone::tzif(test_file.name, test_file.data).unwrap();
3902
        let err = tz.to_timestamp(DateTime::MIN).unwrap_err();
3903
        assert_eq!(
3904
            err.to_string(),
3905
            "converting datetime with time zone offset `-04:02:40` to timestamp overflowed: parameter 'unix-seconds' with value -377705102240 is not in the required range of -377705023201..=253402207200",
3906
        );
3907
    }
3908
}