use chrono::{DateTime, FixedOffset, NaiveDate, ParseError};
use serde::ser::Serializer;
use serde::Serialize;
use std::fmt;
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum W3CDateTime {
Date(NaiveDate),
DateTime(DateTime<FixedOffset>, bool, bool),
}
impl W3CDateTime {
pub fn new(string: &str) -> Result<W3CDateTime, ParseError> {
Self::parse(string)
}
fn parse(string: &str) -> Result<W3CDateTime, ParseError> {
if string.len() == 10 {
Ok(W3CDateTime::Date(string.parse::<NaiveDate>()?))
} else {
Ok(W3CDateTime::DateTime(
DateTime::parse_from_rfc3339(string)?,
string.contains('.'),
string.to_uppercase().ends_with('Z'),
))
}
}
}
impl Serialize for W3CDateTime {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match *self {
Self::Date(ref d) => Ok(serializer.serialize_str(d.to_string().as_str()))?,
Self::DateTime(ref dt, _, _) => Ok(serializer.serialize_str(dt.to_string().as_str())?),
}
}
}
impl fmt::Display for W3CDateTime {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Self::Date(date) => f.write_str(&date.format("%Y-%m-%d").to_string()),
Self::DateTime(datetime, fractional, use_z) => {
let formatted = datetime.to_rfc3339_opts(
if fractional {
chrono::SecondsFormat::Millis
} else {
chrono::SecondsFormat::Secs
},
use_z,
);
f.write_str(&formatted)
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_w3c_date_only() -> Result<(), ParseError> {
let date_string = "2024-02-27";
let result = W3CDateTime::parse(date_string)?;
assert_eq!(date_string, result.to_string());
Ok(())
}
#[test]
fn test_w3c_midnight_utc() -> Result<(), ParseError> {
let date_string = "2024-02-27T00:00:00Z";
let result = W3CDateTime::parse(date_string)?;
let expected = "2024-02-27T00:00:00Z";
assert_eq!(expected, result.to_string());
Ok(())
}
}