use alloc::sync::Arc;
use core::fmt::{self, Debug, Formatter};
use core::time::Duration;
use crate::error::ErrorKind;
#[derive(Clone, Hash, Ord, PartialOrd, Eq, PartialEq)]
pub enum OffsetName {
Fixed,
Location(Arc<str>),
}
#[derive(Clone, Hash, Ord, PartialOrd, Eq, PartialEq)]
pub struct Offset {
pub(crate) dst: Option<bool>,
pub(crate) seconds: i32,
pub(crate) name: OffsetName,
}
impl Offset {
pub const UTC: Self = Self {
seconds: 0,
dst: None,
name: OffsetName::Fixed,
};
}
impl Offset {
pub const MAX: Self = Self {
seconds: 18 * 60 * 60,
dst: None,
name: OffsetName::Fixed,
};
pub const MIN: Self = Self {
seconds: -18 * 60 * 60,
dst: None,
name: OffsetName::Fixed,
};
}
impl Offset {
pub const fn of(hours: i8, minutes: i8, seconds: i8) -> crate::Result<Self> {
ensure_range!(-18, 18, hours);
ensure_range!(-59, 59, minutes);
ensure_range!(-59, 59, seconds);
let negative = if hours != 0 {
hours.is_negative()
} else if minutes != 0 {
minutes.is_negative()
} else {
seconds.is_negative()
};
let mut seconds =
(hours.abs() as i32 * 60 * 60) + (minutes.abs() as i32 * 60) + seconds.abs() as i32;
if negative {
seconds = -seconds;
}
Self::from_seconds(seconds)
}
}
impl Offset {
pub const fn from_hours(hours: i8) -> crate::Result<Self> {
Self::from_minutes((hours as i16) * 60)
}
pub const fn from_minutes(minutes: i16) -> crate::Result<Self> {
Self::from_seconds((minutes as i32) * 60)
}
pub const fn from_seconds(seconds: i32) -> crate::Result<Self> {
ensure_range!(Self::MIN.seconds, Self::MAX.seconds, seconds);
Ok(Self {
seconds,
dst: None,
name: OffsetName::Fixed,
})
}
}
impl Offset {
#[must_use]
pub const fn components(&self) -> (i8, i8, i8) {
let mut seconds = self.seconds;
let hours = seconds / 3_600;
seconds -= hours * 3_600;
let minutes = seconds / 60;
seconds -= minutes * 60;
(hours as i8, minutes as i8, seconds as i8)
}
#[must_use]
pub const fn hours(&self) -> i8 {
(self.seconds / 3_600) as i8
}
#[must_use]
pub const fn minutes(&self) -> i8 {
((self.seconds % 3_600) / 60) as i8
}
#[must_use]
pub const fn seconds(&self) -> i8 {
((self.seconds % 3_600) % 60) as i8
}
}
impl Offset {
#[must_use]
pub const fn is_utc(&self) -> bool {
self.seconds == 0
}
#[must_use]
pub const fn is_positive(&self) -> bool {
self.seconds.is_positive()
}
#[must_use]
pub const fn is_negative(&self) -> bool {
self.seconds.is_negative()
}
}
impl Offset {
#[must_use]
pub const fn as_total_hours(&self) -> i32 {
self.seconds / 3_600
}
#[must_use]
pub const fn as_total_minutes(&self) -> i32 {
self.seconds / 60
}
#[must_use]
pub const fn as_total_seconds(&self) -> i32 {
self.seconds
}
#[must_use]
pub const fn as_total_nanoseconds(&self) -> i64 {
self.seconds as i64 * 1_000_000_000
}
}
impl Debug for Offset {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.pad(&self.format_rfc3339())
}
}
impl TryFrom<Duration> for Offset {
type Error = crate::Error;
fn try_from(value: Duration) -> crate::Result<Self> {
let seconds: i32 = value
.as_secs()
.try_into()
.map_err(|_| ErrorKind::OutOfRange)?;
Self::from_seconds(seconds)
}
}
impl TryFrom<Offset> for Duration {
type Error = crate::Error;
fn try_from(value: Offset) -> crate::Result<Self> {
let seconds = value.as_total_seconds();
if seconds < 0 {
Err(ErrorKind::OutOfRange.into())
} else {
Ok(Duration::from_secs(seconds as u64))
}
}
}
#[cfg(test)]
mod tests {
use test_case::test_case;
use crate::Offset;
#[test_case((0, 0), "Z")]
#[test_case((7, 0), "+07:00")]
#[test_case((-8, 0), "-08:00")]
#[test_case((8, 45), "+08:45")]
fn expect_rfc3339_offset((hours, minutes): (i8, i8), text: &str) -> crate::Result<()> {
let offset: Offset = Offset::parse_rfc3339(text)?;
assert_eq!(offset, Offset::of(hours, minutes, 0)?);
Ok(())
}
}