clickhouse_client/value/ext/
time.rs

1//! Extension for `time` crate
2
3use crate::{
4    error::Error,
5    value::{ChValue, Type, Value},
6};
7
8use time::PrimitiveDateTime;
9pub use time::{
10    format_description::FormatItem, macros::format_description, Date, Month, OffsetDateTime,
11};
12
13// -- Date --
14
15/// SQL date format
16static FORMAT_YY_MM_DD: &[FormatItem] = format_description!("[year]-[month]-[day]");
17
18/// Extends a [Date] with additional methods
19pub trait DateExt: Sized {
20    /// Returns the number of days since the beginning of the UNIX period
21    fn unix_days(&self) -> i32;
22
23    /// From the number of days since the beginning of the UNIX period, returns the [Date]
24    fn from_unix_days(days: i32) -> Result<Self, Error>
25    where
26        Self: Sized;
27
28    /// Formats as `YYYY-MM-DD`
29    fn format_yyyy_mm_dd(&self) -> String;
30
31    /// Parses from `YYYY-MM-DD`
32    fn parse_yyyy_mm_dd(value: &str) -> Result<Self, Error>;
33
34    /// Returns the [Date] for the UNIX epoch start
35    fn unix_day0() -> Date {
36        Date::from_calendar_date(1970, Month::January, 1).unwrap()
37    }
38}
39
40impl DateExt for Date {
41    fn unix_days(&self) -> i32 {
42        self.to_julian_day() - Self::unix_day0().to_julian_day()
43    }
44
45    fn from_unix_days(days: i32) -> Result<Self, Error> {
46        let julian_days = Self::unix_day0().to_julian_day() + days;
47        Ok(Date::from_julian_day(julian_days)?)
48    }
49
50    fn format_yyyy_mm_dd(&self) -> String {
51        self.format(FORMAT_YY_MM_DD).unwrap()
52    }
53
54    fn parse_yyyy_mm_dd(value: &str) -> Result<Self, Error> {
55        Ok(Date::parse(value, FORMAT_YY_MM_DD)?)
56    }
57}
58
59// NB: Date is mapped to Value::Date32
60impl ChValue for Date {
61    fn ch_type() -> crate::value::Type {
62        Type::Date32
63    }
64
65    fn into_ch_value(self) -> Value {
66        Value::Date32(self.unix_days())
67    }
68
69    fn from_ch_value(value: Value) -> Result<Self, Error> {
70        match value {
71            Value::Date(v) => Ok(Date::from_unix_days(v.into())?),
72            Value::Date32(v) => Ok(Date::from_unix_days(v)?),
73            _ => Err(Error::new("Cannot convert Value to base type")),
74        }
75    }
76}
77
78// -- PrimitiveDateTime --
79
80/// Extension trait for date times
81pub trait DateTimeExt: Sized {
82    /// Formats as `YYYY-MM-DD HH:MM:SS`
83    fn format_yyyy_mm_dd_hh_mm_ss(&self) -> String;
84
85    /// Formats as `YYYY-MM-DD HH:MM:SS.X`
86    fn format_yyyy_mm_dd_hh_mm_ss_ns(&self) -> String;
87
88    /// Parses from `YYYY-MM-DD HH:MM:SS`
89    fn parse_yyyy_mm_dd_hh_mm_ss(value: &str) -> Result<Self, Error>;
90
91    /// Parses from `YYYY-MM-DD HH:MM:SS.X`
92    fn parse_yyyy_mm_dd_hh_mm_ss_ns(value: &str) -> Result<Self, Error>;
93
94    /// Returns the UNIX seconds
95    fn unix_seconds(&self) -> i64;
96
97    /// Returns the UNIX nanoseconds
98    fn unix_nanoseconds(&self) -> i64;
99
100    /// Creates from UNIX seconds
101    fn from_unix_seconds(value: i64) -> Self;
102
103    /// Creates from UNIX nanoseconds
104    fn from_unix_nanoseconds(value: i128) -> Self;
105}
106
107/// SQL datetime format
108static FORMAT_YY_MM_DD_HH_MM_SS: &[FormatItem] =
109    format_description!("[year]-[month]-[day] [hour]:[minute]:[second]");
110
111/// SQL datetime format, with subseconds
112static FORMAT_YY_MM_DD_HH_MM_SS_NS: &[FormatItem] =
113    format_description!("[year]-[month]-[day] [hour]:[minute]:[second].[subsecond]");
114
115impl DateTimeExt for PrimitiveDateTime {
116    fn format_yyyy_mm_dd_hh_mm_ss(&self) -> String {
117        self.format(FORMAT_YY_MM_DD_HH_MM_SS).unwrap()
118    }
119
120    fn format_yyyy_mm_dd_hh_mm_ss_ns(&self) -> String {
121        self.format(FORMAT_YY_MM_DD_HH_MM_SS_NS).unwrap()
122    }
123
124    fn parse_yyyy_mm_dd_hh_mm_ss(value: &str) -> Result<Self, Error> {
125        Ok(PrimitiveDateTime::parse(value, FORMAT_YY_MM_DD_HH_MM_SS)?)
126    }
127
128    fn parse_yyyy_mm_dd_hh_mm_ss_ns(value: &str) -> Result<Self, Error> {
129        Ok(PrimitiveDateTime::parse(
130            value,
131            FORMAT_YY_MM_DD_HH_MM_SS_NS,
132        )?)
133    }
134
135    fn unix_seconds(&self) -> i64 {
136        self.assume_utc().unix_seconds()
137    }
138
139    fn unix_nanoseconds(&self) -> i64 {
140        self.assume_utc().unix_nanoseconds()
141    }
142
143    fn from_unix_seconds(value: i64) -> Self {
144        let dt = OffsetDateTime::from_unix_seconds(value);
145        let date = dt.date();
146        let time = dt.time();
147        PrimitiveDateTime::new(date, time)
148    }
149
150    fn from_unix_nanoseconds(value: i128) -> Self {
151        let dt = OffsetDateTime::from_unix_nanoseconds(value);
152        let date = dt.date();
153        let time = dt.time();
154        PrimitiveDateTime::new(date, time)
155    }
156}
157
158// NB: OffsetDateTime is mapped to Value::DateTime64
159impl ChValue for PrimitiveDateTime {
160    fn ch_type() -> Type {
161        Type::DateTime64(9)
162    }
163
164    fn into_ch_value(self) -> Value {
165        Value::DateTime64(self.unix_nanoseconds())
166    }
167
168    fn from_ch_value(value: Value) -> Result<Self, Error> {
169        match value {
170            Value::DateTime(secs) => Ok(Self::from_unix_seconds(secs.into())),
171            Value::DateTime64(nanosecs) => Ok(Self::from_unix_nanoseconds(nanosecs.into())),
172            _ => Err(Error::new("Cannot convert Value to base type")),
173        }
174    }
175}
176
177// -- OffsetDateTime --
178
179impl DateTimeExt for OffsetDateTime {
180    fn format_yyyy_mm_dd_hh_mm_ss(&self) -> String {
181        self.format(FORMAT_YY_MM_DD_HH_MM_SS).unwrap()
182    }
183
184    fn format_yyyy_mm_dd_hh_mm_ss_ns(&self) -> String {
185        self.format(FORMAT_YY_MM_DD_HH_MM_SS_NS).unwrap()
186    }
187
188    fn parse_yyyy_mm_dd_hh_mm_ss(value: &str) -> Result<Self, Error> {
189        Ok(PrimitiveDateTime::parse_yyyy_mm_dd_hh_mm_ss(value)?.assume_utc())
190    }
191
192    fn parse_yyyy_mm_dd_hh_mm_ss_ns(value: &str) -> Result<Self, Error> {
193        Ok(PrimitiveDateTime::parse_yyyy_mm_dd_hh_mm_ss_ns(value)?.assume_utc())
194    }
195
196    fn unix_seconds(&self) -> i64 {
197        self.unix_timestamp()
198    }
199
200    fn unix_nanoseconds(&self) -> i64 {
201        let ns = self.unix_timestamp_nanos();
202        if ns <= i64::MIN as i128 {
203            i64::MIN
204        } else if ns >= i64::MAX as i128 {
205            i64::MAX
206        } else {
207            ns as i64
208        }
209    }
210
211    fn from_unix_seconds(value: i64) -> Self {
212        Self::from_unix_timestamp(value).expect("invalid unix timestamp")
213    }
214
215    fn from_unix_nanoseconds(value: i128) -> Self {
216        Self::from_unix_timestamp_nanos(value).expect("invalid unix timestamp")
217    }
218}
219
220// NB: OffsetDateTime is mapped to Value::DateTime64
221impl ChValue for OffsetDateTime {
222    fn ch_type() -> Type {
223        Type::DateTime64(9)
224    }
225
226    fn into_ch_value(self) -> Value {
227        Value::DateTime64(self.unix_nanoseconds())
228    }
229
230    fn from_ch_value(value: Value) -> Result<Self, Error> {
231        match value {
232            Value::DateTime(secs) => Ok(Self::from_unix_seconds(secs.into())),
233            Value::DateTime64(nanosecs) => Ok(Self::from_unix_nanoseconds(nanosecs.into())),
234            _ => Err(Error::new("Cannot convert Value to base type")),
235        }
236    }
237}
238
239// -- Option<Date> --
240
241impl ChValue for Option<Date> {
242    fn ch_type() -> Type {
243        Type::NullableDate32
244    }
245
246    fn into_ch_value(self) -> Value {
247        match self {
248            Some(v) => Value::NullableDate32(Some(v.unix_days())),
249            None => Value::NullableDate32(None),
250        }
251    }
252
253    fn from_ch_value(value: Value) -> Result<Self, Error> {
254        match value {
255            Value::NullableDate(v) => match v {
256                Some(v) => Date::from_unix_days(v.into()).map(Some),
257                None => Ok(None),
258            },
259            Value::NullableDate32(v) => match v {
260                Some(v) => Date::from_unix_days(v).map(Some),
261                None => Ok(None),
262            },
263            _ => Err(Error::new("Cannot convert Value to base type")),
264        }
265    }
266}
267
268// -- Option<PrimitiveDateTime> --
269
270impl ChValue for Option<PrimitiveDateTime> {
271    fn ch_type() -> Type {
272        Type::NullableDateTime64(9)
273    }
274
275    fn into_ch_value(self) -> Value {
276        match self {
277            Some(v) => PrimitiveDateTime::into_ch_value(v),
278            None => Value::NullableDateTime64(None),
279        }
280    }
281
282    fn from_ch_value(value: Value) -> Result<Self, Error> {
283        match value {
284            Value::NullableDateTime(secs) => match secs {
285                Some(secs) => Ok(Some(PrimitiveDateTime::from_unix_seconds(secs.into()))),
286                None => Ok(None),
287            },
288            Value::NullableDateTime64(nanosecs) => match nanosecs {
289                Some(nanosecs) => Ok(Some(PrimitiveDateTime::from_unix_nanoseconds(
290                    nanosecs.into(),
291                ))),
292                None => Ok(None),
293            },
294            _ => Err(Error::new("Cannot convert Value to base type")),
295        }
296    }
297}
298
299// -- Option<OffsetDateTime> --
300
301impl ChValue for Option<OffsetDateTime> {
302    fn ch_type() -> Type {
303        Type::NullableDateTime64(9)
304    }
305
306    fn into_ch_value(self) -> Value {
307        match self {
308            Some(v) => OffsetDateTime::into_ch_value(v),
309            None => Value::NullableDateTime64(None),
310        }
311    }
312
313    fn from_ch_value(value: Value) -> Result<Self, Error> {
314        match value {
315            Value::NullableDateTime(secs) => match secs {
316                Some(secs) => Ok(Some(OffsetDateTime::from_unix_seconds(secs.into()))),
317                None => Ok(None),
318            },
319            Value::NullableDateTime64(nanosecs) => match nanosecs {
320                Some(nanosecs) => Ok(Some(OffsetDateTime::from_unix_nanoseconds(nanosecs.into()))),
321                None => Ok(None),
322            },
323            _ => Err(Error::new("Cannot convert Value to base type")),
324        }
325    }
326}