chuchi_postgres_types/time/
date.rs1use super::DateTime;
2use 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#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
15#[cfg_attr(feature = "juniper", derive(juniper::GraphQLScalar))]
17#[cfg_attr(feature = "juniper", graphql(with = graphql))]
18pub struct Date(chrono::NaiveDate);
19
20impl Date {
21 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 (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 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
100impl 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#[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}