1use crate::result::DateTimeRangeError;
4use core::fmt;
5
6#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
24pub struct DateTime {
25 datepart: u16,
26 timepart: u16,
27}
28
29impl fmt::Debug for DateTime {
30 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31 if *self == Self::default() {
32 return f.write_str("DateTime::default()");
33 }
34 f.write_fmt(format_args!(
35 "DateTime::from_date_and_time({}, {}, {}, {}, {}, {})?",
36 self.year(),
37 self.month(),
38 self.day(),
39 self.hour(),
40 self.minute(),
41 self.second()
42 ))
43 }
44}
45
46#[cfg(feature = "_arbitrary")]
47impl arbitrary::Arbitrary<'_> for DateTime {
48 fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
49 const MAX_DOS_SECONDS: u16 = 58;
52
53 let year: u16 = u.int_in_range(1980..=2107)?;
54 let month: u16 = u.int_in_range(1..=12)?;
55 let day: u16 = u.int_in_range(1..=31)?;
56 let datepart = day | (month << 5) | ((year - 1980) << 9);
57 let hour: u16 = u.int_in_range(0..=23)?;
58 let minute: u16 = u.int_in_range(0..=59)?;
59 let second: u16 = u.int_in_range(0..=MAX_DOS_SECONDS)?;
60 let timepart = (second >> 1) | (minute << 5) | (hour << 11);
61 Ok(DateTime { datepart, timepart })
62 }
63}
64
65#[cfg(feature = "chrono")]
66impl TryFrom<chrono::NaiveDateTime> for DateTime {
67 type Error = DateTimeRangeError;
68
69 fn try_from(value: chrono::NaiveDateTime) -> Result<Self, Self::Error> {
70 use chrono::{Datelike, Timelike};
71
72 DateTime::from_date_and_time(
73 value.year().try_into()?,
74 value.month().try_into()?,
75 value.day().try_into()?,
76 value.hour().try_into()?,
77 value.minute().try_into()?,
78 value.second().try_into()?,
79 )
80 }
81}
82
83#[cfg(feature = "chrono")]
84impl TryFrom<DateTime> for chrono::NaiveDateTime {
85 type Error = DateTimeRangeError;
86
87 fn try_from(value: DateTime) -> Result<Self, Self::Error> {
88 let date = chrono::NaiveDate::from_ymd_opt(
89 value.year().into(),
90 value.month().into(),
91 value.day().into(),
92 )
93 .ok_or(DateTimeRangeError)?;
94 let time = chrono::NaiveTime::from_hms_opt(
95 value.hour().into(),
96 value.minute().into(),
97 value.second().into(),
98 )
99 .ok_or(DateTimeRangeError)?;
100 Ok(chrono::NaiveDateTime::new(date, time))
101 }
102}
103
104#[cfg(feature = "jiff-02")]
105impl TryFrom<jiff::civil::DateTime> for DateTime {
106 type Error = DateTimeRangeError;
107
108 fn try_from(value: jiff::civil::DateTime) -> Result<Self, Self::Error> {
109 Self::from_date_and_time(
110 value.year().try_into()?,
111 value.month() as u8,
112 value.day() as u8,
113 value.hour() as u8,
114 value.minute() as u8,
115 value.second() as u8,
116 )
117 }
118}
119
120#[cfg(feature = "jiff-02")]
121impl TryFrom<DateTime> for jiff::civil::DateTime {
122 type Error = jiff::Error;
123
124 fn try_from(value: DateTime) -> Result<Self, Self::Error> {
125 Self::new(
126 value.year() as i16,
127 value.month() as i8,
128 value.day() as i8,
129 value.hour() as i8,
130 value.minute() as i8,
131 value.second() as i8,
132 0,
133 )
134 }
135}
136
137impl TryFrom<(u16, u16)> for DateTime {
138 type Error = DateTimeRangeError;
139
140 #[inline]
141 fn try_from(values: (u16, u16)) -> Result<Self, Self::Error> {
142 Self::try_from_msdos(values.0, values.1)
143 }
144}
145
146impl From<DateTime> for (u16, u16) {
147 #[inline]
148 fn from(dt: DateTime) -> Self {
149 (dt.datepart(), dt.timepart())
150 }
151}
152
153impl Default for DateTime {
154 fn default() -> DateTime {
156 DateTime::DEFAULT
157 }
158}
159
160impl fmt::Display for DateTime {
161 #[inline]
162 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
163 write!(
164 f,
165 "{:04}-{:02}-{:02} {:02}:{:02}:{:02}",
166 self.year(),
167 self.month(),
168 self.day(),
169 self.hour(),
170 self.minute(),
171 self.second()
172 )
173 }
174}
175
176impl DateTime {
177 pub const DEFAULT: Self = DateTime {
179 datepart: 0b0000_0000_0010_0001,
180 timepart: 0,
181 };
182
183 #[cfg(feature = "time")]
185 #[must_use]
186 pub fn default_for_write() -> Self {
187 let now = time::OffsetDateTime::now_utc();
188 time::PrimitiveDateTime::new(now.date(), now.time())
189 .try_into()
190 .unwrap_or_else(|_| DateTime::default())
191 }
192
193 #[cfg(not(feature = "time"))]
195 #[must_use]
196 pub fn default_for_write() -> Self {
197 DateTime::default()
198 }
199
200 #[cfg(feature = "chrono")]
201 pub(crate) fn datetime_to_systemtime(&self) -> Option<std::time::SystemTime> {
203 if let Some(chrono_datetime) = self.generate_chrono_datetime() {
204 let time = chrono::DateTime::<chrono::Utc>::from_naive_utc_and_offset(
205 chrono_datetime,
206 chrono::Utc,
207 );
208 return Some(time.into());
209 }
210 None
211 }
212
213 #[cfg(feature = "chrono")]
214 fn generate_chrono_datetime(&self) -> Option<chrono::NaiveDateTime> {
216 if let Some(chrono_date) = chrono::NaiveDate::from_ymd_opt(
217 self.year().into(),
218 self.month().into(),
219 self.day().into(),
220 ) && let Some(chrono_datetime) = chrono_date.and_hms_opt(
221 self.hour().into(),
222 self.minute().into(),
223 self.second().into(),
224 ) {
225 return Some(chrono_datetime);
226 }
227 None
228 }
229
230 #[must_use]
235 pub const unsafe fn from_msdos_unchecked(datepart: u16, timepart: u16) -> DateTime {
236 DateTime { datepart, timepart }
237 }
238
239 pub(crate) fn is_leap_year(year: u16) -> bool {
240 year.is_multiple_of(4) && (!year.is_multiple_of(100) || year.is_multiple_of(400))
241 }
242
243 pub fn try_from_msdos(datepart: u16, timepart: u16) -> Result<DateTime, DateTimeRangeError> {
246 let seconds = (timepart & 0b0000_0000_0001_1111) << 1;
247 let minutes = (timepart & 0b0000_0111_1110_0000) >> 5;
248 let hours = (timepart & 0b1111_1000_0000_0000) >> 11;
249 let days = datepart & 0b0000_0000_0001_1111;
250 let months = (datepart & 0b0000_0001_1110_0000) >> 5;
251 let years = (datepart & 0b1111_1110_0000_0000) >> 9;
252 Self::from_date_and_time(
253 years.checked_add(1980).ok_or(DateTimeRangeError)?,
254 months.try_into()?,
255 days.try_into()?,
256 hours.try_into()?,
257 minutes.try_into()?,
258 seconds.try_into()?,
259 )
260 }
261
262 pub fn from_date_and_time(
272 year: u16,
273 month: u8,
274 day: u8,
275 hour: u8,
276 minute: u8,
277 second: u8,
278 ) -> Result<DateTime, DateTimeRangeError> {
279 if (1980..=2107).contains(&year)
280 && (1..=12).contains(&month)
281 && (1..=31).contains(&day)
282 && hour <= 23
283 && minute <= 59
284 && second <= 60
285 {
286 let second = second.min(58);
288 let max_day = match month {
289 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
290 4 | 6 | 9 | 11 => 30,
291 2 if Self::is_leap_year(year) => 29,
292 2 => 28,
293 _ => unreachable!(),
294 };
295 if day > max_day {
296 return Err(DateTimeRangeError);
297 }
298 let datepart = u16::from(day) | (u16::from(month) << 5) | ((year - 1980) << 9);
299 let timepart =
300 (u16::from(second) >> 1) | (u16::from(minute) << 5) | (u16::from(hour) << 11);
301 Ok(DateTime { datepart, timepart })
302 } else {
303 Err(DateTimeRangeError)
304 }
305 }
306
307 #[must_use]
309 pub fn is_valid(&self) -> bool {
310 Self::try_from_msdos(self.datepart, self.timepart).is_ok()
311 }
312
313 #[must_use]
315 pub const fn timepart(&self) -> u16 {
316 self.timepart
317 }
318
319 #[must_use]
321 pub const fn datepart(&self) -> u16 {
322 self.datepart
323 }
324
325 #[must_use]
327 pub const fn year(&self) -> u16 {
328 (self.datepart >> 9) + 1980
329 }
330
331 #[must_use]
337 pub const fn month(&self) -> u8 {
338 ((self.datepart & 0b0000_0001_1110_0000) >> 5) as u8
339 }
340
341 #[must_use]
347 pub const fn day(&self) -> u8 {
348 (self.datepart & 0b0000_0000_0001_1111) as u8
349 }
350
351 #[must_use]
357 pub const fn hour(&self) -> u8 {
358 (self.timepart >> 11) as u8
359 }
360
361 #[must_use]
367 pub const fn minute(&self) -> u8 {
368 ((self.timepart & 0b0000_0111_1110_0000) >> 5) as u8
369 }
370
371 #[must_use]
377 pub const fn second(&self) -> u8 {
378 ((self.timepart & 0b0000_0000_0001_1111) << 1) as u8
379 }
380}
381
382#[cfg(feature = "time")]
383impl TryFrom<time::PrimitiveDateTime> for DateTime {
384 type Error = DateTimeRangeError;
385
386 fn try_from(dt: time::PrimitiveDateTime) -> Result<Self, Self::Error> {
387 Self::from_date_and_time(
388 dt.year().try_into()?,
389 dt.month().into(),
390 dt.day(),
391 dt.hour(),
392 dt.minute(),
393 dt.second(),
394 )
395 }
396}
397
398#[cfg(feature = "time")]
399impl TryFrom<DateTime> for time::PrimitiveDateTime {
400 type Error = time::error::ComponentRange;
401
402 fn try_from(dt: DateTime) -> Result<Self, Self::Error> {
403 use time::{Date, Month, Time};
404 let date =
405 Date::from_calendar_date(i32::from(dt.year()), Month::try_from(dt.month())?, dt.day())?;
406 let time = Time::from_hms(dt.hour(), dt.minute(), dt.second())?;
407 Ok(time::PrimitiveDateTime::new(date, time))
408 }
409}
410
411#[cfg(test)]
412mod tests {
413 #[test]
414 #[allow(clippy::unusual_byte_groupings)]
415 fn datetime_default() {
416 use super::DateTime;
417 let dt = DateTime::default();
418 assert_eq!(dt.timepart(), 0);
419 assert_eq!(dt.datepart(), 0b0000000_0001_00001);
420 }
421
422 #[test]
423 #[allow(clippy::unusual_byte_groupings)]
424 fn datetime_max() {
425 use super::DateTime;
426 let dt = DateTime::from_date_and_time(2107, 12, 31, 23, 59, 58).unwrap();
427 assert_eq!(dt.timepart(), 0b10111_111011_11101);
428 assert_eq!(dt.datepart(), 0b1111111_1100_11111);
429 }
430
431 #[test]
432 fn datetime_equality() {
433 use super::DateTime;
434
435 let dt = DateTime::from_date_and_time(2018, 11, 17, 10, 38, 30).unwrap();
436 assert_eq!(
437 dt,
438 DateTime::from_date_and_time(2018, 11, 17, 10, 38, 30).unwrap()
439 );
440 assert_ne!(dt, DateTime::default());
441 }
442
443 #[test]
444 fn datetime_order() {
445 use std::cmp::Ordering;
446
447 use super::DateTime;
448
449 let dt = DateTime::from_date_and_time(2018, 11, 17, 10, 38, 30).unwrap();
450 assert_eq!(
451 dt.cmp(&DateTime::from_date_and_time(2018, 11, 17, 10, 38, 30).unwrap()),
452 Ordering::Equal
453 );
454 assert!(dt < DateTime::from_date_and_time(2019, 11, 17, 10, 38, 30).unwrap());
456 assert!(dt > DateTime::from_date_and_time(2017, 11, 17, 10, 38, 30).unwrap());
457 assert!(dt < DateTime::from_date_and_time(2018, 12, 17, 10, 38, 30).unwrap());
459 assert!(dt > DateTime::from_date_and_time(2018, 10, 17, 10, 38, 30).unwrap());
460 assert!(dt < DateTime::from_date_and_time(2018, 11, 18, 10, 38, 30).unwrap());
462 assert!(dt > DateTime::from_date_and_time(2018, 11, 16, 10, 38, 30).unwrap());
463 assert!(dt < DateTime::from_date_and_time(2018, 11, 17, 11, 38, 30).unwrap());
465 assert!(dt > DateTime::from_date_and_time(2018, 11, 17, 9, 38, 30).unwrap());
466 assert!(dt < DateTime::from_date_and_time(2018, 11, 17, 10, 39, 30).unwrap());
468 assert!(dt > DateTime::from_date_and_time(2018, 11, 17, 10, 37, 30).unwrap());
469 assert!(dt < DateTime::from_date_and_time(2018, 11, 17, 10, 38, 32).unwrap());
471 assert_eq!(
472 dt.cmp(&DateTime::from_date_and_time(2018, 11, 17, 10, 38, 31).unwrap()),
473 Ordering::Equal
474 );
475 assert!(dt > DateTime::from_date_and_time(2018, 11, 17, 10, 38, 29).unwrap());
476 assert!(dt > DateTime::from_date_and_time(2018, 11, 17, 10, 38, 28).unwrap());
477 }
478
479 #[test]
480 fn datetime_display() {
481 use super::DateTime;
482
483 assert_eq!(format!("{}", DateTime::default()), "1980-01-01 00:00:00");
484 assert_eq!(
485 format!(
486 "{}",
487 DateTime::from_date_and_time(2018, 11, 17, 10, 38, 30).unwrap()
488 ),
489 "2018-11-17 10:38:30"
490 );
491 assert_eq!(
492 format!(
493 "{}",
494 DateTime::from_date_and_time(2107, 12, 31, 23, 59, 58).unwrap()
495 ),
496 "2107-12-31 23:59:58"
497 );
498 }
499
500 #[test]
501 fn datetime_bounds() {
502 use super::DateTime;
503
504 assert!(DateTime::from_date_and_time(2000, 1, 1, 23, 59, 60).is_ok());
505 assert!(DateTime::from_date_and_time(2000, 1, 1, 24, 0, 0).is_err());
506 assert!(DateTime::from_date_and_time(2000, 1, 1, 0, 60, 0).is_err());
507 assert!(DateTime::from_date_and_time(2000, 1, 1, 0, 0, 61).is_err());
508
509 assert!(DateTime::from_date_and_time(2107, 12, 31, 0, 0, 0).is_ok());
510 assert!(DateTime::from_date_and_time(1980, 1, 1, 0, 0, 0).is_ok());
511 assert!(DateTime::from_date_and_time(1979, 1, 1, 0, 0, 0).is_err());
512 assert!(DateTime::from_date_and_time(1980, 0, 1, 0, 0, 0).is_err());
513 assert!(DateTime::from_date_and_time(1980, 1, 0, 0, 0, 0).is_err());
514 assert!(DateTime::from_date_and_time(2108, 12, 31, 0, 0, 0).is_err());
515 assert!(DateTime::from_date_and_time(2107, 13, 31, 0, 0, 0).is_err());
516 assert!(DateTime::from_date_and_time(2107, 12, 32, 0, 0, 0).is_err());
517
518 assert!(DateTime::from_date_and_time(2018, 1, 31, 0, 0, 0).is_ok());
519 assert!(DateTime::from_date_and_time(2018, 2, 28, 0, 0, 0).is_ok());
520 assert!(DateTime::from_date_and_time(2018, 2, 29, 0, 0, 0).is_err());
521 assert!(DateTime::from_date_and_time(2018, 3, 31, 0, 0, 0).is_ok());
522 assert!(DateTime::from_date_and_time(2018, 4, 30, 0, 0, 0).is_ok());
523 assert!(DateTime::from_date_and_time(2018, 4, 31, 0, 0, 0).is_err());
524 assert!(DateTime::from_date_and_time(2018, 5, 31, 0, 0, 0).is_ok());
525 assert!(DateTime::from_date_and_time(2018, 6, 30, 0, 0, 0).is_ok());
526 assert!(DateTime::from_date_and_time(2018, 6, 31, 0, 0, 0).is_err());
527 assert!(DateTime::from_date_and_time(2018, 7, 31, 0, 0, 0).is_ok());
528 assert!(DateTime::from_date_and_time(2018, 8, 31, 0, 0, 0).is_ok());
529 assert!(DateTime::from_date_and_time(2018, 9, 30, 0, 0, 0).is_ok());
530 assert!(DateTime::from_date_and_time(2018, 9, 31, 0, 0, 0).is_err());
531 assert!(DateTime::from_date_and_time(2018, 10, 31, 0, 0, 0).is_ok());
532 assert!(DateTime::from_date_and_time(2018, 11, 30, 0, 0, 0).is_ok());
533 assert!(DateTime::from_date_and_time(2018, 11, 31, 0, 0, 0).is_err());
534 assert!(DateTime::from_date_and_time(2018, 12, 31, 0, 0, 0).is_ok());
535
536 assert!(DateTime::from_date_and_time(2024, 2, 29, 0, 0, 0).is_ok());
538 assert!(DateTime::from_date_and_time(2000, 2, 29, 0, 0, 0).is_ok());
540 assert!(DateTime::from_date_and_time(2100, 2, 29, 0, 0, 0).is_err());
542 }
543
544 #[cfg(feature = "time")]
545 #[test]
546 fn datetime_try_from_primitive_datetime() {
547 use time::macros::datetime;
548
549 use super::DateTime;
550
551 let dt = DateTime::try_from(datetime!(2018-11-17 10:38:30)).unwrap();
553 assert_eq!(dt.year(), 2018);
554 assert_eq!(dt.month(), 11);
555 assert_eq!(dt.day(), 17);
556 assert_eq!(dt.hour(), 10);
557 assert_eq!(dt.minute(), 38);
558 assert_eq!(dt.second(), 30);
559 }
560
561 #[cfg(feature = "time")]
562 #[test]
563 fn datetime_try_from_bounds() {
564 use super::DateTime;
565 use time::macros::datetime;
566
567 assert!(DateTime::try_from(datetime!(1979-12-31 23:59:59)).is_err());
569
570 assert!(DateTime::try_from(datetime!(1980-01-01 00:00:00)).is_ok());
572
573 assert!(DateTime::try_from(datetime!(2107-12-31 23:59:59)).is_ok());
575
576 assert!(DateTime::try_from(datetime!(2108-01-01 00:00:00)).is_err());
578 }
579
580 #[cfg(feature = "time")]
581 #[test]
582 fn primitive_datetime_try_from_datetime() {
583 use time::PrimitiveDateTime;
584 use time::macros::datetime;
585
586 use super::DateTime;
587
588 let dt =
590 PrimitiveDateTime::try_from(DateTime::try_from_msdos(0x4D71, 0x54CF).unwrap()).unwrap();
591 assert_eq!(dt, datetime!(2018-11-17 10:38:30));
592 }
593
594 #[cfg(feature = "time")]
595 #[test]
596 fn primitive_datetime_try_from_bounds() {
597 use super::DateTime;
598 use time::PrimitiveDateTime;
599
600 assert!(
602 PrimitiveDateTime::try_from(unsafe { DateTime::from_msdos_unchecked(0x0000, 0x0000) })
603 .is_err()
604 );
605
606 assert!(
608 PrimitiveDateTime::try_from(unsafe { DateTime::from_msdos_unchecked(0xFFFF, 0xFFFF) })
609 .is_err()
610 );
611 }
612
613 #[cfg(feature = "jiff-02")]
614 #[test]
615 fn datetime_try_from_civil_datetime() {
616 use jiff::civil;
617
618 use super::DateTime;
619
620 let dt = DateTime::try_from(civil::datetime(2018, 11, 17, 10, 38, 30, 0)).unwrap();
622 assert_eq!(dt.year(), 2018);
623 assert_eq!(dt.month(), 11);
624 assert_eq!(dt.day(), 17);
625 assert_eq!(dt.hour(), 10);
626 assert_eq!(dt.minute(), 38);
627 assert_eq!(dt.second(), 30);
628 }
629
630 #[cfg(feature = "jiff-02")]
631 #[test]
632 fn datetime_try_from_civil_datetime_bounds() {
633 use jiff::civil;
634
635 use super::DateTime;
636
637 assert!(DateTime::try_from(civil::datetime(1979, 12, 31, 23, 59, 59, 0)).is_err());
639
640 assert!(DateTime::try_from(civil::datetime(1980, 1, 1, 0, 0, 0, 0)).is_ok());
642
643 assert!(DateTime::try_from(civil::datetime(2107, 12, 31, 23, 59, 59, 0)).is_ok());
645
646 assert!(DateTime::try_from(civil::datetime(2108, 1, 1, 0, 0, 0, 0)).is_err());
648 }
649
650 #[cfg(feature = "jiff-02")]
651 #[test]
652 fn civil_datetime_try_from_datetime() {
653 use jiff::civil;
654
655 use super::DateTime;
656
657 let dt =
659 civil::DateTime::try_from(DateTime::try_from_msdos(0x4D71, 0x54CF).unwrap()).unwrap();
660 assert_eq!(dt, civil::datetime(2018, 11, 17, 10, 38, 30, 0));
661 }
662
663 #[cfg(feature = "jiff-02")]
664 #[test]
665 fn civil_datetime_try_from_datetime_bounds() {
666 use jiff::civil;
667
668 use super::DateTime;
669
670 assert!(
672 civil::DateTime::try_from(unsafe { DateTime::from_msdos_unchecked(0x0000, 0x0000) })
673 .is_err()
674 );
675
676 assert!(
678 civil::DateTime::try_from(unsafe { DateTime::from_msdos_unchecked(0xFFFF, 0xFFFF) })
679 .is_err()
680 );
681 }
682
683 #[test]
684 fn time_conversion() {
685 use super::DateTime;
686 let dt = DateTime::try_from_msdos(0x4D71, 0x54CF).unwrap();
687 assert_eq!(dt.year(), 2018);
688 assert_eq!(dt.month(), 11);
689 assert_eq!(dt.day(), 17);
690 assert_eq!(dt.hour(), 10);
691 assert_eq!(dt.minute(), 38);
692 assert_eq!(dt.second(), 30);
693
694 let dt = DateTime::try_from((0x4D71, 0x54CF)).unwrap();
695 assert_eq!(dt.year(), 2018);
696 assert_eq!(dt.month(), 11);
697 assert_eq!(dt.day(), 17);
698 assert_eq!(dt.hour(), 10);
699 assert_eq!(dt.minute(), 38);
700 assert_eq!(dt.second(), 30);
701
702 assert_eq!(<(u16, u16)>::from(dt), (0x4D71, 0x54CF));
703 }
704
705 #[test]
706 fn time_out_of_bounds() {
707 use super::DateTime;
708 let dt = unsafe { DateTime::from_msdos_unchecked(0xFFFF, 0xFFFF) };
709 assert_eq!(dt.year(), 2107);
710 assert_eq!(dt.month(), 15);
711 assert_eq!(dt.day(), 31);
712 assert_eq!(dt.hour(), 31);
713 assert_eq!(dt.minute(), 63);
714 assert_eq!(dt.second(), 62);
715
716 let dt = unsafe { DateTime::from_msdos_unchecked(0x0000, 0x0000) };
717 assert_eq!(dt.year(), 1980);
718 assert_eq!(dt.month(), 0);
719 assert_eq!(dt.day(), 0);
720 assert_eq!(dt.hour(), 0);
721 assert_eq!(dt.minute(), 0);
722 assert_eq!(dt.second(), 0);
723 }
724
725 #[cfg(feature = "time")]
726 #[test]
727 fn time_at_january() {
728 use super::DateTime;
729 use time::{OffsetDateTime, PrimitiveDateTime};
730
731 let clock = OffsetDateTime::from_unix_timestamp(1_577_836_800).unwrap();
733
734 assert!(DateTime::try_from(PrimitiveDateTime::new(clock.date(), clock.time())).is_ok());
735 }
736
737 #[test]
738 fn test_is_leap_year() {
739 use crate::DateTime;
740 assert!(DateTime::is_leap_year(2000));
741 assert!(!DateTime::is_leap_year(2026));
742 assert!(!DateTime::is_leap_year(2027));
743 assert!(DateTime::is_leap_year(2028));
744 assert!(DateTime::is_leap_year(1600));
745 assert!(DateTime::is_leap_year(2400));
746 assert!(!DateTime::is_leap_year(1900));
747 assert!(!DateTime::is_leap_year(2100));
748 }
749}