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