fire_postgres/time/
date.rs1use 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#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
20#[cfg_attr(feature = "graphql", derive(juniper::GraphQLScalar))]
22#[cfg_attr(feature = "graphql", graphql(with = graphql))]
23pub struct Date(chrono::NaiveDate);
24
25impl Date {
26 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 (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 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
105impl 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
122impl 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
137impl 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}