diff --git a/Cargo.toml b/Cargo.toml index e2cbaf4599..9311aba86b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ default = ["clock", "std", "oldtime"] alloc = [] libc = [] std = [] -clock = ["std", "winapi"] +clock = ["std", "winapi", "iana-time-zone"] oldtime = ["time"] wasmbind = [] # TODO: empty feature to avoid breaking change in 0.4.20, can be removed later unstable-locales = ["pure-rust-locales", "alloc"] @@ -40,6 +40,7 @@ serde = { version = "1.0.99", default-features = false, optional = true } pure-rust-locales = { version = "0.5.2", optional = true } criterion = { version = "0.3", optional = true } rkyv = {version = "0.7", optional = true} +iana-time-zone = { version = "0.1.41", optional = true } [target.'cfg(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi"))))'.dependencies] wasm-bindgen = { version = "0.2" } diff --git a/src/offset/local/tz_info/timezone.rs b/src/offset/local/tz_info/timezone.rs index aba8622960..c7eb3038c8 100644 --- a/src/offset/local/tz_info/timezone.rs +++ b/src/offset/local/tz_info/timezone.rs @@ -89,7 +89,7 @@ impl TimeZone { /// Construct a time zone from the contents of a time zone file /// /// Parse TZif data as described in [RFC 8536](https://datatracker.ietf.org/doc/html/rfc8536). - pub(super) fn from_tz_data(bytes: &[u8]) -> Result { + pub(crate) fn from_tz_data(bytes: &[u8]) -> Result { parser::parse(bytes) } diff --git a/src/offset/local/unix.rs b/src/offset/local/unix.rs index 6719bb9760..024f8325c4 100644 --- a/src/offset/local/unix.rs +++ b/src/offset/local/unix.rs @@ -57,6 +57,7 @@ impl Default for Source { }, Err(_) => { // as above, now() should be a better default than some constant + // TODO: see if we can improve caching in the case where the fallback is a valid timezone Source::LocalTime { mtime: SystemTime::now(), last_checked: SystemTime::now() } } }, @@ -95,11 +96,33 @@ struct Cache { source: Source, } +#[cfg(any(target_os = "emscripten", target_os = "wasi", target_os = "solaris"))] +fn get_fallback_timezone() -> Option { + TimeZone::utc() +} + +#[cfg(not(any(target_os = "emscripten", target_os = "wasi", target_os = "solaris")))] +fn get_fallback_timezone() -> Option { + let tz_name = try_opt!(iana_time_zone::get_timezone().ok()); + + #[cfg(target_os = "android")] + let tzdb_location = " /system/usr/share/zoneinfo"; + + #[cfg(not(target_os = "android"))] + let tzdb_location = "/usr/share/zoneinfo"; + + let bytes = try_opt!(fs::read(format!("{tzdb_location}/{tz_name}")).ok()); + TimeZone::from_tz_data(&bytes).ok() +} + impl Default for Cache { fn default() -> Cache { // default to UTC if no local timezone can be found Cache { - zone: TimeZone::local().unwrap_or_else(|_| TimeZone::utc()), + zone: TimeZone::local() + .ok() + .or_else(get_fallback_timezone) + .unwrap_or_else(TimeZone::utc), source: Source::default(), } }