use chrono::{DateTime, LocalResult, NaiveDateTime, TimeZone, Utc};
use chrono_tz::Tz;
use std::{fmt::Debug, str::FromStr};
use crate::DateOrDateTime;
use crate::errors::TzIdDateTimeParseError;
#[derive(Debug, Clone)]
pub struct TzIdDateTime {
pub time_zone: Tz,
pub date_time: DateOrDateTime,
}
impl FromStr for TzIdDateTime {
type Err = TzIdDateTimeParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
s.try_into()
}
}
impl<T: TimeZone> From<DateTime<T>> for TzIdDateTime {
fn from(dt: DateTime<T>) -> Self {
Self {
time_zone: chrono_tz::UTC,
date_time: DateOrDateTime::DateTime(dt.with_timezone(&Utc)),
}
}
}
impl TryFrom<&str> for TzIdDateTime {
type Error = TzIdDateTimeParseError;
fn try_from(line: &str) -> Result<Self, Self::Error> {
if let Some(line) = line.strip_prefix("TZID=") {
let mut tokens = line.split(':');
let tz_str = tokens.next().ok_or(TzIdDateTimeParseError::MissingTimeZonePart)?;
let tz: Tz = tz_str.parse::<Tz>().map_err(|e: chrono_tz::ParseError| TzIdDateTimeParseError::InvalidTimeZone(e.to_string()))?;
let date_time = tokens.next().ok_or(TzIdDateTimeParseError::MissingDateTimePart)?;
let date_time = NaiveDateTime::parse_from_str(date_time, "%Y%m%dT%H%M%S")?;
if let LocalResult::Single(d) = tz.from_local_datetime(&date_time) {
Ok(Self {
time_zone: tz,
date_time: DateOrDateTime::DateTime(d.with_timezone(&Utc)),
})
} else {
Err(TzIdDateTimeParseError::AmbiguousTimeZone)
}
} else if let Some(line) = line.strip_prefix("VALUE=DATE:") {
let date = Utc.from_utc_datetime(&NaiveDateTime::parse_from_str(
&format!("{line}T000000"),
"%Y%m%dT%H%M%S",
)?);
Ok(Self {
time_zone: chrono_tz::UTC,
date_time: DateOrDateTime::WholeDay(date),
})
} else {
Err(TzIdDateTimeParseError::MissingTZIDToken)
}
}
}
#[cfg(test)]
mod tests {
use crate::TzIdDateTime;
#[test]
fn parse_00() {
let s = "TZID=Europe/Rome:20220106T154000";
let _: TzIdDateTime = s.try_into().unwrap();
}
#[test]
fn parse_01() {
let s = "TZID=Europe/Rome:20211006T170000";
let _: TzIdDateTime = s.try_into().unwrap();
}
}