use core::fmt::Debug;
use crate::{
civil::DateTime,
error::{tz::posix::Error as E, Error, ErrorContext},
shared,
timestamp::Timestamp,
tz::{
timezone::TimeZoneAbbreviation, AmbiguousOffset, Dst, Offset,
TimeZoneOffsetInfo, TimeZoneTransition,
},
util::{array_str::Abbreviation, parse},
};
#[cfg(feature = "tz-system")]
#[derive(Debug, Eq, PartialEq)]
pub(crate) enum PosixTzEnv {
Rule(PosixTimeZoneOwned),
Implementation(alloc::boxed::Box<str>),
}
#[cfg(feature = "tz-system")]
impl PosixTzEnv {
fn parse(bytes: impl AsRef<[u8]>) -> Result<PosixTzEnv, Error> {
let bytes = bytes.as_ref();
if bytes.get(0) == Some(&b':') {
let Ok(string) = core::str::from_utf8(&bytes[1..]) else {
return Err(Error::from(E::ColonPrefixInvalidUtf8));
};
Ok(PosixTzEnv::Implementation(string.into()))
} else {
PosixTimeZone::parse(bytes).map(PosixTzEnv::Rule)
}
}
pub(crate) fn parse_os_str(
osstr: impl AsRef<std::ffi::OsStr>,
) -> Result<PosixTzEnv, Error> {
PosixTzEnv::parse(parse::os_str_bytes(osstr.as_ref())?)
}
}
#[cfg(feature = "tz-system")]
impl core::fmt::Display for PosixTzEnv {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
match *self {
PosixTzEnv::Rule(ref tz) => core::fmt::Display::fmt(tz, f),
PosixTzEnv::Implementation(ref imp) => {
f.write_str(":")?;
core::fmt::Display::fmt(imp, f)
}
}
}
}
pub(crate) type PosixTimeZoneOwned = PosixTimeZone<Abbreviation>;
pub(crate) type PosixTimeZoneStatic = PosixTimeZone<&'static str>;
#[derive(Clone, Debug, Eq, PartialEq)]
#[doc(hidden)]
#[repr(align(8))]
pub struct PosixTimeZone<ABBREV> {
inner: shared::PosixTimeZone<ABBREV>,
}
impl PosixTimeZone<Abbreviation> {
#[cfg(feature = "alloc")]
pub(crate) fn parse(
bytes: impl AsRef<[u8]>,
) -> Result<PosixTimeZoneOwned, Error> {
let bytes = bytes.as_ref();
let inner = shared::PosixTimeZone::parse(bytes.as_ref())
.map_err(Error::posix_tz)
.context(E::InvalidPosixTz)?;
Ok(PosixTimeZone { inner })
}
#[cfg(feature = "alloc")]
pub(crate) fn parse_prefix<'b, B: AsRef<[u8]> + ?Sized + 'b>(
bytes: &'b B,
) -> Result<(PosixTimeZoneOwned, &'b [u8]), Error> {
let bytes = bytes.as_ref();
let (inner, remaining) =
shared::PosixTimeZone::parse_prefix(bytes.as_ref())
.map_err(Error::posix_tz)
.context(E::InvalidPosixTz)?;
Ok((PosixTimeZone { inner }, remaining))
}
#[cfg(feature = "alloc")]
pub(crate) fn from_shared_owned(
sh: shared::PosixTimeZone<Abbreviation>,
) -> PosixTimeZoneOwned {
PosixTimeZone { inner: sh }
}
}
impl PosixTimeZone<&'static str> {
pub(crate) const fn from_shared_const(
sh: shared::PosixTimeZone<&'static str>,
) -> PosixTimeZoneStatic {
PosixTimeZone { inner: sh }
}
}
impl<ABBREV: AsRef<str> + Debug> PosixTimeZone<ABBREV> {
pub(crate) fn to_offset(&self, timestamp: Timestamp) -> Offset {
Offset::from_ioffset_const(
self.inner.to_offset(timestamp.to_itimestamp_const()),
)
}
pub(crate) fn to_offset_info(
&self,
timestamp: Timestamp,
) -> TimeZoneOffsetInfo<'_> {
let (ioff, abbrev, is_dst) =
self.inner.to_offset_info(timestamp.to_itimestamp_const());
let offset = Offset::from_ioffset_const(ioff);
let abbreviation = TimeZoneAbbreviation::Borrowed(abbrev);
TimeZoneOffsetInfo { offset, dst: Dst::from(is_dst), abbreviation }
}
pub(crate) fn to_ambiguous_kind(&self, dt: DateTime) -> AmbiguousOffset {
let iamoff = self.inner.to_ambiguous_kind(dt.to_idatetime_const());
AmbiguousOffset::from_iambiguous_offset_const(iamoff)
}
pub(crate) fn previous_transition<'t>(
&'t self,
timestamp: Timestamp,
) -> Option<TimeZoneTransition<'t>> {
let (its, ioff, abbrev, is_dst) =
self.inner.previous_transition(timestamp.to_itimestamp_const())?;
let timestamp = Timestamp::from_itimestamp_const(its);
let offset = Offset::from_ioffset_const(ioff);
let dst = Dst::from(is_dst);
Some(TimeZoneTransition { timestamp, offset, abbrev, dst })
}
pub(crate) fn next_transition<'t>(
&'t self,
timestamp: Timestamp,
) -> Option<TimeZoneTransition<'t>> {
let (its, ioff, abbrev, is_dst) =
self.inner.next_transition(timestamp.to_itimestamp_const())?;
let timestamp = Timestamp::from_itimestamp_const(its);
let offset = Offset::from_ioffset_const(ioff);
let dst = Dst::from(is_dst);
Some(TimeZoneTransition { timestamp, offset, abbrev, dst })
}
}
impl<ABBREV: AsRef<str>> core::fmt::Display for PosixTimeZone<ABBREV> {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
core::fmt::Display::fmt(&self.inner, f)
}
}
#[cfg(feature = "alloc")]
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "tz-system")]
#[test]
fn parse_posix_tz() {
assert!(PosixTzEnv::parse("EST5EDT").is_err());
let tz = PosixTzEnv::parse(":EST5EDT").unwrap();
assert_eq!(tz, PosixTzEnv::Implementation("EST5EDT".into()));
assert!(PosixTzEnv::parse(b":EST5\xFFEDT").is_err());
}
}