#![allow(dead_code)]
use std::{sync::RwLock, time::Duration};
use alloc::{string::ToString, sync::Arc};
use crate::{
error::{err, Error, ErrorContext},
tz::{posix::PosixTz, TimeZone, TimeZoneDatabase},
util::cache::Expiration,
};
#[cfg(unix)]
#[path = "unix.rs"]
mod sys;
#[cfg(windows)]
#[path = "windows/mod.rs"]
mod sys;
#[cfg(all(
feature = "js",
any(target_arch = "wasm32", target_arch = "wasm64"),
target_os = "unknown"
))]
#[path = "wasm_js.rs"]
mod sys;
#[cfg(not(any(
unix,
windows,
all(
feature = "js",
any(target_arch = "wasm32", target_arch = "wasm64"),
target_os = "unknown"
)
)))]
mod sys {
use crate::tz::{TimeZone, TimeZoneDatabase};
pub(super) fn get(_db: &TimeZoneDatabase) -> Option<TimeZone> {
warn!("getting system time zone on this platform is unsupported");
None
}
pub(super) fn read(
_db: &TimeZoneDatabase,
path: &str,
) -> Option<TimeZone> {
match super::read_unnamed_tzif_file(path) {
Ok(tz) => Some(tz),
Err(_err) => {
trace!("failed to read {path} as unnamed time zone: {_err}");
None
}
}
}
}
static TTL: Duration = Duration::new(5 * 60, 0);
static CACHE: RwLock<Cache> = RwLock::new(Cache::empty());
struct Cache {
tz: Option<TimeZone>,
expiration: Expiration,
}
impl Cache {
const fn empty() -> Cache {
Cache { tz: None, expiration: Expiration::expired() }
}
}
pub(crate) fn get(db: &TimeZoneDatabase) -> Result<TimeZone, Error> {
{
let cache = CACHE.read().unwrap();
if let Some(ref tz) = cache.tz {
if !cache.expiration.is_expired() {
return Ok(tz.clone());
}
}
}
let tz = get_force(db)?;
{
let mut cache = CACHE.write().unwrap();
cache.tz = Some(tz.clone());
cache.expiration = Expiration::after(TTL);
}
Ok(tz)
}
pub(crate) fn get_force(db: &TimeZoneDatabase) -> Result<TimeZone, Error> {
match get_env_tz(db) {
Ok(Some(tz)) => {
debug!("checked TZ environment variable and found {tz:?}");
return Ok(tz);
}
Ok(None) => {
trace!("checked TZ environment variable but found nothing");
}
Err(_err) => {
trace!("checked TZ environment variable but got error: {_err}");
}
}
if let Some(tz) = sys::get(db) {
return Ok(tz);
}
Err(err!("failed to find system time zone"))
}
pub(crate) fn reset() {
*CACHE.write().unwrap() = Cache::empty();
}
fn get_env_tz(db: &TimeZoneDatabase) -> Result<Option<TimeZone>, Error> {
let Some(tzenv) = std::env::var_os("TZ") else { return Ok(None) };
if tzenv.is_empty() {
return Ok(None);
}
let tz_name_or_path = match PosixTz::parse_os_str(&tzenv) {
Err(_err) => {
trace!(
"failed to parse {tzenv:?} as POSIX TZ rule \
(attempting to treat it as an IANA time zone): {_err}",
);
tzenv
.to_str()
.ok_or_else(|| {
err!(
"failed to parse {tzenv:?} as a POSIX TZ transition \
string, or as valid UTF-8 \
(therefore ignoring TZ environment variable)",
)
})?
.to_string()
}
Ok(PosixTz::Implementation(string)) => string.to_string(),
Ok(PosixTz::Rule(tz)) => match tz.reasonable() {
Ok(reasonable_posix_tz) => {
let kind = super::TimeZoneKind::Posix(
super::TimeZonePosix::from(reasonable_posix_tz),
);
let tz = TimeZone { kind: Some(Arc::new(kind)) };
return Ok(Some(tz));
}
Err(_) => {
warn!(
"parsed {tzenv:?} as POSIX TZ transition string, \
but Jiff considers it unreasonable since \
it specifies DST but without a rule \
(therefore ignoring TZ environment variable)",
);
return Ok(None);
}
},
};
let needle = "zoneinfo/";
let Some(rpos) = tz_name_or_path.rfind(needle) else {
trace!(
"could not find {needle:?} in TZ={tz_name_or_path:?}, \
therefore attempting lookup in {db:?}",
);
return match db.get(&tz_name_or_path) {
Ok(tz) => Ok(Some(tz)),
Err(_err) => {
trace!(
"using TZ={tz_name_or_path:?} as time zone name failed, \
could not find time zone in zoneinfo database {db:?} \
(continuing to try and use {tz_name_or_path:?}",
);
Ok(sys::read(db, &tz_name_or_path))
}
};
};
let name = &tz_name_or_path[rpos + needle.len()..];
trace!(
"extracted {name:?} from TZ={tz_name_or_path:?} \
and assuming it is an IANA time zone name",
);
match db.get(&name) {
Ok(tz) => return Ok(Some(tz)),
Err(_err) => {
trace!(
"using {name:?} from TZ={tz_name_or_path:?}, \
could not find time zone in zoneinfo database {db:?} \
(continuing to try and use {tz_name_or_path:?})",
);
}
}
Ok(sys::read(db, &tz_name_or_path))
}
fn read_unnamed_tzif_file(path: &str) -> Result<TimeZone, Error> {
let data = std::fs::read(path)
.map_err(Error::io)
.with_context(|| err!("failed to read {path:?} as TZif file"))?;
let tz = TimeZone::tzif_system(&data)
.with_context(|| err!("found invalid TZif data at {path:?}"))?;
Ok(tz)
}