use alloc::{string::String, vec::Vec};
use crate::tz::TimeZone;
#[derive(Debug)]
pub(crate) struct BundledZoneInfo;
impl BundledZoneInfo {
pub(crate) fn new() -> BundledZoneInfo {
BundledZoneInfo
}
pub(crate) fn reset(&self) {
#[cfg(feature = "std")]
self::global::clear();
}
pub(crate) fn get(&self, name: &str) -> Option<TimeZone> {
#[cfg(feature = "std")]
if let Some(tz) = self::global::get(name) {
return Some(tz);
}
let (canonical_name, tzif) = lookup(name)?;
let tz = match TimeZone::tzif(canonical_name, tzif) {
Ok(tz) => tz,
Err(_err) => {
warn!(
"failed to parse TZif data from bundled \
tzdb for time zone {canonical_name} \
(this is like a bug, please report it): {_err}"
);
return None;
}
};
#[cfg(feature = "std")]
self::global::add(&tz);
Some(tz)
}
pub(crate) fn available(&self) -> Vec<String> {
available().into_iter().map(String::from).collect()
}
pub(crate) fn is_definitively_empty(&self) -> bool {
false
}
}
fn available() -> impl Iterator<Item = &'static str> {
#[cfg(feature = "tzdb-bundle-always")]
{
jiff_tzdb::available()
}
#[cfg(not(feature = "tzdb-bundle-always"))]
{
jiff_tzdb_platform::jiff_tzdb::available()
}
}
fn lookup(name: &str) -> Option<(&'static str, &'static [u8])> {
#[cfg(feature = "tzdb-bundle-always")]
{
jiff_tzdb::get(name)
}
#[cfg(not(feature = "tzdb-bundle-always"))]
{
jiff_tzdb_platform::jiff_tzdb::get(name)
}
}
#[cfg(feature = "std")]
mod global {
use std::{sync::RwLock, vec::Vec};
use crate::tz::TimeZone;
static CACHED_ZONES: RwLock<CachedZones> =
RwLock::new(CachedZones { zones: Vec::new() });
pub(super) fn get(name: &str) -> Option<TimeZone> {
CACHED_ZONES.read().unwrap().get(name).cloned()
}
pub(super) fn add(tz: &TimeZone) {
let mut cache = CACHED_ZONES.write().unwrap();
if let Err(i) = cache.get_zone_index(tz.diagnostic_name()) {
cache.zones.insert(i, tz.clone());
}
}
pub(super) fn clear() {
CACHED_ZONES.write().unwrap().clear();
}
#[derive(Debug)]
struct CachedZones {
zones: Vec<TimeZone>,
}
impl CachedZones {
fn get(&self, query: &str) -> Option<&TimeZone> {
self.get_zone_index(query).ok().map(|i| &self.zones[i])
}
fn get_zone_index(&self, query: &str) -> Result<usize, usize> {
self.zones.binary_search_by(|tz| {
cmp_ignore_ascii_case(tz.diagnostic_name(), query)
})
}
fn clear(&mut self) {
self.zones.clear();
}
}
fn cmp_ignore_ascii_case(s1: &str, s2: &str) -> core::cmp::Ordering {
let it1 = s1.as_bytes().iter().map(|&b| b.to_ascii_lowercase());
let it2 = s2.as_bytes().iter().map(|&b| b.to_ascii_lowercase());
it1.cmp(it2)
}
}