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