1use std::{
5 fmt::{self, Display, Formatter},
6 ops::{Add, Rem, Sub},
7 str::FromStr,
8};
9
10use serde::{
11 Deserialize, Deserializer, Serialize, Serializer,
12 de::{self, Visitor},
13};
14
15use crate::{
16 error::{Error, TemporalKind, TypeError},
17 fragment::Fragment,
18 value::{date::Date, duration::Duration, temporal::parse::datetime::parse_datetime, time::Time},
19};
20
21const NANOS_PER_SECOND: i64 = 1_000_000_000;
22const NANOS_PER_MILLI: i64 = 1_000_000;
23const NANOS_PER_MICRO: i64 = 1_000;
24const NANOS_PER_DAY: i64 = 86_400 * NANOS_PER_SECOND;
25const ORDER_SIGN_BIT: u64 = 1 << 63;
26
27pub static CREATED_AT_COLUMN_NAME: &str = "created_at";
28pub static UPDATED_AT_COLUMN_NAME: &str = "updated_at";
29pub static TIME_COLUMN_NAME: &str = "time";
30
31#[repr(transparent)]
32#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
33pub struct DateTime {
34 nanos: i64,
35}
36
37impl DateTime {
38 pub fn new(year: i32, month: u32, day: u32, hour: u32, min: u32, sec: u32, nano: u32) -> Option<Self> {
39 let date = Date::new(year, month, day)?;
40 let time = Time::new(hour, min, sec, nano)?;
41
42 let days = date.to_days_since_epoch();
43 let nanos = days as i128 * NANOS_PER_DAY as i128 + time.to_nanos_since_midnight() as i128;
44 Some(Self {
45 nanos: i64::try_from(nanos).ok()?,
46 })
47 }
48
49 pub fn from_ymd_hms(
50 year: i32,
51 month: u32,
52 day: u32,
53 hour: u32,
54 min: u32,
55 sec: u32,
56 ) -> Result<Self, Box<TypeError>> {
57 Self::new(year, month, day, hour, min, sec, 0).ok_or_else(|| {
58 Box::new(Self::overflow_err(format!(
59 "invalid datetime: {}-{:02}-{:02} {:02}:{:02}:{:02}",
60 year, month, day, hour, min, sec
61 )))
62 })
63 }
64
65 fn overflow_err(message: impl Into<String>) -> TypeError {
66 TypeError::Temporal {
67 kind: TemporalKind::DateTimeOverflow {
68 message: message.into(),
69 },
70 message: "datetime overflow".to_string(),
71 fragment: Fragment::None,
72 }
73 }
74
75 pub fn to_order(&self) -> u64 {
76 (self.nanos as u64) ^ ORDER_SIGN_BIT
77 }
78
79 pub fn from_order(order: u64) -> Self {
80 Self {
81 nanos: (order ^ ORDER_SIGN_BIT) as i64,
82 }
83 }
84
85 pub fn from_nanos(nanos: i64) -> Self {
86 Self {
87 nanos,
88 }
89 }
90
91 pub fn to_nanos(&self) -> i64 {
92 self.nanos
93 }
94
95 pub fn from_epoch_secs(secs: i64) -> Result<Self, Box<TypeError>> {
96 let nanos = secs.checked_mul(NANOS_PER_SECOND).ok_or_else(|| {
97 Box::new(Self::overflow_err(format!("{} seconds overflows DateTime range", secs)))
98 })?;
99 Ok(Self {
100 nanos,
101 })
102 }
103
104 pub fn from_epoch_millis(millis: i64) -> Result<Self, Box<TypeError>> {
105 let nanos = millis.checked_mul(NANOS_PER_MILLI).ok_or_else(|| {
106 Box::new(Self::overflow_err(format!("{} milliseconds overflows DateTime range", millis)))
107 })?;
108 Ok(Self {
109 nanos,
110 })
111 }
112
113 pub fn to_epoch_secs(&self) -> i64 {
114 self.nanos.div_euclid(NANOS_PER_SECOND)
115 }
116
117 pub fn to_epoch_millis(&self) -> i64 {
118 self.nanos.div_euclid(NANOS_PER_MILLI)
119 }
120
121 pub fn try_date(&self) -> Result<Date, Box<TypeError>> {
122 let days = self.nanos.div_euclid(NANOS_PER_DAY);
123 let days = i32::try_from(days)
124 .map_err(|_| Box::new(Self::overflow_err("DateTime overflows Date range")))?;
125 Date::from_days_since_epoch(days)
126 .ok_or_else(|| Box::new(Self::overflow_err("DateTime overflows Date range")))
127 }
128
129 pub fn date(&self) -> Date {
130 self.try_date().expect("DateTime overflows Date range")
131 }
132
133 pub fn time(&self) -> Time {
134 Time::from_nanos_since_midnight(self.nanos.rem_euclid(NANOS_PER_DAY) as u64).unwrap()
135 }
136
137 pub fn year(&self) -> i32 {
138 self.date().year()
139 }
140
141 pub fn month(&self) -> u32 {
142 self.date().month()
143 }
144
145 pub fn day(&self) -> u32 {
146 self.date().day()
147 }
148
149 pub fn hour(&self) -> u32 {
150 self.time().hour()
151 }
152
153 pub fn minute(&self) -> u32 {
154 self.time().minute()
155 }
156
157 pub fn second(&self) -> u32 {
158 self.time().second()
159 }
160
161 pub fn nanosecond(&self) -> u32 {
162 self.time().nanosecond()
163 }
164
165 pub fn add_duration(&self, dur: &Duration) -> Result<Self, Box<TypeError>> {
166 let date = self.date();
167 let time = self.time();
168 let mut year = date.year();
169 let mut month = date.month() as i32;
170 let mut day = date.day();
171
172 let total_months = month + dur.get_months();
173 year += (total_months - 1).div_euclid(12);
174 month = (total_months - 1).rem_euclid(12) + 1;
175
176 let max_day = Date::days_in_month(year, month as u32);
177 if day > max_day {
178 day = max_day;
179 }
180
181 let base_date = Date::new(year, month as u32, day).ok_or_else(|| {
182 Box::new(Self::overflow_err(format!(
183 "invalid datetime after adding duration: {}-{:02}-{:02}",
184 year, month, day
185 )))
186 })?;
187 let base_days = base_date.to_days_since_epoch() as i64 + dur.get_days() as i64;
188 let time_nanos = time.to_nanos_since_midnight() as i64 + dur.get_nanos();
189
190 let total_nanos = base_days as i128 * 86_400_000_000_000i128 + time_nanos as i128;
191
192 let nanos = i64::try_from(total_nanos)
193 .map_err(|_| Box::new(Self::overflow_err("the result overflows DateTime range")))?;
194 Ok(Self {
195 nanos,
196 })
197 }
198}
199
200impl DateTime {
201 pub fn saturating_add(self, rhs: Duration) -> DateTime {
202 let total = rhs.as_nanos().unwrap_or(if rhs.is_negative() {
203 i64::MIN
204 } else {
205 i64::MAX
206 });
207 let nanos = (self.to_nanos() as i128 + total as i128).clamp(i64::MIN as i128, i64::MAX as i128);
208 DateTime::from_nanos(nanos as i64)
209 }
210
211 pub fn saturating_sub(self, rhs: Duration) -> DateTime {
212 let total = rhs.as_nanos().unwrap_or(if rhs.is_negative() {
213 i64::MIN
214 } else {
215 i64::MAX
216 });
217 let nanos = (self.to_nanos() as i128 - total as i128).clamp(i64::MIN as i128, i64::MAX as i128);
218 DateTime::from_nanos(nanos as i64)
219 }
220
221 pub fn checked_add(self, rhs: Duration) -> Option<DateTime> {
222 let total = rhs.as_nanos().ok()?;
223 let nanos = self.to_nanos() as i128 + total as i128;
224 i64::try_from(nanos).ok().map(DateTime::from_nanos)
225 }
226
227 pub fn checked_sub(self, rhs: Duration) -> Option<DateTime> {
228 let total = rhs.as_nanos().ok()?;
229 let nanos = self.to_nanos() as i128 - total as i128;
230 i64::try_from(nanos).ok().map(DateTime::from_nanos)
231 }
232
233 pub fn saturating_duration_since(self, earlier: DateTime) -> Duration {
234 let diff = (self.to_nanos() as i128 - earlier.to_nanos() as i128)
235 .clamp(i64::MIN as i128, i64::MAX as i128) as i64;
236 Duration::from_nanoseconds(diff).unwrap_or_else(|_| Duration::zero())
237 }
238}
239
240impl DateTime {
241 pub const ALIGNMENT: usize = 8;
242
243 pub const EPOCH: DateTime = DateTime {
244 nanos: 0,
245 };
246
247 pub const MIN: DateTime = DateTime {
248 nanos: i64::MIN,
249 };
250
251 pub const MAX: DateTime = DateTime {
252 nanos: i64::MAX,
253 };
254
255 pub fn is_epoch(&self) -> bool {
256 self.nanos == 0
257 }
258
259 pub fn from_millis(millis: i64) -> Self {
260 Self {
261 nanos: millis.saturating_mul(NANOS_PER_MILLI),
262 }
263 }
264
265 pub fn to_millis(&self) -> i64 {
266 self.nanos.div_euclid(NANOS_PER_MILLI)
267 }
268
269 pub fn to_micros(&self) -> i64 {
270 self.nanos.div_euclid(NANOS_PER_MICRO)
271 }
272
273 pub fn to_secs(&self) -> i64 {
274 self.nanos.div_euclid(NANOS_PER_SECOND)
275 }
276
277 pub fn saturating_add_millis(self, millis: u64) -> DateTime {
278 let delta = i64::try_from(millis).unwrap_or(i64::MAX).saturating_mul(NANOS_PER_MILLI);
279 DateTime::from_nanos(self.nanos.saturating_add(delta))
280 }
281
282 pub fn saturating_sub_millis(self, millis: u64) -> DateTime {
283 let delta = i64::try_from(millis).unwrap_or(i64::MAX).saturating_mul(NANOS_PER_MILLI);
284 DateTime::from_nanos(self.nanos.saturating_sub(delta))
285 }
286
287 pub fn floor_to_millis(self, millis: u64) -> DateTime {
288 let width = i64::try_from(millis).unwrap_or(i64::MAX).saturating_mul(NANOS_PER_MILLI);
289 if width == 0 {
290 return self;
291 }
292 DateTime::from_nanos(self.nanos.saturating_sub(self.nanos.rem_euclid(width)))
293 }
294}
295
296impl Add<Duration> for DateTime {
297 type Output = DateTime;
298
299 #[inline]
300 fn add(self, rhs: Duration) -> DateTime {
301 let total = rhs.as_nanos().expect("duration exceeds i64 nanoseconds");
302 let nanos = self.to_nanos() as i128 + total as i128;
303 DateTime::from_nanos(i64::try_from(nanos).expect("datetime addition out of range"))
304 }
305}
306
307impl Sub<Duration> for DateTime {
308 type Output = DateTime;
309
310 #[inline]
311 fn sub(self, rhs: Duration) -> DateTime {
312 let total = rhs.as_nanos().expect("duration exceeds i64 nanoseconds");
313 let nanos = self.to_nanos() as i128 - total as i128;
314 DateTime::from_nanos(i64::try_from(nanos).expect("datetime subtraction out of range"))
315 }
316}
317
318impl Sub<DateTime> for DateTime {
319 type Output = Duration;
320
321 #[inline]
322 fn sub(self, rhs: DateTime) -> Duration {
323 let diff = self.to_nanos() as i128 - rhs.to_nanos() as i128;
324 Duration::from_nanoseconds(i64::try_from(diff).expect("datetime difference exceeds i64 nanoseconds"))
325 .expect("datetime difference out of duration range")
326 }
327}
328
329impl Rem<Duration> for DateTime {
330 type Output = Duration;
331
332 #[inline]
333 fn rem(self, rhs: Duration) -> Duration {
334 let total = rhs.as_nanos().expect("duration exceeds i64 nanoseconds");
335 let total = u64::try_from(total).expect("duration must be positive for windowing");
336 Duration::from_nanoseconds(self.nanos.rem_euclid(total as i64))
337 .expect("datetime remainder out of duration range")
338 }
339}
340
341impl Display for DateTime {
342 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
343 let date = self.date();
344 let time = self.time();
345
346 write!(f, "{}T{}Z", date, time)
347 }
348}
349
350impl Serialize for DateTime {
351 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
352 where
353 S: Serializer,
354 {
355 serializer.serialize_i64(self.nanos)
356 }
357}
358
359struct DateTimeVisitor;
360
361impl<'de> Visitor<'de> for DateTimeVisitor {
362 type Value = DateTime;
363
364 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
365 formatter.write_str("a datetime as signed nanoseconds since the Unix epoch (i64)")
366 }
367
368 fn visit_i64<E>(self, value: i64) -> Result<DateTime, E>
369 where
370 E: de::Error,
371 {
372 Ok(DateTime::from_nanos(value))
373 }
374
375 fn visit_u64<E>(self, value: u64) -> Result<DateTime, E>
376 where
377 E: de::Error,
378 {
379 i64::try_from(value).map(DateTime::from_nanos).map_err(|_| E::custom("datetime out of range"))
380 }
381}
382
383impl<'de> Deserialize<'de> for DateTime {
384 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
385 where
386 D: Deserializer<'de>,
387 {
388 deserializer.deserialize_i64(DateTimeVisitor)
389 }
390}
391
392impl FromStr for DateTime {
393 type Err = Error;
394
395 fn from_str(s: &str) -> Result<Self, Self::Err> {
396 parse_datetime(Fragment::internal(s.trim()))
397 }
398}
399
400#[cfg(test)]
401pub mod tests {
402 use std::fmt::Debug;
403
404 use postcard::{from_bytes, to_allocvec};
405 use serde_json::{from_str, to_string};
406
407 use crate::{
408 error::{TemporalKind, TypeError},
409 value::{datetime::DateTime, duration::Duration},
410 };
411
412 #[test]
413 fn test_datetime_display_standard_format() {
414 let datetime = DateTime::new(2024, 3, 15, 14, 30, 45, 123456789).unwrap();
415 assert_eq!(format!("{}", datetime), "2024-03-15T14:30:45.123456789Z");
416
417 let datetime = DateTime::new(2000, 1, 1, 0, 0, 0, 0).unwrap();
418 assert_eq!(format!("{}", datetime), "2000-01-01T00:00:00.000000000Z");
419
420 let datetime = DateTime::new(1999, 12, 31, 23, 59, 59, 999999999).unwrap();
421 assert_eq!(format!("{}", datetime), "1999-12-31T23:59:59.999999999Z");
422 }
423
424 #[test]
425 fn test_datetime_display_millisecond_precision() {
426 let datetime = DateTime::new(2024, 3, 15, 14, 30, 45, 123000000).unwrap();
427 assert_eq!(format!("{}", datetime), "2024-03-15T14:30:45.123000000Z");
428
429 let datetime = DateTime::new(2024, 3, 15, 14, 30, 45, 1000000).unwrap();
430 assert_eq!(format!("{}", datetime), "2024-03-15T14:30:45.001000000Z");
431
432 let datetime = DateTime::new(2024, 3, 15, 14, 30, 45, 999000000).unwrap();
433 assert_eq!(format!("{}", datetime), "2024-03-15T14:30:45.999000000Z");
434 }
435
436 #[test]
437 fn test_datetime_display_microsecond_precision() {
438 let datetime = DateTime::new(2024, 3, 15, 14, 30, 45, 123456000).unwrap();
439 assert_eq!(format!("{}", datetime), "2024-03-15T14:30:45.123456000Z");
440
441 let datetime = DateTime::new(2024, 3, 15, 14, 30, 45, 1000).unwrap();
442 assert_eq!(format!("{}", datetime), "2024-03-15T14:30:45.000001000Z");
443
444 let datetime = DateTime::new(2024, 3, 15, 14, 30, 45, 999999000).unwrap();
445 assert_eq!(format!("{}", datetime), "2024-03-15T14:30:45.999999000Z");
446 }
447
448 #[test]
449 fn test_datetime_display_nanosecond_precision() {
450 let datetime = DateTime::new(2024, 3, 15, 14, 30, 45, 123456789).unwrap();
451 assert_eq!(format!("{}", datetime), "2024-03-15T14:30:45.123456789Z");
452
453 let datetime = DateTime::new(2024, 3, 15, 14, 30, 45, 1).unwrap();
454 assert_eq!(format!("{}", datetime), "2024-03-15T14:30:45.000000001Z");
455
456 let datetime = DateTime::new(2024, 3, 15, 14, 30, 45, 999999999).unwrap();
457 assert_eq!(format!("{}", datetime), "2024-03-15T14:30:45.999999999Z");
458 }
459
460 #[test]
461 fn test_datetime_display_zero_fractional_seconds() {
462 let datetime = DateTime::new(2024, 3, 15, 14, 30, 45, 0).unwrap();
463 assert_eq!(format!("{}", datetime), "2024-03-15T14:30:45.000000000Z");
464
465 let datetime = DateTime::new(2024, 3, 15, 0, 0, 0, 0).unwrap();
466 assert_eq!(format!("{}", datetime), "2024-03-15T00:00:00.000000000Z");
467 }
468
469 #[test]
470 fn test_datetime_display_edge_times() {
471 let datetime = DateTime::new(2024, 3, 15, 0, 0, 0, 0).unwrap();
472 assert_eq!(format!("{}", datetime), "2024-03-15T00:00:00.000000000Z");
473
474 let datetime = DateTime::new(2024, 3, 15, 23, 59, 59, 999999999).unwrap();
475 assert_eq!(format!("{}", datetime), "2024-03-15T23:59:59.999999999Z");
476
477 let datetime = DateTime::new(2024, 3, 15, 12, 0, 0, 0).unwrap();
478 assert_eq!(format!("{}", datetime), "2024-03-15T12:00:00.000000000Z");
479 }
480
481 #[test]
482 fn test_datetime_display_unix_epoch() {
483 let datetime = DateTime::new(1970, 1, 1, 0, 0, 0, 0).unwrap();
484 assert_eq!(format!("{}", datetime), "1970-01-01T00:00:00.000000000Z");
485
486 let datetime = DateTime::new(1970, 1, 1, 0, 0, 1, 0).unwrap();
487 assert_eq!(format!("{}", datetime), "1970-01-01T00:00:01.000000000Z");
488 }
489
490 #[test]
491 fn test_datetime_display_leap_year() {
492 let datetime = DateTime::new(2024, 2, 29, 12, 30, 45, 123456789).unwrap();
493 assert_eq!(format!("{}", datetime), "2024-02-29T12:30:45.123456789Z");
494
495 let datetime = DateTime::new(2000, 2, 29, 0, 0, 0, 0).unwrap();
496 assert_eq!(format!("{}", datetime), "2000-02-29T00:00:00.000000000Z");
497 }
498
499 #[test]
500 fn test_datetime_display_boundary_dates() {
501 let datetime = DateTime::new(2000, 1, 1, 0, 0, 0, 0).unwrap();
502 assert_eq!(format!("{}", datetime), "2000-01-01T00:00:00.000000000Z");
503
504 let datetime = DateTime::new(2100, 1, 1, 0, 0, 0, 0).unwrap();
505 assert_eq!(format!("{}", datetime), "2100-01-01T00:00:00.000000000Z");
506
507 let datetime = DateTime::new(2262, 4, 11, 23, 47, 16, 854775807).unwrap();
508 assert_eq!(format!("{}", datetime), "2262-04-11T23:47:16.854775807Z");
509 assert_eq!(datetime, DateTime::MAX);
510
511 assert!(DateTime::new(2262, 4, 11, 23, 47, 16, 854775808).is_none());
512 assert!(DateTime::new(9999, 12, 31, 23, 59, 59, 999999999).is_none());
513 }
514
515 #[test]
516 fn test_datetime_accepts_pre_epoch_down_to_min() {
517 assert_eq!(DateTime::new(1969, 12, 31, 23, 59, 59, 999999999), Some(DateTime::from_nanos(-1)));
518
519 assert!(DateTime::new(1900, 1, 1, 0, 0, 0, 0).is_some());
520
521 assert_eq!(DateTime::new(1677, 9, 21, 0, 12, 43, 145224192), Some(DateTime::MIN));
522 assert!(DateTime::new(1677, 9, 21, 0, 12, 43, 145224191).is_none());
523
524 assert_eq!(DateTime::from_epoch_secs(-1).unwrap().to_nanos(), -1_000_000_000);
525 }
526
527 #[test]
528 fn test_datetime_display_default() {
529 let datetime = DateTime::default();
530 assert_eq!(format!("{}", datetime), "1970-01-01T00:00:00.000000000Z");
531 }
532
533 #[test]
534 fn test_datetime_display_all_hours() {
535 for hour in 0..24 {
536 let datetime = DateTime::new(2024, 3, 15, hour, 30, 45, 123456789).unwrap();
537 let expected = format!("2024-03-15T{:02}:30:45.123456789Z", hour);
538 assert_eq!(format!("{}", datetime), expected);
539 }
540 }
541
542 #[test]
543 fn test_datetime_display_all_minutes() {
544 for minute in 0..60 {
545 let datetime = DateTime::new(2024, 3, 15, 14, minute, 45, 123456789).unwrap();
546 let expected = format!("2024-03-15T14:{:02}:45.123456789Z", minute);
547 assert_eq!(format!("{}", datetime), expected);
548 }
549 }
550
551 #[test]
552 fn test_datetime_display_all_seconds() {
553 for second in 0..60 {
554 let datetime = DateTime::new(2024, 3, 15, 14, 30, second, 123456789).unwrap();
555 let expected = format!("2024-03-15T14:30:{:02}.123456789Z", second);
556 assert_eq!(format!("{}", datetime), expected);
557 }
558 }
559
560 #[test]
561 fn test_datetime_display_from_epoch_secs() {
562 let datetime = DateTime::from_epoch_secs(0).unwrap();
563 assert_eq!(format!("{}", datetime), "1970-01-01T00:00:00.000000000Z");
564
565 let datetime = DateTime::from_epoch_secs(1234567890).unwrap();
566 assert_eq!(format!("{}", datetime), "2009-02-13T23:31:30.000000000Z");
567 }
568
569 #[test]
570 fn test_datetime_display_from_epoch_millis() {
571 let datetime = DateTime::from_epoch_millis(1234567890123).unwrap();
572 assert_eq!(format!("{}", datetime), "2009-02-13T23:31:30.123000000Z");
573
574 let datetime = DateTime::from_epoch_millis(0).unwrap();
575 assert_eq!(format!("{}", datetime), "1970-01-01T00:00:00.000000000Z");
576 }
577
578 #[test]
579 fn test_datetime_bits_roundtrip_preserves_every_component() {
580 let cases = [
583 DateTime::new(1970, 1, 1, 0, 0, 0, 0).unwrap(),
584 DateTime::new(2024, 3, 15, 14, 30, 45, 123456789).unwrap(),
585 DateTime::new(2000, 2, 29, 23, 59, 59, 999999999).unwrap(),
586 DateTime::MAX,
587 DateTime::MIN,
588 DateTime::from_nanos(-1),
589 ];
590
591 for datetime in cases {
592 let recovered = DateTime::from_order(datetime.to_order());
593
594 assert_eq!(datetime, recovered);
595 assert_eq!(datetime.nanosecond(), recovered.nanosecond(), "sub-second precision must survive");
596 }
597 }
598
599 #[test]
600 fn test_datetime_bits_are_monotonic_in_instant_order() {
601 let ordered = [
603 DateTime::MIN,
604 DateTime::from_nanos(-1),
605 DateTime::new(1970, 1, 1, 0, 0, 0, 0).unwrap(),
606 DateTime::new(1970, 1, 1, 0, 0, 0, 1).unwrap(),
607 DateTime::new(2024, 3, 15, 14, 30, 45, 123456789).unwrap(),
608 DateTime::new(2024, 3, 15, 14, 30, 45, 123456790).unwrap(),
609 DateTime::MAX,
610 ];
611
612 for pair in ordered.windows(2) {
613 let (lo, hi) = (pair[0], pair[1]);
614 assert!(lo < hi, "fixture must be ordered");
615 assert!(lo.to_order() < hi.to_order(), "bit order must follow instant order");
616 }
617 }
618
619 #[test]
620 fn test_datetime_from_nanos_roundtrip() {
621 let datetime = DateTime::new(2024, 3, 15, 14, 30, 45, 123456789).unwrap();
622 let nanos = datetime.to_nanos();
623 let recovered = DateTime::from_nanos(nanos);
624 assert_eq!(datetime, recovered);
625 }
626
627 #[test]
628 fn test_datetime_roundtrip() {
629 let test_cases = [
630 (1970, 1, 1, 0, 0, 0, 0u32),
631 (2024, 3, 15, 14, 30, 45, 123456789),
632 (2000, 2, 29, 23, 59, 59, 999999999),
633 ];
634
635 for (y, m, d, h, min, s, n) in test_cases {
636 let datetime = DateTime::new(y, m, d, h, min, s, n).unwrap();
637 let nanos = datetime.to_nanos();
638 let recovered = DateTime::from_nanos(nanos);
639
640 assert_eq!(datetime.year(), recovered.year());
641 assert_eq!(datetime.month(), recovered.month());
642 assert_eq!(datetime.day(), recovered.day());
643 assert_eq!(datetime.hour(), recovered.hour());
644 assert_eq!(datetime.minute(), recovered.minute());
645 assert_eq!(datetime.second(), recovered.second());
646 assert_eq!(datetime.nanosecond(), recovered.nanosecond());
647 }
648 }
649
650 #[test]
651 fn test_datetime_components() {
652 let datetime = DateTime::new(2024, 3, 15, 14, 30, 45, 123456789).unwrap();
653
654 assert_eq!(datetime.year(), 2024);
655 assert_eq!(datetime.month(), 3);
656 assert_eq!(datetime.day(), 15);
657 assert_eq!(datetime.hour(), 14);
658 assert_eq!(datetime.minute(), 30);
659 assert_eq!(datetime.second(), 45);
660 assert_eq!(datetime.nanosecond(), 123456789);
661 }
662
663 #[test]
664 fn test_serde_roundtrip() {
665 let datetime = DateTime::new(2024, 3, 15, 14, 30, 45, 123456789).unwrap();
666 let json = to_string(&datetime).unwrap();
667 assert_eq!(json, datetime.to_nanos().to_string());
669
670 let recovered: DateTime = from_str(&json).unwrap();
671 assert_eq!(datetime, recovered);
672
673 let pre_epoch = DateTime::new(1900, 6, 15, 12, 34, 56, 789000000).unwrap();
674 let json = to_string(&pre_epoch).unwrap();
675 assert_eq!(json, pre_epoch.to_nanos().to_string());
676
677 let recovered: DateTime = from_str(&json).unwrap();
678 assert_eq!(pre_epoch, recovered);
679 }
680
681 #[test]
682 fn test_serde_postcard_roundtrip_preserves_all_components() {
683 for (y, mo, d, h, mi, s, n) in [
686 (1970u32 as i32, 1u32, 1u32, 0u32, 0u32, 0u32, 0u32),
687 (2024, 3, 15, 14, 30, 45, 123456789),
688 (1999, 12, 31, 23, 59, 59, 999999999),
689 (2024, 3, 15, 14, 30, 45, 1),
690 (1900, 6, 15, 12, 34, 56, 789000000),
691 ] {
692 let dt = DateTime::new(y, mo, d, h, mi, s, n).unwrap();
693 let bytes = to_allocvec(&dt).unwrap();
694 let recovered: DateTime = from_bytes(&bytes).unwrap();
695 assert_eq!(dt, recovered);
696 assert_eq!(recovered.year(), y);
697 assert_eq!(recovered.month(), mo);
698 assert_eq!(recovered.day(), d);
699 assert_eq!(recovered.hour(), h);
700 assert_eq!(recovered.minute(), mi);
701 assert_eq!(recovered.second(), s);
702 assert_eq!(recovered.nanosecond(), n);
703 }
704 }
705
706 fn assert_datetime_overflow<T: Debug>(result: Result<T, Box<TypeError>>) {
707 let err = result.expect_err("expected DateTimeOverflow error");
708 match *err {
709 TypeError::Temporal {
710 kind: TemporalKind::DateTimeOverflow {
711 ..
712 },
713 ..
714 } => {}
715 other => panic!("expected DateTimeOverflow, got: {:?}", other),
716 }
717 }
718
719 #[test]
720 fn test_from_epoch_secs_large_value_overflow() {
721 assert_datetime_overflow(DateTime::from_epoch_secs(i64::MAX));
722 }
723
724 #[test]
725 fn test_from_epoch_secs_negative_overflow() {
726 assert!(DateTime::from_epoch_secs(-1).is_ok());
727 assert_datetime_overflow(DateTime::from_epoch_secs(i64::MIN));
728 }
729
730 #[test]
731 fn test_from_epoch_millis_overflow() {
732 assert_datetime_overflow(DateTime::from_epoch_millis(i64::MAX));
733 }
734
735 #[test]
736 fn test_from_epoch_millis_boundary_ok() {
737 let dt = DateTime::from_epoch_millis(1_700_000_000_000).unwrap();
738 assert!(dt.to_nanos() > 0);
739 }
740
741 #[test]
742 fn test_try_date_max_nanos_ok() {
743 let date = DateTime::MAX.try_date().unwrap();
744 assert_eq!(date.year(), 2262);
745
746 let date = DateTime::MIN.try_date().unwrap();
747 assert_eq!(date.year(), 1677);
748 }
749
750 #[test]
751 fn test_add_duration_overflow() {
752 let dt = DateTime::from_nanos(i64::MAX - 1);
753 let dur = Duration::from_days(1).unwrap();
754 assert_datetime_overflow(dt.add_duration(&dur));
755 }
756
757 #[test]
758 fn test_add_duration_before_epoch() {
759 let epoch = DateTime::new(1970, 1, 1, 0, 0, 0, 0).unwrap();
760 let dur = Duration::from_seconds(-1).unwrap();
761 assert_eq!(epoch.add_duration(&dur).unwrap(), DateTime::from_nanos(-1_000_000_000));
762
763 assert_datetime_overflow(DateTime::MIN.add_duration(&dur));
764 }
765
766 #[test]
767 fn test_add_duration_negative_nanos_borrows_from_days() {
768 let dt = DateTime::new(2024, 3, 15, 0, 0, 30, 0).unwrap();
769 let dur = Duration::from_seconds(-60).unwrap();
770 let result = dt.add_duration(&dur).unwrap();
771 assert_eq!(result.year(), 2024);
772 assert_eq!(result.month(), 3);
773 assert_eq!(result.day(), 14);
774 assert_eq!(result.hour(), 23);
775 assert_eq!(result.minute(), 59);
776 assert_eq!(result.second(), 30);
777 }
778
779 #[test]
780 fn test_add_duration_nanos_overflow_into_next_day() {
781 let dt = DateTime::new(2024, 3, 15, 23, 59, 30, 0).unwrap();
782 let dur = Duration::from_seconds(60).unwrap();
783 let result = dt.add_duration(&dur).unwrap();
784 assert_eq!(result.year(), 2024);
785 assert_eq!(result.month(), 3);
786 assert_eq!(result.day(), 16);
787 assert_eq!(result.hour(), 0);
788 assert_eq!(result.minute(), 0);
789 assert_eq!(result.second(), 30);
790 }
791
792 #[test]
793 fn add_and_sub_duration_operators() {
794 let dt = DateTime::from_ymd_hms(2024, 1, 15, 10, 30, 25).unwrap();
795 let minute = Duration::from_seconds(60).unwrap();
796 assert_eq!(dt + minute, DateTime::from_ymd_hms(2024, 1, 15, 10, 31, 25).unwrap());
797 assert_eq!(dt - minute, DateTime::from_ymd_hms(2024, 1, 15, 10, 29, 25).unwrap());
798 }
799
800 #[test]
801 fn sub_datetime_yields_duration() {
802 let a = DateTime::from_ymd_hms(2024, 1, 15, 10, 31, 0).unwrap();
803 let b = DateTime::from_ymd_hms(2024, 1, 15, 10, 30, 0).unwrap();
804 assert_eq!(a - b, Duration::from_seconds(60).unwrap());
805 }
806
807 #[test]
808 fn rem_duration_aligns_to_window_boundary() {
809 let dt = DateTime::from_ymd_hms(2024, 1, 15, 10, 30, 25).unwrap();
811 let minute = Duration::from_seconds(60).unwrap();
812 assert_eq!(dt % minute, Duration::from_seconds(25).unwrap());
813 assert_eq!(dt - (dt % minute), DateTime::from_ymd_hms(2024, 1, 15, 10, 30, 0).unwrap());
814
815 let second = Duration::from_seconds(1).unwrap();
816 assert_eq!(dt % second, Duration::from_seconds(0).unwrap());
817 }
818
819 #[test]
820 fn saturating_sub_clamps_at_min() {
821 let epoch = DateTime::from_nanos(0);
823 assert_eq!(
824 epoch.saturating_sub(Duration::from_seconds(1).unwrap()),
825 DateTime::from_nanos(-1_000_000_000)
826 );
827
828 assert_eq!(DateTime::MIN.saturating_sub(Duration::from_seconds(1).unwrap()), DateTime::MIN);
829 }
830
831 #[test]
832 fn checked_sub_gives_a_pre_epoch_cutoff_and_none_only_below_min() {
833 let now = DateTime::from_epoch_secs(1).unwrap();
835 assert_eq!(
836 now.checked_sub(Duration::from_seconds(3).unwrap()),
837 Some(DateTime::from_nanos(-2_000_000_000))
838 );
839 assert_eq!(DateTime::MIN.checked_sub(Duration::from_seconds(1).unwrap()), None);
840 }
841
842 #[test]
843 fn checked_sub_matches_subtraction_when_in_range() {
844 let now = DateTime::from_ymd_hms(2024, 1, 15, 10, 30, 25).unwrap();
845 let minute = Duration::from_seconds(60).unwrap();
846 assert_eq!(now.checked_sub(minute), Some(now - minute));
847 }
848
849 #[test]
850 fn saturating_add_above_max_clamps_to_max() {
851 let near_max = DateTime::from_nanos(i64::MAX - 1);
853 assert_eq!(near_max.saturating_add(Duration::from_days(1).unwrap()), DateTime::from_nanos(i64::MAX));
854 }
855
856 #[test]
857 fn saturating_add_sub_match_operators_in_range() {
858 let dt = DateTime::from_ymd_hms(2024, 1, 15, 10, 30, 25).unwrap();
860 let minute = Duration::from_seconds(60).unwrap();
861 assert_eq!(dt.saturating_add(minute), dt + minute);
862 assert_eq!(dt.saturating_sub(minute), dt - minute);
863 }
864
865 #[test]
866 fn saturating_duration_since_normal_and_clamped() {
867 let a = DateTime::from_ymd_hms(2024, 1, 15, 10, 31, 0).unwrap();
870 let b = DateTime::from_ymd_hms(2024, 1, 15, 10, 30, 0).unwrap();
871 assert_eq!(a.saturating_duration_since(b), Duration::from_seconds(60).unwrap());
872 assert_eq!(b.saturating_duration_since(a), Duration::from_seconds(-60).unwrap());
873 let clamped = DateTime::MAX.saturating_duration_since(DateTime::MIN);
874 assert_eq!(clamped.as_nanos().unwrap(), i64::MAX);
875 }
876
877 #[test]
878 fn checked_add_matches_addition_when_in_range() {
879 let now = DateTime::from_ymd_hms(2024, 1, 15, 10, 30, 25).unwrap();
880 let minute = Duration::from_seconds(60).unwrap();
881 assert_eq!(now.checked_add(minute), Some(now + minute));
882 }
883
884 #[test]
885 fn checked_add_returns_none_past_the_representable_range() {
886 let near_max = DateTime::from_nanos(i64::MAX - 1);
889 assert_eq!(near_max.checked_add(Duration::from_days(1).unwrap()), None);
890 }
891
892 #[test]
893 fn checked_sub_of_a_negative_duration_cannot_wrap_past_the_range() {
894 let near_max = DateTime::from_nanos(i64::MAX - 1);
897 assert_eq!(near_max.checked_sub(Duration::from_days(-1).unwrap()), None);
898 }
899
900 #[test]
901 fn floor_to_millis_reproduces_the_millis_truncated_bucket_boundary() {
902 let nanos = 1_700_000_123_456_789i64;
911 let window_ms = 1_000u64;
912
913 let ts_ms = nanos / 1_000_000;
914 let legacy_bucket_start_nanos = (ts_ms - ts_ms % window_ms as i64) * 1_000_000;
915
916 assert_eq!(
917 DateTime::from_nanos(nanos).floor_to_millis(window_ms),
918 DateTime::from_nanos(legacy_bucket_start_nanos)
919 );
920 }
921
922 #[test]
923 fn floor_to_millis_agrees_with_the_rem_duration_operator() {
924 let dt = DateTime::from_nanos(1_700_000_123_456_789);
927 let width_ms = 60_000u64;
928 let width = Duration::from_milliseconds(width_ms as i64).unwrap();
929
930 assert_eq!(dt.floor_to_millis(width_ms), dt - (dt % width));
931 }
932
933 #[test]
934 fn floor_to_millis_keeps_a_boundary_instant_where_it_is() {
935 let width_ms = 1_000u64;
937 let boundary = DateTime::from_nanos(2_000_000_000);
938
939 assert_eq!(
940 DateTime::from_nanos(1_999_999_999).floor_to_millis(width_ms),
941 DateTime::from_nanos(1_000_000_000)
942 );
943 assert_eq!(boundary.floor_to_millis(width_ms), boundary);
944 assert_eq!(DateTime::from_nanos(2_000_000_001).floor_to_millis(width_ms), boundary);
945 }
946
947 #[test]
948 fn floor_to_millis_of_a_zero_width_grid_is_the_instant_itself() {
949 let dt = DateTime::from_nanos(1_700_000_123_456_789);
952 assert_eq!(dt.floor_to_millis(0), dt);
953 }
954
955 #[test]
956 fn saturating_sub_millis_clamps_at_min() {
957 assert_eq!(DateTime::EPOCH.saturating_sub_millis(30_000), DateTime::from_nanos(-30_000_000_000));
959 assert_eq!(DateTime::MIN.saturating_sub_millis(1), DateTime::MIN);
960 }
961
962 #[test]
963 fn saturating_add_millis_clamps_at_the_maximum() {
964 assert_eq!(DateTime::MAX.saturating_add_millis(1), DateTime::MAX);
967 assert_eq!(DateTime::from_nanos(1_000_000).saturating_add_millis(1), DateTime::from_nanos(2_000_000));
968 }
969
970 #[test]
971 fn millis_conversions_round_trip_and_truncate_in_one_direction_only() {
972 assert_eq!(DateTime::from_millis(1_500).to_millis(), 1_500);
975 assert_eq!(DateTime::from_millis(1_500), DateTime::from_nanos(1_500_000_000));
976
977 let precise = DateTime::from_nanos(1_500_000_999);
978 assert_eq!(precise.to_millis(), 1_500);
979 assert_eq!(DateTime::from_millis(precise.to_millis()), DateTime::from_nanos(1_500_000_000));
980 }
981
982 #[test]
983 fn coarser_unit_accessors_floor() {
984 let dt = DateTime::from_nanos(1_700_000_123_456_789);
987
988 assert_eq!(dt.to_secs(), 1_700_000);
989 assert_eq!(dt.to_micros(), 1_700_000_123_456);
990 assert_eq!(dt.to_millis(), 1_700_000_123);
991
992 assert_eq!(dt.to_secs(), dt.to_nanos() / 1_000_000_000);
993 assert_eq!(dt.to_micros(), dt.to_nanos() / 1_000);
994
995 assert_eq!(DateTime::from_nanos(1_999_999_999).to_secs(), 1);
996 assert_eq!(DateTime::from_nanos(1_999).to_micros(), 1);
997 assert_eq!(DateTime::EPOCH.to_secs(), 0);
998 assert_eq!(DateTime::EPOCH.to_micros(), 0);
999
1000 assert_eq!(DateTime::from_nanos(-1).to_secs(), -1);
1001 assert_eq!(DateTime::from_nanos(-1).to_millis(), -1);
1002 assert_eq!(DateTime::from_nanos(-1).to_micros(), -1);
1003 }
1004
1005 #[test]
1006 fn from_millis_saturates_where_from_epoch_millis_errors() {
1007 assert_eq!(DateTime::from_millis(1_500), DateTime::from_epoch_millis(1_500).unwrap());
1010 assert!(DateTime::from_epoch_millis(i64::MAX).is_err());
1011 assert_eq!(DateTime::from_millis(i64::MAX), DateTime::MAX);
1012 assert_eq!(DateTime::from_millis(i64::MIN), DateTime::MIN);
1013 }
1014
1015 #[test]
1016 fn from_epoch_secs_reads_its_argument_as_whole_seconds() {
1017 assert_eq!(DateTime::from_epoch_secs(1).unwrap().to_nanos(), 1_000_000_000);
1019 assert_eq!(DateTime::from_epoch_secs(0).unwrap(), DateTime::EPOCH);
1020 assert_eq!(
1021 DateTime::from_epoch_secs(1_234_567_890).unwrap(),
1022 DateTime::from_epoch_millis(1_234_567_890_000).unwrap()
1023 );
1024 }
1025
1026 #[test]
1027 fn the_epoch_constant_is_the_zero_instant() {
1028 assert_eq!(DateTime::EPOCH, DateTime::from_nanos(0));
1030 assert_eq!(DateTime::EPOCH, DateTime::default());
1031 assert!(DateTime::EPOCH.is_epoch());
1032 assert!(!DateTime::from_nanos(1).is_epoch());
1033 }
1034
1035 #[test]
1036 fn a_pre_epoch_instant_round_trips_every_component() {
1037 let datetime = DateTime::new(1900, 6, 15, 12, 34, 56, 789000001).unwrap();
1039 assert!(datetime.to_nanos() < 0);
1040 assert_eq!(datetime.year(), 1900);
1041 assert_eq!(datetime.month(), 6);
1042 assert_eq!(datetime.day(), 15);
1043 assert_eq!(datetime.hour(), 12);
1044 assert_eq!(datetime.minute(), 34);
1045 assert_eq!(datetime.second(), 56);
1046 assert_eq!(datetime.nanosecond(), 789000001);
1047 }
1048
1049 #[test]
1050 fn min_and_max_display_the_representable_range_boundaries() {
1051 assert_eq!(format!("{}", DateTime::MIN), "1677-09-21T00:12:43.145224192Z");
1054 assert_eq!(format!("{}", DateTime::MAX), "2262-04-11T23:47:16.854775807Z");
1055 }
1056
1057 #[test]
1058 fn a_pre_epoch_instant_floors_and_remainders_toward_the_earlier_boundary() {
1059 let dt = DateTime::from_nanos(-1);
1062 assert_eq!(dt % Duration::from_seconds(1).unwrap(), Duration::from_nanoseconds(999_999_999).unwrap());
1063 assert_eq!(dt.floor_to_millis(1_000), DateTime::from_nanos(-1_000_000_000));
1064 }
1065}
1066
1067#[cfg(test)]
1068mod now_tests {
1069 use super::DateTime;
1070 use crate::clock::{ClockNow, testing::TestClock};
1071
1072 #[test]
1073 fn now_reads_the_clock() {
1074 let clock = TestClock::from_millis(1500);
1076 assert_eq!(clock.now(), DateTime::from_nanos(1_500_000_000));
1077 }
1078
1079 #[test]
1080 fn from_str_round_trips_display() {
1081 let dt = DateTime::from_nanos(1_700_000_000_000_000_000);
1082 let parsed: DateTime = dt.to_string().parse().unwrap();
1083 assert_eq!(parsed, dt);
1084 }
1085
1086 #[test]
1087 fn from_str_rejects_garbage() {
1088 assert!("not a datetime".parse::<DateTime>().is_err());
1089 }
1090}