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}