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