Skip to main content

timezone_data/
lib.rs

1//! `timezone-data` provides direct, allocation-free access to IANA timezone
2//! data, exposing the transitions, zone types, POSIX TZ rules, leap seconds,
3//! and metadata that most timezone libraries keep private.
4//!
5//! Timezone data is compiled from the official IANA source and embedded in the
6//! crate as an uncompressed zip archive, so there is no dependency on the host
7//! system's timezone files. The crate is `#![no_std]` and never allocates:
8//! every accessor borrows into the embedded bytes and decodes records lazily.
9//!
10//! # Example
11//!
12//! ```
13//! let z = timezone_data::load("America/New_York").unwrap();
14//!
15//! // Inspect zone types (EST, EDT, ...).
16//! for zt in z.types() {
17//!     // zt.abbrev, zt.offset, zt.is_dst
18//!     let _ = zt;
19//! }
20//!
21//! // Look up the active zone at a specific Unix timestamp.
22//! let zt = z.lookup(1_700_000_000);
23//! assert_eq!(zt.abbrev, "EST");
24//!
25//! // Compute future transitions from the POSIX TZ rule.
26//! if let Some(rule) = z.extend() {
27//!     let (start, end) = rule.transitions_for_year(2025).unwrap();
28//!     assert!(start < end);
29//! }
30//! ```
31#![no_std]
32#![forbid(unsafe_code)]
33
34mod error;
35mod meta;
36mod parse;
37mod posix;
38mod zipstore;
39
40pub use error::Error;
41pub use meta::{meta, parse_iso6709, Country, ZoneMeta};
42pub use parse::{parse, LeapSecond, RangeTransition, Transition, Zone, ZoneType};
43pub use posix::{parse_posix_tz, PosixTz, RuleKind, TransitionRule};
44
45/// The embedded IANA timezone database (an uncompressed zip archive).
46pub(crate) static ZONEINFO_ZIP: &[u8] = include_bytes!("../zoneinfo.zip");
47
48/// Loads a [`Zone`] by IANA timezone name from the embedded database.
49///
50/// An empty name or `"UTC"` resolves to the `UTC` zone.
51pub fn load(name: &str) -> Result<Zone<'static>, Error> {
52    let query = if name.is_empty() { "UTC" } else { name };
53    let (canonical, data) = zipstore::find_named(ZONEINFO_ZIP, query)?;
54    parse(canonical, data)
55}
56
57/// Loads a [`Zone`] by name, falling back to case-insensitive matching.
58pub fn load_insensitive(name: &str) -> Result<Zone<'static>, Error> {
59    if let Ok(z) = load(name) {
60        return Ok(z);
61    }
62    for canonical in names() {
63        if canonical.eq_ignore_ascii_case(name) {
64            return load(canonical);
65        }
66    }
67    Err(Error::NotFound)
68}
69
70/// Returns an iterator over every entry name in the embedded database.
71///
72/// This includes the data tables `iso3166.tab` and `zone1970.tab`, which are
73/// not loadable as zones.
74pub fn names() -> impl Iterator<Item = &'static str> {
75    zipstore::names(ZONEINFO_ZIP)
76}
77
78impl Zone<'_> {
79    /// Returns metadata (countries, coordinates) for this timezone, or `None`.
80    pub fn meta(&self) -> Option<ZoneMeta<'static>> {
81        meta(self.name())
82    }
83}