aws_sdk_manager/utils/
rfc3339.rs

1use std::{
2    io::{self, Error, ErrorKind},
3    time::SystemTime,
4};
5
6use chrono::{DateTime, NaiveDateTime, SecondsFormat, TimeZone, Utc};
7use serde::{self, Deserialize, Deserializer};
8
9/// ref. https://serde.rs/custom-date-format.html
10pub mod serde_format {
11    use chrono::{DateTime, SecondsFormat, TimeZone, Utc};
12    use serde::{self, Deserialize, Deserializer, Serializer};
13
14    pub fn serialize<S>(dt: &DateTime<Utc>, serializer: S) -> Result<S::Ok, S::Error>
15    where
16        S: Serializer,
17    {
18        // ref. https://docs.rs/chrono/0.4.19/chrono/struct.DateTime.html#method.to_rfc3339_opts
19        serializer.serialize_str(&dt.to_rfc3339_opts(SecondsFormat::Millis, true))
20    }
21
22    pub fn deserialize<'de, D>(deserializer: D) -> Result<DateTime<Utc>, D::Error>
23    where
24        D: Deserializer<'de>,
25    {
26        let s = String::deserialize(deserializer)?;
27
28        // ref. https://docs.rs/chrono/0.4.19/chrono/struct.DateTime.html#method.parse_from_rfc3339
29        match DateTime::parse_from_rfc3339(&s).map_err(serde::de::Error::custom) {
30            Ok(dt) => Ok(Utc.from_utc_datetime(&dt.naive_utc())),
31            Err(e) => Err(e),
32        }
33    }
34}
35
36fn fmt_date<'de, D>(deserializer: D) -> Result<DateTime<Utc>, D::Error>
37where
38    D: Deserializer<'de>,
39{
40    let s = String::deserialize(deserializer)?;
41    // ref. https://docs.rs/chrono/0.4.19/chrono/struct.DateTime.html#method.parse_from_rfc3339
42    match DateTime::parse_from_rfc3339(&s).map_err(serde::de::Error::custom) {
43        Ok(dt) => Ok(Utc.from_utc_datetime(&dt.naive_utc())),
44        Err(e) => Err(e),
45    }
46}
47
48pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<DateTime<Utc>>, D::Error>
49where
50    D: Deserializer<'de>,
51{
52    #[derive(Deserialize)]
53    struct Wrapper(#[serde(deserialize_with = "fmt_date")] DateTime<Utc>);
54    let v = Option::deserialize(deserializer)?;
55    Ok(v.map(|Wrapper(a)| a))
56}
57
58pub fn now_str() -> io::Result<String> {
59    let now_unix = SystemTime::now()
60        .duration_since(SystemTime::UNIX_EPOCH)
61        .expect("unexpected None duration_since")
62        .as_secs();
63    let native_dt = NaiveDateTime::from_timestamp(now_unix as i64, 0);
64    let now_utc = DateTime::<Utc>::from_utc(native_dt, Utc);
65    Ok(now_utc.to_rfc3339_opts(SecondsFormat::Millis, true))
66}
67
68pub fn to_str(now_unix: u64) -> io::Result<String> {
69    let native_dt = NaiveDateTime::from_timestamp(now_unix as i64, 0);
70    let now_utc = DateTime::<Utc>::from_utc(native_dt, Utc);
71    Ok(now_utc.to_rfc3339_opts(SecondsFormat::Millis, true))
72}
73
74pub fn parse(s: &str) -> io::Result<DateTime<Utc>> {
75    match DateTime::parse_from_rfc3339(s) {
76        Ok(dt) => Ok(Utc.from_utc_datetime(&dt.naive_utc())),
77        Err(e) => {
78            return Err(Error::new(
79                ErrorKind::Other,
80                format!("failed to parse {} ({})", s, e),
81            ));
82        }
83    }
84}
85
86/// RUST_LOG=debug cargo test --all-features --package avalanche-utils --lib -- rfc3339::test_parse --exact --show-output
87#[test]
88fn test_parse() {
89    let _ = env_logger::builder()
90        .filter_level(log::LevelFilter::Info)
91        .is_test(true)
92        .try_init();
93    use log::info;
94
95    let dt = Utc.ymd(2018, 1, 26).and_hms_micro(18, 30, 9, 453_000);
96    let s = "2018-01-26T18:30:09.453Z";
97    let parsed = parse(s).unwrap();
98    assert_eq!(dt, parsed);
99
100    let parsed = parse(&(now_str().unwrap())).unwrap();
101    info!("{:?}", parsed);
102}
103
104/// RUST_LOG=debug cargo test --all-features --package avalanche-utils --lib -- rfc3339::tests::rfc3339_parse --exact --show-output
105/// ref. https://altsysrq.github.io/proptest-book/proptest/getting-started.html
106#[cfg(test)]
107mod tests {
108    use crate::utils::rfc3339;
109    use chrono::{TimeZone, Utc};
110
111    proptest::proptest! {
112        #[test]
113        fn rfc3339_parse(
114            y in 0i32..10000,
115            m in 1u32..13,
116            d in 1u32..28,
117            hr in 0u32..24,
118            min in 0u32..60,
119            sec in 0u32..60,
120            msec in 0u32..1000,
121        ) {
122            let dt = Utc.ymd(y, m, d).and_hms_micro(hr, min, sec, msec);
123            let s = format!("{:04}-{:02}-{:02}T{:02}:{:02}:{:02}.{:06}Z", y, m, d, hr, min, sec, msec);
124            let parsed = rfc3339::parse(&s).unwrap();
125            assert_eq!(dt, parsed);
126        }
127    }
128}