datetime/
lib.rs

1//! `datetime-rs` provides a representation of a date and time.
2//!
3//! Internal storage is a Unix timestamp and, if the `tz` feature is enabled (which it is not by
4//! default), optionally a `TimeZone`.
5
6#![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/// Construct a date and time from a `YYYY-MM-DD HH:MM:SS` literal.
21#[macro_export]
22macro_rules! datetime {
23  ($y:literal-$m:literal-$d:literal $h:literal : $mi:literal : $s:literal) => {{
24    #[allow(clippy::zero_prefixed_literal)]
25    {
26      $crate::DateTime::ymd($y, $m, $d).hms($h, $mi, $s).build()
27    }
28  }};
29  ($y:literal-$m:literal-$d:literal $h:literal : $mi:literal : $s:literal $($tz:ident)::+) => {{
30    #[cfg(feature = "tz")]
31    #[allow(clippy::zero_prefixed_literal)]
32    {
33      match $crate::DateTime::ymd($y, $m, $d).hms($h, $mi, $s).tz($crate::tz::$($tz)::+) {
34        Ok(dt) => dt.build(),
35        Err(_) => panic!("invalid date/time and time zone combination"),
36      }
37    }
38    #[cfg(not(feature = "tz"))]
39    {
40      compile_error!("The `tz` feature must be enabled to specify a time zone.");
41    }
42  }};
43}
44
45#[cfg(feature = "diesel-pg")]
46mod diesel_pg;
47#[cfg(feature = "duckdb")]
48mod duckdb;
49mod format;
50pub mod interval;
51#[cfg(feature = "serde")]
52mod serde;
53
54pub use date::Date;
55pub use date::Weekday;
56pub use date::date;
57
58/// Time zone compnents.
59///
60/// These are re-exported from the `date-rs` crate.
61#[cfg(feature = "tz")]
62#[cfg_attr(docsrs, doc(cfg(feature = "tz")))]
63pub mod tz {
64  pub use date::tz::*;
65
66  #[derive(Clone, Copy, Debug, Eq, PartialEq)]
67  pub(crate) enum TimeZone {
68    Unspecified,
69    Tz(crate::tz::TimeZoneRef<'static>),
70    FixedOffset(i32),
71  }
72
73  impl TimeZone {
74    pub(crate) const fn ut_offset(&self, timestamp: i64) -> TzResult<i32> {
75      match self {
76        Self::Unspecified => Ok(0),
77        Self::FixedOffset(offset) => Ok(*offset),
78        Self::Tz(tz) => match tz.find_local_time_type(timestamp) {
79          Ok(t) => Ok(t.ut_offset()),
80          Err(e) => Err(e),
81        },
82      }
83    }
84  }
85}
86
87/// A representation of a date and time.
88#[derive(Clone, Copy, Eq)]
89#[cfg_attr(feature = "diesel-pg", derive(diesel::AsExpression, diesel::FromSqlRow))]
90#[cfg_attr(feature = "diesel-pg", diesel(
91    sql_type = diesel::sql_types::Timestamp,
92    sql_type = diesel::sql_types::Timestamptz))]
93pub struct DateTime {
94  seconds: i64,
95  nanos: u32,
96  #[cfg(feature = "tz")]
97  tz: tz::TimeZone,
98}
99
100impl DateTime {
101  /// Create a new date and time object.
102  pub const fn ymd(year: i16, month: u8, day: u8) -> DateTimeBuilder {
103    DateTimeBuilder {
104      date: Date::new(year, month, day),
105      seconds: 0,
106      nanos: 0,
107      #[cfg(feature = "tz")]
108      tz: tz::TimeZone::Unspecified,
109      offset: 0,
110    }
111  }
112
113  /// Create a new date and time object from the given Unix timestamp.
114  pub const fn from_timestamp(timestamp: i64, nanos: u32) -> Self {
115    let mut timestamp = timestamp;
116    let mut nanos = nanos;
117    while nanos >= 1_000_000_000 {
118      nanos -= 1_000_000_000;
119      timestamp += 1;
120    }
121    Self {
122      seconds: timestamp,
123      nanos,
124      #[cfg(feature = "tz")]
125      tz: tz::TimeZone::Unspecified,
126    }
127  }
128
129  /// Create a new date and time object from the given Unix timestamp in milliseconds.
130  pub const fn from_timestamp_millis(millis: i64) -> Self {
131    Self::from_timestamp(millis.div_euclid(1_000), millis.rem_euclid(1_000) as u32)
132  }
133
134  /// Create a new date and time object from the given Unix timestamp in microseconds.
135  pub const fn from_timestamp_micros(micros: i64) -> Self {
136    Self::from_timestamp(micros.div_euclid(1_000_000), micros.rem_euclid(1_000_000) as u32)
137  }
138
139  /// Create a new date and time object from the given Unix timestamp in nanoseconds.
140  pub const fn from_timestamp_nanos(nanos: i128) -> Self {
141    Self::from_timestamp(
142      nanos.div_euclid(1_000_000_000) as i64,
143      nanos.rem_euclid(1_000_000_000) as u32,
144    )
145  }
146
147  /// Return the current timestamp.
148  ///
149  /// ## Panic
150  ///
151  /// Panics if the system clock is set prior to January 1, 1970.
152  pub fn now() -> Self {
153    let dur = SystemTime::now()
154      .duration_since(SystemTime::UNIX_EPOCH)
155      .expect("System clock set prior to January 1, 1970");
156    Self::from_timestamp(dur.as_secs() as i64, dur.subsec_nanos())
157  }
158}
159
160#[cfg(feature = "tz")]
161impl DateTime {
162  /// Set the time zone to the provided time zone, without adjusting the underlying absolute
163  /// timestamp.
164  ///
165  /// This method modifies the wall clock time while maintaining the underlying absolute timestamp.
166  /// To modify the timestamp instead, use `in_tz`.
167  #[inline]
168  pub const fn with_tz(mut self, tz: tz::TimeZoneRef<'static>) -> Self {
169    self.tz = tz::TimeZone::Tz(tz);
170    self
171  }
172
173  /// Set the timestamp to the same wall clock time in the provided time zone.
174  ///
175  /// This method modifies the underlying timestamp while maintaining the wall clock time.
176  /// To maintain the timestamp instead, use `with_tz`.
177  #[inline]
178  pub const fn in_tz(mut self, tz: tz::TimeZoneRef<'static>) -> Self {
179    let existing_ut_offset = match self.tz.ut_offset(self.seconds) {
180      Ok(offset) => offset as i64,
181      Err(_) => panic!("Invalid time zone."),
182    };
183    let desired_ut_offset = match tz.find_local_time_type(self.seconds) {
184      Ok(t) => t.ut_offset() as i64,
185      Err(_) => panic!("Invalid time zone for this timestamp."),
186    };
187    self.seconds += existing_ut_offset - desired_ut_offset;
188    self.tz = tz::TimeZone::Tz(tz);
189    self
190  }
191}
192
193/// Accessors
194impl DateTime {
195  /// The year for this date.
196  #[inline]
197  pub const fn year(&self) -> i16 {
198    Date::from_timestamp(self.tz_adjusted_seconds()).year()
199  }
200
201  /// The month for this date.
202  #[inline]
203  pub const fn month(&self) -> u8 {
204    Date::from_timestamp(self.tz_adjusted_seconds()).month()
205  }
206
207  /// The day of the month for this date.
208  #[inline]
209  pub const fn day(&self) -> u8 {
210    Date::from_timestamp(self.tz_adjusted_seconds()).day()
211  }
212
213  /// The day of the week for this date.
214  #[inline]
215  pub const fn weekday(&self) -> Weekday {
216    Date::from_timestamp(self.tz_adjusted_seconds()).weekday()
217  }
218
219  /// The hour of the day for this date and time. Range: `[0, 24)`
220  #[inline]
221  pub const fn hour(&self) -> u8 {
222    (self.tz_adjusted_seconds() % 86_400 / 3_600) as u8
223  }
224
225  /// The minute of the hour for this date and time. Range: `[0, 60)`
226  #[inline]
227  pub const fn minute(&self) -> u8 {
228    ((self.tz_adjusted_seconds() % 3600) / 60) as u8
229  }
230
231  /// The second of the minute for this date and time. Range: `[0, 60)`
232  #[inline]
233  pub const fn second(&self) -> u8 {
234    (self.tz_adjusted_seconds() % 60) as u8
235  }
236
237  /// The nanosecond of the second for this date and time. Range: `[0, 1_000_000_000)`
238  #[inline]
239  pub const fn nanosecond(&self) -> u32 {
240    self.nanos
241  }
242
243  /// The ordinal day of the year.
244  #[inline]
245  pub const fn day_of_year(&self) -> u16 {
246    self.date().day_of_year()
247  }
248
249  /// The date corresponding to this datetime.
250  #[inline]
251  pub const fn date(&self) -> Date {
252    Date::from_timestamp(self.tz_adjusted_seconds())
253  }
254
255  /// The number of seconds since the Unix epoch for this date and time.
256  #[inline]
257  pub const fn as_seconds(&self) -> i64 {
258    self.seconds
259  }
260
261  /// The number of milliseconds since the Unix epoch for this date and time.
262  #[inline]
263  pub const fn as_milliseconds(&self) -> i64 {
264    self.seconds * 1_000 + (self.nanos / 1_000_000) as i64
265  }
266
267  /// The number of microseconds since the Unix epoch for this date and time.
268  #[inline]
269  pub const fn as_microseconds(&self) -> i64 {
270    self.seconds * 1_000_000 + (self.nanos / 1_000) as i64
271  }
272
273  /// The number of nanoseconds since the Unix epoch for this date and time.
274  #[inline]
275  pub const fn as_nanoseconds(&self) -> i128 {
276    self.seconds as i128 * 1_000_000_000 + self.nanos as i128
277  }
278
279  /// The precision required to represent this timestamp with no fidelity loss.
280  #[inline]
281  pub const fn precision(&self) -> Precision {
282    if self.nanos == 0 {
283      Precision::Second
284    } else if self.nanos % 1_000_000 == 0 {
285      Precision::Millisecond
286    } else if self.nanos % 1_000 == 0 {
287      Precision::Microsecond
288    } else {
289      Precision::Nanosecond
290    }
291  }
292
293  /// Provide the number of seconds since the epoch in the time zone with the same offset as this
294  /// datetime's time zone.
295  #[inline(always)]
296  const fn tz_adjusted_seconds(&self) -> i64 {
297    self.seconds + self.tz_offset()
298  }
299
300  /// Provide the offset, in seconds
301  const fn tz_offset(&self) -> i64 {
302    #[cfg(feature = "tz")]
303    {
304      match self.tz.ut_offset(self.seconds) {
305        Ok(offset) => offset as i64,
306        Err(_) => panic!("Invalid time zone"),
307      }
308    }
309    #[cfg(not(feature = "tz"))]
310    0
311  }
312}
313
314impl DateTime {
315  /// Format the given date and time according to the provided `strftime`-like string.
316  pub fn format(&self, format: &'static str) -> FormattedDateTime {
317    FormattedDateTime { dt: self, format }
318  }
319}
320
321impl DateTime {
322  /// Parse a date from a string, according to the provided format string.
323  pub fn parse(datetime_str: impl AsRef<str>, fmt: &'static str) -> ParseResult<Self> {
324    let parser = Parser::new(fmt);
325    parser.parse(datetime_str)?.try_into()
326  }
327}
328
329impl PartialEq for DateTime {
330  fn eq(&self, other: &Self) -> bool {
331    self.seconds == other.seconds && self.nanos == other.nanos
332  }
333}
334
335impl PartialOrd for DateTime {
336  fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
337    Some(self.cmp(other))
338  }
339}
340
341impl Ord for DateTime {
342  fn cmp(&self, other: &Self) -> Ordering {
343    let seconds_cmp = self.seconds.cmp(&other.seconds);
344    match seconds_cmp {
345      Ordering::Equal => self.nanos.cmp(&other.nanos),
346      _ => seconds_cmp,
347    }
348  }
349}
350
351impl FromStr for DateTime {
352  type Err = ParseError;
353
354  #[rustfmt::skip]
355  fn from_str(s: &str) -> ParseResult<Self> {
356    // Attempt several common formats.
357    if let Ok(dt) = Parser::new("%Y-%m-%dT%H:%M:%S").parse(s) { return dt.try_into(); }
358    if let Ok(dt) = Parser::new("%Y-%m-%dT%H:%M:%S%z").parse(s) { return dt.try_into(); }
359    if let Ok(dt) = Parser::new("%Y-%m-%d %H:%M:%S").parse(s) { return dt.try_into(); }
360    if let Ok(dt) = Parser::new("%Y-%m-%d %H:%M:%S%z").parse(s) { return dt.try_into(); }
361    if let Ok(dt) = Parser::new("%Y-%m-%dT%H:%M:%S%.6f").parse(s) { return dt.try_into(); }
362    if let Ok(dt) = Parser::new("%Y-%m-%dT%H:%M:%S%.6f%z").parse(s) { return dt.try_into(); }
363    if let Ok(dt) = Parser::new("%Y-%m-%d %H:%M:%S%.6f").parse(s) { return dt.try_into(); }
364    if let Ok(dt) = Parser::new("%Y-%m-%d %H:%M:%S%.6f%z").parse(s) { return dt.try_into(); }
365    if let Ok(dt) = Parser::new("%Y-%m-%dT%H:%M:%S%.9f").parse(s) { return dt.try_into(); }
366    if let Ok(dt) = Parser::new("%Y-%m-%dT%H:%M:%S%.9f%z").parse(s) { return dt.try_into(); }
367    if let Ok(dt) = Parser::new("%Y-%m-%d %H:%M:%S%.9f").parse(s) { return dt.try_into(); }
368    if let Ok(dt) = Parser::new("%Y-%m-%d %H:%M:%S%.9f%z").parse(s) { return dt.try_into(); }
369    if let Ok(dt) = Parser::new("%Y-%m-%d %H:%M:%SZ").parse(s) { return dt.try_into(); }
370    Parser::new("%Y-%m-%dT%H:%M:%SZ").parse(s)?.try_into()
371  }
372}
373
374impl TryFrom<RawDateTime> for DateTime {
375  type Error = ParseError;
376
377  fn try_from(value: RawDateTime) -> ParseResult<Self> {
378    let date = value.date()?;
379    let time = value.time().unwrap_or_default();
380    Ok(match time.utc_offset() {
381      #[cfg(feature = "tz")]
382      Some(utc_offset) => Self::ymd(date.year(), date.month(), date.day())
383        .hms(time.hour(), time.minute(), time.second())
384        .nanos(time.nanosecond() as u32)
385        .utc_offset(utc_offset)
386        .build(),
387      #[cfg(not(feature = "tz"))]
388      Some(_) => panic!("Enable the `tz` feature to parse datetimes with UTC offsets."),
389      None => Self::ymd(date.year(), date.month(), date.day())
390        .hms(time.hour(), time.minute(), time.second())
391        .nanos(time.nanosecond() as u32)
392        .build(),
393    })
394  }
395}
396
397impl fmt::Debug for DateTime {
398  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
399    if self.nanos == 0 {
400      write!(f, "{}", self.format("%Y-%m-%d %H:%M:%S"))
401    } else if self.nanos % 1_000_000 == 0 {
402      write!(f, "{}", self.format("%Y-%m-%d %H:%M:%S%.3f"))
403    } else if self.nanos % 1_000 == 0 {
404      write!(f, "{}", self.format("%Y-%m-%d %H:%M:%S%.6f"))
405    } else {
406      write!(f, "{}", self.format("%Y-%m-%d %H:%M:%S%.9f"))
407    }
408  }
409}
410
411#[cfg(feature = "log")]
412impl log::kv::ToValue for DateTime {
413  fn to_value(&self) -> log::kv::Value<'_> {
414    log::kv::Value::from_debug(self)
415  }
416}
417
418/// An intermediate builder for [`DateTime`].
419#[must_use]
420pub struct DateTimeBuilder {
421  date: Date,
422  seconds: i64,
423  nanos: u32,
424  #[cfg(feature = "tz")]
425  tz: tz::TimeZone,
426  offset: i64,
427}
428
429impl DateTimeBuilder {
430  /// Attach an hour, minute, and second to the datetime.
431  pub const fn hms(mut self, hour: u8, minute: u8, second: u8) -> Self {
432    assert!(hour < 24, "Hour out of bounds");
433    assert!(minute < 60, "Minute out of bounds");
434    assert!(second < 60, "Second out of bounds");
435    self.seconds = (hour as i64 * 3600) + (minute as i64 * 60) + second as i64;
436    self
437  }
438
439  /// Attach fractional to the datetime.
440  pub const fn nanos(mut self, nanos: u32) -> Self {
441    assert!(nanos < 1_000_000_000, "Nanos out of bounds.");
442    self.nanos = nanos;
443    self
444  }
445
446  /// Attach a timezone to the datetime.
447  ///
448  /// This method assumes that the timezone _modifies_ the underlying timestamp; in other words,
449  /// the YMD/HMS specified to the date and time builder should be preserved, and the time zone's
450  /// offset applied to the underlying timestamp to preserve the date and time on the wall clock.
451  #[cfg(feature = "tz")]
452  pub const fn tz(mut self, tz: tz::TimeZoneRef<'static>) -> tz::TzResult<Self> {
453    self.offset = match tz.find_local_time_type(self.date.timestamp() + self.seconds) {
454      Ok(t) => t.ut_offset() as i64,
455      Err(e) => return Err(e),
456    };
457    self.tz = tz::TimeZone::Tz(tz);
458    Ok(self)
459  }
460
461  /// Attach a UTC offset to the datetime.
462  ///
463  /// This method assumes that the offset _modifies_ the underlying timestamp; in other words, the
464  /// YMD/HMS specified to the date and time builder should be preserved, and the offset applied to
465  /// the underlying timestamp to preserve the date and time on the wall clock.
466  #[cfg(feature = "tz")]
467  pub(crate) const fn utc_offset(mut self, offset: i32) -> Self {
468    self.offset = offset as i64;
469    self.tz = tz::TimeZone::FixedOffset(offset);
470    self
471  }
472
473  /// Build the final [`DateTime`] object.
474  pub const fn build(self) -> DateTime {
475    DateTime {
476      seconds: self.date.timestamp() + self.seconds - self.offset,
477      nanos: self.nanos,
478      #[cfg(feature = "tz")]
479      tz: self.tz,
480    }
481  }
482}
483
484trait Sealed {}
485impl Sealed for date::Date {}
486
487/// Convert from a date into a datetime, by way of a builder.
488#[allow(private_bounds)]
489pub trait FromDate: Sealed {
490  /// Create a `DateTimeBuilder` for this Date.
491  fn hms(self, hour: u8, minute: u8, second: u8) -> DateTimeBuilder;
492}
493
494impl FromDate for date::Date {
495  fn hms(self, hour: u8, minute: u8, second: u8) -> DateTimeBuilder {
496    DateTimeBuilder {
497      date: self,
498      seconds: 0,
499      nanos: 0,
500      #[cfg(feature = "tz")]
501      tz: tz::TimeZone::Unspecified,
502      offset: 0,
503    }
504    .hms(hour, minute, second)
505  }
506}
507
508/// The precision that this timestamp requires in order to represent with no fidelity loss.
509#[derive(Clone, Copy, Debug, Eq, PartialEq)]
510pub enum Precision {
511  Second,
512  Millisecond,
513  Microsecond,
514  Nanosecond,
515}
516
517#[cfg(test)]
518mod tests {
519  use assert2::check;
520  use strptime::ParseResult;
521
522  use crate::DateTime;
523  use crate::FromDate;
524  use crate::Precision;
525  use crate::interval::TimeInterval;
526  #[cfg(feature = "tz")]
527  use crate::tz;
528
529  #[test]
530  fn test_zero() {
531    let dt = datetime! { 1970-01-01 00:00:00 };
532    check!(dt.seconds == 0);
533  }
534
535  #[test]
536  fn test_accessors() {
537    let dt = datetime! { 2012-04-21 11:00:00 };
538    check!(dt.year() == 2012);
539    check!(dt.month() == 4);
540    check!(dt.day() == 21);
541    check!(dt.hour() == 11);
542    check!(dt.minute() == 0);
543    check!(dt.second() == 0);
544  }
545
546  #[test]
547  fn test_more_accessors() {
548    let dt = datetime! { 2024-02-29 13:15:45 };
549    check!(dt.year() == 2024);
550    check!(dt.month() == 2);
551    check!(dt.day() == 29);
552    check!(dt.hour() == 13);
553    check!(dt.minute() == 15);
554    check!(dt.second() == 45);
555  }
556
557  #[test]
558  fn test_parse_str() -> ParseResult<()> {
559    for s in [
560      "2012-04-21 11:00:00",
561      "2012-04-21T11:00:00",
562      "2012-04-21 11:00:00.000000",
563      "2012-04-21 11:00:00Z",
564      "2012-04-21T11:00:00.000000",
565      "2012-04-21T11:00:00Z",
566    ] {
567      let dt = s.parse::<DateTime>()?;
568      check!(dt.year() == 2012);
569      check!(dt.month() == 4);
570      check!(dt.day() == 21);
571      check!(dt.hour() == 11);
572    }
573
574    Ok(())
575  }
576
577  #[test]
578  #[cfg(feature = "tz")]
579  fn test_parse_str_tz() -> ParseResult<()> {
580    for s in
581      ["2012-04-21 11:00:00-0400", "2012-04-21T11:00:00-0400", "2012-04-21 11:00:00.000000-0400"]
582    {
583      let dt = s.parse::<DateTime>()?;
584      check!(dt.year() == 2012);
585      check!(dt.month() == 4);
586      check!(dt.day() == 21);
587      check!(dt.hour() == 11);
588    }
589    Ok(())
590  }
591
592  #[test]
593  #[allow(clippy::inconsistent_digit_grouping)]
594  fn test_as_precision() {
595    let dt = DateTime::ymd(2012, 4, 21).hms(15, 0, 0).build();
596    check!(dt.as_seconds() == 1335020400);
597    check!(dt.as_milliseconds() == 1335020400_000);
598    check!(dt.as_microseconds() == 1335020400_000_000);
599    check!(dt.as_nanoseconds() == 1335020400_000_000_000);
600  }
601
602  #[test]
603  fn test_precision() {
604    let mut dt = DateTime::ymd(2012, 4, 21).hms(15, 0, 0).build();
605    check!(dt.precision() == Precision::Second);
606    dt += TimeInterval::new(0, 1_000_000);
607    check!(dt.precision() == Precision::Millisecond);
608    dt += TimeInterval::new(0, 1_000);
609    check!(dt.precision() == Precision::Microsecond);
610    dt += TimeInterval::new(0, 1);
611    check!(dt.precision() == Precision::Nanosecond);
612  }
613
614  #[cfg(feature = "tz")]
615  #[test]
616  fn test_tz() -> tz::TzResult<()> {
617    let dt = DateTime::ymd(2012, 4, 21).hms(11, 0, 0).tz(tz::us::EASTERN)?.build();
618    check!(dt.as_seconds() == 1335020400);
619    check!(dt.year() == 2012);
620    check!(dt.month() == 4);
621    check!(dt.day() == 21);
622    check!(dt.hour() == 11);
623    let dt = DateTime::ymd(1970, 1, 1).tz(tz::us::PACIFIC)?.build();
624    check!(dt.as_seconds() == 3600 * 8);
625    Ok(())
626  }
627
628  #[cfg(feature = "tz")]
629  #[test]
630  fn test_unix_tz() {
631    #[allow(clippy::inconsistent_digit_grouping)]
632    for dt in [
633      DateTime::from_timestamp(1335020400, 0),
634      DateTime::from_timestamp_millis(1335020400_000),
635      DateTime::from_timestamp_micros(1335020400_000_000),
636      DateTime::from_timestamp_nanos(1335020400_000_000_000),
637    ] {
638      let dt = dt.with_tz(tz::us::EASTERN);
639      check!(dt.as_seconds() == 1335020400);
640      check!(dt.year() == 2012);
641      check!(dt.month() == 4);
642      check!(dt.day() == 21);
643      check!(dt.hour() == 11);
644    }
645  }
646
647  #[cfg(feature = "tz")]
648  #[test]
649  fn test_in_tz() {
650    let dt = DateTime::from_timestamp(1335020400, 0).with_tz(tz::us::EASTERN);
651    check!(dt.hour() == 11);
652    check!(dt.in_tz(tz::us::CENTRAL).hour() == 11);
653    check!(dt.as_seconds() - dt.in_tz(tz::us::CENTRAL).as_seconds() == -3600);
654    check!(dt.in_tz(tz::europe::LONDON).hour() == 11);
655    check!(dt.as_seconds() - dt.in_tz(tz::europe::LONDON).as_seconds() == 3600 * 5);
656  }
657
658  #[test]
659  fn test_from_date_trait() {
660    let dt = date::date! { 2012-04-21 }.hms(11, 0, 0).build();
661    check!(dt.year() == 2012);
662    check!(dt.month() == 4);
663    check!(dt.day() == 21);
664    check!(dt.hour() == 11);
665  }
666
667  #[test]
668  fn test_debug() {
669    let dt = date::date! { 2012-04-21 }.hms(15, 0, 0).build();
670    check!(format!("{:?}", dt) == "2012-04-21 15:00:00");
671    let dt = date::date! { 2012-04-21 }.hms(15, 0, 0).nanos(500_000_000).build();
672    check!(format!("{:?}", dt) == "2012-04-21 15:00:00.500");
673    let dt = date::date! { 2012-04-21 }.hms(15, 0, 0).nanos(123_450_000).build();
674    check!(format!("{:?}", dt) == "2012-04-21 15:00:00.123450");
675    let dt = date::date! { 2012-04-21 }.hms(15, 0, 0).nanos(123_456_789).build();
676    check!(format!("{:?}", dt) == "2012-04-21 15:00:00.123456789");
677  }
678}