2024-01-29 10:58:27 -05:00
|
|
|
use {
|
|
|
|
crate::SECONDS_PER_HOUR,
|
|
|
|
serde::{Deserialize, Serialize},
|
|
|
|
std::{error, fmt, str::FromStr},
|
|
|
|
};
|
|
|
|
|
|
|
|
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, 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, Deserialize, Eq, PartialEq, 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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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,
|
|
|
|
} => {
|
2024-01-29 19:02:09 -05:00
|
|
|
write!(f, "{sign}{hours:0>2}:{minutes:0>2}")
|
2024-01-29 10:58:27 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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 {}
|