use icu_provider::prelude::*;
use zerovec::vecs::{VarZeroSliceIter, ZeroSliceIter};
use crate::{
provider::iana::{
IanaNames, IanaToBcp47Map, TimezoneIdentifiersIanaCoreV1,
TimezoneIdentifiersIanaExtendedV1, NON_REGION_CITY_PREFIX,
},
TimeZone,
};
#[derive(Debug, Clone)]
pub struct IanaParser {
data: DataPayload<TimezoneIdentifiersIanaCoreV1>,
checksum: u64,
}
impl IanaParser {
#[cfg(feature = "compiled_data")]
#[expect(clippy::new_ret_no_self)]
pub fn new() -> IanaParserBorrowed<'static> {
IanaParserBorrowed::new()
}
icu_provider::gen_buffer_data_constructors!(() -> error: DataError,
functions: [
new: skip,
try_new_with_buffer_provider,
try_new_unstable,
Self,
]
);
#[doc = icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::new)]
pub fn try_new_unstable<P>(provider: &P) -> Result<Self, DataError>
where
P: DataProvider<TimezoneIdentifiersIanaCoreV1> + ?Sized,
{
let response = provider.load(Default::default())?;
Ok(Self {
data: response.payload,
checksum: response.metadata.checksum.ok_or_else(|| {
DataError::custom("Missing checksum")
.with_req(TimezoneIdentifiersIanaCoreV1::INFO, Default::default())
})?,
})
}
pub fn as_borrowed(&self) -> IanaParserBorrowed<'_> {
IanaParserBorrowed {
data: self.data.get(),
checksum: self.checksum,
}
}
}
impl AsRef<IanaParser> for IanaParser {
#[inline]
fn as_ref(&self) -> &IanaParser {
self
}
}
#[derive(Debug, Copy, Clone)]
pub struct IanaParserBorrowed<'a> {
data: &'a IanaToBcp47Map<'a>,
checksum: u64,
}
#[cfg(feature = "compiled_data")]
impl Default for IanaParserBorrowed<'static> {
fn default() -> Self {
Self::new()
}
}
impl IanaParserBorrowed<'static> {
#[cfg(feature = "compiled_data")]
pub fn new() -> Self {
Self {
data: crate::provider::Baked::SINGLETON_TIMEZONE_IDENTIFIERS_IANA_CORE_V1,
checksum: crate::provider::Baked::SINGLETON_TIMEZONE_IDENTIFIERS_IANA_CORE_V1_CHECKSUM,
}
}
pub fn static_to_owned(&self) -> IanaParser {
IanaParser {
data: DataPayload::from_static_ref(self.data),
checksum: self.checksum,
}
}
}
impl<'a> IanaParserBorrowed<'a> {
pub fn parse(&self, iana_id: &str) -> TimeZone {
self.parse_from_utf8(iana_id.as_bytes())
}
pub fn parse_from_utf8(&self, iana_id: &[u8]) -> TimeZone {
let Some(trie_value) = self.trie_value(iana_id) else {
return TimeZone::UNKNOWN;
};
let Some(tz) = self.data.bcp47_ids.get(trie_value.index()) else {
debug_assert!(false, "index should be in range");
return TimeZone::UNKNOWN;
};
tz
}
fn trie_value(&self, iana_id: &[u8]) -> Option<IanaTrieValue> {
let mut cursor = self.data.map.cursor();
if !iana_id.contains(&b'/') {
cursor.step(NON_REGION_CITY_PREFIX);
}
for &b in iana_id {
cursor.step(b);
}
cursor.take_value().map(IanaTrieValue)
}
pub fn iter(&self) -> TimeZoneIter<'a> {
TimeZoneIter {
inner: self.data.bcp47_ids.iter(),
}
}
}
#[derive(Debug)]
pub struct TimeZoneIter<'a> {
inner: ZeroSliceIter<'a, TimeZone>,
}
impl Iterator for TimeZoneIter<'_> {
type Item = TimeZone;
fn next(&mut self) -> Option<Self::Item> {
self.inner.next()
}
}
#[derive(Debug, Clone)]
pub struct IanaParserExtended<I> {
inner: I,
data: DataPayload<TimezoneIdentifiersIanaExtendedV1>,
}
impl IanaParserExtended<IanaParser> {
#[cfg(feature = "compiled_data")]
#[expect(clippy::new_ret_no_self)]
pub fn new() -> IanaParserExtendedBorrowed<'static> {
IanaParserExtendedBorrowed::new()
}
icu_provider::gen_buffer_data_constructors!(() -> error: DataError,
functions: [
new: skip,
try_new_with_buffer_provider,
try_new_unstable,
Self,
]
);
#[doc = icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::new)]
pub fn try_new_unstable<P>(provider: &P) -> Result<Self, DataError>
where
P: DataProvider<TimezoneIdentifiersIanaCoreV1>
+ DataProvider<TimezoneIdentifiersIanaExtendedV1>
+ ?Sized,
{
let parser = IanaParser::try_new_unstable(provider)?;
Self::try_new_with_parser_unstable(provider, parser)
}
}
impl<I> IanaParserExtended<I>
where
I: AsRef<IanaParser>,
{
#[cfg(feature = "compiled_data")]
pub fn try_new_with_parser(parser: I) -> Result<Self, DataError> {
if parser.as_ref().checksum
!= crate::provider::Baked::SINGLETON_TIMEZONE_IDENTIFIERS_IANA_EXTENDED_V1_CHECKSUM
{
return Err(
DataErrorKind::InconsistentData(TimezoneIdentifiersIanaCoreV1::INFO)
.with_marker(TimezoneIdentifiersIanaExtendedV1::INFO),
);
}
Ok(Self {
inner: parser,
data: DataPayload::from_static_ref(
crate::provider::Baked::SINGLETON_TIMEZONE_IDENTIFIERS_IANA_EXTENDED_V1,
),
})
}
icu_provider::gen_buffer_data_constructors!((parser: I) -> error: DataError,
functions: [
try_new_with_parser: skip,
try_new_with_parser_with_buffer_provider,
try_new_with_parser_unstable,
Self,
]
);
#[doc = icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::new)]
pub fn try_new_with_parser_unstable<P>(provider: &P, parser: I) -> Result<Self, DataError>
where
P: DataProvider<TimezoneIdentifiersIanaCoreV1>
+ DataProvider<TimezoneIdentifiersIanaExtendedV1>
+ ?Sized,
{
let response = provider.load(Default::default())?;
if Some(parser.as_ref().checksum) != response.metadata.checksum {
return Err(
DataErrorKind::InconsistentData(TimezoneIdentifiersIanaCoreV1::INFO)
.with_marker(TimezoneIdentifiersIanaExtendedV1::INFO),
);
}
Ok(Self {
inner: parser,
data: response.payload,
})
}
pub fn as_borrowed(&self) -> IanaParserExtendedBorrowed<'_> {
IanaParserExtendedBorrowed {
inner: self.inner.as_ref().as_borrowed(),
data: self.data.get(),
}
}
}
#[derive(Debug, Copy, Clone)]
pub struct IanaParserExtendedBorrowed<'a> {
inner: IanaParserBorrowed<'a>,
data: &'a IanaNames<'a>,
}
#[cfg(feature = "compiled_data")]
impl Default for IanaParserExtendedBorrowed<'static> {
fn default() -> Self {
Self::new()
}
}
impl IanaParserExtendedBorrowed<'static> {
#[cfg(feature = "compiled_data")]
pub fn new() -> Self {
const _: () = assert!(
crate::provider::Baked::SINGLETON_TIMEZONE_IDENTIFIERS_IANA_CORE_V1_CHECKSUM
== crate::provider::Baked::SINGLETON_TIMEZONE_IDENTIFIERS_IANA_EXTENDED_V1_CHECKSUM,
);
Self {
inner: IanaParserBorrowed::new(),
data: crate::provider::Baked::SINGLETON_TIMEZONE_IDENTIFIERS_IANA_EXTENDED_V1,
}
}
pub fn static_to_owned(&self) -> IanaParserExtended<IanaParser> {
IanaParserExtended {
inner: self.inner.static_to_owned(),
data: DataPayload::from_static_ref(self.data),
}
}
}
impl<'a> IanaParserExtendedBorrowed<'a> {
pub fn parse(&self, iana_id: &str) -> TimeZoneAndCanonicalAndNormalized<'a> {
self.parse_from_utf8(iana_id.as_bytes())
}
pub fn parse_from_utf8(&self, iana_id: &[u8]) -> TimeZoneAndCanonicalAndNormalized<'a> {
let Some(trie_value) = self.inner.trie_value(iana_id) else {
return TimeZoneAndCanonicalAndNormalized::UKNONWN;
};
let Some(time_zone) = self.inner.data.bcp47_ids.get(trie_value.index()) else {
debug_assert!(false, "index should be in range");
return TimeZoneAndCanonicalAndNormalized::UKNONWN;
};
let Some(canonical) = self.data.normalized_iana_ids.get(trie_value.index()) else {
debug_assert!(false, "index should be in range");
return TimeZoneAndCanonicalAndNormalized::UKNONWN;
};
let normalized = if trie_value.is_canonical() {
canonical
} else {
let Some(Ok(index)) = self.data.normalized_iana_ids.binary_search_in_range_by(
|a| {
a.as_bytes()
.iter()
.map(u8::to_ascii_lowercase)
.cmp(iana_id.iter().map(u8::to_ascii_lowercase))
},
self.inner.data.bcp47_ids.len()..self.data.normalized_iana_ids.len(),
) else {
debug_assert!(
false,
"binary search should succeed if trie lookup succeeds"
);
return TimeZoneAndCanonicalAndNormalized::UKNONWN;
};
let Some(normalized) = self
.data
.normalized_iana_ids
.get(self.inner.data.bcp47_ids.len() + index)
else {
debug_assert!(false, "binary search returns valid index");
return TimeZoneAndCanonicalAndNormalized::UKNONWN;
};
normalized
};
TimeZoneAndCanonicalAndNormalized {
time_zone,
canonical,
normalized,
}
}
pub fn iter(&self) -> TimeZoneAndCanonicalIter<'a> {
TimeZoneAndCanonicalIter(
self.inner
.data
.bcp47_ids
.iter()
.zip(self.data.normalized_iana_ids.iter()),
)
}
pub fn iter_all(&self) -> TimeZoneAndCanonicalAndNormalizedIter<'a> {
TimeZoneAndCanonicalAndNormalizedIter(0, *self)
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct TimeZoneAndCanonical<'a> {
pub time_zone: TimeZone,
pub canonical: &'a str,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct TimeZoneAndCanonicalAndNormalized<'a> {
pub time_zone: TimeZone,
pub canonical: &'a str,
pub normalized: &'a str,
}
impl TimeZoneAndCanonicalAndNormalized<'static> {
const UKNONWN: Self = TimeZoneAndCanonicalAndNormalized {
time_zone: TimeZone::UNKNOWN,
canonical: "Etc/Unknown",
normalized: "Etc/Unknown",
};
}
#[derive(Debug)]
pub struct TimeZoneAndCanonicalIter<'a>(
core::iter::Zip<ZeroSliceIter<'a, TimeZone>, VarZeroSliceIter<'a, str>>,
);
impl<'a> Iterator for TimeZoneAndCanonicalIter<'a> {
type Item = TimeZoneAndCanonical<'a>;
fn next(&mut self) -> Option<Self::Item> {
let (time_zone, canonical) = self.0.next()?;
Some(TimeZoneAndCanonical {
time_zone,
canonical,
})
}
}
#[derive(Debug)]
pub struct TimeZoneAndCanonicalAndNormalizedIter<'a>(usize, IanaParserExtendedBorrowed<'a>);
impl<'a> Iterator for TimeZoneAndCanonicalAndNormalizedIter<'a> {
type Item = TimeZoneAndCanonicalAndNormalized<'a>;
fn next(&mut self) -> Option<Self::Item> {
if let (Some(time_zone), Some(canonical)) = (
self.1.inner.data.bcp47_ids.get(self.0),
self.1.data.normalized_iana_ids.get(self.0),
) {
self.0 += 1;
Some(TimeZoneAndCanonicalAndNormalized {
time_zone,
canonical,
normalized: canonical,
})
} else if let Some(normalized) = self.1.data.normalized_iana_ids.get(self.0) {
let Some(trie_value) = self.1.inner.trie_value(normalized.as_bytes()) else {
debug_assert!(false, "normalized value should be in trie");
return None;
};
let (Some(time_zone), Some(canonical)) = (
self.1.inner.data.bcp47_ids.get(trie_value.index()),
self.1.data.normalized_iana_ids.get(trie_value.index()),
) else {
debug_assert!(false, "index should be in range");
return None;
};
self.0 += 1;
Some(TimeZoneAndCanonicalAndNormalized {
time_zone,
canonical,
normalized,
})
} else {
None
}
}
}
#[derive(Copy, Clone, PartialEq, Eq)]
#[repr(transparent)]
struct IanaTrieValue(usize);
impl IanaTrieValue {
#[inline]
pub(crate) fn index(self) -> usize {
self.0 >> 1
}
#[inline]
pub(crate) fn is_canonical(self) -> bool {
(self.0 & 0x1) != 0
}
}