Skip to content

Commit

Permalink
default to UTC when iana-time-zone errors and /etc/localtime is missi…
Browse files Browse the repository at this point in the history
…ng, support android

fix function name
  • Loading branch information
esheppa committed Aug 9, 2022
1 parent 9712fdb commit 669d555
Show file tree
Hide file tree
Showing 3 changed files with 40 additions and 19 deletions.
5 changes: 4 additions & 1 deletion Cargo.toml
Expand Up @@ -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"]
Expand All @@ -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 }

Expand Down
13 changes: 2 additions & 11 deletions src/offset/local/tz_info/timezone.rs
Expand Up @@ -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<Self, Error> {
pub(crate) fn from_tz_data(bytes: &[u8]) -> Result<Self, Error> {
parser::parse(bytes)
}

Expand All @@ -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],
Expand Down Expand Up @@ -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")?;
Expand Down
41 changes: 34 additions & 7 deletions src/offset/local/unix.rs
Expand Up @@ -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() }
}
},
}
}
Expand Down Expand Up @@ -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<TimeZone> {
Some(TimeZone::utc())
}

#[cfg(not(any(target_os = "emscripten", target_os = "wasi", target_os = "solaris")))]
fn fallback_timezone() -> Option<TimeZone> {
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(),
}
}
Expand Down

0 comments on commit 669d555

Please sign in to comment.