oparl_types/
date_time.rs

1// SPDX-FileCopyrightText: Politik im Blick developers
2// SPDX-FileCopyrightText: Wolfgang Silbermayr <wolfgang@silbermayr.at>
3//
4// SPDX-License-Identifier: AGPL-3.0-or-later OR EUPL-1.2
5
6use std::{fmt::Display, str::FromStr};
7
8use time::OffsetDateTime;
9
10#[derive(
11    Debug,
12    Clone,
13    PartialEq,
14    Eq,
15    PartialOrd,
16    Ord,
17    Hash,
18    derive_more::AsRef,
19    derive_more::From,
20    derive_more::Into,
21    serde_with::SerializeDisplay,
22    serde_with::DeserializeFromStr,
23)]
24#[cfg_attr(feature = "sea-orm", derive(sea_orm::DeriveValueType))]
25pub struct DateTime(OffsetDateTime);
26
27impl DateTime {
28    pub fn now() -> Self {
29        Self(OffsetDateTime::now_utc())
30    }
31}
32
33impl FromStr for DateTime {
34    type Err = time::error::Parse;
35
36    fn from_str(s: &str) -> Result<Self, Self::Err> {
37        use time::format_description::well_known::Iso8601;
38
39        Ok(Self(OffsetDateTime::parse(s, &Iso8601::DEFAULT)?))
40    }
41}
42
43impl Display for DateTime {
44    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45        use time::format_description::well_known::{
46            iso8601::{Config, EncodedConfig},
47            Iso8601,
48        };
49
50        const CONFIG: EncodedConfig = Config::DEFAULT
51            .set_time_precision(
52                time::format_description::well_known::iso8601::TimePrecision::Second {
53                    decimal_digits: None,
54                },
55            )
56            .encode();
57
58        write!(
59            f,
60            "{}",
61            self.0.format(&Iso8601::<CONFIG>).expect("valid date")
62        )
63    }
64}
65
66#[cfg(test)]
67mod tests {
68    use super::DateTime;
69
70    use time::macros::datetime;
71
72    #[test]
73    fn from_str() {
74        let parsed: DateTime = "2025-01-07T05:06:07+01:00"
75            .parse()
76            .expect("string must be parseable as DateTime");
77        assert_eq!(parsed, DateTime(datetime!(2025-01-07 05:06:07 +01:00)));
78    }
79
80    #[test]
81    fn to_string() {
82        let dt = DateTime(datetime!(2025-01-07 05:06:07 +01:00));
83
84        assert_eq!(dt.to_string(), "2025-01-07T05:06:07+01:00".to_string());
85    }
86}
87
88#[cfg(test)]
89mod serde_tests {
90    use super::DateTime;
91    use pretty_assertions::assert_eq;
92    use serde_json::json;
93    use time::macros::datetime;
94
95    #[test]
96    fn serialize() {
97        assert_eq!(
98            json!(DateTime(datetime!(2025-01-07 05:06:07 +01:00))),
99            json!("2025-01-07T05:06:07+01:00")
100        );
101    }
102
103    #[test]
104    fn deserialize_good() {
105        let deserialized: DateTime = serde_json::from_value(json!("2025-01-07T05:06:07+01:00"))
106            .expect("json must be parseable as DateTime");
107
108        assert_eq!(
109            deserialized,
110            DateTime(datetime!(2025-01-07 05:06:07 +01:00))
111        );
112    }
113
114    #[test]
115    fn deserialize_bad() {
116        assert!(serde_json::from_value::<DateTime>(json!("xyzabcd")).is_err());
117        assert!(serde_json::from_value::<DateTime>(json!(true)).is_err());
118        assert!(serde_json::from_value::<DateTime>(json!(123)).is_err());
119    }
120}