jiff/tz/
timezone.rs

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