Skip to main content

jiff/tz/
timezone.rs

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