jiff/tz/
timezone.rs

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