Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

default to UTC when /etc/localtime is missing #756

Merged
merged 2 commits into from Aug 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/test-release.yml
Expand Up @@ -31,11 +31,11 @@ jobs:
- os: ubuntu-latest
rust_version: nightly
- os: ubuntu-20.04
rust_version: 1.32.0
rust_version: 1.38.0
- os: macos-latest
rust_version: 1.32.0
rust_version: 1.38.0
- os: windows-latest
rust_version: 1.32.0
rust_version: 1.38.0

runs-on: ${{ matrix.os }}

Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/test.yml
Expand Up @@ -39,11 +39,11 @@ jobs:
- os: ubuntu-latest
rust_version: nightly
- os: ubuntu-20.04
rust_version: 1.32.0
rust_version: 1.38.0
- os: macos-latest
rust_version: 1.32.0
rust_version: 1.38.0
- os: windows-latest
rust_version: 1.32.0
rust_version: 1.38.0

runs-on: ${{ matrix.os }}

Expand Down
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]
djc marked this conversation as resolved.
Show resolved Hide resolved
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
4 changes: 2 additions & 2 deletions ci/github.sh
Expand Up @@ -29,7 +29,7 @@ meaningful in the github actions feature matrix UI.

runv cargo --version

if [[ ${RUST_VERSION:-} != 1.32.0 ]]; then
if [[ ${RUST_VERSION:-} != 1.38.0 ]]; then
if [[ ${WASM:-} == yes_wasm ]]; then
test_wasm
elif [[ ${WASM:-} == wasm_simple ]]; then
Expand All @@ -49,7 +49,7 @@ meaningful in the github actions feature matrix UI.
else
test_regular UTC0
fi
elif [[ ${RUST_VERSION:-} == 1.32.0 ]]; then
elif [[ ${RUST_VERSION:-} == 1.38.0 ]]; then
test_132
else
echo "ERROR: didn't run any tests"
Expand Down
2 changes: 1 addition & 1 deletion clippy.toml
@@ -1 +1 @@
msrv = "1.32"
msrv = "1.38"
2 changes: 1 addition & 1 deletion src/format/mod.rs
Expand Up @@ -529,7 +529,7 @@ fn format_inner<'a>(
};

if let Some(v) = v {
if (spec == &Year || spec == &IsoYear) && !(0 <= v && v < 10_000) {
if (spec == &Year || spec == &IsoYear) && !(0..10_000).contains(&v) {
// non-four-digit years require an explicit sign as per ISO 8601
match *pad {
Pad::None => write!(result, "{:+}", v),
Expand Down
2 changes: 1 addition & 1 deletion src/format/parsed.rs
Expand Up @@ -232,7 +232,7 @@ impl Parsed {
/// given hour number in 12-hour clocks.
#[inline]
pub fn set_hour12(&mut self, value: i64) -> ParseResult<()> {
if value < 1 || value > 12 {
if !(1..=12).contains(&value) {
return Err(OUT_OF_RANGE);
}
set_if_consistent(&mut self.hour_mod_12, value as u32 % 12)
Expand Down
4 changes: 2 additions & 2 deletions src/format/scan.rs
Expand Up @@ -48,7 +48,7 @@ pub(super) fn number(s: &str, min: usize, max: usize) -> ParseResult<(&str, i64)
let mut n = 0i64;
for (i, c) in bytes.iter().take(max).cloned().enumerate() {
// cloned() = copied()
if c < b'0' || b'9' < c {
if !(b'0'..=b'9').contains(&c) {
if i < min {
return Err(INVALID);
} else {
Expand Down Expand Up @@ -79,7 +79,7 @@ pub(super) fn nanosecond(s: &str) -> ParseResult<(&str, i64)> {
let v = v.checked_mul(SCALE[consumed]).ok_or(OUT_OF_RANGE)?;

// if there are more than 9 digits, skip next digits.
let s = s.trim_left_matches(|c: char| '0' <= c && c <= '9');
let s = s.trim_left_matches(|c: char| ('0'..='9').contains(&c));

Ok((s, v))
}
Expand Down
2 changes: 1 addition & 1 deletion src/naive/date.rs
Expand Up @@ -1887,7 +1887,7 @@ impl fmt::Debug for NaiveDate {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let year = self.year();
let mdf = self.mdf();
if 0 <= year && year <= 9999 {
if (0..=9999).contains(&year) {
write!(f, "{:04}-{:02}-{:02}", year, mdf.month(), mdf.day())
} else {
// ISO 8601 requires the explicit sign for out-of-range years
Expand Down
2 changes: 1 addition & 1 deletion src/naive/internals.rs
Expand Up @@ -297,7 +297,7 @@ impl Of {
pub(super) fn valid(&self) -> bool {
let Of(of) = *self;
let ol = of >> 3;
MIN_OL <= ol && ol <= MAX_OL
(MIN_OL..=MAX_OL).contains(&ol)
}

#[inline]
Expand Down
2 changes: 1 addition & 1 deletion src/naive/isoweek.rs
Expand Up @@ -135,7 +135,7 @@ impl fmt::Debug for IsoWeek {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let year = self.year();
let week = self.week();
if 0 <= year && year <= 9999 {
if (0..=9999).contains(&year) {
write!(f, "{:04}-W{:02}", year, week)
} else {
// ISO 8601 requires the explicit sign for out-of-range years
Expand Down
4 changes: 2 additions & 2 deletions src/naive/time/mod.rs
Expand Up @@ -586,7 +586,7 @@ impl NaiveTime {
secs += 1;
}
debug_assert!(-86_400 <= secs && secs < 2 * 86_400);
debug_assert!(0 <= frac && frac < 1_000_000_000);
debug_assert!((0..1_000_000_000).contains(&frac));

if secs < 0 {
secs += 86_400;
Expand All @@ -595,7 +595,7 @@ impl NaiveTime {
secs -= 86_400;
morerhssecs += 86_400;
}
debug_assert!(0 <= secs && secs < 86_400);
debug_assert!((0..86_400).contains(&secs));

(NaiveTime { secs: secs as u32, frac: frac as u32 }, morerhssecs)
}
Expand Down
22 changes: 11 additions & 11 deletions src/offset/local/tz_info/rule.rs
Expand Up @@ -365,13 +365,13 @@ fn parse_name<'a>(cursor: &mut Cursor<'a>) -> Result<&'a [u8], Error> {
fn parse_offset(cursor: &mut Cursor) -> Result<i32, Error> {
let (sign, hour, minute, second) = parse_signed_hhmmss(cursor)?;

if hour < 0 || hour > 24 {
if !(0..=24).contains(&hour) {
return Err(Error::InvalidTzString("invalid offset hour"));
}
if minute < 0 || minute > 59 {
if !(0..=59).contains(&minute) {
return Err(Error::InvalidTzString("invalid offset minute"));
}
if second < 0 || second > 59 {
if !(0..=59).contains(&second) {
return Err(Error::InvalidTzString("invalid offset second"));
}

Expand All @@ -382,13 +382,13 @@ fn parse_offset(cursor: &mut Cursor) -> Result<i32, Error> {
fn parse_rule_time(cursor: &mut Cursor) -> Result<i32, Error> {
let (hour, minute, second) = parse_hhmmss(cursor)?;

if hour < 0 || hour > 24 {
if !(0..=24).contains(&hour) {
return Err(Error::InvalidTzString("invalid day time hour"));
}
if minute < 0 || minute > 59 {
if !(0..=59).contains(&minute) {
return Err(Error::InvalidTzString("invalid day time minute"));
}
if second < 0 || second > 59 {
if !(0..=59).contains(&second) {
return Err(Error::InvalidTzString("invalid day time second"));
}

Expand All @@ -402,10 +402,10 @@ fn parse_rule_time_extended(cursor: &mut Cursor) -> Result<i32, Error> {
if hour < -167 || hour > 167 {
return Err(Error::InvalidTzString("invalid day time hour"));
}
if minute < 0 || minute > 59 {
if !(0..=59).contains(&minute) {
return Err(Error::InvalidTzString("invalid day time minute"));
}
if second < 0 || second > 59 {
if !(0..=59).contains(&second) {
return Err(Error::InvalidTzString("invalid day time second"));
}

Expand Down Expand Up @@ -496,7 +496,7 @@ impl RuleDay {

/// Construct a transition rule day represented by a Julian day in `[1, 365]`, without taking occasional Feb 29 into account, which is not referenceable
fn julian_1(julian_day_1: u16) -> Result<Self, Error> {
if julian_day_1 < 1 || julian_day_1 > 365 {
if !(1..=365).contains(&julian_day_1) {
return Err(Error::TransitionRule("invalid rule day julian day"));
}

Expand All @@ -514,11 +514,11 @@ impl RuleDay {

/// Construct a transition rule day represented by a month, a month week and a week day
fn month_weekday(month: u8, week: u8, week_day: u8) -> Result<Self, Error> {
if month < 1 || month > 12 {
if !(1..=12).contains(&month) {
return Err(Error::TransitionRule("invalid rule day month"));
}

if week < 1 || week > 5 {
if !(1..=5).contains(&week) {
return Err(Error::TransitionRule("invalid rule day week"));
}

Expand Down
15 changes: 3 additions & 12 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 @@ -482,7 +482,7 @@ impl TimeZoneName {
fn new(input: &[u8]) -> Result<Self, Error> {
let len = input.len();

if len < 3 || len > 7 {
if !(3..=7).contains(&len) {
return Err(Error::LocalTimeType(
"time zone name must have between 3 and 7 characters",
));
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
djc marked this conversation as resolved.
Show resolved Hide resolved
#[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