jiff/tz/
mod.rs

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