fire_postgres_types/time/
datetime.rs1use super::Date;
2
3use std::fmt;
4use std::ops::{Add, Sub};
5use std::time::{Duration as StdDuration, SystemTime};
6
7use chrono::format::ParseError;
8use chrono::offset::TimeZone;
9use chrono::Duration;
10use chrono::Utc;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
14#[cfg_attr(feature = "juniper", derive(juniper::GraphQLScalar))]
16#[cfg_attr(feature = "juniper", graphql(with = graphql))]
17pub struct DateTime(chrono::DateTime<Utc>);
18
19impl DateTime {
20 pub fn new(secs: i64, ns: u32) -> Self {
21 let datetime = chrono::DateTime::from_timestamp(secs, ns)
22 .expect("secs and ns out of range");
23
24 Self(datetime)
25 }
26
27 pub fn now() -> Self {
28 Self(Utc::now())
29 }
30
31 pub fn from_std(time: SystemTime) -> Self {
32 Self(time.into())
33 }
34
35 pub fn from_secs(secs: i64) -> Self {
36 Self::new(secs, 0)
37 }
38
39 pub fn from_ms(ms: u64) -> Self {
41 let secs = ms / 1_000;
42 let ns = (ms - (secs * 1_000)) * 1_000_000;
43
44 Self::new(secs as i64, ns as u32)
45 }
46
47 pub fn inner(&self) -> &chrono::DateTime<Utc> {
48 &self.0
49 }
50
51 pub fn inner_mut(&mut self) -> &mut chrono::DateTime<Utc> {
52 &mut self.0
53 }
54
55 pub fn into_inner(self) -> chrono::DateTime<Utc> {
56 self.0
57 }
58
59 pub fn to_microsecs_since_2000(&self) -> i64 {
60 let date = Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap();
61 self.0
62 .signed_duration_since(date)
63 .num_microseconds()
64 .expect("value too large")
65 }
66
67 pub fn from_microsecs_since_2000(secs: i64) -> Self {
68 let date = Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap();
69 Self(date + Duration::microseconds(secs))
70 }
71
72 pub fn to_iso8601(&self) -> String {
73 self.0.to_rfc3339()
74 }
75
76 pub fn to_date(&self) -> Date {
77 self.0.date_naive().into()
78 }
79
80 pub fn parse_from_iso8601(s: &str) -> Result<Self, ParseError> {
81 Ok(Self(s.parse()?))
82 }
83
84 pub fn abs_diff(&self, other: &Self) -> Option<StdDuration> {
86 (self.0 - other.0).abs().to_std().ok()
87 }
88}
89
90impl From<chrono::DateTime<Utc>> for DateTime {
91 fn from(d: chrono::DateTime<Utc>) -> Self {
92 Self(d)
93 }
94}
95
96impl From<DateTime> for chrono::DateTime<Utc> {
97 fn from(d: DateTime) -> Self {
98 d.0
99 }
100}
101
102impl Add<StdDuration> for DateTime {
103 type Output = Self;
104
105 fn add(self, rhs: StdDuration) -> Self {
108 Self(self.0 + Duration::from_std(rhs).unwrap())
109 }
110}
111
112impl Sub<StdDuration> for DateTime {
113 type Output = Self;
114
115 fn sub(self, rhs: StdDuration) -> Self {
116 Self(self.0 - Duration::from_std(rhs).unwrap())
117 }
118}
119
120impl fmt::Display for DateTime {
122 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
123 f.write_str(&self.to_iso8601())
124 }
125}
126
127#[cfg(feature = "serde")]
130mod impl_serde {
131 use super::*;
132
133 use std::borrow::Cow;
134
135 use serde::de::{Deserializer, Error};
136 use serde::ser::Serializer;
137 use serde::{Deserialize, Serialize};
138
139 impl Serialize for DateTime {
140 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
141 where
142 S: Serializer,
143 {
144 serializer.serialize_str(&self.to_iso8601())
145 }
146 }
147
148 impl<'de> Deserialize<'de> for DateTime {
149 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
150 where
151 D: Deserializer<'de>,
152 {
153 let s: Cow<'_, str> = Deserialize::deserialize(deserializer)?;
154 DateTime::parse_from_iso8601(s.as_ref()).map_err(D::Error::custom)
155 }
156 }
157}
158
159#[cfg(feature = "postgres")]
160mod postgres {
161 use super::*;
162 use bytes::BytesMut;
163 use postgres_protocol::types;
164 use postgres_types::{
165 accepts, to_sql_checked, FromSql, IsNull, ToSql, Type,
166 };
167
168 impl ToSql for DateTime {
169 fn to_sql(
170 &self,
171 _ty: &Type,
172 out: &mut BytesMut,
173 ) -> Result<IsNull, Box<dyn std::error::Error + Sync + Send>> {
174 types::timestamp_to_sql(self.to_microsecs_since_2000(), out);
175
176 Ok(IsNull::No)
177 }
178
179 accepts!(TIMESTAMP);
180
181 to_sql_checked!();
182 }
183
184 impl<'a> FromSql<'a> for DateTime {
185 fn from_sql(
186 _ty: &Type,
187 raw: &'a [u8],
188 ) -> Result<Self, Box<dyn std::error::Error + Sync + Send>> {
189 let micros = types::timestamp_from_sql(raw)?;
190
191 Ok(DateTime::from_microsecs_since_2000(micros))
192 }
193
194 accepts!(TIMESTAMP);
195 }
196}
197
198#[cfg(feature = "protobuf")]
199mod protobuf {
200 use super::*;
201
202 use fire_protobuf::{
203 bytes::BytesWrite,
204 decode::{DecodeError, DecodeMessage, FieldKind},
205 encode::{
206 EncodeError, EncodeMessage, FieldOpt, MessageEncoder, SizeBuilder,
207 },
208 WireType,
209 };
210
211 impl EncodeMessage for DateTime {
212 const WIRE_TYPE: WireType = WireType::Varint;
213
214 fn is_default(&self) -> bool {
215 false
216 }
217
218 fn encoded_size(
219 &mut self,
220 field: Option<FieldOpt>,
221 builder: &mut SizeBuilder,
222 ) -> Result<(), EncodeError> {
223 self.to_microsecs_since_2000().encoded_size(field, builder)
224 }
225
226 fn encode<B>(
227 &mut self,
228 field: Option<FieldOpt>,
229 encoder: &mut MessageEncoder<B>,
230 ) -> Result<(), EncodeError>
231 where
232 B: BytesWrite,
233 {
234 self.to_microsecs_since_2000().encode(field, encoder)
235 }
236 }
237
238 impl<'m> DecodeMessage<'m> for DateTime {
239 const WIRE_TYPE: WireType = WireType::Varint;
240
241 fn decode_default() -> Self {
242 Self::from_microsecs_since_2000(0)
243 }
244
245 fn merge(
246 &mut self,
247 kind: FieldKind<'m>,
248 is_field: bool,
249 ) -> Result<(), DecodeError> {
250 let mut n = 0i64;
251 n.merge(kind, is_field)?;
252
253 *self = Self::from_microsecs_since_2000(n);
254
255 Ok(())
256 }
257 }
258}
259
260#[cfg(feature = "juniper")]
261mod graphql {
262 use super::*;
263
264 use juniper::{
265 InputValue, ParseScalarResult, ScalarToken, ScalarValue, Value,
266 };
267
268 pub(crate) fn to_output<S: ScalarValue>(v: &DateTime) -> Value<S> {
269 Value::scalar(v.to_string())
270 }
271
272 pub(crate) fn from_input<S: ScalarValue>(
273 v: &InputValue<S>,
274 ) -> Result<DateTime, String> {
275 v.as_string_value()
276 .and_then(|s| DateTime::parse_from_iso8601(s.as_ref()).ok())
277 .ok_or_else(|| "Expected a datetime in iso8601 format".into())
278 }
279
280 pub(crate) fn parse_token<S: ScalarValue>(
281 value: ScalarToken<'_>,
282 ) -> ParseScalarResult<S> {
283 <String as juniper::ParseScalarValue<S>>::from_str(value)
284 }
285}
286
287#[cfg(all(test, feature = "serde"))]
288mod tests {
289
290 use super::*;
291 use serde_json::{from_str, from_value, Value};
292
293 #[test]
294 fn serde_test() {
295 let s = "\"2021-04-26T08:16:02+00:00\"";
296 let d: DateTime = from_str(s).unwrap();
297 assert_eq!(d.to_string(), "2021-04-26T08:16:02+00:00");
298
299 let v = Value::String("2021-04-26T08:16:02+00:00".into());
300 let d: DateTime = from_value(v).unwrap();
301 assert_eq!(d.to_string(), "2021-04-26T08:16:02+00:00");
302 }
303}