use log::trace; use std::time::{Duration, SystemTime}; use time::{ format_description::well_known::{Iso8601, Rfc2822}, format_description::FormatItem, macros::format_description, parsing::Parsable, OffsetDateTime, PrimitiveDateTime, }; #[derive(Debug, thiserror::Error)] pub enum TimeError { #[error("failed to parse date: {0:?}")] UnknownTimestamp(String), } pub fn parse_timestamp(timestamp: &str) -> Result { trace!("parsing timestamp {:?}", timestamp); let odt = parse(timestamp)?; let unix = odt.unix_timestamp(); trace!("unix: {}", unix); let sys = system_time(unix); trace!("system time: {:?}", sys); Ok(system_time(unix)) } fn system_time(unix: i64) -> SystemTime { let offset = Duration::from_secs(unix as u64); SystemTime::UNIX_EPOCH.checked_add(offset).unwrap() } fn parse(timestamp: &str) -> Result { const SIMPLIFIED_ISO9601: &[FormatItem<'static>] = format_description!("[year]-[month]-[day] [hour]:[minute]:[second]"); const SIMPLIFIED_ISO9601_MIN: &[FormatItem<'static>] = format_description!("[year]-[month]-[day] [hour]:[minute]"); const SIMPLIFIED_ISO9601_TZ: &[FormatItem<'static>] = format_description!( "[year]-[month]-[day] [hour]:[minute]:[second] [offset_hour][offset_minute]" ); const SIMPLIFIED_ISO9601_MIN_TZ: &[FormatItem<'static>] = format_description!("[year]-[month]-[day] [hour]:[minute] [offset_hour][offset_minute]"); if let Ok(t) = parse_one_time_format(timestamp, "simplified", SIMPLIFIED_ISO9601) { Ok(t) } else if let Ok(t) = parse_one_time_format(timestamp, "simplified-min", SIMPLIFIED_ISO9601_MIN) { Ok(t) } else if let Ok(t) = parse_one_time_format(timestamp, "simplified-tz", SIMPLIFIED_ISO9601_TZ) { Ok(t) } else if let Ok(t) = parse_one_time_format(timestamp, "simplified-tz", SIMPLIFIED_ISO9601_MIN_TZ) { Ok(t) } else if let Ok(t) = parse_one_time_format(timestamp, "ISO8601", &Iso8601::PARSING) { Ok(t) } else if let Ok(t) = parse_one_time_format(timestamp, "RFC2822", &Rfc2822) { Ok(t) } else { Err(TimeError::UnknownTimestamp(timestamp.into())) } } fn parse_one_time_format( timestamp: &str, what: &str, fmt: &(impl Parsable + ?Sized), ) -> Result { trace!("trying to parse using: {}", what); let r = PrimitiveDateTime::parse(timestamp, fmt); if let Ok(t) = r { Ok(t.assume_utc()) } else { let e = r.err().unwrap(); trace!("error: {}", e); Err(e) } } #[cfg(test)] mod test { use super::parse; fn unix_time(t: &str) -> i64 { parse(t).unwrap().unix_timestamp() } #[test] fn simple() { assert_eq!(unix_time("1970-01-01 00:00:00"), 0); } #[test] fn simple_with_minutes_only() { assert_eq!(unix_time("1970-01-01 00:00"), 0); } #[test] fn simple_with_tz() { assert_eq!(unix_time("1970-01-01 00:00:00 +0000"), 0); } #[test] fn iso9601() { assert_eq!(unix_time("1970-01-01T00:00:00+00:00"), 0); } #[test] fn rfc2822() { assert_eq!(unix_time("Thu, 01 Jan 1970 00:00:00 +0000"), 0); } }