mbta_rs/models/
datetime.rs

1//! Serialization and deserialization methods for MBTA dates and datetimes.
2
3/// Datetime string format.
4pub const DATETIME_FORMAT: &str = "%FT%T%:z";
5
6/// Date string format.
7pub const DATE_FORMAT: &str = "%F";
8
9/// Serialization and deserialization for the MBTA datetime format.
10pub mod mbta_datetime_format {
11    use chrono::{DateTime, FixedOffset};
12    use serde::{Deserialize, Deserializer, Serializer};
13
14    use super::DATETIME_FORMAT;
15
16    /// Serialize an MBTA datetime.
17    ///
18    /// # Arguments
19    ///
20    /// * `datetime` - the datetime
21    /// * `serializer` - the serializer
22    pub fn serialize<S: Serializer>(datetime: &DateTime<FixedOffset>, serializer: S) -> Result<S::Ok, S::Error> {
23        serializer.serialize_str(&format!("{}", datetime.format(DATETIME_FORMAT)))
24    }
25
26    /// Attempt to deserialize an MBTA datetime.
27    ///
28    /// # Arguments
29    ///
30    /// * `deserializer` - the deserializer
31    pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<DateTime<FixedOffset>, D::Error> {
32        let s = String::deserialize(deserializer)?;
33        DateTime::parse_from_str(&s, DATETIME_FORMAT).map_err(serde::de::Error::custom)
34    }
35
36    #[cfg(test)]
37    mod tests {
38        use super::*;
39
40        use rstest::*;
41        use serde_json::{Deserializer, Serializer};
42
43        #[fixture]
44        fn serializer() -> Serializer<Vec<u8>> {
45            Serializer::new(Vec::new())
46        }
47
48        #[rstest]
49        #[case::simple_case(
50            DateTime::parse_from_str("2022-05-08T13:18:08-04:00", "%FT%T%:z").expect("invalid input"), 
51            "\"2022-05-08T13:18:08-04:00\"",
52        )]
53        fn test_serialize(mut serializer: Serializer<Vec<u8>>, #[case] input: DateTime<FixedOffset>, #[case] expected: &str) {
54            // Arrange
55
56            // Act
57            serialize(&input, &mut serializer).expect("failed to serialize");
58            let inner = serializer.into_inner();
59            let actual = std::str::from_utf8(&inner).expect("failed to convert to string");
60
61            // Assert
62            assert_eq!(actual, expected);
63        }
64
65        #[rstest]
66        #[case::valid_format(
67            "\"2022-05-08T13:18:08-04:00\"",
68            DateTime::parse_from_str("2022-05-08T13:18:08-04:00", "%FT%T%:z").expect("invalid input"),
69        )]
70        #[should_panic = "failed to deserialize"]
71        #[case::invalid_format(
72            "\"2022-05-08 13:18:08-04:00\"",
73            DateTime::parse_from_str("2022-05-08T13:18:08-04:00", "%FT%T%:z").expect("invalid input"),
74        )]
75        fn test_deserialize(#[case] input: &str, #[case] expected: DateTime<FixedOffset>) {
76            // Arrange
77            let mut deserializer = Deserializer::from_str(input);
78
79            // Act
80            let actual = deserialize(&mut deserializer).expect("failed to deserialize");
81
82            // Assert
83            assert_eq!(actual, expected);
84        }
85    }
86}
87
88/// Serialization and deserialization for an optional MBTA datetime format.
89pub mod optional_mbta_datetime_format {
90    use chrono::{DateTime, FixedOffset};
91    use serde::{Deserialize, Deserializer, Serializer};
92
93    use super::{mbta_datetime_format::serialize as datetime_serialize, DATETIME_FORMAT};
94
95    /// Serialize an optional MBTA datetime.
96    ///
97    /// # Arguments
98    ///
99    /// * `datetime` - the optional datetime
100    /// * `serializer` - the serializer
101    pub fn serialize<S>(datetime: &Option<DateTime<FixedOffset>>, serializer: S) -> Result<S::Ok, S::Error>
102    where
103        S: Serializer,
104    {
105        match datetime {
106            Some(d) => datetime_serialize(d, serializer),
107            None => serializer.serialize_none(),
108        }
109    }
110
111    /// Attempt to deserialize an optional MBTA datetime.
112    ///
113    /// # Arguments
114    ///
115    /// * `deserializer` - the deserializer
116    pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<DateTime<FixedOffset>>, D::Error>
117    where
118        D: Deserializer<'de>,
119    {
120        let s = Option::<String>::deserialize(deserializer)?;
121        match s {
122            Some(s) => {
123                let date = DateTime::parse_from_str(&s, DATETIME_FORMAT).map_err(serde::de::Error::custom)?;
124                Ok(Some(date))
125            }
126            None => Ok(None),
127        }
128    }
129
130    #[cfg(test)]
131    mod tests {
132        use super::*;
133
134        use rstest::*;
135        use serde_json::{Deserializer, Serializer};
136
137        #[fixture]
138        fn serializer() -> Serializer<Vec<u8>> {
139            Serializer::new(Vec::new())
140        }
141
142        #[rstest]
143        #[case::some_dateime(
144            Some(DateTime::parse_from_str("2022-05-08T13:18:08-04:00", "%FT%T%:z").expect("invalid input")), 
145            "\"2022-05-08T13:18:08-04:00\"",
146        )]
147        #[case::no_datetime(None, "null")]
148        fn test_serialize(mut serializer: Serializer<Vec<u8>>, #[case] input: Option<DateTime<FixedOffset>>, #[case] expected: &str) {
149            // Arrange
150
151            // Act
152            serialize(&input, &mut serializer).expect("failed to serialize");
153            let inner = serializer.into_inner();
154            let actual = std::str::from_utf8(&inner).expect("failed to convert to string");
155
156            // Assert
157            assert_eq!(actual, expected);
158        }
159
160        #[rstest]
161        #[case::valid_format(
162            "\"2022-05-08T13:18:08-04:00\"",
163            Some(DateTime::parse_from_str("2022-05-08T13:18:08-04:00", "%FT%T%:z").expect("invalid input")), 
164        )]
165        #[case::valid_format("null", None)]
166        #[should_panic = "failed to deserialize"]
167        #[case::invalid_format("\"2022-05-08 13:18:08-04:00\"", None)]
168        fn test_deserialize(#[case] input: &str, #[case] expected: Option<DateTime<FixedOffset>>) {
169            // Arrange
170            let mut deserializer = Deserializer::from_str(input);
171
172            // Act
173            let actual = deserialize(&mut deserializer).expect("failed to deserialize");
174
175            // Assert
176            assert_eq!(actual, expected);
177        }
178    }
179}
180
181/// Serialization and deserialization for the MBTA date format.
182pub mod mbta_date_format {
183    use chrono::{Date, DateTime, FixedOffset};
184    use serde::{Deserialize, Deserializer, Serializer};
185
186    use super::{DATETIME_FORMAT, DATE_FORMAT};
187
188    /// Serialize an MBTA date.
189    ///
190    /// # Arguments
191    ///
192    /// * `date` - the date
193    /// * `serializer` - the serializer
194    pub fn serialize<S: Serializer>(date: &Date<FixedOffset>, serializer: S) -> Result<S::Ok, S::Error> {
195        serializer.serialize_str(&format!("{}", date.format(DATE_FORMAT)))
196    }
197
198    /// Attempt to deserialize an MBTA date.
199    ///
200    /// # Arguments
201    ///
202    /// * `deserializer` - the deserializer
203    pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Date<FixedOffset>, D::Error> {
204        let s = format!("{}T00:00:00-04:00", String::deserialize(deserializer)?);
205        DateTime::parse_from_str(&s, DATETIME_FORMAT).map(|dt| dt.date()).map_err(serde::de::Error::custom)
206    }
207
208    #[cfg(test)]
209    mod tests {
210        use super::*;
211
212        use chrono::{Date, DateTime, FixedOffset};
213        use rstest::*;
214        use serde_json::{Deserializer, Serializer};
215
216        #[fixture]
217        fn serializer() -> Serializer<Vec<u8>> {
218            Serializer::new(Vec::new())
219        }
220
221        #[rstest]
222        #[case::simple_case(
223            DateTime::parse_from_str("2022-05-08T13:18:08-04:00", "%FT%T%:z").expect("invalid input").date(), 
224            "\"2022-05-08\""
225        )]
226        fn test_serialize(mut serializer: Serializer<Vec<u8>>, #[case] input: Date<FixedOffset>, #[case] expected: &str) {
227            // Arrange
228
229            // Act
230            serialize(&input, &mut serializer).expect("failed to serialize");
231            let inner = serializer.into_inner();
232            let actual = std::str::from_utf8(&inner).expect("failed to convert to string");
233
234            // Assert
235            assert_eq!(actual, expected);
236        }
237
238        #[rstest]
239        #[case::valid_format(
240            "\"2022-05-08\"",
241            DateTime::parse_from_str("2022-05-08T13:18:08-04:00", "%FT%T%:z").expect("invalid input").date(), 
242        )]
243        #[should_panic = "failed to deserialize"]
244        #[case::invalid_format(
245            "\"2022 05 08\"",
246            DateTime::parse_from_str("2022-05-08T13:18:08-04:00", "%FT%T%:z").expect("invalid input").date(), 
247        )]
248        fn test_deserialize(#[case] input: &str, #[case] expected: Date<FixedOffset>) {
249            // Arrange
250            let mut deserializer = Deserializer::from_str(input);
251
252            // Act
253            let actual = deserialize(&mut deserializer).expect("failed to deserialize");
254
255            // Assert
256            assert_eq!(actual, expected);
257        }
258    }
259}
260
261/// Serialization and deserialization for an optional MBTA date format.
262pub mod optional_mbta_date_format {
263    use chrono::{Date, DateTime, FixedOffset};
264    use serde::{Deserialize, Deserializer, Serializer};
265
266    use super::{mbta_date_format::serialize as date_serialize, DATETIME_FORMAT};
267
268    /// Serialize an optional MBTA date.
269    ///
270    /// # Arguments
271    ///
272    /// * `date` - the optional date
273    /// * `serializer` - the serializer
274    pub fn serialize<S>(date: &Option<Date<FixedOffset>>, serializer: S) -> Result<S::Ok, S::Error>
275    where
276        S: Serializer,
277    {
278        match date {
279            Some(d) => date_serialize(d, serializer),
280            None => serializer.serialize_none(),
281        }
282    }
283
284    /// Attempt to deserialize an optional MBTA date.
285    ///
286    /// # Arguments
287    ///
288    /// * `deserializer` - the deserializer
289    pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Date<FixedOffset>>, D::Error>
290    where
291        D: Deserializer<'de>,
292    {
293        let s = Option::<String>::deserialize(deserializer)?;
294        match s {
295            Some(s) => {
296                let date = DateTime::parse_from_str(&format!("{}T00:00:00-04:00", s), DATETIME_FORMAT)
297                    .map(|dt| dt.date())
298                    .map_err(serde::de::Error::custom)?;
299                Ok(Some(date))
300            }
301            None => Ok(None),
302        }
303    }
304
305    #[cfg(test)]
306    mod tests {
307        use super::*;
308
309        use chrono::{Date, DateTime, FixedOffset};
310        use rstest::*;
311        use serde_json::{Deserializer, Serializer};
312
313        #[fixture]
314        fn serializer() -> Serializer<Vec<u8>> {
315            Serializer::new(Vec::new())
316        }
317
318        #[rstest]
319        #[case::some_date(
320            Some(DateTime::parse_from_str("2022-05-08T13:18:08-04:00", "%FT%T%:z").expect("invalid input").date()), 
321            "\"2022-05-08\"",
322        )]
323        #[case::no_date(None, "null")]
324        fn test_serialize(mut serializer: Serializer<Vec<u8>>, #[case] input: Option<Date<FixedOffset>>, #[case] expected: &str) {
325            // Arrange
326
327            // Act
328            serialize(&input, &mut serializer).expect("failed to serialize");
329            let inner = serializer.into_inner();
330            let actual = std::str::from_utf8(&inner).expect("failed to convert to string");
331
332            // Assert
333            assert_eq!(actual, expected);
334        }
335
336        #[rstest]
337        #[case::valid_format(
338            "\"2022-05-08\"",
339            Some(DateTime::parse_from_str("2022-05-08T13:18:08-04:00", "%FT%T%:z").expect("invalid input").date()), 
340        )]
341        #[case::no_date("null", None)]
342        #[should_panic = "failed to deserialize"]
343        #[case::invalid_format("\"2022 05 08\"", None)]
344        fn test_deserialize(#[case] input: &str, #[case] expected: Option<Date<FixedOffset>>) {
345            // Arrange
346            let mut deserializer = Deserializer::from_str(input);
347
348            // Act
349            let actual = deserialize(&mut deserializer).expect("failed to deserialize");
350
351            // Assert
352            assert_eq!(actual, expected);
353        }
354    }
355}
356
357/// Serialization and deserialization for an vector of MBTA dates format.
358pub mod vec_mbta_date_format {
359    use chrono::{Date, DateTime, FixedOffset};
360    use serde::{Deserialize, Deserializer, Serializer};
361
362    use super::{DATETIME_FORMAT, DATE_FORMAT};
363
364    /// Serialize a vector of MBTA dates.
365    ///
366    /// # Arguments
367    ///
368    /// * `dates` - the dates
369    /// * `serializer` - the serializer
370    pub fn serialize<S>(dates: &[Date<FixedOffset>], serializer: S) -> Result<S::Ok, S::Error>
371    where
372        S: Serializer,
373    {
374        serializer.collect_seq(dates.iter().map(|dt| format!("{}", dt.format(DATE_FORMAT))))
375    }
376
377    /// Attempt to deserialize an optional MBTA dates.
378    ///
379    /// # Arguments
380    ///
381    /// * `deserializer` - the deserializer
382    pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<Date<FixedOffset>>, D::Error>
383    where
384        D: Deserializer<'de>,
385    {
386        let v = Vec::<String>::deserialize(deserializer)?;
387        let mut dates = Vec::new();
388        for dt in v {
389            dates.push(
390                DateTime::parse_from_str(&format!("{}T00:00:00-04:00", dt), DATETIME_FORMAT)
391                    .map(|dt| dt.date())
392                    .map_err(serde::de::Error::custom)?,
393            )
394        }
395        Ok(dates)
396    }
397
398    #[cfg(test)]
399    mod tests {
400        use super::*;
401
402        use chrono::{Date, DateTime, FixedOffset};
403        use rstest::*;
404        use serde_json::{Deserializer, Serializer};
405
406        #[fixture]
407        fn serializer() -> Serializer<Vec<u8>> {
408            Serializer::new(Vec::new())
409        }
410
411        #[rstest]
412        #[case::some_dates(
413            vec![DateTime::parse_from_str("2022-05-08T13:18:08-04:00", "%FT%T%:z").expect("invalid input").date()], 
414            "[\"2022-05-08\"]",
415        )]
416        #[case::no_dates(vec![], "[]")]
417        fn test_serialize(mut serializer: Serializer<Vec<u8>>, #[case] input: Vec<Date<FixedOffset>>, #[case] expected: &str) {
418            // Arrange
419
420            // Act
421            serialize(&input, &mut serializer).expect("failed to serialize");
422            let inner = serializer.into_inner();
423            let actual = std::str::from_utf8(&inner).expect("failed to convert to string");
424
425            // Assert
426            assert_eq!(actual, expected);
427        }
428
429        #[rstest]
430        #[case::valid_format(
431            "[\"2022-05-08\"]",
432            vec![DateTime::parse_from_str("2022-05-08T13:18:08-04:00", "%FT%T%:z").expect("invalid input").date()], 
433        )]
434        #[case::no_dates("[]", vec![])]
435        #[should_panic = "failed to deserialize"]
436        #[case::invalid_format("[\"2022 05 08\"]", vec![])]
437        fn test_deserialize(#[case] input: &str, #[case] expected: Vec<Date<FixedOffset>>) {
438            // Arrange
439            let mut deserializer = Deserializer::from_str(input);
440
441            // Act
442            let actual = deserialize(&mut deserializer).expect("failed to deserialize");
443
444            // Assert
445            assert_eq!(actual, expected);
446        }
447    }
448}