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`](crate::Timestamp) to a [`Zoned`](crate::Zoned)
39is infallible:
40
41```
42use jiff::{tz::TimeZone, Timestamp, Zoned};
43
44let tz = TimeZone::get("America/New_York")?;
45let ts = Timestamp::UNIX_EPOCH;
46let zdt = ts.to_zoned(tz);
47assert_eq!(zdt.to_string(), "1969-12-31T19:00:00-05:00[America/New_York]");
48
49# Ok::<(), Box<dyn std::error::Error>>(())
50```
51
52# The [IANA Time Zone Database]
53
54Since a time zone is a set of rules for determining the civil time, via an
55offset from UTC, in a particular geographic region, a database is required to
56represent the full complexity of these rules in practice. The standard database
57is widespread use is the [IANA Time Zone Database]. On Unix systems, this is
58typically found at `/usr/share/zoneinfo`, and Jiff will read it automatically.
59On Windows systems, there is no canonical Time Zone Database installation, and
60so Jiff embeds it into the compiled artifact. (This does not happen on Unix
61by default.)
62
63See the [`TimeZoneDatabase`] for more information.
64
65# The system or "local" time zone
66
67In many cases, the operating system manages a "default" time zone. It might,
68for example, be how the `date` program converts a Unix timestamp to a time that
69is "local" to you.
70
71Unfortunately, there is no universal approach to discovering a system's default
72time zone. Instead, Jiff uses heuristics like reading `/etc/localtime` on Unix,
73and calling [`GetDynamicTimeZoneInformation`] on Windows. But in all cases,
74Jiff will always use the IANA Time Zone Database for implementing time zone
75transition rules. (For example, Windows specific APIs for time zone transitions
76are not supported by Jiff.)
77
78Moreover, Jiff supports reading the `TZ` environment variable, as specified
79by POSIX, on all systems.
80
81To get the system's default time zone, use [`TimeZone::system`].
82
83# Core-only environments
84
85By default, Jiff attempts to read time zone rules from `/usr/share/zoneinfo`
86on Unix and a bundled database on other platforms (like on Windows). This happens
87at runtime, and aside from requiring APIs to interact with the file system
88on Unix, it also requires dynamic memory allocation.
89
90For core-only environments that don't have file system APIs or dynamic
91memory allocation, Jiff provides a way to construct `TimeZone` values at
92compile time by compiling time zone rules into your binary. This does mean
93that your program will need to be re-compiled if the time zone rules change
94(in contrast to Jiff's default behavior of reading `/usr/share/zoneinfo` at
95runtime on Unix), but sometimes there isn't a practical alternative.
96
97With the `static` crate feature enabled, the [`jiff::tz::get`](crate::tz::get)
98macro becomes available in this module. This example shows how use it to build
99a `TimeZone` at compile time. Here, we find the next DST transition from a
100particular timestamp in `Europe/Zurich`, and then print that in local time for
101Zurich:
102
103```
104use jiff::{tz::{self, TimeZone}, Timestamp};
105
106static TZ: TimeZone = tz::get!("Europe/Zurich");
107
108let ts: Timestamp = "2025-02-25T00:00Z".parse()?;
109let Some(next_transition) = TZ.following(ts).next() else {
110 return Err("no time zone transitions".into());
111};
112let zdt = next_transition.timestamp().to_zoned(TZ.clone());
113assert_eq!(zdt.to_string(), "2025-03-30T03:00:00+02:00[Europe/Zurich]");
114
115# Ok::<(), Box<dyn std::error::Error>>(())
116```
117
118The above example does not require dynamic memory allocation or access to file
119system APIs. It also _only_ embeds the `Europe/Zurich` time zone into your
120compiled binary.
121
122[IANA Time Zone Database]: https://en.wikipedia.org/wiki/Tz_database
123[`GetDynamicTimeZoneInformation`]: https://learn.microsoft.com/en-us/windows/win32/api/timezoneapi/nf-timezoneapi-getdynamictimezoneinformation
124*/
125
126pub use self::{
127 ambiguous::{
128 AmbiguousOffset, AmbiguousTimestamp, AmbiguousZoned, Disambiguation,
129 },
130 db::{db, TimeZoneDatabase, TimeZoneName, TimeZoneNameIter},
131 offset::{Dst, Offset, OffsetArithmetic, OffsetConflict, OffsetRound},
132 timezone::{
133 TimeZone, TimeZoneFollowingTransitions, TimeZoneOffsetInfo,
134 TimeZonePrecedingTransitions, TimeZoneTransition,
135 },
136};
137
138mod ambiguous;
139#[cfg(feature = "tzdb-concatenated")]
140mod concatenated;
141mod db;
142mod offset;
143pub(crate) mod posix;
144#[cfg(feature = "tz-system")]
145mod system;
146#[cfg(all(test, feature = "alloc"))]
147mod testdata;
148mod timezone;
149pub(crate) mod tzif;
150// See module comment for WIP status. :-(
151#[cfg(test)]
152mod zic;
153
154/// Create a `TimeZone` value from TZif data in [`jiff-tzdb`] at compile time.
155///
156/// This reads the data for the time zone with the IANA identifier given from
157/// [`jiff-tzdb`], parses it as TZif specified by [RFC 9636], and constructs a
158/// `TimeZone` value for use in a `const` context. This enables using IANA time
159/// zones with Jiff in core-only environments. No dynamic memory allocation is
160/// used.
161///
162/// # Input
163///
164/// This macro takes one positional parameter that must be a literal string.
165/// The string should be an IANA time zone identifier, e.g.,
166/// `America/New_York`.
167///
168/// # Return type
169///
170/// This macro returns a value with type `TimeZone`. To get a `&'static
171/// TimeZone`, simply use `&include("...")`.
172///
173/// # Usage
174///
175/// Callers should only call this macro once for each unique IANA time zone
176/// identifier you need. Otherwise, multiple copies of the same embedded
177/// time zone data could appear in your binary. There are no correctness
178/// issues with this, but it could make your binary bigger than it needs to be.
179///
180/// # When should I use this?
181///
182/// Users should only use this macro if they have a _specific need_ for it
183/// (like using a time zone on an embedded device). In particular, this will
184/// embed the time zone transition rules into your binary. If the time zone
185/// rules change, your program will need to be re-compiled.
186///
187/// In contrast, Jiff's default configuration on Unix is to read from
188/// `/usr/share/zoneinfo` at runtime. This means your application will
189/// automatically use time zone updates and doesn't need to be re-compiled.
190///
191/// Using a static `TimeZone` may also be faster in some cases. In particular,
192/// a `TimeZone` created at runtime from a `/usr/share/zoneinfo` uses
193/// automic reference counting internally. In contrast, a `TimeZone` created
194/// with this macro does not.
195///
196/// # Example
197///
198/// This example shows how to find the next DST transition from a particular
199/// timestamp in `Europe/Zurich`, and then print that in local time for Zurich:
200///
201/// ```
202/// use jiff::{tz::{self, TimeZone}, Timestamp};
203///
204/// static TZ: TimeZone = tz::get!("Europe/Zurich");
205///
206/// let ts: Timestamp = "2025-02-25T00:00Z".parse()?;
207/// let Some(next_transition) = TZ.following(ts).next() else {
208/// return Err("no time zone transitions".into());
209/// };
210/// let zdt = next_transition.timestamp().to_zoned(TZ.clone());
211/// assert_eq!(zdt.to_string(), "2025-03-30T03:00:00+02:00[Europe/Zurich]");
212///
213/// # Ok::<(), Box<dyn std::error::Error>>(())
214/// ```
215///
216/// [RFC 9636]: https://datatracker.ietf.org/doc/rfc9636/
217/// [`jiff-tzdb`]: https://docs.rs/jiff-tzdb
218#[cfg(feature = "static")]
219pub use jiff_static::get;
220
221/// Create a `TimeZone` value from TZif data in a file at compile time.
222///
223/// This reads the data in the file path given, parses it as TZif specified by
224/// [RFC 9636], and constructs a `TimeZone` value for use in a `const` context.
225/// This enables using IANA time zones with Jiff in core-only environments. No
226/// dynamic memory allocation is used.
227///
228/// Unlike [`jiff::tz::get`](get), this reads TZif data from a file.
229/// `jiff::tz::get`, in contrast, reads TZif data from the [`jiff-tzdb`] crate.
230/// `jiff::tz::get` is more convenient and doesn't require using managing TZif
231/// files, but it comes at the cost of a dependency on `jiff-tzdb` and being
232/// forced to use whatever data is in `jiff-tzdb`.
233///
234/// # Input
235///
236/// This macro takes two positional parameters that must be literal strings.
237///
238/// The first is required and is a path to a file containing TZif data. For
239/// example, `/usr/share/zoneinfo/America/New_York`.
240///
241/// The second parameter is an IANA time zone identifier, e.g.,
242/// `America/New_York`, and is required only when an IANA time zone identifier
243/// could not be determined from the file path. The macro will automatically
244/// infer an IANA time zone identifier as anything after the last occurrence
245/// of the literal `zoneinfo/` in the file path.
246///
247/// # Return type
248///
249/// This macro returns a value with type `TimeZone`. To get a `&'static
250/// TimeZone`, simply use `&include("...")`.
251///
252/// # Usage
253///
254/// Callers should only call this macro once for each unique IANA time zone
255/// identifier you need. Otherwise, multiple copies of the same embedded
256/// time zone data could appear in your binary. There are no correctness
257/// issues with this, but it could make your binary bigger than it needs to be.
258///
259/// # When should I use this?
260///
261/// Users should only use this macro if they have a _specific need_ for it
262/// (like using a time zone on an embedded device). In particular, this will
263/// embed the time zone transition rules into your binary. If the time zone
264/// rules change, your program will need to be re-compiled.
265///
266/// In contrast, Jiff's default configuration on Unix is to read from
267/// `/usr/share/zoneinfo` at runtime. This means your application will
268/// automatically use time zone updates and doesn't need to be re-compiled.
269///
270/// Using a static `TimeZone` may also be faster in some cases. In particular,
271/// a `TimeZone` created at runtime from a `/usr/share/zoneinfo` uses
272/// automic reference counting internally. In contrast, a `TimeZone` created
273/// with this macro does not.
274///
275/// # Example
276///
277/// This example shows how to find the next DST transition from a particular
278/// timestamp in `Europe/Zurich`, and then print that in local time for Zurich:
279///
280/// ```ignore
281/// use jiff::{tz::{self, TimeZone}, Timestamp};
282///
283/// static TZ: TimeZone = tz::include!("/usr/share/zoneinfo/Europe/Zurich");
284///
285/// let ts: Timestamp = "2025-02-25T00:00Z".parse()?;
286/// let Some(next_transition) = TZ.following(ts).next() else {
287/// return Err("no time zone transitions".into());
288/// };
289/// let zdt = next_transition.timestamp().to_zoned(TZ.clone());
290/// assert_eq!(zdt.to_string(), "2025-03-30T03:00:00+02:00[Europe/Zurich]");
291///
292/// # Ok::<(), Box<dyn std::error::Error>>(())
293/// ```
294///
295/// # Example: using `/etc/localtime`
296///
297/// On most Unix systems, `/etc/localtime` is a symbolic link to a file in
298/// your `/usr/share/zoneinfo` directory. This means it is a valid input to
299/// this macro. However, Jiff currently does not detect the IANA time zone
300/// identifier, so you'll need to provide it yourself:
301///
302/// ```ignore
303/// use jiff::{tz::{self, TimeZone}, Timestamp};
304///
305/// static TZ: TimeZone = tz::include!("/etc/localtime", "America/New_York");
306///
307/// let ts: Timestamp = "2025-02-25T00:00Z".parse()?;
308/// let zdt = ts.to_zoned(TZ.clone());
309/// assert_eq!(zdt.to_string(), "2025-02-24T19:00:00-05:00[America/New_York]");
310///
311/// # Ok::<(), Box<dyn std::error::Error>>(())
312/// ```
313///
314/// Note that this is reading `/etc/localtime` _at compile time_, which means
315/// that the program will only use the time zone on the system in which it
316/// was compiled. It will _not_ use the time zone of the system running it.
317///
318/// [RFC 9636]: https://datatracker.ietf.org/doc/rfc9636/
319/// [`jiff-tzdb`]: https://docs.rs/jiff-tzdb
320#[cfg(feature = "static-tz")]
321pub use jiff_static::include;
322
323/// Creates a new time zone offset in a `const` context from a given number
324/// of hours.
325///
326/// Negative offsets correspond to time zones west of the prime meridian,
327/// while positive offsets correspond to time zones east of the prime
328/// meridian. Equivalently, in all cases, `civil-time - offset = UTC`.
329///
330/// The fallible non-const version of this constructor is
331/// [`Offset::from_hours`].
332///
333/// This is a convenience free function for [`Offset::constant`]. It is
334/// intended to provide a terse syntax for constructing `Offset` values from
335/// a value that is known to be valid.
336///
337/// # Panics
338///
339/// This routine panics when the given number of hours is out of range.
340/// Namely, `hours` must be in the range `-25..=25`.
341///
342/// Similarly, when used in a const context, an out of bounds hour will prevent
343/// your Rust program from compiling.
344///
345/// # Example
346///
347/// ```
348/// use jiff::tz::offset;
349///
350/// let o = offset(-5);
351/// assert_eq!(o.seconds(), -18_000);
352/// let o = offset(5);
353/// assert_eq!(o.seconds(), 18_000);
354/// ```
355#[inline]
356pub const fn offset(hours: i8) -> Offset {
357 Offset::constant(hours)
358}