chuchi_postgres_types/time/
date.rs

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