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_str(&self.to_string())
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 in ISO 8601 format (YYYY-MM-DDTHH:MM:SS[.nnnnnnnnn]Z)")
240 }
241
242 fn visit_str<E>(self, value: &str) -> Result<DateTime, E>
243 where
244 E: de::Error,
245 {
246 let value = value.strip_suffix('Z').unwrap_or(value);
247
248 let parts: Vec<&str> = value.split('T').collect();
249 if parts.len() != 2 {
250 return Err(E::custom(format!("invalid datetime format: {}", value)));
251 }
252
253 let date_parts: Vec<&str> = parts[0].split('-').collect();
254 if date_parts.len() != 3 {
255 return Err(E::custom(format!("invalid date format: {}", parts[0])));
256 }
257
258 let (year_str, month_str, day_str) = (date_parts[0], date_parts[1], date_parts[2]);
259
260 let year = year_str.parse::<i32>().map_err(|_| E::custom(format!("invalid year: {}", year_str)))?;
261 if year < 1970 {
262 return Err(E::custom(format!("DateTime does not support pre-epoch years: {}", year)));
263 }
264 let month = month_str.parse::<u32>().map_err(|_| E::custom(format!("invalid month: {}", month_str)))?;
265 let day = day_str.parse::<u32>().map_err(|_| E::custom(format!("invalid day: {}", day_str)))?;
266
267 let (time_part, nano_part) = if let Some(dot_pos) = parts[1].find('.') {
268 (&parts[1][..dot_pos], Some(&parts[1][dot_pos + 1..]))
269 } else {
270 (parts[1], None)
271 };
272
273 let time_parts: Vec<&str> = time_part.split(':').collect();
274 if time_parts.len() != 3 {
275 return Err(E::custom(format!("invalid time format: {}", parts[1])));
276 }
277
278 let hour = time_parts[0]
279 .parse::<u32>()
280 .map_err(|_| E::custom(format!("invalid hour: {}", time_parts[0])))?;
281 let minute = time_parts[1]
282 .parse::<u32>()
283 .map_err(|_| E::custom(format!("invalid minute: {}", time_parts[1])))?;
284 let second = time_parts[2]
285 .parse::<u32>()
286 .map_err(|_| E::custom(format!("invalid second: {}", time_parts[2])))?;
287
288 let nano = if let Some(nano_str) = nano_part {
289 let padded = if nano_str.len() < 9 {
290 format!("{:0<9}", nano_str)
291 } else {
292 nano_str[..9].to_string()
293 };
294 padded.parse::<u32>().map_err(|_| E::custom(format!("invalid nanoseconds: {}", nano_str)))?
295 } else {
296 0
297 };
298
299 DateTime::new(year, month, day, hour, minute, second, nano).ok_or_else(|| {
300 E::custom(format!(
301 "invalid datetime: {}-{:02}-{:02}T{:02}:{:02}:{:02}.{:09}Z",
302 year, month, day, hour, minute, second, nano
303 ))
304 })
305 }
306}
307
308impl<'de> Deserialize<'de> for DateTime {
309 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
310 where
311 D: Deserializer<'de>,
312 {
313 deserializer.deserialize_str(DateTimeVisitor)
314 }
315}
316
317#[cfg(test)]
318pub mod tests {
319 use std::fmt::Debug;
320
321 use serde_json::{from_str, to_string};
322
323 use crate::{
324 error::{TemporalKind, TypeError},
325 value::{datetime::DateTime, duration::Duration},
326 };
327
328 #[test]
329 fn test_datetime_display_standard_format() {
330 let datetime = DateTime::new(2024, 3, 15, 14, 30, 45, 123456789).unwrap();
331 assert_eq!(format!("{}", datetime), "2024-03-15T14:30:45.123456789Z");
332
333 let datetime = DateTime::new(2000, 1, 1, 0, 0, 0, 0).unwrap();
334 assert_eq!(format!("{}", datetime), "2000-01-01T00:00:00.000000000Z");
335
336 let datetime = DateTime::new(1999, 12, 31, 23, 59, 59, 999999999).unwrap();
337 assert_eq!(format!("{}", datetime), "1999-12-31T23:59:59.999999999Z");
338 }
339
340 #[test]
341 fn test_datetime_display_millisecond_precision() {
342 let datetime = DateTime::new(2024, 3, 15, 14, 30, 45, 123000000).unwrap();
343 assert_eq!(format!("{}", datetime), "2024-03-15T14:30:45.123000000Z");
344
345 let datetime = DateTime::new(2024, 3, 15, 14, 30, 45, 001000000).unwrap();
346 assert_eq!(format!("{}", datetime), "2024-03-15T14:30:45.001000000Z");
347
348 let datetime = DateTime::new(2024, 3, 15, 14, 30, 45, 999000000).unwrap();
349 assert_eq!(format!("{}", datetime), "2024-03-15T14:30:45.999000000Z");
350 }
351
352 #[test]
353 fn test_datetime_display_microsecond_precision() {
354 let datetime = DateTime::new(2024, 3, 15, 14, 30, 45, 123456000).unwrap();
355 assert_eq!(format!("{}", datetime), "2024-03-15T14:30:45.123456000Z");
356
357 let datetime = DateTime::new(2024, 3, 15, 14, 30, 45, 000001000).unwrap();
358 assert_eq!(format!("{}", datetime), "2024-03-15T14:30:45.000001000Z");
359
360 let datetime = DateTime::new(2024, 3, 15, 14, 30, 45, 999999000).unwrap();
361 assert_eq!(format!("{}", datetime), "2024-03-15T14:30:45.999999000Z");
362 }
363
364 #[test]
365 fn test_datetime_display_nanosecond_precision() {
366 let datetime = DateTime::new(2024, 3, 15, 14, 30, 45, 123456789).unwrap();
367 assert_eq!(format!("{}", datetime), "2024-03-15T14:30:45.123456789Z");
368
369 let datetime = DateTime::new(2024, 3, 15, 14, 30, 45, 000000001).unwrap();
370 assert_eq!(format!("{}", datetime), "2024-03-15T14:30:45.000000001Z");
371
372 let datetime = DateTime::new(2024, 3, 15, 14, 30, 45, 999999999).unwrap();
373 assert_eq!(format!("{}", datetime), "2024-03-15T14:30:45.999999999Z");
374 }
375
376 #[test]
377 fn test_datetime_display_zero_fractional_seconds() {
378 let datetime = DateTime::new(2024, 3, 15, 14, 30, 45, 0).unwrap();
379 assert_eq!(format!("{}", datetime), "2024-03-15T14:30:45.000000000Z");
380
381 let datetime = DateTime::new(2024, 3, 15, 0, 0, 0, 0).unwrap();
382 assert_eq!(format!("{}", datetime), "2024-03-15T00:00:00.000000000Z");
383 }
384
385 #[test]
386 fn test_datetime_display_edge_times() {
387 let datetime = DateTime::new(2024, 3, 15, 0, 0, 0, 0).unwrap();
389 assert_eq!(format!("{}", datetime), "2024-03-15T00:00:00.000000000Z");
390
391 let datetime = DateTime::new(2024, 3, 15, 23, 59, 59, 999999999).unwrap();
393 assert_eq!(format!("{}", datetime), "2024-03-15T23:59:59.999999999Z");
394
395 let datetime = DateTime::new(2024, 3, 15, 12, 0, 0, 0).unwrap();
397 assert_eq!(format!("{}", datetime), "2024-03-15T12:00:00.000000000Z");
398 }
399
400 #[test]
401 fn test_datetime_display_unix_epoch() {
402 let datetime = DateTime::new(1970, 1, 1, 0, 0, 0, 0).unwrap();
403 assert_eq!(format!("{}", datetime), "1970-01-01T00:00:00.000000000Z");
404
405 let datetime = DateTime::new(1970, 1, 1, 0, 0, 1, 0).unwrap();
406 assert_eq!(format!("{}", datetime), "1970-01-01T00:00:01.000000000Z");
407 }
408
409 #[test]
410 fn test_datetime_display_leap_year() {
411 let datetime = DateTime::new(2024, 2, 29, 12, 30, 45, 123456789).unwrap();
412 assert_eq!(format!("{}", datetime), "2024-02-29T12:30:45.123456789Z");
413
414 let datetime = DateTime::new(2000, 2, 29, 0, 0, 0, 0).unwrap();
415 assert_eq!(format!("{}", datetime), "2000-02-29T00:00:00.000000000Z");
416 }
417
418 #[test]
419 fn test_datetime_display_boundary_dates() {
420 let datetime = DateTime::new(2000, 1, 1, 0, 0, 0, 0).unwrap();
422 assert_eq!(format!("{}", datetime), "2000-01-01T00:00:00.000000000Z");
423
424 let datetime = DateTime::new(2100, 1, 1, 0, 0, 0, 0).unwrap();
425 assert_eq!(format!("{}", datetime), "2100-01-01T00:00:00.000000000Z");
426
427 let datetime = DateTime::new(2554, 1, 1, 0, 0, 0, 0).unwrap();
429 assert_eq!(format!("{}", datetime), "2554-01-01T00:00:00.000000000Z");
430
431 assert!(DateTime::new(9999, 12, 31, 23, 59, 59, 999999999).is_none());
433 }
434
435 #[test]
436 fn test_datetime_rejects_pre_epoch() {
437 assert!(DateTime::new(1, 1, 1, 0, 0, 0, 0).is_none());
439
440 assert!(DateTime::new(1900, 1, 1, 0, 0, 0, 0).is_none());
442
443 assert!(DateTime::new(1969, 12, 31, 23, 59, 59, 999999999).is_none());
445
446 assert!(DateTime::from_timestamp(-1).is_err());
448 }
449
450 #[test]
451 fn test_datetime_display_default() {
452 let datetime = DateTime::default();
453 assert_eq!(format!("{}", datetime), "1970-01-01T00:00:00.000000000Z");
454 }
455
456 #[test]
457 fn test_datetime_display_all_hours() {
458 for hour in 0..24 {
459 let datetime = DateTime::new(2024, 3, 15, hour, 30, 45, 123456789).unwrap();
460 let expected = format!("2024-03-15T{:02}:30:45.123456789Z", hour);
461 assert_eq!(format!("{}", datetime), expected);
462 }
463 }
464
465 #[test]
466 fn test_datetime_display_all_minutes() {
467 for minute in 0..60 {
468 let datetime = DateTime::new(2024, 3, 15, 14, minute, 45, 123456789).unwrap();
469 let expected = format!("2024-03-15T14:{:02}:45.123456789Z", minute);
470 assert_eq!(format!("{}", datetime), expected);
471 }
472 }
473
474 #[test]
475 fn test_datetime_display_all_seconds() {
476 for second in 0..60 {
477 let datetime = DateTime::new(2024, 3, 15, 14, 30, second, 123456789).unwrap();
478 let expected = format!("2024-03-15T14:30:{:02}.123456789Z", second);
479 assert_eq!(format!("{}", datetime), expected);
480 }
481 }
482
483 #[test]
484 fn test_datetime_display_from_timestamp() {
485 let datetime = DateTime::from_timestamp(0).unwrap();
486 assert_eq!(format!("{}", datetime), "1970-01-01T00:00:00.000000000Z");
487
488 let datetime = DateTime::from_timestamp(1234567890).unwrap();
489 assert_eq!(format!("{}", datetime), "2009-02-13T23:31:30.000000000Z");
490 }
491
492 #[test]
493 fn test_datetime_display_from_timestamp_millis() {
494 let datetime = DateTime::from_timestamp_millis(1234567890123).unwrap();
495 assert_eq!(format!("{}", datetime), "2009-02-13T23:31:30.123000000Z");
496
497 let datetime = DateTime::from_timestamp_millis(0).unwrap();
498 assert_eq!(format!("{}", datetime), "1970-01-01T00:00:00.000000000Z");
499 }
500
501 #[test]
502 fn test_datetime_from_nanos_roundtrip() {
503 let datetime = DateTime::new(2024, 3, 15, 14, 30, 45, 123456789).unwrap();
504 let nanos = datetime.to_nanos();
505 let recovered = DateTime::from_nanos(nanos);
506 assert_eq!(datetime, recovered);
507 }
508
509 #[test]
510 fn test_datetime_roundtrip() {
511 let test_cases = [
512 (1970, 1, 1, 0, 0, 0, 0u32),
513 (2024, 3, 15, 14, 30, 45, 123456789),
514 (2000, 2, 29, 23, 59, 59, 999999999),
515 ];
516
517 for (y, m, d, h, min, s, n) in test_cases {
518 let datetime = DateTime::new(y, m, d, h, min, s, n).unwrap();
519 let nanos = datetime.to_nanos();
520 let recovered = DateTime::from_nanos(nanos);
521
522 assert_eq!(datetime.year(), recovered.year());
523 assert_eq!(datetime.month(), recovered.month());
524 assert_eq!(datetime.day(), recovered.day());
525 assert_eq!(datetime.hour(), recovered.hour());
526 assert_eq!(datetime.minute(), recovered.minute());
527 assert_eq!(datetime.second(), recovered.second());
528 assert_eq!(datetime.nanosecond(), recovered.nanosecond());
529 }
530 }
531
532 #[test]
533 fn test_datetime_components() {
534 let datetime = DateTime::new(2024, 3, 15, 14, 30, 45, 123456789).unwrap();
535
536 assert_eq!(datetime.year(), 2024);
537 assert_eq!(datetime.month(), 3);
538 assert_eq!(datetime.day(), 15);
539 assert_eq!(datetime.hour(), 14);
540 assert_eq!(datetime.minute(), 30);
541 assert_eq!(datetime.second(), 45);
542 assert_eq!(datetime.nanosecond(), 123456789);
543 }
544
545 #[test]
546 fn test_serde_roundtrip() {
547 let datetime = DateTime::new(2024, 3, 15, 14, 30, 45, 123456789).unwrap();
548 let json = to_string(&datetime).unwrap();
549 assert_eq!(json, "\"2024-03-15T14:30:45.123456789Z\"");
550
551 let recovered: DateTime = from_str(&json).unwrap();
552 assert_eq!(datetime, recovered);
553 }
554
555 fn assert_datetime_overflow<T: Debug>(result: Result<T, Box<TypeError>>) {
556 let err = result.expect_err("expected DateTimeOverflow error");
557 match *err {
558 TypeError::Temporal {
559 kind: TemporalKind::DateTimeOverflow {
560 ..
561 },
562 ..
563 } => {}
564 other => panic!("expected DateTimeOverflow, got: {:?}", other),
565 }
566 }
567
568 #[test]
569 fn test_from_timestamp_nanos_overflow() {
570 let huge: u128 = u64::MAX as u128 + 1;
571 assert_datetime_overflow(DateTime::from_timestamp_nanos(huge));
572 }
573
574 #[test]
575 fn test_from_timestamp_nanos_max_u64_ok() {
576 let dt = DateTime::from_timestamp_nanos(u64::MAX as u128).unwrap();
577 assert_eq!(dt.to_nanos(), u64::MAX);
578 }
579
580 #[test]
581 fn test_from_timestamp_large_value_overflow() {
582 assert_datetime_overflow(DateTime::from_timestamp(i64::MAX));
583 }
584
585 #[test]
586 fn test_from_timestamp_negative_overflow() {
587 assert_datetime_overflow(DateTime::from_timestamp(-1));
588 }
589
590 #[test]
591 fn test_from_timestamp_millis_overflow() {
592 assert_datetime_overflow(DateTime::from_timestamp_millis(u64::MAX));
593 }
594
595 #[test]
596 fn test_from_timestamp_millis_boundary_ok() {
597 let dt = DateTime::from_timestamp_millis(1_700_000_000_000).unwrap();
598 assert!(dt.to_nanos() > 0);
599 }
600
601 #[test]
602 fn test_timestamp_nanos_large_value_returns_err() {
603 let dt = DateTime::from_nanos(i64::MAX as u64 + 1);
604 assert_datetime_overflow(dt.timestamp_nanos());
605 }
606
607 #[test]
608 fn test_timestamp_nanos_within_range_ok() {
609 let dt = DateTime::from_nanos(i64::MAX as u64);
610 assert_eq!(dt.timestamp_nanos().unwrap(), i64::MAX);
611 }
612
613 #[test]
614 fn test_try_date_max_nanos_ok() {
615 let dt = DateTime::from_nanos(u64::MAX);
617 let date = dt.try_date().unwrap();
618 assert!(date.year() > 2500);
619 }
620
621 #[test]
622 fn test_add_duration_overflow() {
623 let dt = DateTime::from_nanos(u64::MAX - 1);
624 let dur = Duration::from_days(1).unwrap();
625 assert_datetime_overflow(dt.add_duration(&dur));
626 }
627
628 #[test]
629 fn test_add_duration_before_epoch() {
630 let dt = DateTime::new(1970, 1, 1, 0, 0, 0, 0).unwrap();
631 let dur = Duration::from_seconds(-1).unwrap();
632 assert_datetime_overflow(dt.add_duration(&dur));
633 }
634
635 #[test]
636 fn test_add_duration_negative_nanos_borrows_from_days() {
637 let dt = DateTime::new(2024, 3, 15, 0, 0, 30, 0).unwrap();
638 let dur = Duration::from_seconds(-60).unwrap();
639 let result = dt.add_duration(&dur).unwrap();
640 assert_eq!(result.year(), 2024);
641 assert_eq!(result.month(), 3);
642 assert_eq!(result.day(), 14);
643 assert_eq!(result.hour(), 23);
644 assert_eq!(result.minute(), 59);
645 assert_eq!(result.second(), 30);
646 }
647
648 #[test]
649 fn test_add_duration_nanos_overflow_into_next_day() {
650 let dt = DateTime::new(2024, 3, 15, 23, 59, 30, 0).unwrap();
651 let dur = Duration::from_seconds(60).unwrap();
652 let result = dt.add_duration(&dur).unwrap();
653 assert_eq!(result.year(), 2024);
654 assert_eq!(result.month(), 3);
655 assert_eq!(result.day(), 16);
656 assert_eq!(result.hour(), 0);
657 assert_eq!(result.minute(), 0);
658 assert_eq!(result.second(), 30);
659 }
660}