fire_postgres/time/
datetime.rs

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