use core::str::FromStr;
#[cfg(feature = "alloc")]
use crate::provider::legacy::TimezoneVariantsOffsetsV1;
use crate::provider::{TimezonePeriods, TimezonePeriodsV1};
use crate::TimeZone;
use icu_provider::prelude::*;
use displaydoc::Display;
use super::ZoneNameTimestamp;
#[derive(Display, Debug, Copy, Clone, PartialEq)]
#[allow(clippy::exhaustive_structs)]
pub struct InvalidOffsetError;
#[derive(Copy, Clone, Debug, PartialEq, Eq, Default, PartialOrd, Ord)]
pub struct UtcOffset(i32);
impl UtcOffset {
pub const fn try_from_seconds(seconds: i32) -> Result<Self, InvalidOffsetError> {
if seconds.unsigned_abs() > 18 * 60 * 60 {
Err(InvalidOffsetError)
} else {
Ok(Self(seconds))
}
}
pub const fn zero() -> Self {
Self(0)
}
#[inline]
pub const fn try_from_str(s: &str) -> Result<Self, InvalidOffsetError> {
Self::try_from_utf8(s.as_bytes())
}
pub const fn try_from_utf8(mut code_units: &[u8]) -> Result<Self, InvalidOffsetError> {
const fn try_get_time_component([tens, ones]: [u8; 2]) -> Option<i32> {
let Some(tens) = (tens as char).to_digit(10) else {
return None;
};
let Some(ones) = (ones as char).to_digit(10) else {
return None;
};
Some((tens * 10 + ones) as i32)
}
let offset_sign = match code_units {
[b'+', rest @ ..] => {
code_units = rest;
1
}
[b'-', rest @ ..] => {
code_units = rest;
-1
}
[226, 136, 146, rest @ ..] => {
code_units = rest;
-1
}
[b'Z'] => return Ok(Self(0)),
_ => return Err(InvalidOffsetError),
};
let hours = match code_units {
&[h1, h2, ..] => try_get_time_component([h1, h2]),
_ => None,
};
let Some(hours) = hours else {
return Err(InvalidOffsetError);
};
let minutes = match code_units {
&[_, _] => Some(0),
&[_, _, m1, m2] | &[_, _, b':', m1, m2] => try_get_time_component([m1, m2]),
_ => None,
};
let Some(minutes @ ..60) = minutes else {
return Err(InvalidOffsetError);
};
Self::try_from_seconds(offset_sign * (hours * 60 + minutes) * 60)
}
#[inline]
pub const fn from_seconds_unchecked(seconds: i32) -> Self {
Self(seconds)
}
pub const fn to_seconds(self) -> i32 {
self.0
}
pub fn is_non_negative(self) -> bool {
self.0 >= 0
}
pub fn is_zero(self) -> bool {
self.0 == 0
}
pub fn hours_part(self) -> i32 {
self.0 / 3600
}
pub fn minutes_part(self) -> u32 {
(self.0 % 3600 / 60).unsigned_abs()
}
pub fn seconds_part(self) -> u32 {
(self.0 % 60).unsigned_abs()
}
}
impl FromStr for UtcOffset {
type Err = InvalidOffsetError;
#[inline]
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::try_from_str(s)
}
}
#[derive(Debug)]
enum OffsetData {
#[cfg(feature = "alloc")] Old(DataPayload<TimezoneVariantsOffsetsV1>),
New(DataPayload<TimezonePeriodsV1>),
}
#[derive(Debug)]
enum OffsetDataBorrowed<'a> {
#[cfg(feature = "alloc")]
Old(&'a zerovec::ZeroMap2d<'a, TimeZone, ZoneNameTimestamp, VariantOffsets>),
New(&'a TimezonePeriods<'a>),
}
#[derive(Debug)]
#[deprecated(
since = "2.1.0",
note = "this API is a bad approximation of a time zone database"
)]
pub struct VariantOffsetsCalculator {
offset_period: OffsetData,
}
#[derive(Debug)]
#[deprecated(
since = "2.1.0",
note = "this API is a bad approximation of a time zone database"
)]
pub struct VariantOffsetsCalculatorBorrowed<'a> {
offset_period: OffsetDataBorrowed<'a>,
}
#[cfg(feature = "compiled_data")]
#[allow(deprecated)]
impl Default for VariantOffsetsCalculatorBorrowed<'static> {
fn default() -> Self {
VariantOffsetsCalculator::new()
}
}
#[allow(deprecated)]
impl VariantOffsetsCalculator {
#[cfg(feature = "compiled_data")]
#[inline]
#[expect(clippy::new_ret_no_self)]
pub const fn new() -> VariantOffsetsCalculatorBorrowed<'static> {
VariantOffsetsCalculatorBorrowed::new()
}
#[cfg(feature = "serde")]
#[doc = icu_provider::gen_buffer_unstable_docs!(BUFFER, Self::new)]
pub fn try_new_with_buffer_provider(
provider: &(impl BufferProvider + ?Sized),
) -> Result<Self, DataError> {
use icu_provider::buf::AsDeserializingBufferProvider;
{
Ok(Self {
offset_period: match DataProvider::<TimezonePeriodsV1>::load(
&provider.as_deserializing(),
Default::default(),
) {
Ok(payload) => OffsetData::New(payload.payload),
Err(_e) => {
#[cfg(feature = "alloc")]
{
OffsetData::Old(
DataProvider::<TimezoneVariantsOffsetsV1>::load(
&provider.as_deserializing(),
Default::default(),
)?
.payload,
)
}
#[cfg(not(feature = "alloc"))]
return Err(_e);
}
},
})
}
}
#[doc = icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::new)]
pub fn try_new_unstable(
provider: &(impl DataProvider<TimezonePeriodsV1> + ?Sized),
) -> Result<Self, DataError> {
let offset_period = provider.load(Default::default())?.payload;
Ok(Self {
offset_period: OffsetData::New(offset_period),
})
}
pub fn as_borrowed(&self) -> VariantOffsetsCalculatorBorrowed<'_> {
VariantOffsetsCalculatorBorrowed {
offset_period: match self.offset_period {
OffsetData::New(ref payload) => OffsetDataBorrowed::New(payload.get()),
#[cfg(feature = "alloc")]
OffsetData::Old(ref payload) => OffsetDataBorrowed::Old(payload.get()),
},
}
}
}
#[allow(deprecated)]
impl VariantOffsetsCalculatorBorrowed<'static> {
#[cfg(feature = "compiled_data")]
#[inline]
pub const fn new() -> Self {
Self {
offset_period: OffsetDataBorrowed::New(
crate::provider::Baked::SINGLETON_TIMEZONE_PERIODS_V1,
),
}
}
pub fn static_to_owned(&self) -> VariantOffsetsCalculator {
VariantOffsetsCalculator {
offset_period: match self.offset_period {
OffsetDataBorrowed::New(p) => OffsetData::New(DataPayload::from_static_ref(p)),
#[cfg(feature = "alloc")]
OffsetDataBorrowed::Old(p) => OffsetData::Old(DataPayload::from_static_ref(p)),
},
}
}
}
#[allow(deprecated)]
impl VariantOffsetsCalculatorBorrowed<'_> {
pub fn compute_offsets_from_time_zone_and_name_timestamp(
&self,
time_zone_id: TimeZone,
timestamp: ZoneNameTimestamp,
) -> Option<VariantOffsets> {
match self.offset_period {
OffsetDataBorrowed::New(p) => p.get(time_zone_id, timestamp).map(|(os, _)| os),
#[cfg(feature = "alloc")]
OffsetDataBorrowed::Old(p) => {
use zerovec::ule::AsULE;
let mut offsets = None;
for (bytes, id) in p.get0(&time_zone_id)?.iter1_copied().rev() {
if timestamp >= ZoneNameTimestamp::from_unaligned(*bytes) {
offsets = Some(id);
break;
}
}
Some(offsets?)
}
}
}
}
#[deprecated(
since = "2.1.0",
note = "this API is a bad approximation of a time zone database"
)]
pub use crate::provider::VariantOffsets;
#[test]
#[allow(deprecated)]
pub fn test_legacy_offsets_data() {
use icu_locale_core::subtags::subtag;
use icu_provider_blob::BlobDataProvider;
let c = VariantOffsetsCalculator::try_new_with_buffer_provider(
&BlobDataProvider::try_new_from_static_blob(
include_bytes!("../../tests/data/offset_periods_old.blob"),
)
.unwrap(),
)
.unwrap();
let tz = TimeZone(subtag!("aqcas"));
for t in [
ZoneNameTimestamp::from_epoch_seconds(0),
ZoneNameTimestamp::from_epoch_seconds(1255802400),
ZoneNameTimestamp::from_epoch_seconds(1267714800),
ZoneNameTimestamp::from_epoch_seconds(1319738400),
ZoneNameTimestamp::from_epoch_seconds(1329843600),
ZoneNameTimestamp::from_epoch_seconds(1477065600),
ZoneNameTimestamp::from_epoch_seconds(1520701200),
ZoneNameTimestamp::from_epoch_seconds(1538856000),
ZoneNameTimestamp::from_epoch_seconds(1552752000),
ZoneNameTimestamp::from_epoch_seconds(1570129200),
ZoneNameTimestamp::from_epoch_seconds(1583596800),
ZoneNameTimestamp::from_epoch_seconds(1615640400),
ZoneNameTimestamp::from_epoch_seconds(1647090000),
ZoneNameTimestamp::from_epoch_seconds(1678291200),
] {
assert_eq!(
c.as_borrowed()
.compute_offsets_from_time_zone_and_name_timestamp(tz, t),
VariantOffsetsCalculator::new()
.compute_offsets_from_time_zone_and_name_timestamp(tz, t),
"{t:?}",
);
}
}