use crate::{
error::{err, Error},
tz::TimeZone,
util::sync::Arc,
};
use self::{
bundled::BundledZoneInfo, concatenated::Concatenated, zoneinfo::ZoneInfo,
};
mod bundled;
mod concatenated;
mod zoneinfo;
pub fn db() -> &'static TimeZoneDatabase {
#[cfg(not(feature = "std"))]
{
static NONE: TimeZoneDatabase = TimeZoneDatabase::none();
&NONE
}
#[cfg(feature = "std")]
{
use std::sync::OnceLock;
static DB: OnceLock<TimeZoneDatabase> = OnceLock::new();
DB.get_or_init(|| {
let db = TimeZoneDatabase::from_env();
debug!("initialized global time zone database: {db:?}");
db
})
}
}
#[derive(Clone)]
pub struct TimeZoneDatabase {
inner: Option<Arc<TimeZoneDatabaseInner>>,
}
#[derive(Debug)]
#[cfg_attr(not(feature = "alloc"), derive(Clone))]
struct TimeZoneDatabaseInner {
zoneinfo: ZoneInfo,
concatenated: Concatenated,
bundled: BundledZoneInfo,
}
impl TimeZoneDatabase {
pub const fn none() -> TimeZoneDatabase {
TimeZoneDatabase { inner: None }
}
pub fn from_env() -> TimeZoneDatabase {
let zoneinfo = ZoneInfo::from_env();
let concatenated = Concatenated::from_env();
let bundled = BundledZoneInfo::new();
let inner = TimeZoneDatabaseInner { zoneinfo, concatenated, bundled };
let db = TimeZoneDatabase { inner: Some(Arc::new(inner)) };
if db.is_definitively_empty() {
warn!(
"could not find zoneinfo, concatenated tzdata or \
bundled time zone database",
);
}
db
}
#[cfg(feature = "std")]
pub fn from_dir<P: AsRef<std::path::Path>>(
path: P,
) -> Result<TimeZoneDatabase, Error> {
let path = path.as_ref();
let zoneinfo = ZoneInfo::from_dir(path)?;
let concatenated = Concatenated::none();
let bundled = BundledZoneInfo::new();
let inner = TimeZoneDatabaseInner { zoneinfo, concatenated, bundled };
let db = TimeZoneDatabase { inner: Some(Arc::new(inner)) };
if db.is_definitively_empty() {
warn!(
"could not find zoneinfo data at directory {path} \
(and there is no bundled time zone database)",
path = path.display(),
);
}
Ok(db)
}
#[cfg(feature = "std")]
pub fn from_concatenated_path<P: AsRef<std::path::Path>>(
path: P,
) -> Result<TimeZoneDatabase, Error> {
let path = path.as_ref();
let zoneinfo = ZoneInfo::none();
let concatenated = Concatenated::from_path(path)?;
let bundled = BundledZoneInfo::new();
let inner = TimeZoneDatabaseInner { zoneinfo, concatenated, bundled };
let db = TimeZoneDatabase { inner: Some(Arc::new(inner)) };
if db.is_definitively_empty() {
warn!(
"could not find concatenated tzdata in file {path} \
(and there is no bundled time zone database)",
path = path.display(),
);
}
Ok(db)
}
pub fn get(&self, name: &str) -> Result<TimeZone, Error> {
let inner = self.inner.as_deref().ok_or_else(|| {
if cfg!(feature = "std") {
err!(
"failed to find time zone `{name}` since there is no \
time zone database configured",
)
} else {
err!(
"failed to find time zone `{name}`, there is no \
global time zone database configured (and is currently \
impossible to do so without Jiff's `std` feature \
enabled, if you need this functionality, please file \
an issue on Jiff's tracker with your use case)",
)
}
})?;
if let Some(tz) = inner.zoneinfo.get(name) {
trace!("found time zone `{name}` in {:?}", inner.zoneinfo);
return Ok(tz);
}
if let Some(tz) = inner.concatenated.get(name) {
trace!("found time zone `{name}` in {:?}", inner.concatenated);
return Ok(tz);
}
if let Some(tz) = inner.bundled.get(name) {
trace!("found time zone `{name}` in {:?}", inner.bundled);
return Ok(tz);
}
Err(err!("failed to find time zone `{name}` in time zone database"))
}
#[cfg(feature = "alloc")]
pub fn available(&self) -> TimeZoneNameIter {
let Some(ref inner) = self.inner else {
return TimeZoneNameIter {
it: alloc::vec::Vec::new().into_iter(),
};
};
let mut all = inner.zoneinfo.available();
all.extend(inner.concatenated.available());
all.extend(inner.bundled.available());
all.sort();
all.dedup();
TimeZoneNameIter { it: all.into_iter() }
}
pub fn reset(&self) {
let Some(inner) = self.inner.as_deref() else { return };
inner.zoneinfo.reset();
inner.concatenated.reset();
inner.bundled.reset();
}
pub fn is_definitively_empty(&self) -> bool {
let Some(inner) = self.inner.as_deref() else { return true };
inner.zoneinfo.is_definitively_empty()
&& inner.concatenated.is_definitively_empty()
&& inner.bundled.is_definitively_empty()
}
}
impl core::fmt::Debug for TimeZoneDatabase {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
write!(f, "TimeZoneDatabase(")?;
let Some(inner) = self.inner.as_deref() else {
return write!(f, "unavailable)");
};
write!(
f,
"{:?}, {:?}, {:?}",
inner.zoneinfo, inner.concatenated, inner.bundled
)?;
Ok(())
}
}
#[cfg(feature = "alloc")]
#[derive(Clone, Debug)]
pub struct TimeZoneNameIter {
it: alloc::vec::IntoIter<alloc::string::String>,
}
#[cfg(feature = "alloc")]
impl Iterator for TimeZoneNameIter {
type Item = alloc::string::String;
fn next(&mut self) -> Option<alloc::string::String> {
self.it.next()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn time_zone_database_size() {
#[cfg(feature = "alloc")]
{
let word = core::mem::size_of::<usize>();
assert_eq!(word, core::mem::size_of::<TimeZoneDatabase>());
}
#[cfg(not(feature = "alloc"))]
{
assert_eq!(1, core::mem::size_of::<TimeZoneDatabase>());
}
}
}