1use crate::prelude::*;
2pub use chrono::{
3 self, DateTime as ChDateTime, Datelike, Duration, LocalResult, NaiveDate, NaiveTime, TimeZone,
4 Timelike, Utc,
5};
6use std::fmt::{Display, Formatter};
7
8pub trait DateTimeBehavior {
9 fn as_date(&self) -> Option<&NaiveDate>;
10 fn as_time(&self) -> Option<&NaiveTime>;
11 fn as_date_time(&self) -> Option<&ChDateTime<chrono::Utc>>;
12
13 fn year(&self) -> Option<i32>;
15 fn month(&self) -> Option<u32>;
16 fn day(&self) -> Option<u32>;
17 fn hour(&self) -> Option<u32>;
18 fn minute(&self) -> Option<u32>;
19 fn second(&self) -> Option<u32>;
20 fn timestamp(&self) -> Option<i64>;
21 fn timezone(&self) -> Option<Utc>;
22
23 fn to_iso8601(&self) -> String;
25 fn to_rfc3339(&self) -> String;
26
27 fn add_duration(&self, duration: Duration) -> Option<Self>
29 where
30 Self: Sized;
31 fn subtract_duration(&self, duration: Duration) -> Option<Self>
32 where
33 Self: Sized;
34
35 fn duration_between(&self, other: &Self) -> Option<Duration>;
37
38 fn from_ymd_opt(year: i32, month: u32, day: u32) -> Self;
39
40 fn with_ymd_and_hms(year: i32, month: u32, day: u32, hour: u32, min: u32, sec: u32) -> Self;
41
42 fn now() -> Self;
43}
44
45#[derive(Debug, Clone, PartialEq, PartialOrd)]
53pub enum DateTime {
54 Date(NaiveDate),
55 Time(NaiveTime),
56 DateTime(ChDateTime<chrono::Utc>),
57}
58
59impl From<NaiveDate> for DateTime {
61 fn from(value: NaiveDate) -> Self {
62 DateTime::Date(value)
63 }
64}
65
66impl From<Value> for DateTime {
67 fn from(value: Value) -> Self {
68 match value {
69 Value::DateTime(datetime) => datetime,
70 _ => panic!("Cannot convert value to DateTime"),
71 }
72 }
73}
74
75impl From<NaiveTime> for DateTime {
76 fn from(value: NaiveTime) -> Self {
77 DateTime::Time(value)
78 }
79}
80
81impl From<ChDateTime<chrono::Utc>> for DateTime {
82 fn from(value: ChDateTime<chrono::Utc>) -> Self {
83 DateTime::DateTime(value)
84 }
85}
86
87impl From<LocalResult<NaiveDate>> for DateTime {
89 fn from(value: LocalResult<NaiveDate>) -> Self {
90 DateTime::Date(value.unwrap())
91 }
92}
93
94impl From<LocalResult<NaiveTime>> for DateTime {
95 fn from(value: LocalResult<NaiveTime>) -> Self {
96 DateTime::Time(value.unwrap())
97 }
98}
99
100impl From<LocalResult<ChDateTime<chrono::Utc>>> for DateTime {
101 fn from(value: LocalResult<ChDateTime<chrono::Utc>>) -> Self {
102 DateTime::DateTime(value.unwrap())
103 }
104}
105
106impl From<&str> for DateTime {
108 fn from(value: &str) -> Self {
109 match value.parse::<NaiveDate>() {
110 Ok(date) => DateTime::Date(date),
111 Err(_) => match value.parse::<NaiveTime>() {
112 Ok(time) => DateTime::Time(time),
113 Err(_) => match value.parse::<ChDateTime<chrono::Utc>>() {
114 Ok(datetime) => DateTime::DateTime(datetime),
115 Err(_) => panic!("Invalid date, time, or date-time format"),
116 },
117 },
118 }
119 }
120}
121
122impl From<i64> for DateTime {
124 fn from(value: i64) -> Self {
125 DateTime::DateTime(ChDateTime::from_naive_utc_and_offset(
126 chrono::DateTime::from_timestamp_nanos(value).naive_local(),
127 Utc,
128 ))
129 }
130}
131
132impl Display for DateTime {
134 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
135 match self {
136 DateTime::Date(value) => write!(f, "{}", value),
137 DateTime::Time(value) => write!(f, "{}", value),
138 DateTime::DateTime(value) => write!(f, "{}", value.to_rfc3339()),
139 }
140 }
141}
142
143impl DateTimeBehavior for DateTime {
145 fn as_date(&self) -> Option<&NaiveDate> {
146 match self {
147 DateTime::Date(value) => Some(value),
148 _ => None,
149 }
150 }
151
152 fn as_time(&self) -> Option<&NaiveTime> {
153 match self {
154 DateTime::Time(value) => Some(value),
155 _ => None,
156 }
157 }
158
159 fn as_date_time(&self) -> Option<&ChDateTime<chrono::Utc>> {
160 match self {
161 DateTime::DateTime(value) => Some(value),
162 _ => None,
163 }
164 }
165
166 fn year(&self) -> Option<i32> {
167 match self {
168 DateTime::Date(date) => Some(date.year()),
169 DateTime::DateTime(datetime) => Some(datetime.year()),
170 _ => None,
171 }
172 }
173
174 fn month(&self) -> Option<u32> {
175 match self {
176 DateTime::Date(date) => Some(date.month()),
177 DateTime::DateTime(datetime) => Some(datetime.month()),
178 _ => None,
179 }
180 }
181
182 fn day(&self) -> Option<u32> {
183 match self {
184 DateTime::Date(date) => Some(date.day()),
185 DateTime::DateTime(datetime) => Some(datetime.day()),
186 _ => None,
187 }
188 }
189
190 fn hour(&self) -> Option<u32> {
191 match self {
192 DateTime::Time(time) => Some(time.hour()),
193 DateTime::DateTime(datetime) => Some(datetime.hour()),
194 _ => None,
195 }
196 }
197
198 fn minute(&self) -> Option<u32> {
199 match self {
200 DateTime::Time(time) => Some(time.minute()),
201 DateTime::DateTime(datetime) => Some(datetime.minute()),
202 _ => None,
203 }
204 }
205
206 fn second(&self) -> Option<u32> {
207 match self {
208 DateTime::Time(time) => Some(time.second()),
209 DateTime::DateTime(datetime) => Some(datetime.second()),
210 _ => None,
211 }
212 }
213
214 fn timestamp(&self) -> Option<i64> {
215 match self {
216 DateTime::DateTime(datetime) => Some(datetime.timestamp()),
217 DateTime::Date(date) => Some(date.and_hms_opt(0, 0, 0).unwrap().and_utc().timestamp()),
218 DateTime::Time(time) => Some(
219 NaiveDate::from_ymd_opt(1970, 1, 1)
220 .unwrap()
221 .and_hms_opt(time.hour(), time.minute(), time.second())
222 .unwrap()
223 .and_utc()
224 .timestamp(),
225 ),
226 }
227 }
228
229 fn timezone(&self) -> Option<Utc> {
230 match self {
231 DateTime::DateTime(datetime) => Some(datetime.timezone()),
232 _ => None,
233 }
234 }
235
236 fn to_iso8601(&self) -> String {
237 match self {
238 DateTime::Date(date) => date.format("%Y-%m-%d").to_string(),
239 DateTime::Time(time) => time.format("%H:%M:%S%.f").to_string(),
240 DateTime::DateTime(datetime) => datetime.format("%Y-%m-%dT%H:%M:%S").to_string(),
241 }
242 }
243
244 fn to_rfc3339(&self) -> String {
245 match self {
246 DateTime::DateTime(datetime) => datetime.to_rfc3339(),
247 _ => "".to_string(),
248 }
249 }
250
251 fn add_duration(&self, duration: Duration) -> Option<Self> {
252 match self {
253 DateTime::Date(date) => Some(DateTime::Date(
254 *date + chrono::Duration::days(duration.num_days()),
255 )),
256 DateTime::Time(_) => None, DateTime::DateTime(datetime) => Some(DateTime::DateTime(*datetime + duration)),
258 }
259 }
260
261 fn subtract_duration(&self, duration: Duration) -> Option<Self> {
262 match self {
263 DateTime::Date(date) => Some(DateTime::Date(
264 *date - chrono::Duration::days(duration.num_days()),
265 )),
266 DateTime::Time(_) => None, DateTime::DateTime(datetime) => Some(DateTime::DateTime(*datetime - duration)),
268 }
269 }
270
271 fn duration_between(&self, other: &DateTime) -> Option<Duration> {
272 match (self, other) {
273 (DateTime::Date(date1), DateTime::Date(date2)) => {
274 Some(Duration::days((*date2 - *date1).num_days()))
275 }
276 (DateTime::DateTime(dt1), DateTime::DateTime(dt2)) => Some(*dt2 - *dt1),
277 _ => None, }
279 }
280
281 fn from_ymd_opt(year: i32, month: u32, day: u32) -> DateTime {
282 let date = NaiveDate::from_ymd_opt(year, month, day).unwrap();
283 DateTime::from(date)
284 }
285
286 fn with_ymd_and_hms(
287 year: i32,
288 month: u32,
289 day: u32,
290 hour: u32,
291 min: u32,
292 sec: u32,
293 ) -> DateTime {
294 let datetime: chrono::LocalResult<chrono::DateTime<Utc>> =
295 Utc.with_ymd_and_hms(year, month, day, hour, min, sec);
296 DateTime::from(datetime)
297 }
298
299 fn now() -> DateTime {
300 DateTime::from(Utc::now())
301 }
302}
303
304#[cfg(test)]
305mod tests {
306 use crate::prelude::*;
307 use chrono::{Duration, NaiveDate, TimeZone, Utc};
308
309 #[test]
310 fn test_add_duration() {
311 let dt_date = DateTime::from_ymd_opt(2023, 4, 5);
312 let dt_datetime = DateTime::with_ymd_and_hms(2023, 4, 5, 12, 34, 56);
313
314 assert_eq!(
315 dt_date.add_duration(Duration::days(1)),
316 Some(DateTime::from(NaiveDate::from_ymd_opt(2023, 4, 6).unwrap()))
317 );
318 assert_eq!(
319 dt_datetime.add_duration(Duration::days(1)),
320 Some(DateTime::from(Utc.with_ymd_and_hms(2023, 4, 6, 12, 34, 56)))
321 );
322 }
323
324 #[test]
325 fn test_subtract_duration() {
326 let date = NaiveDate::from_ymd_opt(2023, 4, 5).unwrap();
327 let datetime = Utc.with_ymd_and_hms(2023, 4, 5, 12, 34, 56);
328
329 let dt_date = DateTime::from(date);
330 let dt_datetime = DateTime::from(datetime);
331
332 assert_eq!(
333 dt_date.subtract_duration(Duration::days(1)),
334 Some(DateTime::from(NaiveDate::from_ymd_opt(2023, 4, 4).unwrap()))
335 );
336 assert_eq!(
337 dt_datetime.subtract_duration(Duration::days(1)),
338 Some(DateTime::from(Utc.with_ymd_and_hms(2023, 4, 4, 12, 34, 56)))
339 );
340 }
341
342 #[test]
343 fn test_duration_between() {
344 let date1 = NaiveDate::from_ymd_opt(2023, 4, 5).unwrap();
345 let date2 = NaiveDate::from_ymd_opt(2023, 4, 6).unwrap();
346 let datetime1 = Utc.with_ymd_and_hms(2023, 4, 5, 12, 34, 56);
347 let datetime2 = Utc.with_ymd_and_hms(2023, 4, 6, 12, 34, 56);
348
349 let dt_date1 = DateTime::from(date1);
350 let dt_date2 = DateTime::from(date2);
351 let dt_datetime1 = DateTime::from(datetime1);
352 let dt_datetime2 = DateTime::from(datetime2);
353
354 assert_eq!(
355 dt_date1.duration_between(&dt_date2),
356 Some(Duration::days(1))
357 );
358 assert_eq!(
359 dt_datetime1.duration_between(&dt_datetime2),
360 Some(Duration::days(1))
361 );
362 }
363
364 #[test]
365 fn test_timestamp() {
366 let date = NaiveDate::from_ymd_opt(2023, 4, 5).unwrap();
367 let datetime = Utc.with_ymd_and_hms(2023, 4, 5, 12, 34, 56);
368
369 let dt_date = DateTime::from(date);
370 let dt_datetime = DateTime::from(datetime);
371
372 assert_eq!(
373 dt_date.timestamp(),
374 Some(date.and_hms_opt(0, 0, 0).unwrap().and_utc().timestamp())
375 );
376 assert_eq!(dt_datetime.timestamp(), Some(datetime.unwrap().timestamp()));
377 }
378
379 #[test]
380 fn test_timezone() {
381 let datetime = Utc.with_ymd_and_hms(2023, 4, 5, 12, 34, 56);
382 let dt_datetime = DateTime::from(datetime);
383
384 assert_eq!(dt_datetime.timezone(), Some(Utc));
385 }
386
387 #[test]
388 fn test_to_iso8601() {
389 let date = NaiveDate::from_ymd_opt(2023, 4, 5).unwrap();
390 let datetime = Utc.with_ymd_and_hms(2023, 4, 5, 12, 34, 56);
391
392 let dt_date = DateTime::from(date);
393 let dt_datetime = DateTime::from(datetime);
394
395 assert_eq!(dt_date.to_iso8601(), "2023-04-05");
396 assert_eq!(dt_datetime.to_iso8601(), "2023-04-05T12:34:56");
397 }
398
399 #[test]
400 fn test_from_i64() {
401 let timestamp_nanos = 1672539296000000000;
402 let dt_datetime = DateTime::from(timestamp_nanos);
403
404 assert_eq!(
405 dt_datetime,
406 DateTime::from(Utc.timestamp_nanos(timestamp_nanos))
407 );
408 }
409}