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