1#![doc(html_root_url = "https://docs.rs/datetime-rs/latest")]
7#![cfg_attr(docsrs, feature(doc_cfg))]
8
9use std::cmp::Ordering;
10use std::fmt;
11use std::str::FromStr;
12use std::time::SystemTime;
13
14use format::FormattedDateTime;
15use strptime::ParseError;
16use strptime::ParseResult;
17use strptime::Parser;
18use strptime::RawDateTime;
19
20#[macro_export]
25macro_rules! datetime {
26 ($y:literal-$m:literal-$d:literal $h:literal : $mi:literal : $s:literal) => {{
27 #[allow(clippy::zero_prefixed_literal)]
28 {
29 const __SN: (u8, u32) = $crate::__private::parse_second(::core::stringify!($s));
30 $crate::DateTime::ymd($y, $m, $d).hms($h, $mi, __SN.0).nanos(__SN.1).build()
31 }
32 }};
33 ($y:literal-$m:literal-$d:literal $h:literal : $mi:literal : $s:literal $($tz:ident)::+) => {{
34 #[cfg(feature = "tz")]
35 #[allow(clippy::zero_prefixed_literal)]
36 {
37 const __SN: (u8, u32) = $crate::__private::parse_second(::core::stringify!($s));
38 match $crate::DateTime::ymd($y, $m, $d)
39 .hms($h, $mi, __SN.0)
40 .nanos(__SN.1)
41 .tz($crate::tz::$($tz)::+)
42 {
43 Ok(dt) => dt.build(),
44 Err(_) => panic!("invalid date/time and time zone combination"),
45 }
46 }
47 #[cfg(not(feature = "tz"))]
48 {
49 compile_error!("The `tz` feature must be enabled to specify a time zone.");
50 }
51 }};
52}
53
54#[doc(hidden)]
56pub mod __private {
57 pub const fn parse_second(s: &str) -> (u8, u32) {
63 let b = s.as_bytes();
64 let mut sec: u8 = 0;
65 let mut i = 0;
66 while i < b.len() {
67 let c = b[i];
68 if c == b'.' {
69 break;
70 }
71 if c == b'_' {
72 i += 1;
73 continue;
74 }
75 assert!(c >= b'0' && c <= b'9', "datetime! second must be a numeric literal");
76 sec = sec * 10 + (c - b'0');
77 i += 1;
78 }
79 if i == b.len() {
80 return (sec, 0);
81 }
82 i += 1; let mut nanos: u32 = 0;
84 let mut digits = 0;
85 while i < b.len() && digits < 9 {
86 let c = b[i];
87 if c == b'_' {
88 i += 1;
89 continue;
90 }
91 if c < b'0' || c > b'9' {
92 break;
93 }
94 nanos = nanos * 10 + (c - b'0') as u32;
95 i += 1;
96 digits += 1;
97 }
98 while digits < 9 {
99 nanos *= 10;
100 digits += 1;
101 }
102 (sec, nanos)
103 }
104}
105
106#[cfg(feature = "diesel-pg")]
107mod diesel_pg;
108#[cfg(feature = "duckdb")]
109mod duckdb;
110mod format;
111pub mod interval;
112#[cfg(feature = "serde")]
113mod serde;
114
115pub use date::Date;
116pub use date::Weekday;
117pub use date::date;
118
119#[cfg(feature = "tz")]
123#[cfg_attr(docsrs, doc(cfg(feature = "tz")))]
124pub mod tz {
125 pub use date::tz::*;
126
127 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
128 pub(crate) enum TimeZone {
129 Unspecified,
130 Tz(crate::tz::TimeZoneRef<'static>),
131 FixedOffset(i32),
132 }
133
134 impl TimeZone {
135 pub(crate) const fn ut_offset(&self, timestamp: i64) -> TzResult<i32> {
136 match self {
137 Self::Unspecified => Ok(0),
138 Self::FixedOffset(offset) => Ok(*offset),
139 Self::Tz(tz) => match tz.find_local_time_type(timestamp) {
140 Ok(t) => Ok(t.ut_offset()),
141 Err(e) => Err(e),
142 },
143 }
144 }
145 }
146}
147
148#[derive(Clone, Copy, Eq)]
150#[cfg_attr(feature = "diesel-pg", derive(diesel::AsExpression, diesel::FromSqlRow))]
151#[cfg_attr(feature = "diesel-pg", diesel(
152 sql_type = diesel::sql_types::Timestamp,
153 sql_type = diesel::sql_types::Timestamptz))]
154pub struct DateTime {
155 seconds: i64,
156 nanos: u32,
157 #[cfg(feature = "tz")]
158 tz: tz::TimeZone,
159}
160
161impl DateTime {
162 pub const fn ymd(year: i16, month: u8, day: u8) -> DateTimeBuilder {
164 DateTimeBuilder {
165 date: Date::new(year, month, day),
166 seconds: 0,
167 nanos: 0,
168 #[cfg(feature = "tz")]
169 tz: tz::TimeZone::Unspecified,
170 offset: 0,
171 }
172 }
173
174 pub const fn from_timestamp(timestamp: i64, nanos: u32) -> Self {
176 let mut timestamp = timestamp;
177 let mut nanos = nanos;
178 while nanos >= 1_000_000_000 {
179 nanos -= 1_000_000_000;
180 timestamp += 1;
181 }
182 Self {
183 seconds: timestamp,
184 nanos,
185 #[cfg(feature = "tz")]
186 tz: tz::TimeZone::Unspecified,
187 }
188 }
189
190 pub const fn from_timestamp_millis(millis: i64) -> Self {
192 Self::from_timestamp(millis.div_euclid(1_000), millis.rem_euclid(1_000) as u32)
193 }
194
195 pub const fn from_timestamp_micros(micros: i64) -> Self {
197 Self::from_timestamp(micros.div_euclid(1_000_000), micros.rem_euclid(1_000_000) as u32)
198 }
199
200 pub const fn from_timestamp_nanos(nanos: i128) -> Self {
202 Self::from_timestamp(
203 nanos.div_euclid(1_000_000_000) as i64,
204 nanos.rem_euclid(1_000_000_000) as u32,
205 )
206 }
207
208 pub fn now() -> Self {
214 let dur = SystemTime::now()
215 .duration_since(SystemTime::UNIX_EPOCH)
216 .expect("System clock set prior to January 1, 1970");
217 Self::from_timestamp(dur.as_secs() as i64, dur.subsec_nanos())
218 }
219}
220
221#[cfg(feature = "tz")]
222impl DateTime {
223 #[inline]
229 pub const fn with_tz(mut self, tz: tz::TimeZoneRef<'static>) -> Self {
230 self.tz = tz::TimeZone::Tz(tz);
231 self
232 }
233
234 #[inline]
239 pub const fn in_tz(mut self, tz: tz::TimeZoneRef<'static>) -> Self {
240 let existing_ut_offset = match self.tz.ut_offset(self.seconds) {
241 Ok(offset) => offset as i64,
242 Err(_) => panic!("Invalid time zone."),
243 };
244 let desired_ut_offset = match tz.find_local_time_type(self.seconds) {
245 Ok(t) => t.ut_offset() as i64,
246 Err(_) => panic!("Invalid time zone for this timestamp."),
247 };
248 self.seconds += existing_ut_offset - desired_ut_offset;
249 self.tz = tz::TimeZone::Tz(tz);
250 self
251 }
252}
253
254impl DateTime {
256 #[inline]
258 pub const fn year(&self) -> i16 {
259 Date::from_timestamp(self.tz_adjusted_seconds()).year()
260 }
261
262 #[inline]
264 pub const fn month(&self) -> u8 {
265 Date::from_timestamp(self.tz_adjusted_seconds()).month()
266 }
267
268 #[inline]
270 pub const fn day(&self) -> u8 {
271 Date::from_timestamp(self.tz_adjusted_seconds()).day()
272 }
273
274 #[inline]
276 pub const fn weekday(&self) -> Weekday {
277 Date::from_timestamp(self.tz_adjusted_seconds()).weekday()
278 }
279
280 #[inline]
282 pub const fn hour(&self) -> u8 {
283 (self.tz_adjusted_seconds() % 86_400 / 3_600) as u8
284 }
285
286 #[inline]
288 pub const fn minute(&self) -> u8 {
289 ((self.tz_adjusted_seconds() % 3600) / 60) as u8
290 }
291
292 #[inline]
294 pub const fn second(&self) -> u8 {
295 (self.tz_adjusted_seconds() % 60) as u8
296 }
297
298 #[inline]
300 pub const fn nanosecond(&self) -> u32 {
301 self.nanos
302 }
303
304 #[inline]
306 pub const fn day_of_year(&self) -> u16 {
307 self.date().day_of_year()
308 }
309
310 #[inline]
312 pub const fn date(&self) -> Date {
313 Date::from_timestamp(self.tz_adjusted_seconds())
314 }
315
316 #[inline]
318 pub const fn as_seconds(&self) -> i64 {
319 self.seconds
320 }
321
322 #[inline]
324 pub const fn as_milliseconds(&self) -> i64 {
325 self.seconds * 1_000 + (self.nanos / 1_000_000) as i64
326 }
327
328 #[inline]
330 pub const fn as_microseconds(&self) -> i64 {
331 self.seconds * 1_000_000 + (self.nanos / 1_000) as i64
332 }
333
334 #[inline]
336 pub const fn as_nanoseconds(&self) -> i128 {
337 self.seconds as i128 * 1_000_000_000 + self.nanos as i128
338 }
339
340 #[inline]
342 pub const fn precision(&self) -> Precision {
343 if self.nanos == 0 {
344 Precision::Second
345 } else if self.nanos % 1_000_000 == 0 {
346 Precision::Millisecond
347 } else if self.nanos % 1_000 == 0 {
348 Precision::Microsecond
349 } else {
350 Precision::Nanosecond
351 }
352 }
353
354 #[inline(always)]
357 const fn tz_adjusted_seconds(&self) -> i64 {
358 self.seconds + self.tz_offset()
359 }
360
361 const fn tz_offset(&self) -> i64 {
363 #[cfg(feature = "tz")]
364 {
365 match self.tz.ut_offset(self.seconds) {
366 Ok(offset) => offset as i64,
367 Err(_) => panic!("Invalid time zone"),
368 }
369 }
370 #[cfg(not(feature = "tz"))]
371 0
372 }
373}
374
375impl DateTime {
376 pub fn format(&self, format: &'static str) -> FormattedDateTime<'_> {
378 FormattedDateTime { dt: self, format }
379 }
380}
381
382impl DateTime {
383 pub fn parse(datetime_str: impl AsRef<str>, fmt: &'static str) -> ParseResult<Self> {
385 let parser = Parser::new(fmt);
386 parser.parse(datetime_str)?.try_into()
387 }
388}
389
390impl PartialEq for DateTime {
391 fn eq(&self, other: &Self) -> bool {
392 self.seconds == other.seconds && self.nanos == other.nanos
393 }
394}
395
396impl PartialOrd for DateTime {
397 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
398 Some(self.cmp(other))
399 }
400}
401
402impl Ord for DateTime {
403 fn cmp(&self, other: &Self) -> Ordering {
404 let seconds_cmp = self.seconds.cmp(&other.seconds);
405 match seconds_cmp {
406 Ordering::Equal => self.nanos.cmp(&other.nanos),
407 _ => seconds_cmp,
408 }
409 }
410}
411
412impl FromStr for DateTime {
413 type Err = ParseError;
414
415 #[rustfmt::skip]
416 fn from_str(s: &str) -> ParseResult<Self> {
417 if let Ok(dt) = Parser::new("%Y-%m-%dT%H:%M:%S").parse(s) { return dt.try_into(); }
419 if let Ok(dt) = Parser::new("%Y-%m-%dT%H:%M:%S%z").parse(s) { return dt.try_into(); }
420 if let Ok(dt) = Parser::new("%Y-%m-%d %H:%M:%S").parse(s) { return dt.try_into(); }
421 if let Ok(dt) = Parser::new("%Y-%m-%d %H:%M:%S%z").parse(s) { return dt.try_into(); }
422 if let Ok(dt) = Parser::new("%Y-%m-%dT%H:%M:%S%.6f").parse(s) { return dt.try_into(); }
423 if let Ok(dt) = Parser::new("%Y-%m-%dT%H:%M:%S%.6f%z").parse(s) { return dt.try_into(); }
424 if let Ok(dt) = Parser::new("%Y-%m-%d %H:%M:%S%.6f").parse(s) { return dt.try_into(); }
425 if let Ok(dt) = Parser::new("%Y-%m-%d %H:%M:%S%.6f%z").parse(s) { return dt.try_into(); }
426 if let Ok(dt) = Parser::new("%Y-%m-%dT%H:%M:%S%.9f").parse(s) { return dt.try_into(); }
427 if let Ok(dt) = Parser::new("%Y-%m-%dT%H:%M:%S%.9f%z").parse(s) { return dt.try_into(); }
428 if let Ok(dt) = Parser::new("%Y-%m-%d %H:%M:%S%.9f").parse(s) { return dt.try_into(); }
429 if let Ok(dt) = Parser::new("%Y-%m-%d %H:%M:%S%.9f%z").parse(s) { return dt.try_into(); }
430 if let Ok(dt) = Parser::new("%Y-%m-%d %H:%M:%SZ").parse(s) { return dt.try_into(); }
431 Parser::new("%Y-%m-%dT%H:%M:%SZ").parse(s)?.try_into()
432 }
433}
434
435impl TryFrom<RawDateTime> for DateTime {
436 type Error = ParseError;
437
438 fn try_from(value: RawDateTime) -> ParseResult<Self> {
439 let date = value.date()?;
440 let time = value.time().unwrap_or_default();
441 Ok(match time.utc_offset() {
442 #[cfg(feature = "tz")]
443 Some(utc_offset) => Self::ymd(date.year(), date.month(), date.day())
444 .hms(time.hour(), time.minute(), time.second())
445 .nanos(time.nanosecond() as u32)
446 .utc_offset(utc_offset)
447 .build(),
448 #[cfg(not(feature = "tz"))]
449 Some(_) => panic!("Enable the `tz` feature to parse datetimes with UTC offsets."),
450 None => Self::ymd(date.year(), date.month(), date.day())
451 .hms(time.hour(), time.minute(), time.second())
452 .nanos(time.nanosecond() as u32)
453 .build(),
454 })
455 }
456}
457
458impl fmt::Debug for DateTime {
459 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
460 if self.nanos == 0 {
461 write!(f, "{}", self.format("%Y-%m-%d %H:%M:%S"))
462 } else if self.nanos % 1_000_000 == 0 {
463 write!(f, "{}", self.format("%Y-%m-%d %H:%M:%S%.3f"))
464 } else if self.nanos % 1_000 == 0 {
465 write!(f, "{}", self.format("%Y-%m-%d %H:%M:%S%.6f"))
466 } else {
467 write!(f, "{}", self.format("%Y-%m-%d %H:%M:%S%.9f"))
468 }
469 }
470}
471
472#[cfg(feature = "log")]
473impl log::kv::ToValue for DateTime {
474 fn to_value(&self) -> log::kv::Value<'_> {
475 log::kv::Value::from_debug(self)
476 }
477}
478
479#[must_use]
481pub struct DateTimeBuilder {
482 date: Date,
483 seconds: i64,
484 nanos: u32,
485 #[cfg(feature = "tz")]
486 tz: tz::TimeZone,
487 offset: i64,
488}
489
490impl DateTimeBuilder {
491 pub const fn hms(mut self, hour: u8, minute: u8, second: u8) -> Self {
493 assert!(hour < 24, "Hour out of bounds");
494 assert!(minute < 60, "Minute out of bounds");
495 assert!(second < 60, "Second out of bounds");
496 self.seconds = (hour as i64 * 3600) + (minute as i64 * 60) + second as i64;
497 self
498 }
499
500 pub const fn nanos(mut self, nanos: u32) -> Self {
502 assert!(nanos < 1_000_000_000, "Nanos out of bounds.");
503 self.nanos = nanos;
504 self
505 }
506
507 #[cfg(feature = "tz")]
513 pub const fn tz(mut self, tz: tz::TimeZoneRef<'static>) -> tz::TzResult<Self> {
514 self.offset = match tz.find_local_time_type(self.date.timestamp() + self.seconds) {
515 Ok(t) => t.ut_offset() as i64,
516 Err(e) => return Err(e),
517 };
518 self.tz = tz::TimeZone::Tz(tz);
519 Ok(self)
520 }
521
522 #[cfg(feature = "tz")]
528 pub(crate) const fn utc_offset(mut self, offset: i32) -> Self {
529 self.offset = offset as i64;
530 self.tz = tz::TimeZone::FixedOffset(offset);
531 self
532 }
533
534 pub const fn build(self) -> DateTime {
536 DateTime {
537 seconds: self.date.timestamp() + self.seconds - self.offset,
538 nanos: self.nanos,
539 #[cfg(feature = "tz")]
540 tz: self.tz,
541 }
542 }
543}
544
545trait Sealed {}
546impl Sealed for date::Date {}
547
548#[allow(private_bounds)]
550pub trait FromDate: Sealed {
551 fn hms(self, hour: u8, minute: u8, second: u8) -> DateTimeBuilder;
553}
554
555impl FromDate for date::Date {
556 fn hms(self, hour: u8, minute: u8, second: u8) -> DateTimeBuilder {
557 DateTimeBuilder {
558 date: self,
559 seconds: 0,
560 nanos: 0,
561 #[cfg(feature = "tz")]
562 tz: tz::TimeZone::Unspecified,
563 offset: 0,
564 }
565 .hms(hour, minute, second)
566 }
567}
568
569#[derive(Clone, Copy, Debug, Eq, PartialEq)]
571pub enum Precision {
572 Second,
573 Millisecond,
574 Microsecond,
575 Nanosecond,
576}
577
578#[cfg(test)]
579mod tests {
580 use assert2::check;
581 use strptime::ParseResult;
582
583 use crate::DateTime;
584 use crate::FromDate;
585 use crate::Precision;
586 use crate::interval::TimeInterval;
587 #[cfg(feature = "tz")]
588 use crate::tz;
589
590 #[test]
591 fn test_zero() {
592 let dt = datetime! { 1970-01-01 00:00:00 };
593 check!(dt.seconds == 0);
594 }
595
596 #[test]
597 fn test_accessors() {
598 let dt = datetime! { 2012-04-21 11:00:00 };
599 check!(dt.year() == 2012);
600 check!(dt.month() == 4);
601 check!(dt.day() == 21);
602 check!(dt.hour() == 11);
603 check!(dt.minute() == 0);
604 check!(dt.second() == 0);
605 }
606
607 #[test]
608 fn test_more_accessors() {
609 let dt = datetime! { 2024-02-29 13:15:45 };
610 check!(dt.year() == 2024);
611 check!(dt.month() == 2);
612 check!(dt.day() == 29);
613 check!(dt.hour() == 13);
614 check!(dt.minute() == 15);
615 check!(dt.second() == 45);
616 }
617
618 #[test]
619 fn test_fractional_seconds_literal() {
620 let dt = datetime! { 2024-02-29 13:15:45.5 };
621 check!(dt.second() == 45);
622 check!(dt.nanosecond() == 500_000_000);
623
624 let dt = datetime! { 2024-02-29 13:15:45.123_456_789 };
625 check!(dt.second() == 45);
626 check!(dt.nanosecond() == 123_456_789);
627
628 let dt = datetime! { 2024-02-29 13:15:45 };
630 check!(dt.second() == 45);
631 check!(dt.nanosecond() == 0);
632 }
633
634 #[test]
635 #[cfg(feature = "tz")]
636 fn test_fractional_seconds_literal_tz() {
637 let dt = datetime! { 2012-04-21 11:00:00.250 us::EASTERN };
638 check!(dt.hour() == 11);
639 check!(dt.nanosecond() == 250_000_000);
640 }
641
642 #[test]
643 fn test_parse_str() -> ParseResult<()> {
644 for s in [
645 "2012-04-21 11:00:00",
646 "2012-04-21T11:00:00",
647 "2012-04-21 11:00:00.000000",
648 "2012-04-21 11:00:00Z",
649 "2012-04-21T11:00:00.000000",
650 "2012-04-21T11:00:00Z",
651 ] {
652 let dt = s.parse::<DateTime>()?;
653 check!(dt.year() == 2012);
654 check!(dt.month() == 4);
655 check!(dt.day() == 21);
656 check!(dt.hour() == 11);
657 }
658
659 Ok(())
660 }
661
662 #[test]
663 #[cfg(feature = "tz")]
664 fn test_parse_str_tz() -> ParseResult<()> {
665 for s in
666 ["2012-04-21 11:00:00-0400", "2012-04-21T11:00:00-0400", "2012-04-21 11:00:00.000000-0400"]
667 {
668 let dt = s.parse::<DateTime>()?;
669 check!(dt.year() == 2012);
670 check!(dt.month() == 4);
671 check!(dt.day() == 21);
672 check!(dt.hour() == 11);
673 }
674 Ok(())
675 }
676
677 #[test]
678 #[allow(clippy::inconsistent_digit_grouping)]
679 fn test_as_precision() {
680 let dt = DateTime::ymd(2012, 4, 21).hms(15, 0, 0).build();
681 check!(dt.as_seconds() == 1335020400);
682 check!(dt.as_milliseconds() == 1335020400_000);
683 check!(dt.as_microseconds() == 1335020400_000_000);
684 check!(dt.as_nanoseconds() == 1335020400_000_000_000);
685 }
686
687 #[test]
688 fn test_precision() {
689 let mut dt = DateTime::ymd(2012, 4, 21).hms(15, 0, 0).build();
690 check!(dt.precision() == Precision::Second);
691 dt += TimeInterval::new(0, 1_000_000);
692 check!(dt.precision() == Precision::Millisecond);
693 dt += TimeInterval::new(0, 1_000);
694 check!(dt.precision() == Precision::Microsecond);
695 dt += TimeInterval::new(0, 1);
696 check!(dt.precision() == Precision::Nanosecond);
697 }
698
699 #[cfg(feature = "tz")]
700 #[test]
701 fn test_tz() -> tz::TzResult<()> {
702 let dt = DateTime::ymd(2012, 4, 21).hms(11, 0, 0).tz(tz::us::EASTERN)?.build();
703 check!(dt.as_seconds() == 1335020400);
704 check!(dt.year() == 2012);
705 check!(dt.month() == 4);
706 check!(dt.day() == 21);
707 check!(dt.hour() == 11);
708 let dt = DateTime::ymd(1970, 1, 1).tz(tz::us::PACIFIC)?.build();
709 check!(dt.as_seconds() == 3600 * 8);
710 Ok(())
711 }
712
713 #[cfg(feature = "tz")]
714 #[test]
715 fn test_unix_tz() {
716 #[allow(clippy::inconsistent_digit_grouping)]
717 for dt in [
718 DateTime::from_timestamp(1335020400, 0),
719 DateTime::from_timestamp_millis(1335020400_000),
720 DateTime::from_timestamp_micros(1335020400_000_000),
721 DateTime::from_timestamp_nanos(1335020400_000_000_000),
722 ] {
723 let dt = dt.with_tz(tz::us::EASTERN);
724 check!(dt.as_seconds() == 1335020400);
725 check!(dt.year() == 2012);
726 check!(dt.month() == 4);
727 check!(dt.day() == 21);
728 check!(dt.hour() == 11);
729 }
730 }
731
732 #[cfg(feature = "tz")]
733 #[test]
734 fn test_in_tz() {
735 let dt = DateTime::from_timestamp(1335020400, 0).with_tz(tz::us::EASTERN);
736 check!(dt.hour() == 11);
737 check!(dt.in_tz(tz::us::CENTRAL).hour() == 11);
738 check!(dt.as_seconds() - dt.in_tz(tz::us::CENTRAL).as_seconds() == -3600);
739 check!(dt.in_tz(tz::europe::LONDON).hour() == 11);
740 check!(dt.as_seconds() - dt.in_tz(tz::europe::LONDON).as_seconds() == 3600 * 5);
741 }
742
743 #[test]
744 fn test_from_date_trait() {
745 let dt = date::date! { 2012-04-21 }.hms(11, 0, 0).build();
746 check!(dt.year() == 2012);
747 check!(dt.month() == 4);
748 check!(dt.day() == 21);
749 check!(dt.hour() == 11);
750 }
751
752 #[test]
753 fn test_debug() {
754 let dt = date::date! { 2012-04-21 }.hms(15, 0, 0).build();
755 check!(format!("{:?}", dt) == "2012-04-21 15:00:00");
756 let dt = date::date! { 2012-04-21 }.hms(15, 0, 0).nanos(500_000_000).build();
757 check!(format!("{:?}", dt) == "2012-04-21 15:00:00.500");
758 let dt = date::date! { 2012-04-21 }.hms(15, 0, 0).nanos(123_450_000).build();
759 check!(format!("{:?}", dt) == "2012-04-21 15:00:00.123450");
760 let dt = date::date! { 2012-04-21 }.hms(15, 0, 0).nanos(123_456_789).build();
761 check!(format!("{:?}", dt) == "2012-04-21 15:00:00.123456789");
762 }
763}