use azure_core::error::ErrorKind;
use serde::de;
use std::fmt;
use time::format_description::well_known::Rfc3339;
use time::OffsetDateTime;
pub fn format_date_time(date_time: &OffsetDateTime) -> azure_core::error::Result<String> {
date_time.format(&Rfc3339).map_err(|e| {
azure_core::error::Error::new(
ErrorKind::DataConversion,
format!("Failed to format date_time: {date_time}: {e}"),
)
})
}
pub mod rfc3339 {
use super::*;
pub use time::serde::rfc3339::serialize;
#[allow(dead_code)]
pub fn deserialize<'de, D>(d: D) -> Result<OffsetDateTime, D::Error>
where
D: de::Deserializer<'de>,
{
d.deserialize_str(DateTimeVisitor)
}
struct DateTimeVisitor;
impl de::Visitor<'_> for DateTimeVisitor {
type Value = OffsetDateTime;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
write!(formatter, "RFC3339 datetime string or 0001-01-01T00:00:00")
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
let value = match value {
"0001-01-01T00:00:00" => "0001-01-01T00:00:00Z",
_ => value,
};
match OffsetDateTime::parse(value, &Rfc3339) {
Ok(dt) => Ok(dt),
Err(e) => {
if let Ok(dt) = OffsetDateTime::parse(&format!("{value}Z"), &Rfc3339) {
Ok(dt)
} else {
Err(E::custom(format!("Parse error {e} for {value}")))
}
}
}
}
}
pub mod option {
use super::*;
pub use time::serde::rfc3339::option::serialize;
#[allow(dead_code)]
pub fn deserialize<'de, D>(d: D) -> Result<Option<OffsetDateTime>, D::Error>
where
D: de::Deserializer<'de>,
{
d.deserialize_option(OptionalDateTimeVisitor)
}
struct OptionalDateTimeVisitor;
impl<'de> de::Visitor<'de> for OptionalDateTimeVisitor {
type Value = Option<OffsetDateTime>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
write!(formatter, "null or a datetime string")
}
fn visit_none<E>(self) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(None)
}
fn visit_some<D>(self, d: D) -> Result<Option<OffsetDateTime>, D::Error>
where
D: de::Deserializer<'de>,
{
Ok(Some(d.deserialize_str(DateTimeVisitor)?))
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use azure_core::error::ErrorKind;
use serde::{Deserialize, Serialize};
use serde_json;
pub fn parse_rfc3339(s: &str) -> azure_core::Result<OffsetDateTime> {
OffsetDateTime::parse(s, &Rfc3339).map_err(|e| {
azure_core::error::Error::new(
ErrorKind::DataConversion,
format!("unable to parse rfc3339 date '{s}': {e}"),
)
})
}
#[derive(Serialize, Deserialize)]
struct ExampleState {
#[serde(with = "crate::date_time::rfc3339")]
created_time: time::OffsetDateTime,
#[serde(default, with = "crate::date_time::rfc3339::option")]
deleted_time: Option<time::OffsetDateTime>,
}
#[test]
fn test_serde_datetime() {
let json_state = r#"{
"created_time": "2021-07-01T10:45:02Z"
}"#;
let state: ExampleState = serde_json::from_str(json_state).unwrap();
assert_eq!(
parse_rfc3339("2021-07-01T10:45:02Z").unwrap(),
state.created_time
);
assert_eq!(state.deleted_time, None);
}
#[test]
fn test_serde_datetime_beginning_of_time_without_offset() {
let json_state = r#"{
"created_time": "0001-01-01T00:00:00"
}"#;
let state: ExampleState = serde_json::from_str(json_state).unwrap();
assert_eq!(
parse_rfc3339("0001-01-01T00:00:00Z").unwrap(),
state.created_time
);
assert_eq!(state.deleted_time, None);
}
#[test]
fn test_serde_datetime_beginning_of_time_with_offset() {
let json_state = r#"{
"created_time": "0001-01-01T00:00:00Z"
}"#;
let state: ExampleState = serde_json::from_str(json_state).unwrap();
assert_eq!(
state.created_time,
parse_rfc3339("0001-01-01T00:00:00Z").unwrap()
);
assert_eq!(state.deleted_time, None);
}
#[test]
fn test_serde_datetime_invalid_time() {
let json_state = r#"{
"created_time": "0002-01-01T00:0000"
}"#;
let result: Result<ExampleState, _> = serde_json::from_str(json_state);
assert!(result.is_err());
}
#[test]
fn test_serde_datetime_without_offset() {
let json_state = r#"{
"created_time": "2023-05-03T20:09:17.5460824"
}"#;
let state: ExampleState = serde_json::from_str(json_state).unwrap();
assert_eq!(
state.created_time,
parse_rfc3339("2023-05-03T20:09:17.5460824Z").unwrap()
);
}
#[test]
fn test_serde_datetime_optional_time() {
let json_state = r#"{
"created_time": "2022-03-04T00:01:02Z",
"deleted_time": "2022-03-04T01:02:03Z"
}"#;
let state: ExampleState = serde_json::from_str(json_state).unwrap();
assert_eq!(
state.created_time,
parse_rfc3339("2022-03-04T00:01:02Z").unwrap()
);
assert_eq!(
state.deleted_time,
Some(parse_rfc3339("2022-03-04T01:02:03Z").unwrap())
);
}
#[test]
fn test_serde_datetime_optional_beginning_of_time() {
let json_state = r#"{
"created_time": "2022-03-04T00:01:02Z",
"deleted_time": "0001-01-01T00:00:00"
}"#;
let state: ExampleState = serde_json::from_str(json_state).unwrap();
assert_eq!(
state.created_time,
parse_rfc3339("2022-03-04T00:01:02Z").unwrap()
);
assert_eq!(
state.deleted_time,
Some(parse_rfc3339("0001-01-01T00:00:00Z").unwrap())
);
}
}