1use std::fmt::{self, Display, Formatter};
5
6use serde::{
7 Deserialize, Deserializer, Serialize, Serializer,
8 de::{self, Visitor},
9};
10
11use crate::{
12 error::{TemporalKind, TypeError},
13 fragment::Fragment,
14 value::{date::Date, duration::Duration, time::Time},
15};
16
17const NANOS_PER_SECOND: u64 = 1_000_000_000;
18const NANOS_PER_MILLI: u64 = 1_000_000;
19const NANOS_PER_DAY: u64 = 86_400 * NANOS_PER_SECOND;
20
21pub static CREATED_AT_COLUMN_NAME: &str = "created_at";
22pub static UPDATED_AT_COLUMN_NAME: &str = "updated_at";
23
24#[repr(transparent)]
25#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
26pub struct DateTime {
27 nanos: u64,
28}
29
30impl DateTime {
31 pub fn new(year: i32, month: u32, day: u32, hour: u32, min: u32, sec: u32, nano: u32) -> Option<Self> {
32 let date = Date::new(year, month, day)?;
33 let time = Time::new(hour, min, sec, nano)?;
34
35 let days = date.to_days_since_epoch();
36 if days < 0 {
37 return None;
38 }
39
40 let nanos = (days as u64).checked_mul(NANOS_PER_DAY)?.checked_add(time.to_nanos_since_midnight())?;
41 Some(Self {
42 nanos,
43 })
44 }
45
46 pub fn from_ymd_hms(
47 year: i32,
48 month: u32,
49 day: u32,
50 hour: u32,
51 min: u32,
52 sec: u32,
53 ) -> Result<Self, Box<TypeError>> {
54 Self::new(year, month, day, hour, min, sec, 0).ok_or_else(|| {
55 Box::new(Self::overflow_err(format!(
56 "invalid datetime: {}-{:02}-{:02} {:02}:{:02}:{:02}",
57 year, month, day, hour, min, sec
58 )))
59 })
60 }
61
62 fn overflow_err(message: impl Into<String>) -> TypeError {
63 TypeError::Temporal {
64 kind: TemporalKind::DateTimeOverflow {
65 message: message.into(),
66 },
67 message: "datetime overflow".to_string(),
68 fragment: Fragment::None,
69 }
70 }
71
72 pub fn from_nanos(nanos: u64) -> Self {
73 Self {
74 nanos,
75 }
76 }
77
78 pub fn to_nanos(&self) -> u64 {
79 self.nanos
80 }
81
82 pub fn from_timestamp(timestamp: i64) -> Result<Self, Box<TypeError>> {
83 if timestamp < 0 {
84 return Err(Box::new(Self::overflow_err(format!(
85 "DateTime does not support timestamps before Unix epoch: {}",
86 timestamp
87 ))));
88 }
89 let nanos = (timestamp as u64).checked_mul(NANOS_PER_SECOND).ok_or_else(|| {
90 Box::new(Self::overflow_err(format!("timestamp {} overflows DateTime range", timestamp)))
91 })?;
92 Ok(Self {
93 nanos,
94 })
95 }
96
97 pub fn from_timestamp_millis(millis: u64) -> Result<Self, Box<TypeError>> {
98 let nanos = millis.checked_mul(NANOS_PER_MILLI).ok_or_else(|| {
99 Box::new(Self::overflow_err(format!("timestamp_millis {} overflows DateTime range", millis)))
100 })?;
101 Ok(Self {
102 nanos,
103 })
104 }
105
106 pub fn from_timestamp_nanos(nanos: u128) -> Result<Self, Box<TypeError>> {
107 let nanos = u64::try_from(nanos).map_err(|_| {
108 Box::new(Self::overflow_err(format!("timestamp_nanos {} overflows u64 DateTime range", nanos)))
109 })?;
110 Ok(Self {
111 nanos,
112 })
113 }
114
115 pub fn timestamp(&self) -> i64 {
116 (self.nanos / NANOS_PER_SECOND) as i64
117 }
118
119 pub fn timestamp_millis(&self) -> i64 {
120 (self.nanos / NANOS_PER_MILLI) as i64
121 }
122
123 pub fn timestamp_nanos(&self) -> Result<i64, Box<TypeError>> {
124 i64::try_from(self.nanos).map_err(|_| Box::new(Self::overflow_err("DateTime nanos exceeds i64::MAX")))
125 }
126
127 pub fn try_date(&self) -> Result<Date, Box<TypeError>> {
128 let days_u64 = self.nanos / NANOS_PER_DAY;
129 let days = i32::try_from(days_u64)
130 .map_err(|_| Box::new(Self::overflow_err("DateTime nanos too large for date extraction")))?;
131 Date::from_days_since_epoch(days)
132 .ok_or_else(|| Box::new(Self::overflow_err("DateTime days out of range for Date")))
133 }
134
135 pub fn date(&self) -> Date {
136 self.try_date().expect("DateTime nanos too large for date extraction")
137 }
138
139 pub fn time(&self) -> Time {
140 let nanos_in_day = self.nanos % NANOS_PER_DAY;
141 Time::from_nanos_since_midnight(nanos_in_day).unwrap()
142 }
143
144 pub fn to_nanos_since_epoch_u128(&self) -> u128 {
145 self.nanos as u128
146 }
147
148 pub fn year(&self) -> i32 {
149 self.date().year()
150 }
151
152 pub fn month(&self) -> u32 {
153 self.date().month()
154 }
155
156 pub fn day(&self) -> u32 {
157 self.date().day()
158 }
159
160 pub fn hour(&self) -> u32 {
161 self.time().hour()
162 }
163
164 pub fn minute(&self) -> u32 {
165 self.time().minute()
166 }
167
168 pub fn second(&self) -> u32 {
169 self.time().second()
170 }
171
172 pub fn nanosecond(&self) -> u32 {
173 self.time().nanosecond()
174 }
175
176 pub fn add_duration(&self, dur: &Duration) -> Result<Self, Box<TypeError>> {
177 let date = self.date();
178 let time = self.time();
179 let mut year = date.year();
180 let mut month = date.month() as i32;
181 let mut day = date.day();
182
183 let total_months = month + dur.get_months();
184 year += (total_months - 1).div_euclid(12);
185 month = (total_months - 1).rem_euclid(12) + 1;
186
187 let max_day = Date::days_in_month(year, month as u32);
188 if day > max_day {
189 day = max_day;
190 }
191
192 let base_date = Date::new(year, month as u32, day).ok_or_else(|| {
193 Box::new(Self::overflow_err(format!(
194 "invalid date after adding duration: {}-{:02}-{:02}",
195 year, month, day
196 )))
197 })?;
198 let base_days = base_date.to_days_since_epoch() as i64 + dur.get_days() as i64;
199 let time_nanos = time.to_nanos_since_midnight() as i64 + dur.get_nanos();
200
201 let total_nanos = base_days as i128 * 86_400_000_000_000i128 + time_nanos as i128;
202
203 if total_nanos < 0 {
204 return Err(Box::new(Self::overflow_err("result is before Unix epoch")));
205 }
206
207 let nanos = u64::try_from(total_nanos)
208 .map_err(|_| Box::new(Self::overflow_err("result exceeds DateTime range")))?;
209 Ok(Self {
210 nanos,
211 })
212 }
213}
214
215impl Display for DateTime {
216 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
217 let date = self.date();
218 let time = self.time();
219
220 write!(f, "{}T{}Z", date, time)
221 }
222}
223
224impl Serialize for DateTime {
225 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
226 where
227 S: Serializer,
228 {
229 serializer.serialize_u64(self.nanos)
230 }
231}
232
233struct DateTimeVisitor;
234
235impl<'de> Visitor<'de> for DateTimeVisitor {
236 type Value = DateTime;
237
238 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
239 formatter.write_str("a datetime as nanoseconds since the Unix epoch (u64)")
240 }
241
242 fn visit_u64<E>(self, value: u64) -> Result<DateTime, E>
243 where
244 E: de::Error,
245 {
246 Ok(DateTime::from_nanos(value))
247 }
248}
249
250impl<'de> Deserialize<'de> for DateTime {
251 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
252 where
253 D: Deserializer<'de>,
254 {
255 deserializer.deserialize_u64(DateTimeVisitor)
256 }
257}
258
259#[cfg(test)]
260pub mod tests {
261 use std::fmt::Debug;
262
263 use postcard::{from_bytes, to_allocvec};
264 use serde_json::{from_str, to_string};
265
266 use crate::{
267 error::{TemporalKind, TypeError},
268 value::{datetime::DateTime, duration::Duration},
269 };
270
271 #[test]
272 fn test_datetime_display_standard_format() {
273 let datetime = DateTime::new(2024, 3, 15, 14, 30, 45, 123456789).unwrap();
274 assert_eq!(format!("{}", datetime), "2024-03-15T14:30:45.123456789Z");
275
276 let datetime = DateTime::new(2000, 1, 1, 0, 0, 0, 0).unwrap();
277 assert_eq!(format!("{}", datetime), "2000-01-01T00:00:00.000000000Z");
278
279 let datetime = DateTime::new(1999, 12, 31, 23, 59, 59, 999999999).unwrap();
280 assert_eq!(format!("{}", datetime), "1999-12-31T23:59:59.999999999Z");
281 }
282
283 #[test]
284 fn test_datetime_display_millisecond_precision() {
285 let datetime = DateTime::new(2024, 3, 15, 14, 30, 45, 123000000).unwrap();
286 assert_eq!(format!("{}", datetime), "2024-03-15T14:30:45.123000000Z");
287
288 let datetime = DateTime::new(2024, 3, 15, 14, 30, 45, 001000000).unwrap();
289 assert_eq!(format!("{}", datetime), "2024-03-15T14:30:45.001000000Z");
290
291 let datetime = DateTime::new(2024, 3, 15, 14, 30, 45, 999000000).unwrap();
292 assert_eq!(format!("{}", datetime), "2024-03-15T14:30:45.999000000Z");
293 }
294
295 #[test]
296 fn test_datetime_display_microsecond_precision() {
297 let datetime = DateTime::new(2024, 3, 15, 14, 30, 45, 123456000).unwrap();
298 assert_eq!(format!("{}", datetime), "2024-03-15T14:30:45.123456000Z");
299
300 let datetime = DateTime::new(2024, 3, 15, 14, 30, 45, 000001000).unwrap();
301 assert_eq!(format!("{}", datetime), "2024-03-15T14:30:45.000001000Z");
302
303 let datetime = DateTime::new(2024, 3, 15, 14, 30, 45, 999999000).unwrap();
304 assert_eq!(format!("{}", datetime), "2024-03-15T14:30:45.999999000Z");
305 }
306
307 #[test]
308 fn test_datetime_display_nanosecond_precision() {
309 let datetime = DateTime::new(2024, 3, 15, 14, 30, 45, 123456789).unwrap();
310 assert_eq!(format!("{}", datetime), "2024-03-15T14:30:45.123456789Z");
311
312 let datetime = DateTime::new(2024, 3, 15, 14, 30, 45, 000000001).unwrap();
313 assert_eq!(format!("{}", datetime), "2024-03-15T14:30:45.000000001Z");
314
315 let datetime = DateTime::new(2024, 3, 15, 14, 30, 45, 999999999).unwrap();
316 assert_eq!(format!("{}", datetime), "2024-03-15T14:30:45.999999999Z");
317 }
318
319 #[test]
320 fn test_datetime_display_zero_fractional_seconds() {
321 let datetime = DateTime::new(2024, 3, 15, 14, 30, 45, 0).unwrap();
322 assert_eq!(format!("{}", datetime), "2024-03-15T14:30:45.000000000Z");
323
324 let datetime = DateTime::new(2024, 3, 15, 0, 0, 0, 0).unwrap();
325 assert_eq!(format!("{}", datetime), "2024-03-15T00:00:00.000000000Z");
326 }
327
328 #[test]
329 fn test_datetime_display_edge_times() {
330 let datetime = DateTime::new(2024, 3, 15, 0, 0, 0, 0).unwrap();
332 assert_eq!(format!("{}", datetime), "2024-03-15T00:00:00.000000000Z");
333
334 let datetime = DateTime::new(2024, 3, 15, 23, 59, 59, 999999999).unwrap();
336 assert_eq!(format!("{}", datetime), "2024-03-15T23:59:59.999999999Z");
337
338 let datetime = DateTime::new(2024, 3, 15, 12, 0, 0, 0).unwrap();
340 assert_eq!(format!("{}", datetime), "2024-03-15T12:00:00.000000000Z");
341 }
342
343 #[test]
344 fn test_datetime_display_unix_epoch() {
345 let datetime = DateTime::new(1970, 1, 1, 0, 0, 0, 0).unwrap();
346 assert_eq!(format!("{}", datetime), "1970-01-01T00:00:00.000000000Z");
347
348 let datetime = DateTime::new(1970, 1, 1, 0, 0, 1, 0).unwrap();
349 assert_eq!(format!("{}", datetime), "1970-01-01T00:00:01.000000000Z");
350 }
351
352 #[test]
353 fn test_datetime_display_leap_year() {
354 let datetime = DateTime::new(2024, 2, 29, 12, 30, 45, 123456789).unwrap();
355 assert_eq!(format!("{}", datetime), "2024-02-29T12:30:45.123456789Z");
356
357 let datetime = DateTime::new(2000, 2, 29, 0, 0, 0, 0).unwrap();
358 assert_eq!(format!("{}", datetime), "2000-02-29T00:00:00.000000000Z");
359 }
360
361 #[test]
362 fn test_datetime_display_boundary_dates() {
363 let datetime = DateTime::new(2000, 1, 1, 0, 0, 0, 0).unwrap();
365 assert_eq!(format!("{}", datetime), "2000-01-01T00:00:00.000000000Z");
366
367 let datetime = DateTime::new(2100, 1, 1, 0, 0, 0, 0).unwrap();
368 assert_eq!(format!("{}", datetime), "2100-01-01T00:00:00.000000000Z");
369
370 let datetime = DateTime::new(2554, 1, 1, 0, 0, 0, 0).unwrap();
372 assert_eq!(format!("{}", datetime), "2554-01-01T00:00:00.000000000Z");
373
374 assert!(DateTime::new(9999, 12, 31, 23, 59, 59, 999999999).is_none());
376 }
377
378 #[test]
379 fn test_datetime_rejects_pre_epoch() {
380 assert!(DateTime::new(1, 1, 1, 0, 0, 0, 0).is_none());
382
383 assert!(DateTime::new(1900, 1, 1, 0, 0, 0, 0).is_none());
385
386 assert!(DateTime::new(1969, 12, 31, 23, 59, 59, 999999999).is_none());
388
389 assert!(DateTime::from_timestamp(-1).is_err());
391 }
392
393 #[test]
394 fn test_datetime_display_default() {
395 let datetime = DateTime::default();
396 assert_eq!(format!("{}", datetime), "1970-01-01T00:00:00.000000000Z");
397 }
398
399 #[test]
400 fn test_datetime_display_all_hours() {
401 for hour in 0..24 {
402 let datetime = DateTime::new(2024, 3, 15, hour, 30, 45, 123456789).unwrap();
403 let expected = format!("2024-03-15T{:02}:30:45.123456789Z", hour);
404 assert_eq!(format!("{}", datetime), expected);
405 }
406 }
407
408 #[test]
409 fn test_datetime_display_all_minutes() {
410 for minute in 0..60 {
411 let datetime = DateTime::new(2024, 3, 15, 14, minute, 45, 123456789).unwrap();
412 let expected = format!("2024-03-15T14:{:02}:45.123456789Z", minute);
413 assert_eq!(format!("{}", datetime), expected);
414 }
415 }
416
417 #[test]
418 fn test_datetime_display_all_seconds() {
419 for second in 0..60 {
420 let datetime = DateTime::new(2024, 3, 15, 14, 30, second, 123456789).unwrap();
421 let expected = format!("2024-03-15T14:30:{:02}.123456789Z", second);
422 assert_eq!(format!("{}", datetime), expected);
423 }
424 }
425
426 #[test]
427 fn test_datetime_display_from_timestamp() {
428 let datetime = DateTime::from_timestamp(0).unwrap();
429 assert_eq!(format!("{}", datetime), "1970-01-01T00:00:00.000000000Z");
430
431 let datetime = DateTime::from_timestamp(1234567890).unwrap();
432 assert_eq!(format!("{}", datetime), "2009-02-13T23:31:30.000000000Z");
433 }
434
435 #[test]
436 fn test_datetime_display_from_timestamp_millis() {
437 let datetime = DateTime::from_timestamp_millis(1234567890123).unwrap();
438 assert_eq!(format!("{}", datetime), "2009-02-13T23:31:30.123000000Z");
439
440 let datetime = DateTime::from_timestamp_millis(0).unwrap();
441 assert_eq!(format!("{}", datetime), "1970-01-01T00:00:00.000000000Z");
442 }
443
444 #[test]
445 fn test_datetime_from_nanos_roundtrip() {
446 let datetime = DateTime::new(2024, 3, 15, 14, 30, 45, 123456789).unwrap();
447 let nanos = datetime.to_nanos();
448 let recovered = DateTime::from_nanos(nanos);
449 assert_eq!(datetime, recovered);
450 }
451
452 #[test]
453 fn test_datetime_roundtrip() {
454 let test_cases = [
455 (1970, 1, 1, 0, 0, 0, 0u32),
456 (2024, 3, 15, 14, 30, 45, 123456789),
457 (2000, 2, 29, 23, 59, 59, 999999999),
458 ];
459
460 for (y, m, d, h, min, s, n) in test_cases {
461 let datetime = DateTime::new(y, m, d, h, min, s, n).unwrap();
462 let nanos = datetime.to_nanos();
463 let recovered = DateTime::from_nanos(nanos);
464
465 assert_eq!(datetime.year(), recovered.year());
466 assert_eq!(datetime.month(), recovered.month());
467 assert_eq!(datetime.day(), recovered.day());
468 assert_eq!(datetime.hour(), recovered.hour());
469 assert_eq!(datetime.minute(), recovered.minute());
470 assert_eq!(datetime.second(), recovered.second());
471 assert_eq!(datetime.nanosecond(), recovered.nanosecond());
472 }
473 }
474
475 #[test]
476 fn test_datetime_components() {
477 let datetime = DateTime::new(2024, 3, 15, 14, 30, 45, 123456789).unwrap();
478
479 assert_eq!(datetime.year(), 2024);
480 assert_eq!(datetime.month(), 3);
481 assert_eq!(datetime.day(), 15);
482 assert_eq!(datetime.hour(), 14);
483 assert_eq!(datetime.minute(), 30);
484 assert_eq!(datetime.second(), 45);
485 assert_eq!(datetime.nanosecond(), 123456789);
486 }
487
488 #[test]
489 fn test_serde_roundtrip() {
490 let datetime = DateTime::new(2024, 3, 15, 14, 30, 45, 123456789).unwrap();
491 let json = to_string(&datetime).unwrap();
492 assert_eq!(json, datetime.to_nanos().to_string());
494
495 let recovered: DateTime = from_str(&json).unwrap();
496 assert_eq!(datetime, recovered);
497 }
498
499 #[test]
500 fn test_serde_postcard_roundtrip_preserves_all_components() {
501 for (y, mo, d, h, mi, s, n) in [
504 (1970u32 as i32, 1u32, 1u32, 0u32, 0u32, 0u32, 0u32),
505 (2024, 3, 15, 14, 30, 45, 123456789),
506 (1999, 12, 31, 23, 59, 59, 999999999),
507 (2024, 3, 15, 14, 30, 45, 1),
508 ] {
509 let dt = DateTime::new(y, mo, d, h, mi, s, n).unwrap();
510 let bytes = to_allocvec(&dt).unwrap();
511 let recovered: DateTime = from_bytes(&bytes).unwrap();
512 assert_eq!(dt, recovered);
513 assert_eq!(recovered.year(), y);
514 assert_eq!(recovered.month(), mo);
515 assert_eq!(recovered.day(), d);
516 assert_eq!(recovered.hour(), h);
517 assert_eq!(recovered.minute(), mi);
518 assert_eq!(recovered.second(), s);
519 assert_eq!(recovered.nanosecond(), n);
520 }
521 }
522
523 fn assert_datetime_overflow<T: Debug>(result: Result<T, Box<TypeError>>) {
524 let err = result.expect_err("expected DateTimeOverflow error");
525 match *err {
526 TypeError::Temporal {
527 kind: TemporalKind::DateTimeOverflow {
528 ..
529 },
530 ..
531 } => {}
532 other => panic!("expected DateTimeOverflow, got: {:?}", other),
533 }
534 }
535
536 #[test]
537 fn test_from_timestamp_nanos_overflow() {
538 let huge: u128 = u64::MAX as u128 + 1;
539 assert_datetime_overflow(DateTime::from_timestamp_nanos(huge));
540 }
541
542 #[test]
543 fn test_from_timestamp_nanos_max_u64_ok() {
544 let dt = DateTime::from_timestamp_nanos(u64::MAX as u128).unwrap();
545 assert_eq!(dt.to_nanos(), u64::MAX);
546 }
547
548 #[test]
549 fn test_from_timestamp_large_value_overflow() {
550 assert_datetime_overflow(DateTime::from_timestamp(i64::MAX));
551 }
552
553 #[test]
554 fn test_from_timestamp_negative_overflow() {
555 assert_datetime_overflow(DateTime::from_timestamp(-1));
556 }
557
558 #[test]
559 fn test_from_timestamp_millis_overflow() {
560 assert_datetime_overflow(DateTime::from_timestamp_millis(u64::MAX));
561 }
562
563 #[test]
564 fn test_from_timestamp_millis_boundary_ok() {
565 let dt = DateTime::from_timestamp_millis(1_700_000_000_000).unwrap();
566 assert!(dt.to_nanos() > 0);
567 }
568
569 #[test]
570 fn test_timestamp_nanos_large_value_returns_err() {
571 let dt = DateTime::from_nanos(i64::MAX as u64 + 1);
572 assert_datetime_overflow(dt.timestamp_nanos());
573 }
574
575 #[test]
576 fn test_timestamp_nanos_within_range_ok() {
577 let dt = DateTime::from_nanos(i64::MAX as u64);
578 assert_eq!(dt.timestamp_nanos().unwrap(), i64::MAX);
579 }
580
581 #[test]
582 fn test_try_date_max_nanos_ok() {
583 let dt = DateTime::from_nanos(u64::MAX);
585 let date = dt.try_date().unwrap();
586 assert!(date.year() > 2500);
587 }
588
589 #[test]
590 fn test_add_duration_overflow() {
591 let dt = DateTime::from_nanos(u64::MAX - 1);
592 let dur = Duration::from_days(1).unwrap();
593 assert_datetime_overflow(dt.add_duration(&dur));
594 }
595
596 #[test]
597 fn test_add_duration_before_epoch() {
598 let dt = DateTime::new(1970, 1, 1, 0, 0, 0, 0).unwrap();
599 let dur = Duration::from_seconds(-1).unwrap();
600 assert_datetime_overflow(dt.add_duration(&dur));
601 }
602
603 #[test]
604 fn test_add_duration_negative_nanos_borrows_from_days() {
605 let dt = DateTime::new(2024, 3, 15, 0, 0, 30, 0).unwrap();
606 let dur = Duration::from_seconds(-60).unwrap();
607 let result = dt.add_duration(&dur).unwrap();
608 assert_eq!(result.year(), 2024);
609 assert_eq!(result.month(), 3);
610 assert_eq!(result.day(), 14);
611 assert_eq!(result.hour(), 23);
612 assert_eq!(result.minute(), 59);
613 assert_eq!(result.second(), 30);
614 }
615
616 #[test]
617 fn test_add_duration_nanos_overflow_into_next_day() {
618 let dt = DateTime::new(2024, 3, 15, 23, 59, 30, 0).unwrap();
619 let dur = Duration::from_seconds(60).unwrap();
620 let result = dt.add_duration(&dur).unwrap();
621 assert_eq!(result.year(), 2024);
622 assert_eq!(result.month(), 3);
623 assert_eq!(result.day(), 16);
624 assert_eq!(result.hour(), 0);
625 assert_eq!(result.minute(), 0);
626 assert_eq!(result.second(), 30);
627 }
628}