partiql_value/
datetime.rs

1#[cfg(feature = "serde")]
2use serde::{Deserialize, Serialize};
3use std::cmp::Ordering;
4use std::fmt::{Debug, Formatter};
5use std::hash::Hash;
6use std::num::NonZeroU8;
7use time::{Duration, UtcOffset};
8
9#[derive(Hash, PartialEq, Eq, Clone)]
10#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
11pub enum DateTime {
12    Date(time::Date),
13    Time(time::Time),
14    TimeWithTz(time::Time, time::UtcOffset),
15    Timestamp(time::PrimitiveDateTime),
16    TimestampWithTz(time::OffsetDateTime),
17}
18
19impl DateTime {
20    #[must_use]
21    pub fn from_system_now_utc() -> Self {
22        DateTime::TimestampWithTz(time::OffsetDateTime::now_utc())
23    }
24
25    #[must_use]
26    pub fn from_hms(hour: u8, minute: u8, second: u8) -> Self {
27        DateTime::Time(time::Time::from_hms(hour, minute, second).expect("valid time value"))
28    }
29
30    #[must_use]
31    pub fn from_hms_nano(hour: u8, minute: u8, second: u8, nanosecond: u32) -> Self {
32        Self::from_hms_nano_offset(hour, minute, second, nanosecond, None)
33    }
34
35    #[must_use]
36    pub fn from_hms_nano_tz(
37        hour: u8,
38        minute: u8,
39        second: u8,
40        nanosecond: u32,
41        tz_hours: Option<i8>,
42        tz_minutes: Option<i8>,
43    ) -> Self {
44        let offset = match (tz_hours, tz_minutes) {
45            (Some(h), Some(m)) => Some(UtcOffset::from_hms(h, m, 0).expect("valid offset")),
46            (None, Some(m)) => Some(UtcOffset::from_hms(0, m, 0).expect("valid offset")),
47            (Some(h), None) => Some(UtcOffset::from_hms(h, 0, 0).expect("valid offset")),
48            _ => None,
49        };
50
51        Self::from_hms_nano_offset(hour, minute, second, nanosecond, offset)
52    }
53
54    #[must_use]
55    pub fn from_ymd(year: i32, month: NonZeroU8, day: u8) -> Self {
56        let month: time::Month = month.get().try_into().expect("valid month");
57        let date = time::Date::from_calendar_date(year, month, day).expect("valid ymd");
58        DateTime::Date(date)
59    }
60
61    #[allow(clippy::too_many_arguments)]
62    #[must_use]
63    pub fn from_ymdhms_nano_offset_minutes(
64        year: i32,
65        month: NonZeroU8,
66        day: u8,
67        hour: u8,
68        minute: u8,
69        second: u8,
70        nanosecond: u32,
71        offset: Option<i32>,
72    ) -> Self {
73        let month: time::Month = month.get().try_into().expect("valid month");
74        let date = time::Date::from_calendar_date(year, month, day).expect("valid ymd");
75        let time = time_from_hms_nano(hour, minute, second, nanosecond);
76        match offset {
77            None => DateTime::Timestamp(date.with_time(time)),
78            Some(o) => {
79                let offset = UtcOffset::from_whole_seconds(o * 60).expect("offset in range");
80                let date = date.with_time(time).assume_offset(offset);
81                DateTime::TimestampWithTz(date)
82            }
83        }
84    }
85
86    fn from_hms_nano_offset(
87        hour: u8,
88        minute: u8,
89        second: u8,
90        nanosecond: u32,
91        offset: Option<UtcOffset>,
92    ) -> Self {
93        let time = time_from_hms_nano(hour, minute, second, nanosecond);
94        match offset {
95            Some(offset) => DateTime::TimeWithTz(time, offset),
96            None => DateTime::Time(time),
97        }
98    }
99}
100
101fn time_from_hms_nano(hour: u8, minute: u8, second: u8, nanosecond: u32) -> time::Time {
102    time::Time::from_hms_nano(hour, minute, second, nanosecond).expect("valid time value")
103}
104
105impl Debug for DateTime {
106    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
107        match self {
108            DateTime::Date(d) => {
109                write!(f, "DATE '{d:?}'")
110            }
111            DateTime::Time(t) => {
112                write!(f, "TIME '{t:?}'")
113            }
114            DateTime::TimeWithTz(t, tz) => {
115                write!(f, "TIME WITH TIME ZONE '{t:?} {tz:?}'")
116            }
117            DateTime::Timestamp(dt) => {
118                write!(f, "TIMESTAMP '{dt:?}'")
119            }
120            DateTime::TimestampWithTz(dt) => {
121                write!(f, "TIMESTAMP WITH TIME ZONE '{dt:?}'")
122            }
123        }
124    }
125}
126
127impl PartialOrd for DateTime {
128    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
129        Some(self.cmp(other))
130    }
131}
132
133impl Ord for DateTime {
134    #[inline]
135    fn cmp(&self, other: &Self) -> Ordering {
136        match (self, other) {
137            (DateTime::Date(l), DateTime::Date(r)) => l.cmp(r),
138            (DateTime::Date(_), _) => Ordering::Less,
139            (_, DateTime::Date(_)) => Ordering::Greater,
140
141            (DateTime::Time(l), DateTime::Time(r)) => l.cmp(r),
142            (DateTime::Time(_), _) => Ordering::Less,
143            (_, DateTime::Time(_)) => Ordering::Greater,
144
145            (DateTime::TimeWithTz(l, lo), DateTime::TimeWithTz(r, ro)) => {
146                let lod = Duration::new(i64::from(lo.whole_seconds()), 0);
147                let rod = Duration::new(i64::from(ro.whole_seconds()), 0);
148                let l_adjusted = *l + lod;
149                let r_adjusted = *r + rod;
150                l_adjusted.cmp(&r_adjusted)
151            }
152            (DateTime::TimeWithTz(_, _), _) => Ordering::Less,
153            (_, DateTime::TimeWithTz(_, _)) => Ordering::Greater,
154
155            (DateTime::Timestamp(l), DateTime::Timestamp(r)) => l.cmp(r),
156            (DateTime::Timestamp(_), _) => Ordering::Less,
157            (_, DateTime::Timestamp(_)) => Ordering::Greater,
158
159            (DateTime::TimestampWithTz(l), DateTime::TimestampWithTz(r)) => l.cmp(r),
160        }
161    }
162}