1use std::fmt;
26use std::str::FromStr;
27use std::time::SystemTime;
28use std::time::UNIX_EPOCH;
29
30use strptime::ParseError;
31use strptime::ParseResult;
32use strptime::Parser;
33
34#[cfg(feature = "tz")]
36pub mod tz {
37  pub use tz::TimeZoneRef;
38  pub use tzdb::tz_by_name;
39
40  pub type TzResult<T> = Result<T, ::tz::error::TzError>;
45
46  pub use tzdb::time_zone::africa;
47  pub use tzdb::time_zone::america;
48  pub use tzdb::time_zone::antarctica;
49  pub use tzdb::time_zone::arctic;
50  pub use tzdb::time_zone::asia;
51  pub use tzdb::time_zone::atlantic;
52  pub use tzdb::time_zone::australia;
53  pub use tzdb::time_zone::europe;
54  pub use tzdb::time_zone::indian;
55  pub use tzdb::time_zone::us;
56}
57
58#[macro_export]
70macro_rules! date {
71  ($y:literal-$m:literal-$d:literal) => {{
72    #[allow(clippy::zero_prefixed_literal)]
73    {
74      const { $crate::Date::new($y, $m, $d) }
75    }
76  }};
77}
78
79#[cfg(feature = "diesel-pg")]
80mod diesel_pg;
81#[cfg(feature = "duckdb")]
82mod duckdb;
83mod format;
84pub mod interval;
85pub mod iter;
86#[cfg(feature = "serde")]
87mod serde;
88mod utils;
89mod weekday;
90
91pub use weekday::Weekday;
92
93#[derive(Copy, Clone, Eq, Hash, PartialEq, PartialOrd, Ord)]
95#[cfg_attr(feature = "diesel-pg", derive(diesel::AsExpression, diesel::FromSqlRow))]
96#[cfg_attr(feature = "diesel-pg", diesel(sql_type = ::diesel::sql_types::Date))]
97#[repr(transparent)]
98pub struct Date(i32);
99
100impl Date {
101  pub const fn new(year: i16, month: u8, day: u8) -> Self {
119    const MONTH_DAYS: [u8; 12] = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
120    assert!(month >= 1 && month <= 12, "Month out-of-bounds");
121    assert!(day >= 1 && day <= MONTH_DAYS[month as usize - 1], "Day out-of-bounds");
122    if month == 2 && day == 29 {
123      assert!(utils::is_leap_year(year), "February 29 only occurs on leap years")
124    }
125
126    let year = year as i32 - if month <= 2 { 1 } else { 0 };
130    let month = month as i32;
131    let day = day as i32;
132    let era: i32 = if year >= 0 { year } else { year - 399 } / 400;
133    let year_of_era = year - era * 400;
134    let day_of_year = (153 * (if month > 2 { month - 3 } else { month + 9 }) + 2) / 5 + day - 1;
135    let day_of_era = year_of_era * 365 + year_of_era / 4 - year_of_era / 100 + day_of_year;
136    Self(era * 146097 + day_of_era - 719468)
137  }
138
139  pub const fn from_timestamp(unix_timestamp: i64) -> Self {
164    let day_count = unix_timestamp.div_euclid(86_400) as i32;
165    Self(day_count)
166  }
167
168  #[cfg(feature = "ord")]
180  pub const fn from_ord(ord: i32) -> Self {
181    Self(ord)
182  }
183
184  #[cfg(feature = "tz")]
186  pub const fn from_timestamp_tz(
187    unix_timestamp: i64, tz: tz::TimeZoneRef<'static>,
188  ) -> tz::TzResult<Self> {
189    match tz.find_local_time_type(unix_timestamp) {
190      Ok(tz) => Ok(Self::from_timestamp(unix_timestamp + tz.ut_offset() as i64)),
191      Err(e) => Err(e),
192    }
193  }
194
195  pub const fn overflowing_new(year: i16, month: u8, day: u8) -> Self {
205    let mut year = year;
206    let mut month = month;
207    let mut day = day;
208
209    while month > 12 {
211      year += 1;
212      month -= 12;
213    }
214    if day == 0 {
215      if month <= 1 {
216        year -= 1;
217        month += 11;
218      } else {
219        month -= 1;
220      }
221      day = utils::days_in_month(year, month);
222    }
223    if month == 0 {
224      year -= 1;
225      month = 12;
226    }
227    while day > utils::days_in_month(year, month) {
228      day -= utils::days_in_month(year, month);
229      month += 1;
230      if month == 13 {
231        year += 1;
232        month = 1;
233      }
234    }
235
236    Self::new(year, month, day)
238  }
239
240  pub fn parse(date_str: impl AsRef<str>, date_fmt: &'static str) -> ParseResult<Date> {
242    let parser = Parser::new(date_fmt);
243    let raw_date = parser.parse(date_str)?.date()?;
244    Ok(raw_date.into())
245  }
246}
247
248impl Date {
249  pub(crate) const fn ymd(&self) -> (i16, u8, u8) {
251    let shifted = self.0 + 719468; let era = if shifted >= 0 { shifted } else { shifted - 146_096 } / 146_097;
256    let doe = shifted - era * 146_097; let year_of_era = (doe - doe / 1_460 + doe / 36_524 - doe / 146_096) / 365;
258    let year = year_of_era + era * 400;
259    let day_of_year = doe - (365 * year_of_era + year_of_era / 4 - year_of_era / 100);
260    let mp = (5 * day_of_year + 2) / 153;
261    let day = day_of_year - (153 * mp + 2) / 5 + 1;
262    let month = if mp < 10 { mp + 3 } else { mp - 9 };
263    (year as i16 + if month <= 2 { 1 } else { 0 }, month as u8, day as u8)
264  }
265
266  #[inline]
268  #[cfg(feature = "ord")]
269  pub const fn ord(&self) -> i32 {
270    self.0
271  }
272
273  #[inline]
275  pub const fn year(&self) -> i16 {
276    self.ymd().0
277  }
278
279  #[inline]
283  pub const fn month(&self) -> u8 {
284    self.ymd().1
285  }
286
287  #[inline]
291  pub const fn day(&self) -> u8 {
292    self.ymd().2
293  }
294
295  #[inline]
297  pub const fn day_of_year(&self) -> u16 {
298    (self.0 - Date::new(self.year() - 1, 12, 31).0) as u16
299  }
300
301  pub const fn week(&self) -> u16 {
306    let jan1 = Date::new(self.year(), 1, 1);
307    let first_sunday = jan1.0 + if self.0 % 7 == 3 { 0 } else { 7 } - (self.0 + 4) % 7;
308    ((self.0 - first_sunday).div_euclid(7) + 1) as u16
309  }
310
311  #[inline]
313  pub const fn weekday(&self) -> Weekday {
314    match (self.0 + 4) % 7 {
315      0 => Weekday::Sunday,
316      1 | -6 => Weekday::Monday,
317      2 | -5 => Weekday::Tuesday,
318      3 | -4 => Weekday::Wednesday,
319      4 | -3 => Weekday::Thursday,
320      5 | -2 => Weekday::Friday,
321      6 | -1 => Weekday::Saturday,
322      #[cfg(not(tarpaulin_include))]
323      _ => panic!("Unreachable: Anything % 7 must be within -6 to 6"),
324    }
325  }
326}
327
328impl Date {
329  pub const fn timestamp(&self) -> i64 {
341    self.0 as i64 * 86_400
342  }
343
344  #[cfg(feature = "tz")]
346  pub const fn timestamp_tz(&self, tz: tz::TimeZoneRef<'static>) -> tz::TzResult<i64> {
347    match tz.find_local_time_type(self.timestamp()) {
348      Ok(ts) => Ok(self.timestamp() - ts.ut_offset() as i64),
349      Err(e) => Err(e),
350    }
351  }
352}
353
354impl Date {
355  #[cfg(feature = "tz")]
362  pub fn today() -> Self {
363    let tz = tzdb::local_tz().expect("Could not determine local time zone");
364    let now =
365      now().duration_since(UNIX_EPOCH).expect("system time set prior to 1970").as_secs() as i64;
366    let offset = tz
367      .find_local_time_type(now)
368      .expect("Local time zone lacks information for this timestamp")
369      .ut_offset() as i64;
370    Self::from_timestamp(now + offset)
371  }
372
373  #[cfg(feature = "tz")]
375  pub fn today_tz(tz: tz::TimeZoneRef<'static>) -> tz::TzResult<Self> {
376    let now =
377      now().duration_since(UNIX_EPOCH).expect("system time set prior to 1970").as_secs() as i64;
378    let offset = tz.find_local_time_type(now)?.ut_offset() as i64;
379    Ok(Self::from_timestamp(now + offset))
380  }
381
382  pub fn today_utc() -> Self {
388    let now = now().duration_since(UNIX_EPOCH).expect("system time set prior to 1970").as_secs();
389    Self::from_timestamp(now as i64)
390  }
391}
392
393impl Date {
394  #[doc = include_str!("../support/date-format.md")]
396  #[doc = include_str!("../support/padding.md")]
398  #[doc = include_str!("../support/plain-characters.md")]
400  pub const fn format(self, format_str: &str) -> self::format::FormattedDate<'_> {
401    format::FormattedDate { date: self, format: format_str }
402  }
403}
404
405impl Date {
406  pub fn iter_through(&self, end: Date) -> iter::DateIterator {
409    iter::DateIterator::new(self, end)
410  }
411}
412
413impl Date {
414  pub const MAX: Self = Date::new(32767, 12, 31);
416  pub const MIN: Self = Date::new(-32768, 1, 1);
418}
419
420#[cfg(feature = "easter")]
421impl Date {
422  pub const fn easter(year: i16) -> Self {
424    assert!(year >= 1583 || year <= 9999, "Year out of bounds");
425    let a = year % 19;
426    let b = year / 100;
427    let c = year % 100;
428    let d = b / 4;
429    let e = b % 4;
430    let f = (b + 8) / 25;
431    let g = (b - f + 1) / 3;
432    let h = (19 * a + b - d - g + 15) % 30;
433    let i = c / 4;
434    let j = c % 4;
435    let k = (32 + 2 * e + 2 * i - h - j) % 7;
436    let l = (a + 11 * h + 22 * k) / 451;
437    let month = (h + k - 7 * l + 114) / 31;
438    let day = (h + k - 7 * l + 114) % 31 + 1;
439    Self::new(year, month as u8, day as u8)
440  }
441}
442
443impl fmt::Debug for Date {
444  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
445    write!(f, "{}", self.format("%Y-%m-%d"))
446  }
447}
448
449impl fmt::Display for Date {
450  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
451    write!(f, "{}", self.format("%Y-%m-%d"))
452  }
453}
454
455#[cfg(feature = "log")]
456impl log::kv::ToValue for Date {
457  fn to_value(&self) -> log::kv::Value<'_> {
458    log::kv::Value::from_debug(self)
459  }
460}
461
462impl FromStr for Date {
463  type Err = ParseError;
464
465  fn from_str(s: &str) -> ParseResult<Self> {
466    Self::parse(s, "%Y-%m-%d")
467  }
468}
469
470impl From<strptime::RawDate> for Date {
471  fn from(value: strptime::RawDate) -> Self {
472    Self::new(value.year(), value.month(), value.day())
473  }
474}
475
476#[cfg(not(test))]
477fn now() -> SystemTime {
478  SystemTime::now()
479}
480
481#[cfg(test)]
482use tests::now;
483
484#[cfg(test)]
485mod tests {
486  use std::cell::RefCell;
487
488  use assert2::check;
489
490  use super::*;
491
492  thread_local! {
493    static MOCK_TIME: RefCell<Option<SystemTime>> = const { RefCell::new(None) };
494  }
495
496  fn set_now(time: SystemTime) {
497    MOCK_TIME.with(|cell| *cell.borrow_mut() = Some(time));
498  }
499
500  fn clear_now() {
501    MOCK_TIME.with(|cell| *cell.borrow_mut() = None);
502  }
503
504  pub(super) fn now() -> SystemTime {
505    MOCK_TIME.with(|cell| cell.borrow().as_ref().cloned().unwrap_or_else(SystemTime::now))
506  }
507
508  #[test]
509  fn test_internal_repr() {
510    check!(date! { 1969-12-31 }.0 == -1);
511    check!(date! { 1970-01-01 }.0 == 0);
512    check!(date! { 1970-01-02 }.0 == 1);
513  }
514
515  #[test]
516  fn test_ymd_readback() {
517    for year in [2020, 2022, 2100] {
518      for month in 1..=12 {
519        let days = match month {
520          1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
521          4 | 6 | 9 | 11 => 30,
522          2 => match utils::is_leap_year(year) {
523            true => 29,
524            false => 28,
525          },
526          #[cfg(not(tarpaulin_include))]
527          _ => panic!("Unreachable"),
528        };
529        for day in 1..=days {
530          let date = Date::new(year, month, day);
531          check!(date.year() == year);
532          check!(date.month() == month);
533          check!(date.day() == day);
534        }
535      }
536    }
537  }
538
539  #[test]
540  #[should_panic]
541  fn test_overflow_panic_day() {
542    Date::new(2012, 4, 31);
543  }
544
545  #[test]
546  #[should_panic]
547  fn test_overflow_panic_month() {
548    Date::new(2012, 13, 1);
549  }
550
551  #[test]
552  #[should_panic]
553  fn test_overflow_panic_ly() {
554    Date::new(2100, 2, 29);
555  }
556
557  #[test]
558  #[allow(clippy::zero_prefixed_literal)]
559  fn test_ymd_overflow() {
560    macro_rules! overflows_to {
561      ($y1:literal-$m1:literal-$d1:literal
562          == $y2:literal-$m2:literal-$d2:literal) => {
563        let date1 = Date::overflowing_new($y1, $m1, $d1);
564        let date2 = Date::new($y2, $m2, $d2);
565        check!(date1 == date2);
566      };
567    }
568    overflows_to! { 2022-01-32 == 2022-02-01 };
569    overflows_to! { 2022-02-29 == 2022-03-01 };
570    overflows_to! { 2022-03-32 == 2022-04-01 };
571    overflows_to! { 2022-04-31 == 2022-05-01 };
572    overflows_to! { 2022-05-32 == 2022-06-01 };
573    overflows_to! { 2022-06-31 == 2022-07-01 };
574    overflows_to! { 2022-07-32 == 2022-08-01 };
575    overflows_to! { 2022-08-32 == 2022-09-01 };
576    overflows_to! { 2022-09-31 == 2022-10-01 };
577    overflows_to! { 2022-10-32 == 2022-11-01 };
578    overflows_to! { 2022-11-31 == 2022-12-01 };
579    overflows_to! { 2022-12-32 == 2023-01-01 };
580    overflows_to! { 2022-00-00 == 2021-11-30 };
581    overflows_to! { 2022-01-00 == 2021-12-31 };
582    overflows_to! { 2022-02-00 == 2022-01-31 };
583    overflows_to! { 2022-03-00 == 2022-02-28 };
584    overflows_to! { 2022-04-00 == 2022-03-31 };
585    overflows_to! { 2022-05-00 == 2022-04-30 };
586    overflows_to! { 2022-06-00 == 2022-05-31 };
587    overflows_to! { 2022-07-00 == 2022-06-30 };
588    overflows_to! { 2022-08-00 == 2022-07-31 };
589    overflows_to! { 2022-09-00 == 2022-08-31 };
590    overflows_to! { 2022-10-00 == 2022-09-30 };
591    overflows_to! { 2022-11-00 == 2022-10-31 };
592    overflows_to! { 2022-12-00 == 2022-11-30 };
593    overflows_to! { 2020-02-30 == 2020-03-01 };
594    overflows_to! { 2020-03-00 == 2020-02-29 };
595    overflows_to! { 2022-01-45 == 2022-02-14 };
596    overflows_to! { 2022-13-15 == 2023-01-15 };
597    overflows_to! { 2022-00-15 == 2021-12-15 };
598  }
599
600  #[test]
601  fn test_display() {
602    check!(date! { 2012-04-21 }.to_string() == "2012-04-21");
603    check!(format!("{:?}", date! { 2012-04-21 }) == "2012-04-21");
604  }
605
606  #[test]
607  fn test_week() {
608    check!(date! { 2022-01-01 }.week() == 0); check!(date! { 2022-01-02 }.week() == 1); check!(date! { 2023-01-01 }.week() == 1); check!(date! { 2023-12-31 }.week() == 53); check!(date! { 2024-01-01 }.week() == 0); check!(date! { 2024-01-07 }.week() == 1); check!(date! { 2024-01-08 }.week() == 1); check!(date! { 2024-01-14 }.week() == 2); }
617
618  #[test]
619  fn test_today() {
620    set_now(SystemTime::UNIX_EPOCH + std::time::Duration::from_secs(86_400));
621    check!(Date::today_utc() == date! { 1970-01-02 });
622    clear_now();
623  }
624
625  #[cfg(feature = "tz")]
626  #[test]
627  fn test_today_tz() -> tz::TzResult<()> {
628    set_now(SystemTime::UNIX_EPOCH + std::time::Duration::from_secs(86_400));
629    check!([date! { 1970-01-01 }, date! { 1970-01-02 }].contains(&Date::today()));
630    check!(Date::today_tz(tz::us::EASTERN)? == date! { 1970-01-01 });
631    clear_now();
632    Ok(())
633  }
634
635  #[cfg(feature = "tz")]
636  #[test]
637  fn test_timestamp_tz() -> tz::TzResult<()> {
638    check!(Date::from_timestamp_tz(1335020400, tz::us::EASTERN)? == date! { 2012-04-21 });
639    check!(Date::from_timestamp_tz(0, tz::us::PACIFIC)? == date! { 1969-12-31 });
640    check!(date! { 2012-04-21 }.timestamp_tz(tz::us::EASTERN)? == 1334980800);
641    Ok(())
642  }
643
644  #[cfg(feature = "easter")]
645  #[test]
646  fn test_easter() {
647    check!(Date::easter(2013) == date! { 2013-03-31 });
648    check!(Date::easter(2014) == date! { 2014-04-20 });
649    check!(Date::easter(2015) == date! { 2015-04-05 });
650    check!(Date::easter(2016) == date! { 2016-03-27 });
651    check!(Date::easter(2017) == date! { 2017-04-16 });
652    check!(Date::easter(2018) == date! { 2018-04-01 });
653    check!(Date::easter(2019) == date! { 2019-04-21 });
654    check!(Date::easter(2020) == date! { 2020-04-12 });
655    check!(Date::easter(2021) == date! { 2021-04-04 });
656    check!(Date::easter(2022) == date! { 2022-04-17 });
657    check!(Date::easter(2023) == date! { 2023-04-09 });
658    check!(Date::easter(2024) == date! { 2024-03-31 });
659    check!(Date::easter(2025) == date! { 2025-04-20 });
660    check!(Date::easter(2026) == date! { 2026-04-05 });
661    check!(Date::easter(2027) == date! { 2027-03-28 });
662    check!(Date::easter(2028) == date! { 2028-04-16 });
663    check!(Date::easter(2029) == date! { 2029-04-01 });
664    check!(Date::easter(2030) == date! { 2030-04-21 });
665    check!(Date::easter(2031) == date! { 2031-04-13 });
666    check!(Date::easter(2032) == date! { 2032-03-28 });
667    check!(Date::easter(2033) == date! { 2033-04-17 });
668    check!(Date::easter(2034) == date! { 2034-04-09 });
669    check!(Date::easter(2035) == date! { 2035-03-25 });
670  }
671
672  #[test]
673  fn test_from_str() -> ParseResult<()> {
674    check!("2012-04-21".parse::<Date>()? == date! { 2012-04-21 });
675    check!("2012-4-21".parse::<Date>().is_err());
676    check!("04/21/2012".parse::<Date>().is_err());
677    check!("12-04-21".parse::<Date>().is_err());
678    check!("foo".parse::<Date>().map_err(|e| e.to_string()).unwrap_err().contains("foo"));
679    Ok(())
680  }
681
682  #[test]
683  fn test_parse() -> ParseResult<()> {
684    check!(Date::parse("04/21/12", "%m/%d/%y")? == date! { 2012-04-21 });
685    check!(Date::parse("Saturday, April 21, 2012", "%A, %B %-d, %Y")? == date! { 2012-04-21 });
686    Ok(())
687  }
688
689  #[test]
690  #[cfg(feature = "ord")]
691  fn test_ord() {
692    let date = date! { 1970-01-06 };
693    check!(date.ord() == 5);
694  }
695}