fire_postgres_types/time/
datetime.rs

1use 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/// A DateTime in the utc timezone
13#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
14// graphql
15#[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	// shouldnt this be i64
40	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	/// Returns None if the duration would overflow
85	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	/// ## Panic
106	/// May panic if the duration is to big
107	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
120// DISPLAY
121impl 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// SERDE
128
129#[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}