pub mod iana;
mod offset;
pub mod windows;
mod zone_name_timestamp;
use icu_calendar::types::RataDie;
use icu_calendar::AsCalendar;
#[cfg(feature = "compiled_data")]
use icu_locale_core::subtags::Region;
#[doc(inline)]
pub use offset::InvalidOffsetError;
pub use offset::UtcOffset;
pub use offset::VariantOffsets;
#[allow(deprecated)]
pub use offset::VariantOffsetsCalculator;
#[allow(deprecated)]
pub use offset::VariantOffsetsCalculatorBorrowed;
#[doc(no_inline)]
pub use iana::{IanaParser, IanaParserBorrowed};
#[doc(no_inline)]
pub use windows::{WindowsParser, WindowsParserBorrowed};
pub use zone_name_timestamp::ZoneNameTimestamp;
use crate::scaffold::IntoOption;
use crate::DateTime;
use crate::Time;
use core::fmt;
use core::ops::Deref;
use icu_calendar::Iso;
use icu_locale_core::subtags::{subtag, Subtag};
use icu_provider::prelude::yoke;
use zerovec::ule::{AsULE, ULE};
pub mod models {
use super::*;
mod private {
pub trait Sealed {}
}
pub trait TimeZoneModel: private::Sealed {
type TimeZoneVariant: IntoOption<TimeZoneVariant> + fmt::Debug + Copy;
type ZoneNameTimestamp: IntoOption<ZoneNameTimestamp> + fmt::Debug + Copy;
}
#[derive(Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct Base;
impl private::Sealed for Base {}
impl TimeZoneModel for Base {
type TimeZoneVariant = ();
type ZoneNameTimestamp = ();
}
#[derive(Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct AtTime;
impl private::Sealed for AtTime {}
impl TimeZoneModel for AtTime {
type TimeZoneVariant = ();
type ZoneNameTimestamp = ZoneNameTimestamp;
}
#[derive(Debug, PartialEq, Eq)]
#[non_exhaustive]
#[deprecated(
since = "2.1.0",
note = "creating a `TimeZoneInfo<Full>` is not required for formatting anymore. use `TimeZoneInfo<AtTime>`"
)]
pub struct Full;
#[allow(deprecated)]
impl private::Sealed for Full {}
#[allow(deprecated)]
impl TimeZoneModel for Full {
type TimeZoneVariant = TimeZoneVariant;
type ZoneNameTimestamp = ZoneNameTimestamp;
}
}
#[repr(transparent)]
#[derive(Debug, Clone, Copy, Eq, Ord, PartialEq, PartialOrd, yoke::Yokeable, ULE, Hash)]
#[cfg_attr(feature = "datagen", derive(serde::Serialize, databake::Bake))]
#[cfg_attr(feature = "datagen", databake(path = icu_time::provider))]
#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
#[allow(clippy::exhaustive_structs)] pub struct TimeZone(pub Subtag);
impl TimeZone {
pub const UNKNOWN: Self = Self(subtag!("unk"));
pub const fn is_unknown(self) -> bool {
matches!(self, Self::UNKNOWN)
}
#[cfg(feature = "compiled_data")]
pub fn from_iana_id(iana_id: &str) -> Self {
IanaParser::new().parse(iana_id)
}
#[cfg(feature = "compiled_data")]
pub fn from_windows_id(windows_id: &str, region: Option<Region>) -> Self {
WindowsParser::new()
.parse(windows_id, region)
.unwrap_or(Self::UNKNOWN)
}
#[cfg(feature = "compiled_data")]
pub fn from_system_id(id: &str, _region: Option<Region>) -> Self {
#[cfg(target_os = "windows")]
return Self::from_windows_id(id, _region);
#[cfg(not(target_os = "windows"))]
return Self::from_iana_id(id);
}
}
impl Deref for TimeZone {
type Target = Subtag;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl AsULE for TimeZone {
type ULE = Self;
#[inline]
fn to_unaligned(self) -> Self::ULE {
self
}
#[inline]
fn from_unaligned(unaligned: Self::ULE) -> Self {
unaligned
}
}
#[cfg(feature = "alloc")]
impl<'a> zerovec::maps::ZeroMapKV<'a> for TimeZone {
type Container = zerovec::ZeroVec<'a, TimeZone>;
type Slice = zerovec::ZeroSlice<TimeZone>;
type GetType = TimeZone;
type OwnedType = TimeZone;
}
#[derive(Debug, PartialEq, Eq)]
#[allow(clippy::exhaustive_structs)] pub struct TimeZoneInfo<Model: models::TimeZoneModel> {
id: TimeZone,
offset: Option<UtcOffset>,
zone_name_timestamp: Model::ZoneNameTimestamp,
variant: Model::TimeZoneVariant,
}
impl<Model: models::TimeZoneModel> Clone for TimeZoneInfo<Model> {
fn clone(&self) -> Self {
*self
}
}
impl<Model: models::TimeZoneModel> Copy for TimeZoneInfo<Model> {}
impl<Model: models::TimeZoneModel> TimeZoneInfo<Model> {
pub fn id(self) -> TimeZone {
self.id
}
pub fn offset(self) -> Option<UtcOffset> {
self.offset
}
}
impl<Model> TimeZoneInfo<Model>
where
Model: models::TimeZoneModel<ZoneNameTimestamp = ZoneNameTimestamp>,
{
pub fn zone_name_timestamp(self) -> ZoneNameTimestamp {
self.zone_name_timestamp
}
}
impl<Model> TimeZoneInfo<Model>
where
Model: models::TimeZoneModel<TimeZoneVariant = TimeZoneVariant>,
{
pub fn variant(self) -> TimeZoneVariant {
self.variant
}
}
impl TimeZone {
pub const fn with_offset(self, mut offset: Option<UtcOffset>) -> TimeZoneInfo<models::Base> {
let mut id = self;
#[allow(clippy::identity_op, clippy::neg_multiply)]
let correct_offset = match self.0.as_str().as_bytes() {
b"utc" | b"gmt" => Some(UtcOffset::zero()),
b"utce01" => Some(UtcOffset::from_seconds_unchecked(1 * 60 * 60)),
b"utce02" => Some(UtcOffset::from_seconds_unchecked(2 * 60 * 60)),
b"utce03" => Some(UtcOffset::from_seconds_unchecked(3 * 60 * 60)),
b"utce04" => Some(UtcOffset::from_seconds_unchecked(4 * 60 * 60)),
b"utce05" => Some(UtcOffset::from_seconds_unchecked(5 * 60 * 60)),
b"utce06" => Some(UtcOffset::from_seconds_unchecked(6 * 60 * 60)),
b"utce07" => Some(UtcOffset::from_seconds_unchecked(7 * 60 * 60)),
b"utce08" => Some(UtcOffset::from_seconds_unchecked(8 * 60 * 60)),
b"utce09" => Some(UtcOffset::from_seconds_unchecked(9 * 60 * 60)),
b"utce10" => Some(UtcOffset::from_seconds_unchecked(10 * 60 * 60)),
b"utce11" => Some(UtcOffset::from_seconds_unchecked(11 * 60 * 60)),
b"utce12" => Some(UtcOffset::from_seconds_unchecked(12 * 60 * 60)),
b"utce13" => Some(UtcOffset::from_seconds_unchecked(13 * 60 * 60)),
b"utce14" => Some(UtcOffset::from_seconds_unchecked(14 * 60 * 60)),
b"utcw01" => Some(UtcOffset::from_seconds_unchecked(-1 * 60 * 60)),
b"utcw02" => Some(UtcOffset::from_seconds_unchecked(-2 * 60 * 60)),
b"utcw03" => Some(UtcOffset::from_seconds_unchecked(-3 * 60 * 60)),
b"utcw04" => Some(UtcOffset::from_seconds_unchecked(-4 * 60 * 60)),
b"utcw05" => Some(UtcOffset::from_seconds_unchecked(-5 * 60 * 60)),
b"utcw06" => Some(UtcOffset::from_seconds_unchecked(-6 * 60 * 60)),
b"utcw07" => Some(UtcOffset::from_seconds_unchecked(-7 * 60 * 60)),
b"utcw08" => Some(UtcOffset::from_seconds_unchecked(-8 * 60 * 60)),
b"utcw09" => Some(UtcOffset::from_seconds_unchecked(-9 * 60 * 60)),
b"utcw10" => Some(UtcOffset::from_seconds_unchecked(-10 * 60 * 60)),
b"utcw11" => Some(UtcOffset::from_seconds_unchecked(-11 * 60 * 60)),
b"utcw12" => Some(UtcOffset::from_seconds_unchecked(-12 * 60 * 60)),
_ => None,
};
match (correct_offset, offset) {
(Some(c), None) => {
offset = Some(c);
if id.0.as_str().len() > 3 {
id = Self::UNKNOWN;
}
}
(Some(c), Some(o)) if c.to_seconds() != o.to_seconds() => {
offset = None;
id = Self::UNKNOWN;
}
_ => {}
}
TimeZoneInfo {
id,
offset,
zone_name_timestamp: (),
variant: (),
}
}
pub const fn without_offset(self) -> TimeZoneInfo<models::Base> {
self.with_offset(None)
}
}
impl TimeZoneInfo<models::Base> {
pub const fn unknown() -> Self {
Self {
id: TimeZone::UNKNOWN,
offset: None,
zone_name_timestamp: (),
variant: (),
}
}
pub const fn utc() -> Self {
TimeZoneInfo {
id: TimeZone(subtag!("utc")),
offset: Some(UtcOffset::zero()),
zone_name_timestamp: (),
variant: (),
}
}
pub fn with_zone_name_timestamp(
self,
zone_name_timestamp: ZoneNameTimestamp,
) -> TimeZoneInfo<models::AtTime> {
TimeZoneInfo {
offset: self.offset,
id: self.id,
zone_name_timestamp,
variant: (),
}
}
pub fn at_date_time<C: AsCalendar>(
self,
date_time: DateTime<C>,
) -> TimeZoneInfo<models::AtTime> {
self.at_rd_time(date_time.date.to_rata_die(), date_time.time)
}
#[deprecated(since = "2.2.0", note = "use `Self::at_date_time`")]
pub fn at_date_time_iso(self, date_time: DateTime<Iso>) -> TimeZoneInfo<models::AtTime> {
self.at_date_time(date_time)
}
pub(crate) fn at_rd_time(self, rd: RataDie, time: Time) -> TimeZoneInfo<models::AtTime> {
self.with_zone_name_timestamp(ZoneNameTimestamp::from_rd_time_zone(
rd,
time,
self.offset.unwrap_or(UtcOffset::zero()),
))
}
}
impl TimeZoneInfo<models::AtTime> {
#[deprecated(
since = "2.1.0",
note = "creating a `TimeZoneInfo<Full>` is not required for formatting anymore"
)]
#[allow(deprecated)]
pub const fn with_variant(self, variant: TimeZoneVariant) -> TimeZoneInfo<models::Full> {
TimeZoneInfo {
offset: self.offset,
id: self.id,
zone_name_timestamp: self.zone_name_timestamp,
variant,
}
}
#[deprecated(
since = "2.1.0",
note = "creating a `TimeZoneInfo<Full>` is not required for formatting anymore"
)]
#[allow(deprecated)]
pub fn infer_variant(
self,
calculator: VariantOffsetsCalculatorBorrowed,
) -> TimeZoneInfo<models::Full> {
let Some(offset) = self.offset else {
return TimeZone::UNKNOWN
.with_offset(self.offset)
.with_zone_name_timestamp(self.zone_name_timestamp)
.with_variant(TimeZoneVariant::Standard);
};
let Some(variant) = calculator
.compute_offsets_from_time_zone_and_name_timestamp(self.id, self.zone_name_timestamp)
.and_then(|os| {
if os.standard == offset {
Some(TimeZoneVariant::Standard)
} else if os.daylight == Some(offset) {
Some(TimeZoneVariant::Daylight)
} else {
None
}
})
else {
return TimeZone::UNKNOWN
.with_offset(self.offset)
.with_zone_name_timestamp(self.zone_name_timestamp)
.with_variant(TimeZoneVariant::Standard);
};
self.with_variant(variant)
}
}
#[deprecated(
since = "2.1.0",
note = "TimeZoneVariants don't need to be constructed in user code"
)]
pub use crate::provider::TimeZoneVariant;
impl TimeZoneVariant {
#[deprecated(
since = "2.1.0",
note = "TimeZoneVariants don't need to be constructed in user code"
)]
pub const fn from_rearguard_isdst(isdst: bool) -> Self {
if isdst {
TimeZoneVariant::Daylight
} else {
TimeZoneVariant::Standard
}
}
}
#[test]
fn test_zone_info_equality() {
assert_eq!(
TimeZone::from_iana_id("Etc/GMT-8").with_offset(None),
TimeZone::UNKNOWN.with_offset(Some(UtcOffset::from_seconds_unchecked(8 * 60 * 60)))
);
assert_eq!(
TimeZone::from_iana_id("Etc/UTC").with_offset(None),
TimeZoneInfo::utc()
);
assert_eq!(
TimeZone::from_iana_id("Etc/GMT").with_offset(None),
IanaParser::new()
.parse("Etc/GMT")
.with_offset(Some(UtcOffset::zero()))
);
assert_eq!(
IanaParser::new()
.parse("Etc/GMT-8")
.with_offset(Some(UtcOffset::from_seconds_unchecked(123))),
TimeZoneInfo::unknown()
);
assert_eq!(
IanaParser::new()
.parse("Etc/UTC")
.with_offset(Some(UtcOffset::from_seconds_unchecked(123))),
TimeZoneInfo::unknown(),
);
assert_eq!(
IanaParser::new()
.parse("Etc/GMT")
.with_offset(Some(UtcOffset::from_seconds_unchecked(123))),
TimeZoneInfo::unknown()
);
}