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, minutes: Option) -> Result { 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, } => { write!(f, "{sign}{hours:0>2}:{minutes:0>2}") } } } } impl FromStr for TimeZone { type Err = TZError; fn from_str(s: &str) -> Result { 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::().map_err(|_| TZError::ParseTZError)?; let minutes = m.parse::().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 {}