fire_postgres/time/
date.rs

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