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