1 | /*! |
2 | Routines for interacting with time zones and the zoneinfo database. |
3 | |
4 | The main type in this module is [`TimeZone`]. For most use cases, you may not |
5 | even need to interact with this type at all. For example, this code snippet |
6 | converts a civil datetime to a zone aware datetime: |
7 | |
8 | ``` |
9 | use jiff::civil::date; |
10 | |
11 | let zdt = date(2024, 7, 10).at(20, 48, 0, 0).in_tz("America/New_York" )?; |
12 | assert_eq!(zdt.to_string(), "2024-07-10T20:48:00-04:00[America/New_York]" ); |
13 | |
14 | # Ok::<(), Box<dyn std::error::Error>>(()) |
15 | ``` |
16 | |
17 | And this example parses a zone aware datetime from a string: |
18 | |
19 | ``` |
20 | use jiff::Zoned; |
21 | |
22 | let zdt: Zoned = "2024-07-10 20:48[america/new_york]" .parse()?; |
23 | assert_eq!(zdt.year(), 2024); |
24 | assert_eq!(zdt.month(), 7); |
25 | assert_eq!(zdt.day(), 10); |
26 | assert_eq!(zdt.hour(), 20); |
27 | assert_eq!(zdt.minute(), 48); |
28 | assert_eq!(zdt.offset().seconds(), -4 * 60 * 60); |
29 | assert_eq!(zdt.time_zone().iana_name(), Some("America/New_York" )); |
30 | |
31 | # Ok::<(), Box<dyn std::error::Error>>(()) |
32 | ``` |
33 | |
34 | Yet, neither of the above examples require uttering [`TimeZone`]. This is |
35 | because the datetime types in this crate provide higher level abstractions for |
36 | working with time zone identifiers. Nevertheless, sometimes it is useful to |
37 | work with a `TimeZone` directly. For example, if one has a `TimeZone`, then |
38 | conversion from a [`Timestamp`](crate::Timestamp) to a [`Zoned`](crate::Zoned) |
39 | is infallible: |
40 | |
41 | ``` |
42 | use jiff::{tz::TimeZone, Timestamp, Zoned}; |
43 | |
44 | let tz = TimeZone::get("America/New_York" )?; |
45 | let ts = Timestamp::UNIX_EPOCH; |
46 | let zdt = ts.to_zoned(tz); |
47 | assert_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 | |
54 | Since a time zone is a set of rules for determining the civil time, via an |
55 | offset from UTC, in a particular geographic region, a database is required to |
56 | represent the full complexity of these rules in practice. The standard database |
57 | is widespread use is the [IANA Time Zone Database]. On Unix systems, this is |
58 | typically found at `/usr/share/zoneinfo`, and Jiff will read it automatically. |
59 | On Windows systems, there is no canonical Time Zone Database installation, and |
60 | so Jiff embeds it into the compiled artifact. (This does not happen on Unix |
61 | by default.) |
62 | |
63 | See the [`TimeZoneDatabase`] for more information. |
64 | |
65 | # The system or "local" time zone |
66 | |
67 | In many cases, the operating system manages a "default" time zone. It might, |
68 | for example, be how the `date` program converts a Unix timestamp to a time that |
69 | is "local" to you. |
70 | |
71 | Unfortunately, there is no universal approach to discovering a system's default |
72 | time zone. Instead, Jiff uses heuristics like reading `/etc/localtime` on Unix, |
73 | and calling [`GetDynamicTimeZoneInformation`] on Windows. But in all cases, |
74 | Jiff will always use the IANA Time Zone Database for implementing time zone |
75 | transition rules. (For example, Windows specific APIs for time zone transitions |
76 | are not supported by Jiff.) |
77 | |
78 | Moreover, Jiff supports reading the `TZ` environment variable, as specified |
79 | by POSIX, on all systems. |
80 | |
81 | To get the system's default time zone, use [`TimeZone::system`]. |
82 | |
83 | # Core-only environments |
84 | |
85 | By default, Jiff attempts to read time zone rules from `/usr/share/zoneinfo` |
86 | on Unix and a bundled database on other platforms (like on Windows). This happens |
87 | at runtime, and aside from requiring APIs to interact with the file system |
88 | on Unix, it also requires dynamic memory allocation. |
89 | |
90 | For core-only environments that don't have file system APIs or dynamic |
91 | memory allocation, Jiff provides a way to construct `TimeZone` values at |
92 | compile time by compiling time zone rules into your binary. This does mean |
93 | that 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 |
95 | runtime on Unix), but sometimes there isn't a practical alternative. |
96 | |
97 | With the `static` crate feature enabled, the [`jiff::tz::get`](crate::tz::get) |
98 | macro becomes available in this module. This example shows how use it to build |
99 | a `TimeZone` at compile time. Here, we find the next DST transition from a |
100 | particular timestamp in `Europe/Zurich`, and then print that in local time for |
101 | Zurich: |
102 | |
103 | ``` |
104 | use jiff::{tz::{self, TimeZone}, Timestamp}; |
105 | |
106 | static TZ: TimeZone = tz::get!("Europe/Zurich" ); |
107 | |
108 | let ts: Timestamp = "2025-02-25T00:00Z" .parse()?; |
109 | let Some(next_transition) = TZ.following(ts).next() else { |
110 | return Err("no time zone transitions" .into()); |
111 | }; |
112 | let zdt = next_transition.timestamp().to_zoned(TZ.clone()); |
113 | assert_eq!(zdt.to_string(), "2025-03-30T03:00:00+02:00[Europe/Zurich]" ); |
114 | |
115 | # Ok::<(), Box<dyn std::error::Error>>(()) |
116 | ``` |
117 | |
118 | The above example does not require dynamic memory allocation or access to file |
119 | system APIs. It also _only_ embeds the `Europe/Zurich` time zone into your |
120 | compiled 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 | |
126 | pub 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 | |
138 | mod ambiguous; |
139 | #[cfg (feature = "tzdb-concatenated" )] |
140 | mod concatenated; |
141 | mod db; |
142 | mod offset; |
143 | pub(crate) mod posix; |
144 | #[cfg (feature = "tz-system" )] |
145 | mod system; |
146 | #[cfg (all(test, feature = "alloc" ))] |
147 | mod testdata; |
148 | mod timezone; |
149 | pub(crate) mod tzif; |
150 | // See module comment for WIP status. :-( |
151 | #[cfg (test)] |
152 | mod 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" )] |
219 | pub 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" )] |
321 | pub 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 ] |
356 | pub const fn offset(hours: i8) -> Offset { |
357 | Offset::constant(hours) |
358 | } |
359 | |