Skip to content

Commit

Permalink
Rewite caching in local::unix module
Browse files Browse the repository at this point in the history
  • Loading branch information
pitdicker committed Feb 24, 2024
1 parent 02c68d6 commit 672b038
Show file tree
Hide file tree
Showing 2 changed files with 303 additions and 237 deletions.
103 changes: 1 addition & 102 deletions src/offset/local/tz_info/timezone.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
//! Types related to a time zone.

use std::fs::{self, File};
use std::io::{self, Read};
use std::path::{Path, PathBuf};
use std::{cmp::Ordering, fmt, str};

use super::rule::{AlternateTime, TransitionRule};
Expand All @@ -22,43 +19,8 @@ pub(crate) struct TimeZone {
}

impl TimeZone {
/// Returns local time zone.
///
/// This method in not supported on non-UNIX platforms, and returns the UTC time zone instead.
pub(crate) fn local(env_tz: Option<&str>) -> Result<Self, Error> {
match env_tz {
Some(tz) => Self::from_posix_tz(tz),
None => Self::from_posix_tz("localtime"),
}
}

/// Construct a time zone from a POSIX TZ string, as described in [the POSIX documentation of the `TZ` environment variable](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html).
fn from_posix_tz(tz_string: &str) -> Result<Self, Error> {
if tz_string.is_empty() {
return Err(Error::InvalidTzString("empty TZ string"));
}

if tz_string == "localtime" {
return Self::from_tz_data(&fs::read("/etc/localtime")?);
}

// attributes are not allowed on if blocks in Rust 1.38
#[cfg(target_os = "android")]
{
if let Ok(bytes) = android_tzdata::find_tz_data(tz_string) {
return Self::from_tz_data(&bytes);
}
}

let mut chars = tz_string.chars();
if chars.next() == Some(':') {
return Self::from_file(&mut find_tz_file(chars.as_str())?);
}

if let Ok(mut file) = find_tz_file(tz_string) {
return Self::from_file(&mut file);
}

pub(crate) fn from_tz_string(tz_string: &str) -> Result<Self, Error> {

Check warning on line 23 in src/offset/local/tz_info/timezone.rs

View check run for this annotation

Codecov / codecov/patch

src/offset/local/tz_info/timezone.rs#L23

Added line #L23 was not covered by tests
// TZ string extensions are not allowed
let tz_string = tz_string.trim_matches(|c: char| c.is_ascii_whitespace());
let rule = TransitionRule::from_tz_string(tz_string.as_bytes(), false)?;
Expand All @@ -85,13 +47,6 @@ impl TimeZone {
Ok(new)
}

/// Construct a time zone from the contents of a time zone file
fn from_file(file: &mut File) -> Result<Self, Error> {
let mut bytes = Vec::new();
file.read_to_end(&mut bytes)?;
Self::from_tz_data(&bytes)
}

/// 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).
Expand Down Expand Up @@ -606,34 +561,6 @@ impl LocalTimeType {
pub(super) const UTC: LocalTimeType = Self { ut_offset: 0, is_dst: false, name: None };
}

/// Open the TZif file corresponding to a TZ string
fn find_tz_file(path: impl AsRef<Path>) -> Result<File, Error> {
// Don't check system timezone directories on non-UNIX platforms
#[cfg(not(unix))]
return Ok(File::open(path)?);

#[cfg(unix)]
{
let path = path.as_ref();
if path.is_absolute() {
return Ok(File::open(path)?);
}

for folder in &ZONE_INFO_DIRECTORIES {
if let Ok(file) = File::open(PathBuf::from(folder).join(path)) {
return Ok(file);
}
}

Err(Error::Io(io::ErrorKind::NotFound.into()))
}
}

// Possible system timezone directories
#[cfg(unix)]
const ZONE_INFO_DIRECTORIES: [&str; 4] =
["/usr/share/zoneinfo", "/share/zoneinfo", "/etc/zoneinfo", "/usr/share/lib/zoneinfo"];

/// Number of seconds in one week
pub(crate) const SECONDS_PER_WEEK: i64 = SECONDS_PER_DAY * DAYS_PER_WEEK;
/// Number of seconds in 28 days
Expand Down Expand Up @@ -844,34 +771,6 @@ mod tests {
Ok(())
}

#[test]
fn test_time_zone_from_posix_tz() -> Result<(), Error> {
#[cfg(unix)]
{
// if the TZ var is set, this essentially _overrides_ the
// time set by the localtime symlink
// so just ensure that ::local() acts as expected
// in this case
if let Ok(tz) = std::env::var("TZ") {
let time_zone_local = TimeZone::local(Some(tz.as_str()))?;
let time_zone_local_1 = TimeZone::from_posix_tz(&tz)?;
assert_eq!(time_zone_local, time_zone_local_1);
}

// `TimeZone::from_posix_tz("UTC")` will return `Error` if the environment does not have
// a time zone database, like for example some docker containers.
// In that case skip the test.
if let Ok(time_zone_utc) = TimeZone::from_posix_tz("UTC") {
assert_eq!(time_zone_utc.find_local_time_type(0)?.offset(), 0);
}
}

assert!(TimeZone::from_posix_tz("EST5EDT,0/0,J365/25").is_err());
assert!(TimeZone::from_posix_tz("").is_err());

Ok(())
}

#[test]
fn test_leap_seconds() -> Result<(), Error> {
let time_zone = TimeZone::new(
Expand Down

0 comments on commit 672b038

Please sign in to comment.