db_dump/
datetime.rs

1use chrono::{DateTime, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc};
2use serde::de::{Deserializer, Unexpected, Visitor};
3use std::fmt;
4
5// The timestamps in the db dump CSV do not mention a time zone, but in reality
6// they refer to UTC.
7struct CratesioDateTimeVisitor;
8
9impl<'de> Visitor<'de> for CratesioDateTimeVisitor {
10    type Value = DateTime<Utc>;
11
12    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
13        formatter.write_str("datetime in format 'YYYY-MM-DD HH:MM:SS.SSSSSS'")
14    }
15
16    fn visit_str<E>(self, string: &str) -> Result<Self::Value, E>
17    where
18        E: serde::de::Error,
19    {
20        // NaiveDateTime::parse_from_str(string, "%Y-%m-%d %H:%M:%S%.6f")
21        'err: {
22            let string = string.strip_suffix("+00").unwrap_or(string);
23            if string.len() < 19 {
24                break 'err;
25            }
26            let year: u16 = match string[0..4].parse() {
27                Ok(year) => year,
28                Err(_) => break 'err,
29            };
30            if string[4..5] != *"-" {
31                break 'err;
32            }
33            let month: u8 = match string[5..7].parse() {
34                Ok(month) => month,
35                Err(_) => break 'err,
36            };
37            if string[7..8] != *"-" {
38                break 'err;
39            }
40            let day: u8 = match string[8..10].parse() {
41                Ok(day) => day,
42                Err(_) => break 'err,
43            };
44            let Some(naive_date) = NaiveDate::from_ymd_opt(year as i32, month as u32, day as u32)
45            else {
46                break 'err;
47            };
48            if string[10..11] != *" " {
49                break 'err;
50            }
51            let hour: u8 = match string[11..13].parse() {
52                Ok(hour) => hour,
53                Err(_) => break 'err,
54            };
55            if string[13..14] != *":" {
56                break 'err;
57            }
58            let min: u8 = match string[14..16].parse() {
59                Ok(min) => min,
60                Err(_) => break 'err,
61            };
62            if string[16..17] != *":" {
63                break 'err;
64            }
65            let sec: u8 = match string[17..19].parse() {
66                Ok(sec) => sec,
67                Err(_) => break 'err,
68            };
69            let micro: u32 = if string.len() == 19 {
70                0
71            } else if string[19..20] != *"." || string.len() > 26 {
72                break 'err;
73            } else if let Ok(micro) = string[20..].parse::<u32>() {
74                let trailing_zeros = 26 - string.len() as u32;
75                micro * 10u32.pow(trailing_zeros)
76            } else {
77                break 'err;
78            };
79            let Some(naive_time) =
80                NaiveTime::from_hms_micro_opt(hour as u32, min as u32, sec as u32, micro)
81            else {
82                break 'err;
83            };
84            let naive_date_time = NaiveDateTime::new(naive_date, naive_time);
85            return Ok(Utc.from_utc_datetime(&naive_date_time));
86        }
87        Err(serde::de::Error::invalid_value(
88            Unexpected::Str(string),
89            &self,
90        ))
91    }
92}
93
94pub(crate) fn de<'de, D>(deserializer: D) -> Result<DateTime<Utc>, D::Error>
95where
96    D: Deserializer<'de>,
97{
98    deserializer.deserialize_str(CratesioDateTimeVisitor)
99}
100
101#[cfg(test)]
102mod tests {
103    use chrono::{NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc};
104    use serde::de::value::Error;
105    use serde::de::IntoDeserializer;
106
107    #[test]
108    fn test_de() {
109        let csv = "2020-01-01 12:11:10.999999";
110        let deserializer = IntoDeserializer::<Error>::into_deserializer;
111        assert_eq!(
112            super::de(deserializer(csv)).unwrap(),
113            Utc.from_utc_datetime(&NaiveDateTime::new(
114                NaiveDate::from_ymd_opt(2020, 1, 1).unwrap(),
115                NaiveTime::from_hms_micro_opt(12, 11, 10, 999999).unwrap(),
116            )),
117        );
118
119        let csv = "2020-01-01 12:11:10.99";
120        assert_eq!(
121            super::de(deserializer(csv)).unwrap(),
122            Utc.from_utc_datetime(&NaiveDateTime::new(
123                NaiveDate::from_ymd_opt(2020, 1, 1).unwrap(),
124                NaiveTime::from_hms_micro_opt(12, 11, 10, 990000).unwrap(),
125            )),
126        );
127
128        let csv = "2020-01-01 12:11:10";
129        assert_eq!(
130            super::de(deserializer(csv)).unwrap(),
131            Utc.from_utc_datetime(&NaiveDateTime::new(
132                NaiveDate::from_ymd_opt(2020, 1, 1).unwrap(),
133                NaiveTime::from_hms_micro_opt(12, 11, 10, 0).unwrap(),
134            )),
135        );
136    }
137}