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}