1use crate::{
2 bounds::RangeError,
3 civil::{Date, Time},
4 macros::{ctry, unwrapr},
5 tz::Offset,
6 Timestamp,
7};
8
9#[derive(Clone, Copy, Eq, Hash, PartialEq, PartialOrd, Ord)]
11#[cfg_attr(feature = "defmt", derive(defmt::Format))]
12pub struct DateTime {
13 date: Date,
14 time: Time,
15}
16
17impl DateTime {
18 pub const MIN: DateTime = DateTime { date: Date::MIN, time: Time::MIN };
20
21 pub const MAX: DateTime = DateTime { date: Date::MAX, time: Time::MAX };
23
24 #[inline]
30 pub const fn new(
31 year: i16,
32 month: i8,
33 day: i8,
34 hour: i8,
35 minute: i8,
36 second: i8,
37 subsec_nanosecond: i32,
38 ) -> Result<DateTime, RangeError> {
39 let date = ctry!(Date::new(year, month, day));
40 let time = ctry!(Time::new(hour, minute, second, subsec_nanosecond));
41 Ok(DateTime::from_parts(date, time))
42 }
43
44 #[inline]
46 pub const fn from_parts(date: Date, time: Time) -> DateTime {
47 DateTime { date, time }
48 }
49
50 #[inline]
52 pub const fn date(&self) -> Date {
53 self.date
54 }
55
56 #[inline]
58 pub const fn time(&self) -> Time {
59 self.time
60 }
61
62 #[inline]
67 pub const fn checked_add_seconds(
68 &self,
69 seconds: i32,
70 ) -> Result<DateTime, RangeError> {
71 let (second, added_days) =
72 ctry!(self.time().to_second().overflowing_add(seconds));
73 let date = ctry!(self.date().checked_add(added_days));
74 let time = unwrapr!(
75 second
76 .to_time()
77 .with_subsec_nanosecond(self.time().subsec_nanosecond()),
78 "subsec we started from hasn't change and must be valid",
79 );
80 Ok(DateTime::from_parts(date, time))
81 }
82
83 #[inline]
87 pub const fn saturating_add_seconds(&self, seconds: i32) -> DateTime {
88 match self.checked_add_seconds(seconds) {
89 Ok(dt) => dt,
90 Err(_) => {
91 if seconds < 0 {
92 DateTime::MIN
93 } else {
94 DateTime::MAX
95 }
96 }
97 }
98 }
99
100 #[inline]
113 pub const fn to_timestamp(
114 &self,
115 offset: Offset,
116 ) -> Result<Timestamp, RangeError> {
117 offset.to_timestamp(*self)
118 }
119}
120
121impl core::fmt::Debug for DateTime {
122 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
123 write!(f, "{:?}T{:?}", self.date(), self.time())
124 }
125}
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130
131 fn datetime(
132 year: i16,
133 month: i8,
134 day: i8,
135 hour: i8,
136 minute: i8,
137 second: i8,
138 subsec_nanosecond: i32,
139 ) -> DateTime {
140 DateTime::new(
141 year,
142 month,
143 day,
144 hour,
145 minute,
146 second,
147 subsec_nanosecond,
148 )
149 .unwrap()
150 }
151
152 fn stamp(second: i64, subsec: i32) -> Timestamp {
153 Timestamp::new(second, subsec).unwrap()
154 }
155
156 fn offset(second: i32) -> Offset {
157 Offset::from_seconds(second).unwrap()
158 }
159
160 #[test]
161 fn checked_add_seconds() {
162 let dt = datetime(2026, 2, 25, 0, 0, 0, 0);
163 assert_eq!(
164 dt.checked_add_seconds(1),
165 Ok(datetime(2026, 2, 25, 0, 0, 1, 0))
166 );
167 assert_eq!(
168 dt.checked_add_seconds(86_399),
169 Ok(datetime(2026, 2, 25, 23, 59, 59, 0))
170 );
171 assert_eq!(
172 dt.checked_add_seconds(86_400),
173 Ok(datetime(2026, 2, 26, 0, 0, 0, 0))
174 );
175 assert_eq!(
176 dt.checked_add_seconds(-1),
177 Ok(datetime(2026, 2, 24, 23, 59, 59, 0))
178 );
179 assert_eq!(
180 dt.checked_add_seconds(-86_399),
181 Ok(datetime(2026, 2, 24, 0, 0, 1, 0))
182 );
183 assert_eq!(
184 dt.checked_add_seconds(-86_400),
185 Ok(datetime(2026, 2, 24, 0, 0, 0, 0))
186 );
187
188 let dt = datetime(2026, 2, 25, 0, 0, 0, 1);
189 assert_eq!(
190 dt.checked_add_seconds(1),
191 Ok(datetime(2026, 2, 25, 0, 0, 1, 1))
192 );
193 assert_eq!(
194 dt.checked_add_seconds(86_399),
195 Ok(datetime(2026, 2, 25, 23, 59, 59, 1))
196 );
197 assert_eq!(
198 dt.checked_add_seconds(86_400),
199 Ok(datetime(2026, 2, 26, 0, 0, 0, 1))
200 );
201 assert_eq!(
202 dt.checked_add_seconds(-1),
203 Ok(datetime(2026, 2, 24, 23, 59, 59, 1))
204 );
205 assert_eq!(
206 dt.checked_add_seconds(-86_399),
207 Ok(datetime(2026, 2, 24, 0, 0, 1, 1))
208 );
209 assert_eq!(
210 dt.checked_add_seconds(-86_400),
211 Ok(datetime(2026, 2, 24, 0, 0, 0, 1))
212 );
213 }
214
215 #[test]
216 fn to_timestamp_no_subsec() {
217 let dt = datetime(1970, 1, 1, 0, 0, 0, 0);
218 assert_eq!(dt.to_timestamp(offset(0)), Ok(stamp(0, 0)));
219 assert_eq!(dt.to_timestamp(offset(3600)), Ok(stamp(-3600, 0)));
220 assert_eq!(dt.to_timestamp(offset(-3600)), Ok(stamp(3600, 0)));
221
222 let dt = datetime(1969, 12, 31, 23, 30, 0, 0);
223 assert_eq!(dt.to_timestamp(offset(0)), Ok(stamp(-1800, 0)));
224 assert_eq!(dt.to_timestamp(offset(3600)), Ok(stamp(-5400, 0)));
225 assert_eq!(dt.to_timestamp(offset(-3600)), Ok(stamp(1800, 0)));
226
227 let dt = datetime(1970, 1, 1, 0, 30, 0, 0);
228 assert_eq!(dt.to_timestamp(offset(0)), Ok(stamp(1800, 0)));
229 assert_eq!(dt.to_timestamp(offset(3600)), Ok(stamp(-1800, 0)));
230 assert_eq!(dt.to_timestamp(offset(-3600)), Ok(stamp(5400, 0)));
231 }
232
233 #[test]
234 fn to_timestamp_with_subsec() {
235 let dt = datetime(1970, 1, 1, 0, 0, 0, 123);
236 assert_eq!(dt.to_timestamp(offset(0)), Ok(stamp(0, 123)));
237 assert_eq!(
238 dt.to_timestamp(offset(3600)),
239 Ok(stamp(-3599, -999_999_877))
240 );
241 assert_eq!(dt.to_timestamp(offset(-3600)), Ok(stamp(3600, 123)));
242
243 let dt = datetime(1969, 12, 31, 23, 30, 0, 123);
244 assert_eq!(dt.to_timestamp(offset(0)), Ok(stamp(-1799, -999_999_877)));
245 assert_eq!(
246 dt.to_timestamp(offset(3600)),
247 Ok(stamp(-5399, -999_999_877))
248 );
249 assert_eq!(dt.to_timestamp(offset(-3600)), Ok(stamp(1800, 123)));
250
251 let dt = datetime(1970, 1, 1, 0, 30, 0, 123);
252 assert_eq!(dt.to_timestamp(offset(0)), Ok(stamp(1800, 123)));
253 assert_eq!(
254 dt.to_timestamp(offset(3600)),
255 Ok(stamp(-1799, -999_999_877))
256 );
257 assert_eq!(dt.to_timestamp(offset(-3600)), Ok(stamp(5400, 123)));
258 }
259
260 #[test]
261 fn to_timestamp_err() {
262 let dt = datetime(-9999, 1, 1, 0, 0, 0, 0);
263 assert_eq!(dt.to_timestamp(Offset::MIN), Ok(Timestamp::MIN));
264 assert!(dt.to_timestamp(offset(Offset::MIN.seconds() + 1)).is_err());
265
266 let dt = datetime(9999, 12, 31, 23, 59, 59, 999_999_999);
267 assert_eq!(dt.to_timestamp(Offset::MAX), Ok(Timestamp::MAX));
268 assert!(dt.to_timestamp(offset(Offset::MAX.seconds() - 1)).is_err());
269
270 let dt = datetime(9999, 12, 31, 23, 59, 59, 0);
271 assert!(dt.to_timestamp(offset(Offset::MAX.seconds() - 1)).is_err());
272 }
273}