use core::str::FromStr;
use crate::provider::{MinutesSinceEpoch, TimeZoneOffsetsV1};
use crate::{Time, TimeZone};
use icu_calendar::Date;
use icu_calendar::Iso;
use icu_provider::prelude::*;
use displaydoc::Display;
#[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 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 from_eighths_of_hour(eighths_of_hour: i8) -> Self {
Self(eighths_of_hour as i32 * 450)
}
pub const fn zero() -> Self {
Self(0)
}
#[inline]
pub fn try_from_str(s: &str) -> Result<Self, InvalidOffsetError> {
Self::try_from_utf8(s.as_bytes())
}
pub fn try_from_utf8(mut code_units: &[u8]) -> Result<Self, InvalidOffsetError> {
fn try_get_time_component([tens, ones]: [u8; 2]) -> Option<i32> {
Some(((tens as char).to_digit(10)? * 10 + (ones as char).to_digit(10)?) 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,
}
.ok_or(InvalidOffsetError)?;
let minutes = match code_units {
&[_, _] => Some(0),
&[_, _, m1, m2] | &[_, _, b':', m1, m2] => {
try_get_time_component([m1, m2]).filter(|&m| m < 60)
}
_ => None,
}
.ok_or(InvalidOffsetError)?;
Self::try_from_seconds(offset_sign * (hours * 60 + minutes) * 60)
}
#[inline]
pub fn from_seconds_unchecked(seconds: i32) -> Self {
Self(seconds)
}
pub fn to_seconds(self) -> i32 {
self.0
}
pub fn to_eighths_of_hour(self) -> i8 {
(self.0 / 450) as i8
}
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)]
pub struct UtcOffsetCalculator {
pub(super) offset_period: DataPayload<TimeZoneOffsetsV1>,
}
#[cfg(feature = "compiled_data")]
impl Default for UtcOffsetCalculator {
fn default() -> Self {
Self::new()
}
}
impl UtcOffsetCalculator {
#[cfg(feature = "compiled_data")]
#[inline]
pub const fn new() -> Self {
UtcOffsetCalculator {
offset_period: DataPayload::from_static_ref(
crate::provider::Baked::SINGLETON_TIME_ZONE_OFFSETS_V1,
),
}
}
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(
provider: &(impl DataProvider<TimeZoneOffsetsV1> + ?Sized),
) -> Result<Self, DataError> {
let metazone_period = provider.load(Default::default())?.payload;
Ok(Self {
offset_period: metazone_period,
})
}
pub fn compute_offsets_from_time_zone(
&self,
time_zone_id: TimeZone,
dt: (Date<Iso>, Time),
) -> Option<UtcOffsets> {
use zerovec::ule::AsULE;
match self.offset_period.get().get0(&time_zone_id) {
Some(cursor) => {
let mut offsets = None;
let minutes_since_epoch_walltime = MinutesSinceEpoch::from(dt);
for (minutes, id) in cursor.iter1_copied() {
if minutes_since_epoch_walltime >= MinutesSinceEpoch::from_unaligned(*minutes) {
offsets = Some(id);
} else {
break;
}
}
let offsets = offsets?;
Some(UtcOffsets {
standard: UtcOffset::from_eighths_of_hour(offsets.0),
daylight: (offsets.1 != 0)
.then_some(UtcOffset::from_eighths_of_hour(offsets.0 + offsets.1)),
})
}
None => None,
}
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord)]
pub struct UtcOffsets {
pub standard: UtcOffset,
pub daylight: Option<UtcOffset>,
}