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