epoch-rs/src/zone.rs

171 lines
4.5 KiB
Rust

#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use {
crate::SECONDS_PER_HOUR,
std::{error, fmt, str::FromStr},
};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub enum Sign {
Positive,
Negative,
}
impl fmt::Display for Sign {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
match self {
Self::Positive => "+",
Self::Negative => "-",
}
)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub enum TimeZone {
Offset { sign: Sign, hours: u8, minutes: u8 },
Utc,
}
impl TimeZone {
pub fn new(hours: Option<i8>, minutes: Option<u8>) -> Result<Self, TZError> {
match (hours, minutes) {
(Some(h), Some(m)) => {
if (!(-12..=12).contains(&h) || m > 59) || ((h == -12 || h == 12) && m > 0) {
return Err(TZError::Range);
}
let (sign, hours) = if h > 0 {
(Sign::Positive, u8::try_from(h).unwrap())
} else {
(Sign::Negative, u8::try_from(-h).unwrap())
};
Ok(Self::Offset {
sign,
hours,
minutes: m,
})
}
(Some(h), None) => {
if !(-12..=12).contains(&h) {
return Err(TZError::Range);
}
let (sign, hours) = if h > 0 {
(Sign::Positive, u8::try_from(h).unwrap())
} else {
(Sign::Negative, u8::try_from(-h).unwrap())
};
Ok(Self::Offset {
sign,
hours,
minutes: 0,
})
}
(None, Some(m)) => {
if m > 59 {
Err(TZError::Range)
} else {
Ok(Self::Offset {
sign: Sign::Positive,
hours: 0,
minutes: m,
})
}
}
_ => Ok(Self::Utc),
}
}
pub fn as_seconds(&self) -> i64 {
match self {
Self::Utc => 0,
Self::Offset {
sign,
hours,
minutes,
} => {
let base = i64::from(*hours) * SECONDS_PER_HOUR + i64::from(*minutes) * 60;
if let Sign::Negative = sign {
-base
} else {
base
}
}
}
}
pub fn display(&self) -> String {
match self {
Self::Utc => "UTC".into(),
_ => format!("{self}"),
}
}
}
impl fmt::Display for TimeZone {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Utc => write!(f, "Z"),
Self::Offset {
sign,
hours,
minutes,
} => {
write!(f, "{sign}{hours:0>2}:{minutes:0>2}")
}
}
}
}
impl FromStr for TimeZone {
type Err = TZError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s == "z" || s == "Z" {
return Ok(Self::Utc);
}
let sign = match s.get(0..1) {
Some("+") => Sign::Positive,
Some("-") => Sign::Negative,
_ => return Err(TZError::ParseTZError),
};
if let Some(s) = s.get(1..) {
let Some((h, m)) = s.split_once(':') else {
return Err(TZError::ParseTZError);
};
let hours = h.parse::<u8>().map_err(|_| TZError::ParseTZError)?;
let minutes = m.parse::<u8>().map_err(|_| TZError::ParseTZError)?;
if hours > 12 || minutes > 59 || (hours == 12 && minutes > 0) {
Err(TZError::Range)
} else {
Ok(Self::Offset {
sign,
hours,
minutes,
})
}
} else {
Err(TZError::ParseTZError)
}
}
}
#[derive(Debug)]
pub enum TZError {
Range,
ParseTZError,
}
impl fmt::Display for TZError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{self:?}")
}
}
impl error::Error for TZError {}