| 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 | |