date/
lib.rs

1//! The `date-rs` crate provides a simple, easy-to-use `Date` struct (and corresponding macro).
2//! Date provides storage for a single Gregorian calendar date.
3//!
4//! `Date` can currently store any valid calendar date between years -65,536 and -65,535, although
5//! this may change in the future if its internal representation changes.
6//!
7//! ## Examples
8//!
9//! Making a date:
10//!
11//! ```rs
12//! use date::Date;
13//!
14//! let date = Date::new(2012, 4, 21);
15//! ```
16//!
17//! You can also use the `date!` macro to get a syntax resembling a date literal:
18//!
19//! ```rs
20//! use date::date;
21//!
22//! let date = date! { 2012-04-21 };
23//! ```
24
25use std::cmp::Ordering;
26use std::fmt;
27use std::str::FromStr;
28use std::time::SystemTime;
29use std::time::UNIX_EPOCH;
30
31use strptime::ParseError;
32use strptime::ParseResult;
33use strptime::Parser;
34
35/// Time zone compnents (re-exported from `tzdb` crate).
36#[cfg(feature = "tz")]
37pub mod tz {
38  pub use tz::TimeZoneRef;
39  pub use tzdb::tz_by_name;
40
41  /// The result type for evaluating a specific timestamp against a time zone.
42  ///
43  /// Errors occur primarily when the timestamp in question is for a time when the time zone did
44  /// not exist.
45  pub type TzResult<T> = Result<T, ::tz::error::TzError>;
46
47  pub use tzdb::time_zone::africa;
48  pub use tzdb::time_zone::america;
49  pub use tzdb::time_zone::antarctica;
50  pub use tzdb::time_zone::arctic;
51  pub use tzdb::time_zone::asia;
52  pub use tzdb::time_zone::atlantic;
53  pub use tzdb::time_zone::australia;
54  pub use tzdb::time_zone::europe;
55  pub use tzdb::time_zone::indian;
56  pub use tzdb::time_zone::us;
57}
58
59/// Construct a date from a `YYYY-MM-DD` literal.
60///
61/// ## Examples
62///
63/// ```
64/// # use date::date;
65/// let d = date! { 2024-01-01 };
66/// assert_eq!(d.year(), 2024);
67/// assert_eq!(d.month(), 1);
68/// assert_eq!(d.day(), 1);
69/// ```
70#[macro_export]
71macro_rules! date {
72  ($y:literal-$m:literal-$d:literal) => {{
73    #[allow(clippy::zero_prefixed_literal)]
74    {
75      const { $crate::Date::new($y, $m, $d) }
76    }
77  }};
78}
79
80#[cfg(feature = "diesel-pg")]
81mod diesel_pg;
82#[cfg(feature = "duckdb")]
83mod duckdb;
84mod format;
85pub mod interval;
86pub mod iter;
87#[cfg(feature = "serde")]
88mod serde;
89mod utils;
90mod weekday;
91
92pub use weekday::Weekday;
93
94/// A representation of a single date.
95#[derive(Copy, Clone, Eq, Hash, PartialEq, PartialOrd, Ord)]
96#[cfg_attr(feature = "diesel-pg", derive(diesel::AsExpression, diesel::FromSqlRow))]
97#[cfg_attr(feature = "diesel-pg", diesel(sql_type = ::diesel::sql_types::Date))]
98#[repr(transparent)]
99pub struct Date(i32);
100
101impl Date {
102  /// Construct a new `Date` from the provided year, month, and day.
103  ///
104  /// ## Examples
105  ///
106  /// ```
107  /// use date::Date;
108  /// let date = Date::new(2012, 4, 21);
109  /// assert_eq!(date.year(), 2012);
110  /// assert_eq!(date.month(), 4);
111  /// assert_eq!(date.day(), 21);
112  /// ```
113  ///
114  /// ## Panic
115  ///
116  /// This function panics if it receives "out-of-bounds" values (e.g. "March 32" or "February
117  /// 30"). However, it can be convenient to be able to send such values to avoid having to handle
118  /// overflow yourself; use [`Date::overflowing_new`] for this purpose.
119  pub const fn new(year: i16, month: u8, day: u8) -> Self {
120    const MONTH_DAYS: [u8; 12] = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
121    assert!(month >= 1 && month <= 12, "Month out-of-bounds");
122    assert!(day >= 1 && day <= MONTH_DAYS[month as usize - 1], "Day out-of-bounds");
123    if month == 2 && day == 29 {
124      assert!(utils::is_leap_year(year), "February 29 only occurs on leap years")
125    }
126
127    // The algorithm to convert from a civil year/month/day to the number of days that have elapsed
128    // since the epoch is taken from here:
129    // https://howardhinnant.github.io/date_algorithms.html#days_from_civil
130    let year = year as i32 - if month <= 2 { 1 } else { 0 };
131    let month = month as i32;
132    let day = day as i32;
133    let era: i32 = if year >= 0 { year } else { year - 399 } / 400;
134    let year_of_era = year - era * 400;
135    let day_of_year = (153 * (if month > 2 { month - 3 } else { month + 9 }) + 2) / 5 + day - 1;
136    let day_of_era = year_of_era * 365 + year_of_era / 4 - year_of_era / 100 + day_of_year;
137    Self(era * 146097 + day_of_era - 719468)
138  }
139
140  /// Construct a new `Date` based on the Unix timestamp.
141  ///
142  /// ## Examples
143  ///
144  /// ```
145  /// use date::Date;
146  /// use date::date;
147  ///
148  /// let day_one = Date::from_timestamp(0);
149  /// assert_eq!(day_one, date! { 1970-01-01 });
150  /// let later = Date::from_timestamp(15_451 * 86_400);
151  /// assert_eq!(later, date! { 2012-04-21 });
152  /// ```
153  ///
154  /// Negative timestamps are also supported:
155  ///
156  /// ```
157  /// # use date::date;
158  /// # use date::Date;
159  /// let before_unix_era = Date::from_timestamp(-1);
160  /// assert_eq!(before_unix_era, date! { 1969-12-31 });
161  /// let hobbit_publication = Date::from_timestamp(-11_790 * 86_400);
162  /// assert_eq!(hobbit_publication, date! { 1937-09-21 });
163  /// ```
164  pub const fn from_timestamp(unix_timestamp: i64) -> Self {
165    let day_count = unix_timestamp.div_euclid(86_400) as i32;
166    Self(day_count)
167  }
168
169  /// Construct a new `Date` based on the number of days since January 1, 1970.
170  ///
171  /// ## Example
172  ///
173  /// ```
174  /// # use date::Date;
175  /// let date = Date::from_ord(5);
176  /// assert_eq!(date.year(), 1970);
177  /// assert_eq!(date.month(), 1);
178  /// assert_eq!(date.day(), 6);
179  /// ```
180  #[cfg(feature = "ord")]
181  pub const fn from_ord(ord: i32) -> Self {
182    Self(ord)
183  }
184
185  /// The date on which the given timestamp occurred in the provided time zone.
186  #[cfg(feature = "tz")]
187  pub const fn from_timestamp_tz(
188    unix_timestamp: i64, tz: tz::TimeZoneRef<'static>,
189  ) -> tz::TzResult<Self> {
190    match tz.find_local_time_type(unix_timestamp) {
191      Ok(tz) => Ok(Self::from_timestamp(unix_timestamp + tz.ut_offset() as i64)),
192      Err(e) => Err(e),
193    }
194  }
195
196  /// Construct a new `Date` from the provided year, month, and day.
197  ///
198  /// This function accepts "overflow" values that would lead to invalid dates, and canonicalizes
199  /// them to correct dates, allowing for some math to be done on the inputs without needing to
200  /// perform overflow checks yourself.
201  ///
202  /// For example, it's legal to send "March 32" to this function, and it will yield April 1 of the
203  /// same year. It's also legal to send a `month` or `day` value of zero, and it will conform to
204  /// the month or day (respectively) prior to the first.
205  pub const fn overflowing_new(year: i16, month: u8, day: u8) -> Self {
206    let mut year = year;
207    let mut month = month;
208    let mut day = day;
209
210    // Handle month overflows.
211    while month > 12 {
212      year += 1;
213      month -= 12;
214    }
215    if day == 0 {
216      if month <= 1 {
217        year -= 1;
218        month += 11;
219      } else {
220        month -= 1;
221      }
222      day = utils::days_in_month(year, month);
223    }
224    if month == 0 {
225      year -= 1;
226      month = 12;
227    }
228    while day > utils::days_in_month(year, month) {
229      day -= utils::days_in_month(year, month);
230      month += 1;
231      if month == 13 {
232        year += 1;
233        month = 1;
234      }
235    }
236
237    // Return the date.
238    Self::new(year, month, day)
239  }
240
241  /// Parse a date from a string, according to the provided format string.
242  pub fn parse(date_str: impl AsRef<str>, date_fmt: &'static str) -> ParseResult<Date> {
243    let parser = Parser::new(date_fmt);
244    let raw_date = parser.parse(date_str)?.date()?;
245    Ok(raw_date.into())
246  }
247}
248
249impl Date {
250  /// The year, month, and day for the given date.
251  pub(crate) const fn ymd(&self) -> (i16, u8, u8) {
252    // The algorithm to convert from a civil year/month/day to the number of days that have elapsed
253    // since the epoch is taken from here:
254    // https://howardhinnant.github.io/date_algorithms.html#civil_from_days
255    let shifted = self.0 + 719468; // Days from March 1, 0 A.D.
256    let era = if shifted >= 0 { shifted } else { shifted - 146_096 } / 146_097;
257    let doe = shifted - era * 146_097; // day of era: [0, 146_097)
258    let year_of_era = (doe - doe / 1_460 + doe / 36_524 - doe / 146_096) / 365;
259    let year = year_of_era + era * 400;
260    let day_of_year = doe - (365 * year_of_era + year_of_era / 4 - year_of_era / 100);
261    let mp = (5 * day_of_year + 2) / 153;
262    let day = day_of_year - (153 * mp + 2) / 5 + 1;
263    let month = if mp < 10 { mp + 3 } else { mp - 9 };
264    (year as i16 + if month <= 2 { 1 } else { 0 }, month as u8, day as u8)
265  }
266
267  /// Return the number of days since January 1, 1970.
268  #[inline]
269  #[cfg(feature = "ord")]
270  pub const fn ord(&self) -> i32 {
271    self.0
272  }
273
274  /// Returns the year number in the calendar date.
275  #[inline]
276  pub const fn year(&self) -> i16 {
277    self.ymd().0
278  }
279
280  /// Returns the month number, starting from 1.
281  ///
282  /// The return value ranges from 1 to 12.
283  #[inline]
284  pub const fn month(&self) -> u8 {
285    self.ymd().1
286  }
287
288  /// Returns the day of the month, starting from 1.
289  ///
290  /// The return value ranges from 1 to 31. (The last day of the month differs by months.)
291  #[inline]
292  pub const fn day(&self) -> u8 {
293    self.ymd().2
294  }
295
296  /// The day of the current year. Range: `[1, 366]`
297  #[inline]
298  pub const fn day_of_year(&self) -> u16 {
299    (self.0 - Date::new(self.year() - 1, 12, 31).0) as u16
300  }
301
302  /// The week number of the year (between 0 and 53, inclusive), with a new week starting each
303  /// Sunday.
304  ///
305  /// Week 1 begins on the first Sunday of the year; leading days before that are part of week 0.
306  pub const fn week(&self) -> u16 {
307    let jan1 = Date::new(self.year(), 1, 1);
308    let first_sunday = jan1.0 + if self.0 % 7 == 3 { 0 } else { 7 } - (self.0 + 4) % 7;
309    ((self.0 - first_sunday).div_euclid(7) + 1) as u16
310  }
311
312  /// Return the weekday corresponding to the given date.
313  #[inline]
314  pub const fn weekday(&self) -> Weekday {
315    match (self.0 + 4) % 7 {
316      0 => Weekday::Sunday,
317      1 | -6 => Weekday::Monday,
318      2 | -5 => Weekday::Tuesday,
319      3 | -4 => Weekday::Wednesday,
320      4 | -3 => Weekday::Thursday,
321      5 | -2 => Weekday::Friday,
322      6 | -1 => Weekday::Saturday,
323      #[cfg(not(tarpaulin_include))]
324      _ => panic!("Unreachable: Anything % 7 must be within -6 to 6"),
325    }
326  }
327}
328
329impl Date {
330  /// The Unix timestamp for this date at midnight UTC.
331  ///
332  /// ## Examples
333  ///
334  /// ```
335  /// # use date::date;
336  /// assert_eq!(date! { 1969-12-31 }.timestamp(), -86_400);
337  /// assert_eq!(date! { 1970-01-01 }.timestamp(), 0);
338  /// assert_eq!(date! { 1970-01-05 }.timestamp(), 4 * 86_400);
339  /// assert_eq!(date! { 2012-04-21 }.timestamp(), 1334966400);
340  /// ```
341  pub const fn timestamp(&self) -> i64 {
342    self.0 as i64 * 86_400
343  }
344
345  /// The Unix timestamp for this date at midnight in the given time zone.
346  #[cfg(feature = "tz")]
347  pub const fn timestamp_tz(&self, tz: tz::TimeZoneRef<'static>) -> tz::TzResult<i64> {
348    match tz.find_local_time_type(self.timestamp()) {
349      Ok(ts) => Ok(self.timestamp() - ts.ut_offset() as i64),
350      Err(e) => Err(e),
351    }
352  }
353}
354
355impl Date {
356  /// The date representing today, according to the system local clock.
357  ///
358  /// ## Panic
359  ///
360  /// This function will panic if the system clock is set to a time prior to January 1, 1970, or if
361  /// the local time zone can not be determined.
362  #[cfg(feature = "tz")]
363  pub fn today() -> Self {
364    let tz = tzdb::local_tz().expect("Could not determine local time zone");
365    let now =
366      now().duration_since(UNIX_EPOCH).expect("system time set prior to 1970").as_secs() as i64;
367    let offset = tz
368      .find_local_time_type(now)
369      .expect("Local time zone lacks information for this timestamp")
370      .ut_offset() as i64;
371    Self::from_timestamp(now + offset)
372  }
373
374  /// The date representing today, in the provided time zone.
375  #[cfg(feature = "tz")]
376  pub fn today_tz(tz: tz::TimeZoneRef<'static>) -> tz::TzResult<Self> {
377    let now =
378      now().duration_since(UNIX_EPOCH).expect("system time set prior to 1970").as_secs() as i64;
379    let offset = tz.find_local_time_type(now)?.ut_offset() as i64;
380    Ok(Self::from_timestamp(now + offset))
381  }
382
383  /// The date representing today, in UTC.
384  ///
385  /// ## Panic
386  ///
387  /// This function will panic if the system clock is set to a time prior to January 1, 1970.
388  pub fn today_utc() -> Self {
389    let now = now().duration_since(UNIX_EPOCH).expect("system time set prior to 1970").as_secs();
390    Self::from_timestamp(now as i64)
391  }
392}
393
394impl Date {
395  /// Format the date according to the provided `strftime` specifier.
396  #[doc = include_str!("../support/date-format.md")]
397  ///
398  #[doc = include_str!("../support/padding.md")]
399  ///
400  #[doc = include_str!("../support/plain-characters.md")]
401  pub const fn format(self, format_str: &str) -> self::format::FormattedDate<'_> {
402    format::FormattedDate { date: self, format: format_str }
403  }
404}
405
406impl Date {
407  /// An iterator of dates beginning with this date, and ending with the provided end date
408  /// (inclusive).
409  pub fn iter_through(&self, end: Date) -> iter::DateIterator {
410    iter::DateIterator::new(self, end)
411  }
412}
413
414impl Date {
415  /// The maximum date that can be represented.
416  pub const MAX: Self = Date::new(32767, 12, 31);
417  /// The minimum date that can be represented.
418  pub const MIN: Self = Date::new(-32768, 1, 1);
419}
420
421#[cfg(feature = "easter")]
422impl Date {
423  /// The date of Easter in the Gregorian calendar for the given year.
424  pub const fn easter(year: i16) -> Self {
425    assert!(year >= 1583 || year <= 9999, "Year out of bounds");
426    let a = year % 19;
427    let b = year / 100;
428    let c = year % 100;
429    let d = b / 4;
430    let e = b % 4;
431    let f = (b + 8) / 25;
432    let g = (b - f + 1) / 3;
433    let h = (19 * a + b - d - g + 15) % 30;
434    let i = c / 4;
435    let j = c % 4;
436    let k = (32 + 2 * e + 2 * i - h - j) % 7;
437    let l = (a + 11 * h + 22 * k) / 451;
438    let month = (h + k - 7 * l + 114) / 31;
439    let day = (h + k - 7 * l + 114) % 31 + 1;
440    Self::new(year, month as u8, day as u8)
441  }
442}
443
444impl fmt::Debug for Date {
445  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
446    write!(f, "{}", self.format("%Y-%m-%d"))
447  }
448}
449
450impl fmt::Display for Date {
451  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
452    write!(f, "{}", self.format("%Y-%m-%d"))
453  }
454}
455
456#[cfg(feature = "log")]
457impl log::kv::ToValue for Date {
458  fn to_value(&self) -> log::kv::Value<'_> {
459    log::kv::Value::from_debug(self)
460  }
461}
462
463impl FromStr for Date {
464  type Err = ParseError;
465
466  fn from_str(s: &str) -> ParseResult<Self> {
467    Self::parse(s, "%Y-%m-%d")
468  }
469}
470
471impl From<strptime::RawDate> for Date {
472  fn from(value: strptime::RawDate) -> Self {
473    Self::new(value.year(), value.month(), value.day())
474  }
475}
476
477impl PartialEq<Date> for &Date {
478  fn eq(&self, other: &Date) -> bool {
479    *self == other
480  }
481}
482
483impl PartialOrd<Date> for &Date {
484  fn partial_cmp(&self, other: &Date) -> Option<Ordering> {
485    (*self).partial_cmp(other)
486  }
487}
488
489#[cfg(not(test))]
490fn now() -> SystemTime {
491  SystemTime::now()
492}
493
494#[cfg(test)]
495use tests::now;
496
497#[cfg(test)]
498mod tests {
499  use std::cell::RefCell;
500
501  use assert2::check;
502
503  use super::*;
504
505  thread_local! {
506    static MOCK_TIME: RefCell<Option<SystemTime>> = const { RefCell::new(None) };
507  }
508
509  fn set_now(time: SystemTime) {
510    MOCK_TIME.with(|cell| *cell.borrow_mut() = Some(time));
511  }
512
513  fn clear_now() {
514    MOCK_TIME.with(|cell| *cell.borrow_mut() = None);
515  }
516
517  pub(super) fn now() -> SystemTime {
518    MOCK_TIME.with(|cell| cell.borrow().as_ref().cloned().unwrap_or_else(SystemTime::now))
519  }
520
521  #[test]
522  fn test_internal_repr() {
523    check!(date! { 1969-12-31 }.0 == -1);
524    check!(date! { 1970-01-01 }.0 == 0);
525    check!(date! { 1970-01-02 }.0 == 1);
526  }
527
528  #[test]
529  fn test_ymd_readback() {
530    for year in [2020, 2022, 2100] {
531      for month in 1..=12 {
532        let days = match month {
533          1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
534          4 | 6 | 9 | 11 => 30,
535          2 => match utils::is_leap_year(year) {
536            true => 29,
537            false => 28,
538          },
539          #[cfg(not(tarpaulin_include))]
540          _ => panic!("Unreachable"),
541        };
542        for day in 1..=days {
543          let date = Date::new(year, month, day);
544          check!(date.year() == year);
545          check!(date.month() == month);
546          check!(date.day() == day);
547        }
548      }
549    }
550  }
551
552  #[test]
553  #[should_panic]
554  fn test_overflow_panic_day() {
555    Date::new(2012, 4, 31);
556  }
557
558  #[test]
559  #[should_panic]
560  fn test_overflow_panic_month() {
561    Date::new(2012, 13, 1);
562  }
563
564  #[test]
565  #[should_panic]
566  fn test_overflow_panic_ly() {
567    Date::new(2100, 2, 29);
568  }
569
570  #[test]
571  #[allow(clippy::zero_prefixed_literal)]
572  fn test_ymd_overflow() {
573    macro_rules! overflows_to {
574      ($y1:literal-$m1:literal-$d1:literal
575          == $y2:literal-$m2:literal-$d2:literal) => {
576        let date1 = Date::overflowing_new($y1, $m1, $d1);
577        let date2 = Date::new($y2, $m2, $d2);
578        check!(date1 == date2);
579      };
580    }
581    overflows_to! { 2022-01-32 == 2022-02-01 };
582    overflows_to! { 2022-02-29 == 2022-03-01 };
583    overflows_to! { 2022-03-32 == 2022-04-01 };
584    overflows_to! { 2022-04-31 == 2022-05-01 };
585    overflows_to! { 2022-05-32 == 2022-06-01 };
586    overflows_to! { 2022-06-31 == 2022-07-01 };
587    overflows_to! { 2022-07-32 == 2022-08-01 };
588    overflows_to! { 2022-08-32 == 2022-09-01 };
589    overflows_to! { 2022-09-31 == 2022-10-01 };
590    overflows_to! { 2022-10-32 == 2022-11-01 };
591    overflows_to! { 2022-11-31 == 2022-12-01 };
592    overflows_to! { 2022-12-32 == 2023-01-01 };
593    overflows_to! { 2022-00-00 == 2021-11-30 };
594    overflows_to! { 2022-01-00 == 2021-12-31 };
595    overflows_to! { 2022-02-00 == 2022-01-31 };
596    overflows_to! { 2022-03-00 == 2022-02-28 };
597    overflows_to! { 2022-04-00 == 2022-03-31 };
598    overflows_to! { 2022-05-00 == 2022-04-30 };
599    overflows_to! { 2022-06-00 == 2022-05-31 };
600    overflows_to! { 2022-07-00 == 2022-06-30 };
601    overflows_to! { 2022-08-00 == 2022-07-31 };
602    overflows_to! { 2022-09-00 == 2022-08-31 };
603    overflows_to! { 2022-10-00 == 2022-09-30 };
604    overflows_to! { 2022-11-00 == 2022-10-31 };
605    overflows_to! { 2022-12-00 == 2022-11-30 };
606    overflows_to! { 2020-02-30 == 2020-03-01 };
607    overflows_to! { 2020-03-00 == 2020-02-29 };
608    overflows_to! { 2022-01-45 == 2022-02-14 };
609    overflows_to! { 2022-13-15 == 2023-01-15 };
610    overflows_to! { 2022-00-15 == 2021-12-15 };
611  }
612
613  #[test]
614  fn test_display() {
615    check!(date! { 2012-04-21 }.to_string() == "2012-04-21");
616    check!(format!("{:?}", date! { 2012-04-21 }) == "2012-04-21");
617  }
618
619  #[test]
620  fn test_week() {
621    check!(date! { 2022-01-01 }.week() == 0); // Saturday
622    check!(date! { 2022-01-02 }.week() == 1); // Sunday
623    check!(date! { 2023-01-01 }.week() == 1); // Sunday
624    check!(date! { 2023-12-31 }.week() == 53); // Sunday
625    check!(date! { 2024-01-01 }.week() == 0); // Monday
626    check!(date! { 2024-01-07 }.week() == 1); // Sunday
627    check!(date! { 2024-01-08 }.week() == 1); // Monday
628    check!(date! { 2024-01-14 }.week() == 2); // Sunday
629  }
630
631  #[test]
632  fn test_today() {
633    set_now(SystemTime::UNIX_EPOCH + std::time::Duration::from_secs(86_400));
634    check!(Date::today_utc() == date! { 1970-01-02 });
635    clear_now();
636  }
637
638  #[cfg(feature = "tz")]
639  #[test]
640  fn test_today_tz() -> tz::TzResult<()> {
641    set_now(SystemTime::UNIX_EPOCH + std::time::Duration::from_secs(86_400));
642    check!([date! { 1970-01-01 }, date! { 1970-01-02 }].contains(&Date::today()));
643    check!(Date::today_tz(tz::us::EASTERN)? == date! { 1970-01-01 });
644    clear_now();
645    Ok(())
646  }
647
648  #[cfg(feature = "tz")]
649  #[test]
650  fn test_timestamp_tz() -> tz::TzResult<()> {
651    check!(Date::from_timestamp_tz(1335020400, tz::us::EASTERN)? == date! { 2012-04-21 });
652    check!(Date::from_timestamp_tz(0, tz::us::PACIFIC)? == date! { 1969-12-31 });
653    check!(date! { 2012-04-21 }.timestamp_tz(tz::us::EASTERN)? == 1334980800);
654    Ok(())
655  }
656
657  #[cfg(feature = "easter")]
658  #[test]
659  fn test_easter() {
660    check!(Date::easter(2013) == date! { 2013-03-31 });
661    check!(Date::easter(2014) == date! { 2014-04-20 });
662    check!(Date::easter(2015) == date! { 2015-04-05 });
663    check!(Date::easter(2016) == date! { 2016-03-27 });
664    check!(Date::easter(2017) == date! { 2017-04-16 });
665    check!(Date::easter(2018) == date! { 2018-04-01 });
666    check!(Date::easter(2019) == date! { 2019-04-21 });
667    check!(Date::easter(2020) == date! { 2020-04-12 });
668    check!(Date::easter(2021) == date! { 2021-04-04 });
669    check!(Date::easter(2022) == date! { 2022-04-17 });
670    check!(Date::easter(2023) == date! { 2023-04-09 });
671    check!(Date::easter(2024) == date! { 2024-03-31 });
672    check!(Date::easter(2025) == date! { 2025-04-20 });
673    check!(Date::easter(2026) == date! { 2026-04-05 });
674    check!(Date::easter(2027) == date! { 2027-03-28 });
675    check!(Date::easter(2028) == date! { 2028-04-16 });
676    check!(Date::easter(2029) == date! { 2029-04-01 });
677    check!(Date::easter(2030) == date! { 2030-04-21 });
678    check!(Date::easter(2031) == date! { 2031-04-13 });
679    check!(Date::easter(2032) == date! { 2032-03-28 });
680    check!(Date::easter(2033) == date! { 2033-04-17 });
681    check!(Date::easter(2034) == date! { 2034-04-09 });
682    check!(Date::easter(2035) == date! { 2035-03-25 });
683  }
684
685  #[test]
686  fn test_from_str() -> ParseResult<()> {
687    check!("2012-04-21".parse::<Date>()? == date! { 2012-04-21 });
688    check!("2012-4-21".parse::<Date>().is_err());
689    check!("04/21/2012".parse::<Date>().is_err());
690    check!("12-04-21".parse::<Date>().is_err());
691    check!("foo".parse::<Date>().map_err(|e| e.to_string()).unwrap_err().contains("foo"));
692    Ok(())
693  }
694
695  #[test]
696  fn test_parse() -> ParseResult<()> {
697    check!(Date::parse("04/21/12", "%m/%d/%y")? == date! { 2012-04-21 });
698    check!(Date::parse("Saturday, April 21, 2012", "%A, %B %-d, %Y")? == date! { 2012-04-21 });
699    Ok(())
700  }
701
702  #[test]
703  #[cfg(feature = "ord")]
704  fn test_ord() {
705    let date = date! { 1970-01-06 };
706    check!(date.ord() == 5);
707  }
708
709  #[test]
710  fn test_ref_eq() {
711    let date = date! { 2012-04-21 };
712    check!(&date == date);
713    check!(&date > date! { 2012-04-20 });
714  }
715}