diff --git a/Cargo.toml b/Cargo.toml index e2cbaf4599..391f26f68b 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"] @@ -45,6 +45,9 @@ rkyv = {version = "0.7", optional = true} wasm-bindgen = { version = "0.2" } js-sys = { version = "0.3" } # contains FFI bindings for the JS Date API +[target.'cfg(not(any(target_os = "emscripten", target_os = "wasi", target_os = "solaris")))'.dependencies] +iana-time-zone = { version = "0.1.41", optional = true } + [target.'cfg(windows)'.dependencies] winapi = { version = "0.3.0", features = ["std", "minwinbase", "minwindef", "timezoneapi"], optional = true } diff --git a/src/offset/local/tz_info/timezone.rs b/src/offset/local/tz_info/timezone.rs index 8ebf64b702..085b25c117 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) } @@ -104,7 +104,7 @@ impl TimeZone { } /// Construct the time zone associated to UTC - fn utc() -> Self { + pub(crate) fn utc() -> Self { Self { transitions: Vec::new(), local_time_types: vec![LocalTimeType::UTC], @@ -816,15 +816,6 @@ mod tests { let time_zone_local = TimeZone::local()?; let time_zone_local_1 = TimeZone::from_posix_tz(&tz)?; assert_eq!(time_zone_local, time_zone_local_1); - } else { - let time_zone_local = TimeZone::local()?; - let time_zone_local_1 = TimeZone::from_posix_tz("localtime")?; - let time_zone_local_2 = TimeZone::from_posix_tz("/etc/localtime")?; - let time_zone_local_3 = TimeZone::from_posix_tz(":/etc/localtime")?; - - assert_eq!(time_zone_local, time_zone_local_1); - assert_eq!(time_zone_local, time_zone_local_2); - assert_eq!(time_zone_local, time_zone_local_3); } let time_zone_utc = TimeZone::from_posix_tz("UTC")?; diff --git a/src/offset/local/unix.rs b/src/offset/local/unix.rs index 6cec6b751c..ab91dfde38 100644 --- a/src/offset/local/unix.rs +++ b/src/offset/local/unix.rs @@ -47,12 +47,19 @@ impl Default for Source { // to that in `naive_to_local` match env::var_os("TZ") { Some(ref s) if s.to_str().is_some() => Source::Environment, - Some(_) | None => Source::LocalTime { - mtime: fs::symlink_metadata("/etc/localtime") - .expect("localtime should exist") - .modified() - .unwrap(), - last_checked: SystemTime::now(), + Some(_) | None => match fs::symlink_metadata("/etc/localtime") { + Ok(data) => Source::LocalTime { + // we have to pick a sensible default when the mtime fails + // by picking SystemTime::now() we raise the probability of + // the cache being invalidated if/when the mtime starts working + mtime: data.modified().unwrap_or_else(|_| SystemTime::now()), + last_checked: SystemTime::now(), + }, + 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() } + } }, } } @@ -89,10 +96,30 @@ struct Cache { source: Source, } +#[cfg(target_os = "android")] +const TZDB_LOCATION: &str = " /system/usr/share/zoneinfo"; + +#[allow(dead_code)] // keeps the cfg simpler +#[cfg(not(target_os = "android"))] +const TZDB_LOCATION: &str = "/usr/share/zoneinfo"; + +#[cfg(any(target_os = "emscripten", target_os = "wasi", target_os = "solaris"))] +fn fallback_timezone() -> Option { + Some(TimeZone::utc()) +} + +#[cfg(not(any(target_os = "emscripten", target_os = "wasi", target_os = "solaris")))] +fn fallback_timezone() -> Option { + let tz_name = iana_time_zone::get_timezone().ok()?; + let bytes = 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().expect("unable to parse localtime info"), + zone: TimeZone::local().ok().or_else(fallback_timezone).unwrap_or_else(TimeZone::utc), source: Source::default(), } }