use crate::{
bounds::{self as b, RangeError},
civil::{self, DateTime, Weekday},
macros::{ctry, rbail, rtry, unwrapr},
};
#[derive(Clone, Copy, Eq, Hash, PartialEq, PartialOrd, Ord)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct Date {
year: i16,
month: i8,
day: i8,
}
impl Date {
pub const MIN: Date = {
let min = UnixEpochDay::MIN.to_date();
assert!(min.year() == -9999);
assert!(min.month() == 1);
assert!(min.day() == 1);
min
};
pub const MAX: Date = {
let max = UnixEpochDay::MAX.to_date();
assert!(max.year() == 9999);
assert!(max.month() == 12);
assert!(max.day() == 31);
max
};
#[inline]
pub const fn new(
year: i16,
month: i8,
day: i8,
) -> Result<Date, RangeError> {
let year = rtry!(b::Year::checkc(year as i64));
let month = rtry!(b::Month::checkc(month as i64));
if day < 1 {
rbail!(b::Day::error());
} else if day > 28 && day > civil::days_in_month(year, month) {
rbail!(b::SpecialBoundsError::DateInvalidDay { year, month });
}
Ok(Date { year, month, day })
}
#[inline]
pub const fn new_constrain(
year: i16,
month: i8,
day: i8,
) -> Result<Date, RangeError> {
let year = rtry!(b::Year::checkc(year as i64));
let month = rtry!(b::Month::checkc(month as i64));
let day = if day < 1 {
rbail!(b::Day::error());
} else if day > 28 {
let days_in_month = civil::days_in_month(year, month);
if day <= days_in_month {
day
} else {
days_in_month
}
} else {
day
};
Ok(Date { year, month, day })
}
#[inline]
pub const fn from_day_of_year(
year: i16,
day: i16,
) -> Result<Date, RangeError> {
let year = rtry!(b::Year::checkc(year as i64));
let day = rtry!(b::DayOfYear::checkc(day as i64));
let start = Date { year, month: 1, day: 1 }.to_unix_epoch_day();
let end = match start.checked_add(day as i32 - 1) {
Ok(end) => end.to_date(),
Err(_) => {
rbail!(b::SpecialBoundsError::DateInvalidDayOfYear { year })
}
};
if year != end.year {
debug_assert!(day == 366);
debug_assert!(!civil::is_leap_year(year));
rbail!(b::SpecialBoundsError::DateInvalidDayOfYear { year })
}
Ok(end)
}
#[inline]
pub const fn from_day_of_year_no_leap(
year: i16,
day: i16,
) -> Result<Date, RangeError> {
let year = rtry!(b::Year::checkc(year as i64));
let mut day = rtry!(b::DayOfYearNoLeap::checkc(day as i64));
if day >= 60 && civil::is_leap_year(year) {
day += 1;
}
Ok(unwrapr!(Date::from_day_of_year(year, day), "valid day of year"))
}
#[inline]
pub const fn year(self) -> i16 {
self.year
}
#[inline]
pub const fn month(self) -> i8 {
self.month
}
#[inline]
pub const fn day(self) -> i8 {
self.day
}
#[inline]
pub const fn weekday(self) -> Weekday {
self.to_unix_epoch_day().weekday()
}
#[inline]
pub const fn day_of_year(self) -> i16 {
const DAYS_BY_MONTH_NO_LEAP: [i16; 14] =
[0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365];
const DAYS_BY_MONTH_LEAP: [i16; 14] =
[0, 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366];
const TABLES: [[i16; 14]; 2] =
[DAYS_BY_MONTH_NO_LEAP, DAYS_BY_MONTH_LEAP];
TABLES[self.in_leap_year() as usize][self.month() as usize]
+ (self.day() as i16)
}
#[inline]
pub const fn day_of_year_no_leap(self) -> Option<i16> {
let mut days = self.day_of_year();
if self.in_leap_year() {
if days == 60 {
return None;
} else if days > 60 {
days -= 1;
}
}
Some(days)
}
#[inline]
pub const fn first_of_month(self) -> Date {
Date { day: 1, ..self }
}
#[inline]
pub const fn last_of_month(self) -> Date {
Date { day: self.days_in_month(), ..self }
}
#[inline]
pub const fn days_in_month(self) -> i8 {
civil::days_in_month(self.year(), self.month())
}
#[inline]
pub const fn first_of_year(self) -> Date {
Date { month: 1, day: 1, ..self }
}
#[inline]
pub const fn last_of_year(self) -> Date {
Date { month: 12, day: 31, ..self }
}
#[inline]
pub const fn days_in_year(self) -> i16 {
if self.in_leap_year() {
366
} else {
365
}
}
#[inline]
pub const fn in_leap_year(self) -> bool {
civil::is_leap_year(self.year())
}
#[inline]
pub const fn yesterday(self) -> Result<Date, RangeError> {
if self.day() == 1 {
if self.month() == 1 {
let year = ctry!(self.prev_year());
return Ok(Date { year, month: 12, day: 31 });
}
let month = self.month() - 1;
let day = civil::days_in_month(self.year(), month);
return Ok(Date { month, day, ..self });
}
Ok(Date { day: self.day() - 1, ..self })
}
#[inline]
pub const fn tomorrow(self) -> Result<Date, RangeError> {
if self.day() >= 28 && self.day() == self.days_in_month() {
if self.month() == 12 {
let year = ctry!(self.next_year());
return Ok(Date { year, month: 1, day: 1 });
}
let month = self.month() + 1;
return Ok(Date { month, day: 1, ..self });
}
Ok(Date { day: self.day() + 1, ..self })
}
#[inline]
pub const fn nth_weekday_of_month(
&self,
nth: i8,
weekday: Weekday,
) -> Result<Date, RangeError> {
let nth = rtry!(b::NthWeekdayOfMonth::checkc(nth as i64));
if nth == 0 {
rbail!(b::NthWeekdayOfMonth::error());
} else if nth > 0 {
let first = self.first_of_month();
let first_weekday = first.weekday();
let diff = weekday.since(first_weekday);
let day = diff + 1 + (nth - 1) * 7;
Date::new(self.year(), self.month(), day)
} else {
let last = self.last_of_month();
let last_weekday = last.weekday();
let diff = last_weekday.since(weekday);
let day = last.day() - diff - (nth.abs() - 1) * 7;
Date::new(self.year(), self.month(), day)
}
}
#[inline]
pub const fn nth_weekday(
self,
nth: i32,
weekday: Weekday,
) -> Result<Date, RangeError> {
self.to_unix_epoch_day().nth_weekday(nth, weekday)
}
#[inline]
pub(crate) const fn prev_year(self) -> Result<i16, RangeError> {
Ok(rtry!(b::Year::checked_add(self.year(), -1)))
}
#[inline]
pub(crate) const fn next_year(self) -> Result<i16, RangeError> {
Ok(rtry!(b::Year::checked_add(self.year(), 1)))
}
#[inline]
pub const fn checked_add(self, days: i32) -> Result<Date, RangeError> {
match days {
0 => Ok(self),
-1 => self.yesterday(),
1 => self.tomorrow(),
n => Ok(ctry!(self.to_unix_epoch_day().checked_add(n)).to_date()),
}
}
#[inline]
pub const fn checked_sub(self, days: i32) -> Result<Date, RangeError> {
let Some(days) = days.checked_neg() else {
rbail!(b::UnixEpochDays::error());
};
self.checked_add(days)
}
#[inline]
pub const fn until(self, other: Date) -> i32 {
-self.since(other)
}
#[inline]
pub const fn since(self, other: Date) -> i32 {
if self.year() == other.year() {
if self.month() == other.month() {
(self.day() - other.day()) as i32
} else {
(self.day_of_year() - other.day_of_year()) as i32
}
} else {
self.to_unix_epoch_day().since(other.to_unix_epoch_day())
}
}
#[inline]
#[allow(non_upper_case_globals, non_snake_case)] pub const fn to_unix_epoch_day(self) -> UnixEpochDay {
const s: u32 = 82;
const K: u32 = 719468 + 146097 * s;
const L: u32 = 400 * s;
let year = self.year as u32;
let month = self.month as u32;
let day = self.day as u32;
let J = month <= 2;
let Y = year.wrapping_add(L).wrapping_sub(J as u32);
let M = if J { month + 12 } else { month };
let D = day - 1;
let C = Y / 100;
let y_star = 1461 * Y / 4 - C + C / 4;
let m_star = (979 * M - 2919) / 32;
let N = y_star + m_star + D;
let N_U = N.wrapping_sub(K);
let epoch_day = N_U as i32;
UnixEpochDay { day: epoch_day }
}
#[inline]
pub const fn to_iso_week_date(self) -> ISOWeekDate {
let epoch_day = self.to_unix_epoch_day();
let mut year = self.year();
let mut epoch_day_year_start = iso_week_start_from_year(year);
if epoch_day.day() < epoch_day_year_start.day() {
year -= 1;
epoch_day_year_start = iso_week_start_from_year(year);
} else if self.month() == 12 && self.day() >= 29 && year < b::Year::MAX
{
let epoch_day_next_year_week_start =
iso_week_start_from_year(year + 1);
if epoch_day.day() >= epoch_day_next_year_week_start.day() {
epoch_day_year_start = epoch_day_next_year_week_start;
year += 1;
}
}
let week =
(((epoch_day.day() - epoch_day_year_start.day()) / 7) + 1) as i8;
let weekday = epoch_day.weekday();
ISOWeekDate { year, week, weekday }
}
#[inline]
pub const fn at(
self,
hour: i8,
minute: i8,
second: i8,
subsec_nanosecond: i32,
) -> DateTime {
DateTime::from_parts(
self,
civil::time(hour, minute, second, subsec_nanosecond),
)
}
}
impl core::fmt::Debug for Date {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
if self.year() < 0 {
write!(f, "-{:06}", self.year().unsigned_abs())?;
} else {
write!(f, "{:04}", self.year())?;
}
write!(f, "-{:02}-{:02}", self.month(), self.day())
}
}
impl core::ops::Sub for Date {
type Output = i32;
#[inline]
fn sub(self, rhs: Date) -> i32 {
self.since(rhs)
}
}
#[cfg(test)]
impl quickcheck::Arbitrary for Date {
fn arbitrary(g: &mut quickcheck::Gen) -> Date {
let year = b::Year::arbitrary(g);
let month = b::Month::arbitrary(g);
let day = b::Day::arbitrary(g);
Date::new_constrain(year, month, day).unwrap()
}
fn shrink(&self) -> alloc::boxed::Box<dyn Iterator<Item = Date>> {
alloc::boxed::Box::new(
(self.year(), self.month(), self.day()).shrink().filter_map(
|(year, month, day)| {
Date::new_constrain(year, month, day).ok()
},
),
)
}
}
#[derive(Clone, Copy, Eq, Hash, PartialEq, PartialOrd, Ord)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct UnixEpochDay {
day: i32,
}
impl UnixEpochDay {
pub const MIN: UnixEpochDay = UnixEpochDay { day: b::UnixEpochDays::MIN };
pub const MAX: UnixEpochDay = UnixEpochDay { day: b::UnixEpochDays::MAX };
#[inline]
pub const fn new(day: i32) -> Result<UnixEpochDay, RangeError> {
let day = rtry!(b::UnixEpochDays::checkc(day as i64));
Ok(UnixEpochDay { day })
}
#[inline]
pub const fn day(self) -> i32 {
self.day
}
#[inline]
pub const fn weekday(&self) -> Weekday {
let result = Weekday::from_monday_one_offset({
const M: u32 = {
const fn div_ceil(lhs: u64, rhs: u64) -> u64 {
let d = lhs / rhs;
let r = lhs % rhs;
if r > 0 {
d + 1
} else {
d
}
}
let n = div_ceil(1u64 << 32, 7) as u32;
assert!(n == 613_566_757);
n
};
const Z: u32 = 0x90000000; let rd: u32 = self.day() as u32;
(rd.wrapping_mul(M).wrapping_add(Z) >> 29) as i8
});
unwrapr!(result, "weekday must be in range 1..=7")
}
#[inline]
pub const fn nth_weekday(
self,
nth: i32,
weekday: Weekday,
) -> Result<Date, RangeError> {
let nth = rtry!(b::NthWeekday::checkc(nth as i64));
if nth == 0 {
rbail!(b::NthWeekday::error());
} else if nth > 0 {
let weekday_diff = weekday.since(self.weekday().next()) as i32;
let diff = (nth - 1) * 7 + weekday_diff;
let end = ctry!(self.checked_add(diff + 1));
Ok(end.to_date())
} else {
let weekday_diff = self.weekday().previous().since(weekday) as i32;
let nth = nth.abs();
let diff = -((nth - 1) * 7 + weekday_diff);
let end = ctry!(self.checked_add(diff - 1));
Ok(end.to_date())
}
}
#[inline]
pub const fn checked_add(
self,
days: i32,
) -> Result<UnixEpochDay, RangeError> {
let day = rtry!(b::UnixEpochDays::checked_add(self.day(), days));
Ok(UnixEpochDay { day })
}
#[inline]
pub const fn checked_sub(
self,
days: i32,
) -> Result<UnixEpochDay, RangeError> {
let Some(days) = days.checked_neg() else {
rbail!(b::UnixEpochDays::error());
};
self.checked_add(days)
}
#[inline]
pub const fn until(self, other: UnixEpochDay) -> i32 {
-self.since(other)
}
#[inline]
pub const fn since(self, other: UnixEpochDay) -> i32 {
self.day() - other.day()
}
#[inline]
#[allow(non_upper_case_globals, non_snake_case)] pub const fn to_date(self) -> Date {
const s: u32 = 82;
const K: u32 = 719468 + 146097 * s;
const L: u32 = 400 * s;
let N_U = self.day as u32;
let N = N_U.wrapping_add(K);
let N_1 = 4 * N + 3;
let C = N_1 / 146097;
let N_C = (N_1 % 146097) / 4;
let N_2 = 4 * N_C + 3;
let P_2 = 2939745 * (N_2 as u64);
let Z = (P_2 / 4294967296) as u32;
let N_Y = (P_2 % 4294967296) as u32 / 2939745 / 4;
let Y = 100 * C + Z;
let N_3 = 2141 * N_Y + 197913;
let M = N_3 / 65536;
let D = (N_3 % 65536) / 2141;
let J = N_Y >= 306;
let year = Y.wrapping_sub(L).wrapping_add(J as u32) as i16;
let month = (if J { M - 12 } else { M }) as i8;
let day = (D + 1) as i8;
Date { year, month, day }
}
}
impl core::fmt::Debug for UnixEpochDay {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
f.debug_tuple("UnixEpochDay").field(&self.day()).finish()
}
}
impl core::ops::Sub for UnixEpochDay {
type Output = i32;
#[inline]
fn sub(self, rhs: UnixEpochDay) -> i32 {
self.since(rhs)
}
}
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct ISOWeekDate {
year: i16,
week: i8,
weekday: Weekday,
}
impl ISOWeekDate {
pub const MIN: ISOWeekDate = ISOWeekDate {
year: b::ISOYear::MIN,
week: b::ISOWeek::MIN,
weekday: Weekday::Monday,
};
pub const MAX: ISOWeekDate = ISOWeekDate {
year: b::ISOYear::MAX,
week: 52,
weekday: Weekday::Friday,
};
pub const ZERO: ISOWeekDate =
ISOWeekDate { year: 0, week: 1, weekday: Weekday::Monday };
#[inline]
pub const fn new(
year: i16,
week: i8,
weekday: Weekday,
) -> Result<ISOWeekDate, RangeError> {
let year = rtry!(b::ISOYear::checkc(year as i64));
let week = rtry!(b::ISOWeek::checkc(week as i64));
debug_assert!(b::Year::MIN == b::ISOYear::MIN);
debug_assert!(b::Year::MAX == b::ISOYear::MAX);
if week == 53 && !civil::is_long_iso_week_year(year) {
rbail!(b::ISOWeek::error());
}
if year == b::ISOYear::MAX
&& week == 52
&& weekday.to_monday_zero_offset()
> Weekday::Friday.to_monday_zero_offset()
{
rbail!(b::WeekdayMondayOne::error());
}
Ok(ISOWeekDate { year, week, weekday })
}
#[inline]
pub const fn new_constrain(
year: i16,
mut week: i8,
mut weekday: Weekday,
) -> Result<ISOWeekDate, RangeError> {
let year = rtry!(b::ISOYear::checkc(year as i64));
if week < 1 {
rbail!(b::ISOWeek::error());
}
if week == 53 && !civil::is_long_iso_week_year(year) {
week = 52;
}
if year == b::ISOYear::MAX
&& week == 52
&& weekday.to_monday_zero_offset()
> Weekday::Friday.to_monday_zero_offset()
{
weekday = Weekday::Friday;
}
Ok(ISOWeekDate { year, week, weekday })
}
#[inline]
pub const fn year(self) -> i16 {
self.year
}
#[inline]
pub const fn week(self) -> i8 {
self.week
}
#[inline]
pub const fn weekday(self) -> Weekday {
self.weekday
}
#[inline]
pub const fn first_of_week(self) -> Result<ISOWeekDate, RangeError> {
Ok(ISOWeekDate { weekday: Weekday::Monday, ..self })
}
#[inline]
pub const fn last_of_week(self) -> Result<ISOWeekDate, RangeError> {
ISOWeekDate::new(self.year(), self.week(), Weekday::Sunday)
}
#[inline]
pub const fn first_of_year(self) -> Result<ISOWeekDate, RangeError> {
Ok(ISOWeekDate { week: 1, weekday: Weekday::Monday, ..self })
}
#[inline]
pub const fn last_of_year(self) -> Result<ISOWeekDate, RangeError> {
ISOWeekDate::new(self.year(), self.weeks_in_year(), Weekday::Sunday)
}
#[inline]
pub const fn days_in_year(self) -> i16 {
if self.in_long_year() {
371
} else {
364
}
}
#[inline]
pub const fn weeks_in_year(self) -> i8 {
civil::weeks_in_iso_week_year(self.year())
}
#[inline]
pub const fn in_long_year(self) -> bool {
civil::is_long_iso_week_year(self.year())
}
#[inline]
pub const fn tomorrow(self) -> Result<ISOWeekDate, RangeError> {
if self.year() == ISOWeekDate::MAX.year()
&& self.week() == ISOWeekDate::MAX.week()
&& matches!(self.weekday(), Weekday::Friday)
{
rbail!(b::ISOYear::error());
}
if matches!(self.weekday(), Weekday::Sunday) {
if self.week() >= 52 && self.week() == self.weeks_in_year() {
let year = self.year() + 1;
return Ok(ISOWeekDate {
year,
week: 1,
weekday: Weekday::Monday,
});
}
let week = self.week() + 1;
return Ok(ISOWeekDate { week, weekday: Weekday::Monday, ..self });
}
Ok(ISOWeekDate { weekday: self.weekday().next(), ..self })
}
#[inline]
pub fn yesterday(self) -> Result<ISOWeekDate, RangeError> {
if matches!(self.weekday(), Weekday::Monday) {
if self.week() == 1 {
let year = rtry!(b::ISOYear::checked_add(self.year(), -1));
let week = civil::weeks_in_iso_week_year(year);
return Ok(ISOWeekDate {
year,
week,
weekday: Weekday::Sunday,
});
}
let week = self.week() - 1;
return Ok(ISOWeekDate { week, weekday: Weekday::Sunday, ..self });
}
Ok(ISOWeekDate { weekday: self.weekday().previous(), ..self })
}
#[inline]
pub const fn to_unix_epoch_day(self) -> UnixEpochDay {
let epoch_day_year = iso_week_start_from_year(self.year());
let week = self.week() as i32;
let weekday = self.weekday().to_monday_zero_offset() as i32;
unwrapr!(
epoch_day_year.checked_add(((week - 1) * 7) + weekday),
"all valid ISO 8601 dates convert to a valid Unix epoch day",
)
}
#[inline]
pub const fn to_date(self) -> Date {
self.to_unix_epoch_day().to_date()
}
}
impl core::fmt::Debug for ISOWeekDate {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
write!(
f,
"{:04}-W{:02}-{}",
self.year,
self.week,
self.weekday.to_monday_one_offset()
)
}
}
impl Ord for ISOWeekDate {
#[inline]
fn cmp(&self, other: &ISOWeekDate) -> core::cmp::Ordering {
(self.year(), self.week(), self.weekday().to_monday_one_offset()).cmp(
&(
other.year(),
other.week(),
other.weekday().to_monday_one_offset(),
),
)
}
}
impl PartialOrd for ISOWeekDate {
#[inline]
fn partial_cmp(&self, other: &ISOWeekDate) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}
const fn iso_week_start_from_year(year: i16) -> UnixEpochDay {
debug_assert!(b::Year::checkc(year as i64).is_ok());
let epoch_day_in_first_week =
Date { year, month: 1, day: 4 }.to_unix_epoch_day();
let diff_from_monday =
epoch_day_in_first_week.weekday().since(Weekday::Monday);
unwrapr!(
epoch_day_in_first_week.checked_sub(diff_from_monday as i32),
"valid Unix epoch day"
)
}
#[cfg(test)]
impl quickcheck::Arbitrary for ISOWeekDate {
fn arbitrary(g: &mut quickcheck::Gen) -> ISOWeekDate {
let year = b::ISOYear::arbitrary(g);
let week = b::ISOWeek::arbitrary(g);
let weekday = Weekday::arbitrary(g);
ISOWeekDate::new_constrain(year, week, weekday).unwrap()
}
fn shrink(&self) -> alloc::boxed::Box<dyn Iterator<Item = ISOWeekDate>> {
alloc::boxed::Box::new(
(self.year(), self.week(), self.weekday()).shrink().filter_map(
|(year, week, weekday)| {
ISOWeekDate::new_constrain(year, week, weekday).ok()
},
),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn date(year: i16, month: i8, day: i8) -> Date {
Date::new(year, month, day).unwrap()
}
fn week_date(year: i16, week: i8, weekday: Weekday) -> ISOWeekDate {
ISOWeekDate::new(year, week, weekday).unwrap()
}
#[test]
fn date_min() {
assert_eq!(Date::MIN, date(-9999, 1, 1));
}
#[test]
fn date_max() {
assert_eq!(Date::MAX, date(9999, 12, 31));
}
#[test]
fn unix_epoch_to_date_and_back_again_min_to_max() {
for i in 0.. {
let Ok(epoch_day) = UnixEpochDay::MIN.checked_add(i) else {
break;
};
let date = epoch_day.to_date();
let got = date.to_unix_epoch_day();
assert_eq!(epoch_day, got);
}
}
#[test]
fn unix_epoch_to_date_and_back_again_max_to_min() {
for i in 0.. {
let Ok(epoch_day) = UnixEpochDay::MAX.checked_sub(i) else {
break;
};
let date = epoch_day.to_date();
let got = date.to_unix_epoch_day();
assert_eq!(epoch_day, got);
}
}
#[test]
fn date_to_unix_epoch_and_back_again() {
for year in b::Year::MIN..=b::Year::MAX {
for month in b::Month::MIN..=b::Month::MAX {
for day in b::Day::MIN..=civil::days_in_month(year, month) {
let d = date(year, month, day);
let epoch_day = d.to_unix_epoch_day();
let got = epoch_day.to_date();
assert_eq!(d, got);
}
}
}
}
#[test]
fn date_to_week_date_and_back_again() {
for year in b::Year::MIN..=b::Year::MAX {
for month in b::Month::MIN..=b::Month::MAX {
for day in b::Day::MIN..=civil::days_in_month(year, month) {
let d = date(year, month, day);
let wd = d.to_iso_week_date();
let got = wd.to_date();
assert_eq!(d, got);
}
}
}
}
#[test]
fn date_to_day_of_year_and_back_again() {
for year in b::Year::MIN..=b::Year::MAX {
for month in b::Month::MIN..=b::Month::MAX {
for day in b::Day::MIN..=civil::days_in_month(year, month) {
let d = date(year, month, day);
let doy = d.day_of_year();
let got = Date::from_day_of_year(year, doy);
assert_eq!(Ok(d), got);
}
}
}
}
#[test]
fn day_of_year_in_non_leap_year() {
let year = 2026;
let mut doy = 1;
for month in b::Month::MIN..=b::Month::MAX {
for day in b::Day::MIN..=civil::days_in_month(year, month) {
let d = Date::from_day_of_year(year, doy).unwrap();
assert_eq!(d, date(year, month, day));
assert_eq!(d.day_of_year(), doy);
doy += 1;
}
}
}
#[test]
fn day_of_year_in_leap_year() {
let year = 2024;
let mut doy = 1;
for month in b::Month::MIN..=b::Month::MAX {
for day in b::Day::MIN..=civil::days_in_month(year, month) {
let d = Date::from_day_of_year(year, doy).unwrap();
assert_eq!(d, date(year, month, day));
assert_eq!(d.day_of_year(), doy);
doy += 1;
}
}
}
#[test]
fn day_of_year_no_leap_in_non_leap_year() {
let year = 2026;
let mut doy = 1;
for month in b::Month::MIN..=b::Month::MAX {
for day in b::Day::MIN..=civil::days_in_month(year, month) {
let d = Date::from_day_of_year_no_leap(year, doy).unwrap();
assert_eq!(d, date(year, month, day));
assert_eq!(d.day_of_year_no_leap(), Some(doy));
doy += 1;
}
}
}
#[test]
fn day_of_year_no_leap_in_leap_year() {
let year = 2024;
let mut doy = 1;
for month in b::Month::MIN..=b::Month::MAX {
for day in b::Day::MIN..=civil::days_in_month(year, month) {
if month == 2 && day == 29 {
continue;
}
let d = Date::from_day_of_year_no_leap(year, doy).unwrap();
assert_eq!(d, date(year, month, day));
assert_eq!(d.day_of_year_no_leap(), Some(doy));
doy += 1;
}
}
}
#[test]
fn date_to_day_of_year_no_leap_and_back_again() {
for year in b::Year::MIN..=b::Year::MAX {
for month in b::Month::MIN..=b::Month::MAX {
for day in b::Day::MIN..=civil::days_in_month(year, month) {
if month == 2 && day == 29 {
continue;
}
let d = date(year, month, day);
let doy = d.day_of_year_no_leap().unwrap();
let got = Date::from_day_of_year_no_leap(year, doy);
assert_eq!(Ok(d), got);
}
}
}
}
#[test]
fn unix_epoch_day_weekday() {
let mk = |day| date(2026, 2, day).weekday();
assert_eq!(mk(14), Weekday::Saturday);
assert_eq!(mk(15), Weekday::Sunday);
assert_eq!(mk(16), Weekday::Monday);
assert_eq!(mk(17), Weekday::Tuesday);
assert_eq!(mk(18), Weekday::Wednesday);
assert_eq!(mk(19), Weekday::Thursday);
assert_eq!(mk(20), Weekday::Friday);
assert_eq!(mk(21), Weekday::Saturday);
}
#[test]
fn first_of_last_of() {
for year in b::Year::MIN..=b::Year::MAX {
for month in b::Month::MIN..=b::Month::MAX {
for day in b::Day::MIN..=civil::days_in_month(year, month) {
let d = date(year, month, day);
assert_eq!(d.first_of_month(), date(year, month, 1));
assert_eq!(
d.last_of_month(),
date(year, month, d.days_in_month())
);
assert_eq!(d.first_of_year(), date(year, 1, 1));
assert_eq!(d.last_of_year(), date(year, 12, 31));
}
}
}
}
#[test]
fn days_in() {
for year in b::Year::MIN..=b::Year::MAX {
for month in b::Month::MIN..=b::Month::MAX {
for day in b::Day::MIN..=civil::days_in_month(year, month) {
let d = date(year, month, day);
assert!([365, 366].contains(&d.days_in_year()));
assert!([28, 29, 30, 31].contains(&d.days_in_month()));
}
}
}
}
#[test]
fn nth_weekday_of_month_various() {
let d1 = date(2017, 3, 1);
let wday = Weekday::Friday;
assert_eq!(d1.nth_weekday_of_month(2, wday), Ok(date(2017, 3, 10)));
let d1 = date(2024, 3, 1);
let wday = Weekday::Thursday;
assert_eq!(d1.nth_weekday_of_month(-1, wday), Ok(date(2024, 3, 28)));
let d1 = date(2024, 3, 25);
let wday = Weekday::Monday;
assert!(d1.nth_weekday_of_month(5, wday).is_err());
assert!(d1.nth_weekday_of_month(-5, wday).is_err());
let d1 = date(1998, 1, 1);
let wday = Weekday::Saturday;
assert_eq!(d1.nth_weekday_of_month(5, wday), Ok(date(1998, 1, 31)));
}
#[test]
fn nth_weekday_of_month_errors() {
let d = date(2017, 3, 1);
let wday = Weekday::Tuesday;
assert!(d.nth_weekday_of_month(0, wday).is_err());
assert!(d.nth_weekday_of_month(5, wday).is_err());
assert!(d.nth_weekday_of_month(-5, wday).is_err());
assert!(d.nth_weekday_of_month(6, wday).is_err());
assert!(d.nth_weekday_of_month(-6, wday).is_err());
assert!(d.nth_weekday_of_month(i8::MIN, wday).is_err());
assert!(d.nth_weekday_of_month(i8::MAX, wday).is_err());
assert_eq!(
d.nth_weekday_of_month(5, Weekday::Friday),
Ok(date(2017, 3, 31))
);
assert_eq!(
d.nth_weekday_of_month(-5, Weekday::Friday),
Ok(date(2017, 3, 3))
);
}
#[test]
fn nth_weekday_of_month_near_minimum_date() {
let d = date(-9999, 1, 1);
assert_eq!(
d.nth_weekday_of_month(1, Weekday::Monday),
Ok(date(-9999, 1, 1))
);
assert_eq!(
d.nth_weekday_of_month(2, Weekday::Monday),
Ok(date(-9999, 1, 8))
);
assert_eq!(
d.nth_weekday_of_month(3, Weekday::Monday),
Ok(date(-9999, 1, 15))
);
assert_eq!(
d.nth_weekday_of_month(4, Weekday::Monday),
Ok(date(-9999, 1, 22))
);
assert_eq!(
d.nth_weekday_of_month(5, Weekday::Monday),
Ok(date(-9999, 1, 29))
);
assert_eq!(
d.nth_weekday_of_month(-5, Weekday::Monday),
Ok(date(-9999, 1, 1))
);
assert_eq!(
d.nth_weekday_of_month(-4, Weekday::Monday),
Ok(date(-9999, 1, 8))
);
assert_eq!(
d.nth_weekday_of_month(-3, Weekday::Monday),
Ok(date(-9999, 1, 15))
);
assert_eq!(
d.nth_weekday_of_month(-2, Weekday::Monday),
Ok(date(-9999, 1, 22))
);
assert_eq!(
d.nth_weekday_of_month(-1, Weekday::Monday),
Ok(date(-9999, 1, 29))
);
}
#[test]
fn nth_weekday_of_month_near_maximum_date() {
let d = date(9999, 12, 1);
assert_eq!(
d.nth_weekday_of_month(1, Weekday::Friday),
Ok(date(9999, 12, 3))
);
assert_eq!(
d.nth_weekday_of_month(2, Weekday::Friday),
Ok(date(9999, 12, 10))
);
assert_eq!(
d.nth_weekday_of_month(3, Weekday::Friday),
Ok(date(9999, 12, 17))
);
assert_eq!(
d.nth_weekday_of_month(4, Weekday::Friday),
Ok(date(9999, 12, 24))
);
assert_eq!(
d.nth_weekday_of_month(5, Weekday::Friday),
Ok(date(9999, 12, 31))
);
assert_eq!(
d.nth_weekday_of_month(-5, Weekday::Friday),
Ok(date(9999, 12, 3))
);
assert_eq!(
d.nth_weekday_of_month(-4, Weekday::Friday),
Ok(date(9999, 12, 10))
);
assert_eq!(
d.nth_weekday_of_month(-3, Weekday::Friday),
Ok(date(9999, 12, 17))
);
assert_eq!(
d.nth_weekday_of_month(-2, Weekday::Friday),
Ok(date(9999, 12, 24))
);
assert_eq!(
d.nth_weekday_of_month(-1, Weekday::Friday),
Ok(date(9999, 12, 31))
);
}
#[test]
fn nth_weekday_of_month_every_month_has_four_weekdays() {
for year in b::Year::MIN..=b::Year::MAX {
for month in b::Month::MIN..=b::Month::MAX {
let d = date(year, month, 1);
for weekday in Weekday::Sunday.cycle_forward().take(7) {
for nth in [-4, -3, -2, -1, 1, 2, 3, 4] {
assert!(d.nth_weekday_of_month(nth, weekday).is_ok());
assert!(d.nth_weekday_of_month(0, weekday).is_err());
assert!(d.nth_weekday_of_month(6, weekday).is_err());
assert!(d.nth_weekday_of_month(-6, weekday).is_err());
}
}
}
}
}
#[test]
fn nth_weekday_various() {
let d = date(2024, 3, 10);
assert_eq!(d.nth_weekday(1, Weekday::Monday), Ok(date(2024, 3, 11)));
assert_eq!(d.nth_weekday(1, Weekday::Sunday), Ok(date(2024, 3, 17)));
assert_eq!(d.nth_weekday(2, Weekday::Thursday), Ok(date(2024, 3, 21)));
assert_eq!(d.nth_weekday(-1, Weekday::Monday), Ok(date(2024, 3, 4)));
assert_eq!(d.nth_weekday(-1, Weekday::Sunday), Ok(date(2024, 3, 3)));
assert_eq!(
d.nth_weekday(-2, Weekday::Thursday),
Ok(date(2024, 2, 29))
);
let d = date(9999, 12, 24);
assert_eq!(d.nth_weekday(1, Weekday::Friday), Ok(date(9999, 12, 31)));
let d = date(9999, 12, 30);
assert_eq!(d.nth_weekday(1, Weekday::Friday), Ok(date(9999, 12, 31)));
let d = date(9999, 12, 31);
assert_eq!(d.nth_weekday(-1, Weekday::Friday), Ok(date(9999, 12, 24)));
assert!(d.nth_weekday(1, Weekday::Friday).is_err());
let d = date(-9999, 1, 8);
assert_eq!(d.nth_weekday(-1, Weekday::Monday), Ok(date(-9999, 1, 1)));
let d = date(-9999, 1, 2);
assert_eq!(d.nth_weekday(-1, Weekday::Monday), Ok(date(-9999, 1, 1)));
let d = date(-9999, 1, 1);
assert_eq!(d.nth_weekday(1, Weekday::Monday), Ok(date(-9999, 1, 8)));
assert!(d.nth_weekday(-1, Weekday::Monday).is_err());
}
#[test]
fn nth_weekday_errors() {
let d = date(2024, 3, 10);
assert!(d.nth_weekday(0, Weekday::Monday).is_err());
}
#[test]
fn nth_weekday_extreme() {
let weeks = 1_043_497;
let d1 = date(-9999, 1, 1);
let d2 = d1.nth_weekday(weeks, Weekday::Monday).unwrap();
assert_eq!(d2, date(9999, 12, 27));
assert!(d1.nth_weekday(weeks + 1, Weekday::Monday).is_err());
assert!(d1.nth_weekday(i32::MIN, Weekday::Monday).is_err());
assert!(d1.nth_weekday(i32::MAX, Weekday::Monday).is_err());
let d1 = date(9999, 12, 31);
let d2 = d1.nth_weekday(-weeks, Weekday::Friday).unwrap();
assert_eq!(d2, date(-9999, 1, 5));
assert!(d1.nth_weekday(weeks - 1, Weekday::Friday).is_err());
assert!(d1.nth_weekday(i32::MIN, Weekday::Friday).is_err());
assert!(d1.nth_weekday(i32::MAX, Weekday::Friday).is_err());
}
#[test]
fn yesterday() {
assert_eq!(date(2024, 7, 3).yesterday(), Ok(date(2024, 7, 2)));
assert_eq!(date(2024, 7, 1).yesterday(), Ok(date(2024, 6, 30)));
assert_eq!(date(2024, 6, 1).yesterday(), Ok(date(2024, 5, 31)));
assert_eq!(date(2024, 3, 1).yesterday(), Ok(date(2024, 2, 29)));
assert_eq!(date(2023, 3, 1).yesterday(), Ok(date(2023, 2, 28)));
assert_eq!(date(2023, 1, 1).yesterday(), Ok(date(2022, 12, 31)));
assert_eq!(date(-9999, 1, 2).yesterday(), Ok(date(-9999, 1, 1)));
assert_eq!(date(9999, 12, 31).yesterday(), Ok(date(9999, 12, 30)));
assert!(date(-9999, 1, 1).yesterday().is_err());
}
#[test]
fn tomorrow() {
assert_eq!(date(2024, 7, 3).tomorrow(), Ok(date(2024, 7, 4)));
assert_eq!(date(2024, 6, 30).tomorrow(), Ok(date(2024, 7, 1)));
assert_eq!(date(2024, 5, 30).tomorrow(), Ok(date(2024, 5, 31)));
assert_eq!(date(2024, 5, 31).tomorrow(), Ok(date(2024, 6, 1)));
assert_eq!(date(2024, 2, 28).tomorrow(), Ok(date(2024, 2, 29)));
assert_eq!(date(2024, 2, 29).tomorrow(), Ok(date(2024, 3, 1)));
assert_eq!(date(2023, 2, 28).tomorrow(), Ok(date(2023, 3, 1)));
assert_eq!(date(2023, 12, 31).tomorrow(), Ok(date(2024, 1, 1)));
assert_eq!(date(-9999, 1, 1).tomorrow(), Ok(date(-9999, 1, 2)));
assert_eq!(date(9999, 12, 30).tomorrow(), Ok(date(9999, 12, 31)));
assert!(date(9999, 12, 31).tomorrow().is_err());
}
#[test]
fn iso_week_date_tomorrow() {
assert_eq!(
week_date(2024, 27, Weekday::Wednesday).tomorrow(),
Ok(week_date(2024, 27, Weekday::Thursday)),
);
assert_eq!(
week_date(2024, 27, Weekday::Sunday).tomorrow(),
Ok(week_date(2024, 28, Weekday::Monday)),
);
assert_eq!(
week_date(2024, 52, Weekday::Sunday).tomorrow(),
Ok(week_date(2025, 1, Weekday::Monday)),
);
assert_eq!(
week_date(2025, 1, Weekday::Monday).tomorrow(),
Ok(week_date(2025, 1, Weekday::Tuesday)),
);
assert_eq!(
week_date(2025, 1, Weekday::Tuesday).tomorrow(),
Ok(week_date(2025, 1, Weekday::Wednesday)),
);
assert_eq!(
week_date(2026, 52, Weekday::Sunday).tomorrow(),
Ok(week_date(2026, 53, Weekday::Monday)),
);
assert_eq!(
week_date(2026, 53, Weekday::Sunday).tomorrow(),
Ok(week_date(2027, 1, Weekday::Monday)),
);
assert_eq!(
week_date(-9999, 1, Weekday::Monday).tomorrow(),
Ok(week_date(-9999, 1, Weekday::Tuesday)),
);
assert_eq!(
week_date(9999, 52, Weekday::Thursday).tomorrow(),
Ok(week_date(9999, 52, Weekday::Friday)),
);
assert!(week_date(9999, 52, Weekday::Friday).tomorrow().is_err());
}
#[test]
fn iso_week_date_yesterday() {
assert_eq!(
week_date(2024, 27, Weekday::Thursday).yesterday(),
Ok(week_date(2024, 27, Weekday::Wednesday)),
);
assert_eq!(
week_date(2024, 28, Weekday::Monday).yesterday(),
Ok(week_date(2024, 27, Weekday::Sunday)),
);
assert_eq!(
week_date(2025, 1, Weekday::Monday).yesterday(),
Ok(week_date(2024, 52, Weekday::Sunday)),
);
assert_eq!(
week_date(2025, 1, Weekday::Tuesday).yesterday(),
Ok(week_date(2025, 1, Weekday::Monday)),
);
assert_eq!(
week_date(2025, 1, Weekday::Wednesday).yesterday(),
Ok(week_date(2025, 1, Weekday::Tuesday)),
);
assert_eq!(
week_date(2026, 53, Weekday::Monday).yesterday(),
Ok(week_date(2026, 52, Weekday::Sunday)),
);
assert_eq!(
week_date(2027, 1, Weekday::Monday).yesterday(),
Ok(week_date(2026, 53, Weekday::Sunday)),
);
assert_eq!(
week_date(9999, 12, Weekday::Friday).yesterday(),
Ok(week_date(9999, 12, Weekday::Thursday)),
);
assert_eq!(
week_date(-9999, 1, Weekday::Tuesday).yesterday(),
Ok(week_date(-9999, 1, Weekday::Monday)),
);
assert!(week_date(-9999, 1, Weekday::Monday).yesterday().is_err());
}
#[test]
fn add() {
assert_eq!(date(2024, 7, 3).checked_add(-1), Ok(date(2024, 7, 2)));
assert_eq!(date(2024, 7, 1).checked_add(-1), Ok(date(2024, 6, 30)));
assert_eq!(date(2024, 6, 1).checked_add(-1), Ok(date(2024, 5, 31)));
assert_eq!(date(2024, 3, 1).checked_add(-1), Ok(date(2024, 2, 29)));
assert_eq!(date(2023, 3, 1).checked_add(-1), Ok(date(2023, 2, 28)));
assert_eq!(date(2023, 1, 1).checked_add(-1), Ok(date(2022, 12, 31)));
assert_eq!(date(-9999, 1, 2).checked_add(-1), Ok(date(-9999, 1, 1)));
assert_eq!(date(9999, 12, 31).checked_add(-1), Ok(date(9999, 12, 30)));
assert_eq!(date(2024, 7, 3).checked_add(1), Ok(date(2024, 7, 4)));
assert_eq!(date(2024, 6, 30).checked_add(1), Ok(date(2024, 7, 1)));
assert_eq!(date(2024, 5, 30).checked_add(1), Ok(date(2024, 5, 31)));
assert_eq!(date(2024, 5, 31).checked_add(1), Ok(date(2024, 6, 1)));
assert_eq!(date(2024, 2, 28).checked_add(1), Ok(date(2024, 2, 29)));
assert_eq!(date(2024, 2, 29).checked_add(1), Ok(date(2024, 3, 1)));
assert_eq!(date(2023, 2, 28).checked_add(1), Ok(date(2023, 3, 1)));
assert_eq!(date(2023, 12, 31).checked_add(1), Ok(date(2024, 1, 1)));
assert_eq!(date(-9999, 1, 1).checked_add(1), Ok(date(-9999, 1, 2)));
assert_eq!(date(9999, 12, 30).checked_add(1), Ok(date(9999, 12, 31)));
assert_eq!(date(2024, 7, 3).checked_add(2), Ok(date(2024, 7, 5)));
assert_eq!(date(2024, 6, 29).checked_add(2), Ok(date(2024, 7, 1)));
assert_eq!(date(2024, 5, 29).checked_add(2), Ok(date(2024, 5, 31)));
assert_eq!(date(2024, 5, 30).checked_add(2), Ok(date(2024, 6, 1)));
assert_eq!(date(2024, 2, 27).checked_add(2), Ok(date(2024, 2, 29)));
assert_eq!(date(2024, 2, 28).checked_add(2), Ok(date(2024, 3, 1)));
assert_eq!(date(2023, 2, 27).checked_add(2), Ok(date(2023, 3, 1)));
assert_eq!(date(2023, 12, 30).checked_add(2), Ok(date(2024, 1, 1)));
assert_eq!(date(-9999, 1, 1).checked_add(2), Ok(date(-9999, 1, 3)));
assert_eq!(date(9999, 12, 29).checked_add(2), Ok(date(9999, 12, 31)));
let max_days = (b::UnixEpochDays::LEN - 1) as i32;
assert_eq!(
date(-9999, 1, 1).checked_add(max_days),
Ok(date(9999, 12, 31))
);
assert_eq!(
date(9999, 12, 31).checked_add(-max_days),
Ok(date(-9999, 1, 1))
);
assert!(date(-9999, 1, 1).checked_add(-1).is_err());
assert!(date(9999, 12, 31).checked_add(1).is_err());
assert!(date(-9999, 1, 1).checked_add(max_days + 1).is_err());
assert!(date(9999, 12, 31).checked_add(-(max_days + 1)).is_err());
}
#[test]
fn sub() {
assert_eq!(date(-9999, 1, 1).checked_sub(-1), Ok(date(-9999, 1, 2)));
assert_eq!(date(-9999, 1, 1).checked_sub(-2), Ok(date(-9999, 1, 3)));
assert!(date(-9999, 1, 1).checked_sub(1).is_err());
assert!(date(-9999, 1, 1).checked_sub(i32::MIN).is_err());
assert!(date(9999, 12, 31).checked_sub(i32::MAX).is_err());
}
#[test]
fn prev_year() {
assert_eq!(date(2024, 2, 29).prev_year(), Ok(2023));
assert_eq!(date(2024, 1, 1).prev_year(), Ok(2023));
assert_eq!(date(2023, 12, 31).prev_year(), Ok(2022));
assert_eq!(date(9999, 12, 31).prev_year(), Ok(9998));
assert!(date(-9999, 12, 31).prev_year().is_err());
assert!(date(-9999, 1, 1).prev_year().is_err());
}
#[test]
fn next_year() {
assert_eq!(date(2024, 2, 29).next_year(), Ok(2025));
assert_eq!(date(2024, 1, 1).next_year(), Ok(2025));
assert_eq!(date(2023, 12, 31).next_year(), Ok(2024));
assert_eq!(date(-9999, 12, 31).next_year(), Ok(-9998));
assert!(date(9999, 12, 31).next_year().is_err());
assert!(date(9999, 1, 1).next_year().is_err());
}
#[test]
fn to_iso_week_date_various() {
assert_eq!(
date(1995, 1, 1).to_iso_week_date(),
week_date(1994, 52, Weekday::Sunday),
);
assert_eq!(
date(1996, 12, 31).to_iso_week_date(),
week_date(1997, 1, Weekday::Tuesday),
);
assert_eq!(
date(2019, 12, 30).to_iso_week_date(),
week_date(2020, 1, Weekday::Monday),
);
assert_eq!(
date(2031, 12, 29).to_iso_week_date(),
week_date(2032, 1, Weekday::Monday),
);
assert_eq!(
date(2024, 3, 9).to_iso_week_date(),
week_date(2024, 10, Weekday::Saturday),
);
assert_eq!(
Date::MIN.to_iso_week_date(),
week_date(-9999, 1, Weekday::Monday),
);
assert_eq!(
Date::MAX.to_iso_week_date(),
week_date(9999, 52, Weekday::Friday),
);
}
quickcheck::quickcheck! {
fn prop_tomorrow_yesterday_is_identity(d: Date) -> quickcheck::TestResult {
let Ok(yesterday) = d.yesterday() else {
return quickcheck::TestResult::discard()
};
quickcheck::TestResult::from_bool(yesterday.tomorrow() == Ok(d))
}
fn prop_yesterday_tomorrow_is_identity(d: Date) -> quickcheck::TestResult {
let Ok(tomorrow) = d.tomorrow() else {
return quickcheck::TestResult::discard()
};
quickcheck::TestResult::from_bool(tomorrow.yesterday() == Ok(d))
}
fn prop_add_equals_sub(d1: Date, days: i32) -> quickcheck::TestResult {
let Ok(d2) = d1.checked_add(days) else {
return quickcheck::TestResult::discard();
};
quickcheck::TestResult::from_bool(d2.checked_sub(days) == Ok(d1))
}
fn prop_all_long_years_have_53rd_week(year: i16) -> quickcheck::TestResult {
if b::ISOYear::check(year).is_err() {
return quickcheck::TestResult::discard();
}
quickcheck::TestResult::from_bool(
!civil::is_long_iso_week_year(year)
|| ISOWeekDate::new(year, 53, Weekday::Sunday).is_ok()
)
}
fn prop_prev_day_is_less(wd: ISOWeekDate) -> quickcheck::TestResult {
let Ok(prev_date) = wd.to_date().yesterday() else {
return quickcheck::TestResult::discard();
};
quickcheck::TestResult::from_bool(
prev_date.to_iso_week_date() < wd,
)
}
fn prop_next_day_is_greater(wd: ISOWeekDate) -> quickcheck::TestResult {
let Ok(next_date) = wd.to_date().tomorrow() else {
return quickcheck::TestResult::discard();
};
quickcheck::TestResult::from_bool(
wd < next_date.to_iso_week_date(),
)
}
fn prop_iso_tomorrow_yesterday_is_identity(
wd: ISOWeekDate
) -> quickcheck::TestResult {
let Ok(yesterday) = wd.yesterday() else {
return quickcheck::TestResult::discard()
};
quickcheck::TestResult::from_bool(yesterday.tomorrow() == Ok(wd))
}
fn prop_iso_yesterday_tomorrow_is_identity(
wd: ISOWeekDate
) -> quickcheck::TestResult {
let Ok(tomorrow) = wd.tomorrow() else {
return quickcheck::TestResult::discard()
};
quickcheck::TestResult::from_bool(tomorrow.yesterday() == Ok(wd))
}
}
}