valu3/types/
datetime.rs

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    // DateTime methods for accessing specific components of date or time values
14    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    // Methods for formatting DateTime values as strings
24    fn to_iso8601(&self) -> String;
25    fn to_rfc3339(&self) -> String;
26
27    // Methods for adding or subtracting a Duration to/from a DateTime value
28    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    // Method for calculating the duration between two DateTime values
36    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/// Enum representing a date, time, or date-time value.
46///
47/// # Variants
48///
49/// * `Date(NaiveDate)` - Represents a date without timezone information.
50/// * `Time(NaiveTime)` - Represents a time without date and timezone information.
51/// * `DateTime(ChDateTime<chrono::Utc>)` - Represents a date-time with timezone information.
52#[derive(Debug, Clone, PartialEq, PartialOrd)]
53pub enum DateTime {
54    Date(NaiveDate),
55    Time(NaiveTime),
56    DateTime(ChDateTime<chrono::Utc>),
57}
58
59// Implementations of From trait to allow conversion from NaiveDate, NaiveTime, and ChDateTime<Utc>
60impl 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
87// Implementations of From trait to allow conversion from LocalResult variants
88impl 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
106// Implementation of From trait to allow conversion from &str
107impl 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
122// Implementation of From trait to allow conversion from i64
123impl 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
132/// Display implementation for DateTime.
133impl 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
143// DateTime methods for accessing underlying NaiveDate, NaiveTime, or ChDateTime<Utc> values
144impl 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, // Não é possível adicionar duração a um NaiveTime isolado
257            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, // Não é possível subtrair duração de um NaiveTime isolado
267            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, // Retornar None para combinações inválidas
278        }
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}