1use crate::{
2 bounds::{self as b, RangeError},
3 civil::{self, DateTime, Weekday},
4 macros::{ctry, rbail, rtry, unwrapr},
5};
6
7#[derive(Clone, Copy, Eq, Hash, PartialEq, PartialOrd, Ord)]
17#[cfg_attr(feature = "defmt", derive(defmt::Format))]
18pub struct Date {
19 year: i16,
20 month: i8,
21 day: i8,
22}
23
24impl Date {
25 pub const MIN: Date = {
30 let min = UnixEpochDay::MIN.to_date();
31 assert!(min.year() == -9999);
32 assert!(min.month() == 1);
33 assert!(min.day() == 1);
34 min
35 };
36
37 pub const MAX: Date = {
42 let max = UnixEpochDay::MAX.to_date();
43 assert!(max.year() == 9999);
44 assert!(max.month() == 12);
45 assert!(max.day() == 31);
46 max
47 };
48
49 #[inline]
81 pub const fn new(
82 year: i16,
83 month: i8,
84 day: i8,
85 ) -> Result<Date, RangeError> {
86 let year = rtry!(b::Year::checkc(year as i64));
87 let month = rtry!(b::Month::checkc(month as i64));
88 if day < 1 {
89 rbail!(b::Day::error());
90 } else if day > 28 && day > civil::days_in_month(year, month) {
91 rbail!(b::SpecialBoundsError::DateInvalidDay { year, month });
92 }
93 Ok(Date { year, month, day })
94 }
95
96 #[inline]
102 pub const fn new_constrain(
103 year: i16,
104 month: i8,
105 day: i8,
106 ) -> Result<Date, RangeError> {
107 let year = rtry!(b::Year::checkc(year as i64));
108 let month = rtry!(b::Month::checkc(month as i64));
109 let day = if day < 1 {
110 rbail!(b::Day::error());
111 } else if day > 28 {
112 let days_in_month = civil::days_in_month(year, month);
113 if day <= days_in_month {
114 day
115 } else {
116 days_in_month
117 }
118 } else {
119 day
120 };
121 Ok(Date { year, month, day })
122 }
123
124 #[inline]
131 pub const fn from_day_of_year(
132 year: i16,
133 day: i16,
134 ) -> Result<Date, RangeError> {
135 let year = rtry!(b::Year::checkc(year as i64));
136 let day = rtry!(b::DayOfYear::checkc(day as i64));
137 let start = Date { year, month: 1, day: 1 }.to_unix_epoch_day();
138 let end = match start.checked_add(day as i32 - 1) {
139 Ok(end) => end.to_date(),
140 Err(_) => {
142 rbail!(b::SpecialBoundsError::DateInvalidDayOfYear { year })
143 }
144 };
145 if year != end.year {
147 debug_assert!(day == 366);
149 debug_assert!(!civil::is_leap_year(year));
150 rbail!(b::SpecialBoundsError::DateInvalidDayOfYear { year })
151 }
152 Ok(end)
153 }
154
155 #[inline]
163 pub const fn from_day_of_year_no_leap(
164 year: i16,
165 day: i16,
166 ) -> Result<Date, RangeError> {
167 let year = rtry!(b::Year::checkc(year as i64));
168 let mut day = rtry!(b::DayOfYearNoLeap::checkc(day as i64));
169 if day >= 60 && civil::is_leap_year(year) {
170 day += 1;
171 }
172 Ok(unwrapr!(Date::from_day_of_year(year, day), "valid day of year"))
174 }
175
176 #[inline]
181 pub const fn year(self) -> i16 {
182 self.year
183 }
184
185 #[inline]
189 pub const fn month(self) -> i8 {
190 self.month
191 }
192
193 #[inline]
198 pub const fn day(self) -> i8 {
199 self.day
200 }
201
202 #[inline]
204 pub const fn weekday(self) -> Weekday {
205 self.to_unix_epoch_day().weekday()
206 }
207
208 #[inline]
213 pub const fn day_of_year(self) -> i16 {
214 const DAYS_BY_MONTH_NO_LEAP: [i16; 14] =
215 [0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365];
216 const DAYS_BY_MONTH_LEAP: [i16; 14] =
217 [0, 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366];
218 const TABLES: [[i16; 14]; 2] =
219 [DAYS_BY_MONTH_NO_LEAP, DAYS_BY_MONTH_LEAP];
220 TABLES[self.in_leap_year() as usize][self.month() as usize]
221 + (self.day() as i16)
222 }
223
224 #[inline]
234 pub const fn day_of_year_no_leap(self) -> Option<i16> {
235 let mut days = self.day_of_year();
236 if self.in_leap_year() {
237 if days == 60 {
239 return None;
240 } else if days > 60 {
241 days -= 1;
242 }
243 }
244 Some(days)
245 }
246
247 #[inline]
249 pub const fn first_of_month(self) -> Date {
250 Date { day: 1, ..self }
251 }
252
253 #[inline]
255 pub const fn last_of_month(self) -> Date {
256 Date { day: self.days_in_month(), ..self }
257 }
258
259 #[inline]
265 pub const fn days_in_month(self) -> i8 {
266 civil::days_in_month(self.year(), self.month())
267 }
268
269 #[inline]
271 pub const fn first_of_year(self) -> Date {
272 Date { month: 1, day: 1, ..self }
273 }
274
275 #[inline]
277 pub const fn last_of_year(self) -> Date {
278 Date { month: 12, day: 31, ..self }
279 }
280
281 #[inline]
285 pub const fn days_in_year(self) -> i16 {
286 if self.in_leap_year() {
287 366
288 } else {
289 365
290 }
291 }
292
293 #[inline]
295 pub const fn in_leap_year(self) -> bool {
296 civil::is_leap_year(self.year())
297 }
298
299 #[inline]
303 pub const fn yesterday(self) -> Result<Date, RangeError> {
304 if self.day() == 1 {
305 if self.month() == 1 {
306 let year = ctry!(self.prev_year());
307 return Ok(Date { year, month: 12, day: 31 });
308 }
309 let month = self.month() - 1;
310 let day = civil::days_in_month(self.year(), month);
311 return Ok(Date { month, day, ..self });
312 }
313 Ok(Date { day: self.day() - 1, ..self })
314 }
315
316 #[inline]
320 pub const fn tomorrow(self) -> Result<Date, RangeError> {
321 if self.day() >= 28 && self.day() == self.days_in_month() {
322 if self.month() == 12 {
323 let year = ctry!(self.next_year());
324 return Ok(Date { year, month: 1, day: 1 });
325 }
326 let month = self.month() + 1;
327 return Ok(Date { month, day: 1, ..self });
328 }
329 Ok(Date { day: self.day() + 1, ..self })
330 }
331
332 #[inline]
340 pub const fn nth_weekday_of_month(
341 &self,
342 nth: i8,
343 weekday: Weekday,
344 ) -> Result<Date, RangeError> {
345 let nth = rtry!(b::NthWeekdayOfMonth::checkc(nth as i64));
346 if nth == 0 {
347 rbail!(b::NthWeekdayOfMonth::error());
348 } else if nth > 0 {
349 let first = self.first_of_month();
350 let first_weekday = first.weekday();
351 let diff = weekday.since(first_weekday);
352 let day = diff + 1 + (nth - 1) * 7;
353 Date::new(self.year(), self.month(), day)
354 } else {
355 let last = self.last_of_month();
356 let last_weekday = last.weekday();
357 let diff = last_weekday.since(weekday);
358 let day = last.day() - diff - (nth.abs() - 1) * 7;
359 Date::new(self.year(), self.month(), day)
360 }
361 }
362
363 #[inline]
374 pub const fn nth_weekday(
375 self,
376 nth: i32,
377 weekday: Weekday,
378 ) -> Result<Date, RangeError> {
379 self.to_unix_epoch_day().nth_weekday(nth, weekday)
380 }
381
382 #[inline]
387 pub(crate) const fn prev_year(self) -> Result<i16, RangeError> {
388 Ok(rtry!(b::Year::checked_add(self.year(), -1)))
389 }
390
391 #[inline]
393 pub(crate) const fn next_year(self) -> Result<i16, RangeError> {
394 Ok(rtry!(b::Year::checked_add(self.year(), 1)))
395 }
396
397 #[inline]
401 pub const fn checked_add(self, days: i32) -> Result<Date, RangeError> {
402 match days {
403 0 => Ok(self),
404 -1 => self.yesterday(),
405 1 => self.tomorrow(),
406 n => Ok(ctry!(self.to_unix_epoch_day().checked_add(n)).to_date()),
407 }
408 }
409
410 #[inline]
414 pub const fn checked_sub(self, days: i32) -> Result<Date, RangeError> {
415 let Some(days) = days.checked_neg() else {
416 rbail!(b::UnixEpochDays::error());
417 };
418 self.checked_add(days)
419 }
420
421 #[inline]
450 pub const fn until(self, other: Date) -> i32 {
451 -self.since(other)
452 }
453
454 #[inline]
482 pub const fn since(self, other: Date) -> i32 {
483 if self.year() == other.year() {
485 if self.month() == other.month() {
486 (self.day() - other.day()) as i32
487 } else {
488 (self.day_of_year() - other.day_of_year()) as i32
489 }
490 } else {
491 self.to_unix_epoch_day().since(other.to_unix_epoch_day())
492 }
493 }
494
495 #[inline]
497 #[allow(non_upper_case_globals, non_snake_case)] pub const fn to_unix_epoch_day(self) -> UnixEpochDay {
499 const s: u32 = 82;
503 const K: u32 = 719468 + 146097 * s;
504 const L: u32 = 400 * s;
505
506 let year = self.year as u32;
507 let month = self.month as u32;
508 let day = self.day as u32;
509
510 let J = month <= 2;
511 let Y = year.wrapping_add(L).wrapping_sub(J as u32);
512 let M = if J { month + 12 } else { month };
513 let D = day - 1;
514 let C = Y / 100;
515
516 let y_star = 1461 * Y / 4 - C + C / 4;
517 let m_star = (979 * M - 2919) / 32;
518 let N = y_star + m_star + D;
519
520 let N_U = N.wrapping_sub(K);
521 let epoch_day = N_U as i32;
522 UnixEpochDay { day: epoch_day }
523 }
524
525 #[inline]
527 pub const fn to_iso_week_date(self) -> ISOWeekDate {
528 let epoch_day = self.to_unix_epoch_day();
529 let mut year = self.year();
530 let mut epoch_day_year_start = iso_week_start_from_year(year);
531 if epoch_day.day() < epoch_day_year_start.day() {
532 year -= 1;
543 epoch_day_year_start = iso_week_start_from_year(year);
544 } else if self.month() == 12 && self.day() >= 29 && year < b::Year::MAX
545 {
546 let epoch_day_next_year_week_start =
554 iso_week_start_from_year(year + 1);
555 if epoch_day.day() >= epoch_day_next_year_week_start.day() {
556 epoch_day_year_start = epoch_day_next_year_week_start;
557 year += 1;
558 }
559 }
560
561 let week =
565 (((epoch_day.day() - epoch_day_year_start.day()) / 7) + 1) as i8;
566 let weekday = epoch_day.weekday();
567
568 ISOWeekDate { year, week, weekday }
569 }
570
571 #[inline]
587 pub const fn at(
588 self,
589 hour: i8,
590 minute: i8,
591 second: i8,
592 subsec_nanosecond: i32,
593 ) -> DateTime {
594 DateTime::from_parts(
595 self,
596 civil::time(hour, minute, second, subsec_nanosecond),
597 )
598 }
599}
600
601impl core::fmt::Debug for Date {
602 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
603 if self.year() < 0 {
604 write!(f, "-{:06}", self.year().unsigned_abs())?;
605 } else {
606 write!(f, "{:04}", self.year())?;
607 }
608 write!(f, "-{:02}-{:02}", self.month(), self.day())
609 }
610}
611
612impl core::ops::Sub for Date {
640 type Output = i32;
641
642 #[inline]
643 fn sub(self, rhs: Date) -> i32 {
644 self.since(rhs)
645 }
646}
647
648#[cfg(test)]
649impl quickcheck::Arbitrary for Date {
650 fn arbitrary(g: &mut quickcheck::Gen) -> Date {
651 let year = b::Year::arbitrary(g);
652 let month = b::Month::arbitrary(g);
653 let day = b::Day::arbitrary(g);
654 Date::new_constrain(year, month, day).unwrap()
655 }
656
657 fn shrink(&self) -> alloc::boxed::Box<dyn Iterator<Item = Date>> {
658 alloc::boxed::Box::new(
659 (self.year(), self.month(), self.day()).shrink().filter_map(
660 |(year, month, day)| {
661 Date::new_constrain(year, month, day).ok()
662 },
663 ),
664 )
665 }
666}
667
668#[derive(Clone, Copy, Eq, Hash, PartialEq, PartialOrd, Ord)]
676#[cfg_attr(feature = "defmt", derive(defmt::Format))]
677pub struct UnixEpochDay {
678 day: i32,
679}
680
681impl UnixEpochDay {
682 pub const MIN: UnixEpochDay = UnixEpochDay { day: b::UnixEpochDays::MIN };
687
688 pub const MAX: UnixEpochDay = UnixEpochDay { day: b::UnixEpochDays::MAX };
693
694 #[inline]
700 pub const fn new(day: i32) -> Result<UnixEpochDay, RangeError> {
701 let day = rtry!(b::UnixEpochDays::checkc(day as i64));
702 Ok(UnixEpochDay { day })
703 }
704
705 #[inline]
710 pub const fn day(self) -> i32 {
711 self.day
712 }
713
714 #[inline]
716 pub const fn weekday(&self) -> Weekday {
717 let result = Weekday::from_monday_one_offset({
728 const M: u32 = {
729 const fn div_ceil(lhs: u64, rhs: u64) -> u64 {
731 let d = lhs / rhs;
732 let r = lhs % rhs;
733 if r > 0 {
734 d + 1
735 } else {
736 d
737 }
738 }
739
740 let n = div_ceil(1u64 << 32, 7) as u32;
741 assert!(n == 613_566_757);
742 n
743 };
744 const Z: u32 = 0x90000000; let rd: u32 = self.day() as u32;
746 (rd.wrapping_mul(M).wrapping_add(Z) >> 29) as i8
747 });
748 unwrapr!(result, "weekday must be in range 1..=7")
749 }
750
751 #[inline]
762 pub const fn nth_weekday(
763 self,
764 nth: i32,
765 weekday: Weekday,
766 ) -> Result<Date, RangeError> {
767 let nth = rtry!(b::NthWeekday::checkc(nth as i64));
770 if nth == 0 {
771 rbail!(b::NthWeekday::error());
772 } else if nth > 0 {
773 let weekday_diff = weekday.since(self.weekday().next()) as i32;
774 let diff = (nth - 1) * 7 + weekday_diff;
775 let end = ctry!(self.checked_add(diff + 1));
776 Ok(end.to_date())
777 } else {
778 let weekday_diff = self.weekday().previous().since(weekday) as i32;
779 let nth = nth.abs();
781 let diff = -((nth - 1) * 7 + weekday_diff);
783 let end = ctry!(self.checked_add(diff - 1));
784 Ok(end.to_date())
785 }
786 }
787
788 #[inline]
793 pub const fn checked_add(
794 self,
795 days: i32,
796 ) -> Result<UnixEpochDay, RangeError> {
797 let day = rtry!(b::UnixEpochDays::checked_add(self.day(), days));
798 Ok(UnixEpochDay { day })
799 }
800
801 #[inline]
806 pub const fn checked_sub(
807 self,
808 days: i32,
809 ) -> Result<UnixEpochDay, RangeError> {
810 let Some(days) = days.checked_neg() else {
811 rbail!(b::UnixEpochDays::error());
812 };
813 self.checked_add(days)
814 }
815
816 #[inline]
845 pub const fn until(self, other: UnixEpochDay) -> i32 {
846 -self.since(other)
847 }
848
849 #[inline]
877 pub const fn since(self, other: UnixEpochDay) -> i32 {
878 self.day() - other.day()
879 }
880
881 #[inline]
883 #[allow(non_upper_case_globals, non_snake_case)] pub const fn to_date(self) -> Date {
885 const s: u32 = 82;
889 const K: u32 = 719468 + 146097 * s;
890 const L: u32 = 400 * s;
891
892 let N_U = self.day as u32;
893 let N = N_U.wrapping_add(K);
894
895 let N_1 = 4 * N + 3;
896 let C = N_1 / 146097;
897 let N_C = (N_1 % 146097) / 4;
898
899 let N_2 = 4 * N_C + 3;
900 let P_2 = 2939745 * (N_2 as u64);
901 let Z = (P_2 / 4294967296) as u32;
902 let N_Y = (P_2 % 4294967296) as u32 / 2939745 / 4;
903 let Y = 100 * C + Z;
904
905 let N_3 = 2141 * N_Y + 197913;
906 let M = N_3 / 65536;
907 let D = (N_3 % 65536) / 2141;
908
909 let J = N_Y >= 306;
910 let year = Y.wrapping_sub(L).wrapping_add(J as u32) as i16;
911 let month = (if J { M - 12 } else { M }) as i8;
912 let day = (D + 1) as i8;
913 Date { year, month, day }
914 }
915}
916
917impl core::fmt::Debug for UnixEpochDay {
918 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
919 f.debug_tuple("UnixEpochDay").field(&self.day()).finish()
920 }
921}
922
923impl core::ops::Sub for UnixEpochDay {
951 type Output = i32;
952
953 #[inline]
954 fn sub(self, rhs: UnixEpochDay) -> i32 {
955 self.since(rhs)
956 }
957}
958
959#[derive(Clone, Copy, Eq, Hash, PartialEq)]
964#[cfg_attr(feature = "defmt", derive(defmt::Format))]
965pub struct ISOWeekDate {
966 year: i16,
967 week: i8,
968 weekday: Weekday,
969}
970
971impl ISOWeekDate {
972 pub const MIN: ISOWeekDate = ISOWeekDate {
974 year: b::ISOYear::MIN,
975 week: b::ISOWeek::MIN,
976 weekday: Weekday::Monday,
977 };
978
979 pub const MAX: ISOWeekDate = ISOWeekDate {
981 year: b::ISOYear::MAX,
982 week: 52,
984 weekday: Weekday::Friday,
985 };
986
987 pub const ZERO: ISOWeekDate =
989 ISOWeekDate { year: 0, week: 1, weekday: Weekday::Monday };
990
991 #[inline]
996 pub const fn new(
997 year: i16,
998 week: i8,
999 weekday: Weekday,
1000 ) -> Result<ISOWeekDate, RangeError> {
1001 let year = rtry!(b::ISOYear::checkc(year as i64));
1002 let week = rtry!(b::ISOWeek::checkc(week as i64));
1003
1004 debug_assert!(b::Year::MIN == b::ISOYear::MIN);
1013 debug_assert!(b::Year::MAX == b::ISOYear::MAX);
1014 if week == 53 && !civil::is_long_iso_week_year(year) {
1015 rbail!(b::ISOWeek::error());
1016 }
1017 if year == b::ISOYear::MAX
1027 && week == 52
1028 && weekday.to_monday_zero_offset()
1029 > Weekday::Friday.to_monday_zero_offset()
1030 {
1031 rbail!(b::WeekdayMondayOne::error());
1032 }
1033 Ok(ISOWeekDate { year, week, weekday })
1034 }
1035
1036 #[inline]
1044 pub const fn new_constrain(
1045 year: i16,
1046 mut week: i8,
1047 mut weekday: Weekday,
1048 ) -> Result<ISOWeekDate, RangeError> {
1049 let year = rtry!(b::ISOYear::checkc(year as i64));
1050 if week < 1 {
1051 rbail!(b::ISOWeek::error());
1052 }
1053 if week == 53 && !civil::is_long_iso_week_year(year) {
1054 week = 52;
1055 }
1056 if year == b::ISOYear::MAX
1057 && week == 52
1058 && weekday.to_monday_zero_offset()
1059 > Weekday::Friday.to_monday_zero_offset()
1060 {
1061 weekday = Weekday::Friday;
1062 }
1063 Ok(ISOWeekDate { year, week, weekday })
1064 }
1065
1066 #[inline]
1071 pub const fn year(self) -> i16 {
1072 self.year
1073 }
1074
1075 #[inline]
1080 pub const fn week(self) -> i8 {
1081 self.week
1082 }
1083
1084 #[inline]
1086 pub const fn weekday(self) -> Weekday {
1087 self.weekday
1088 }
1089
1090 #[inline]
1103 pub const fn first_of_week(self) -> Result<ISOWeekDate, RangeError> {
1104 Ok(ISOWeekDate { weekday: Weekday::Monday, ..self })
1110 }
1111
1112 #[inline]
1122 pub const fn last_of_week(self) -> Result<ISOWeekDate, RangeError> {
1123 ISOWeekDate::new(self.year(), self.week(), Weekday::Sunday)
1124 }
1125
1126 #[inline]
1139 pub const fn first_of_year(self) -> Result<ISOWeekDate, RangeError> {
1140 Ok(ISOWeekDate { week: 1, weekday: Weekday::Monday, ..self })
1146 }
1147
1148 #[inline]
1158 pub const fn last_of_year(self) -> Result<ISOWeekDate, RangeError> {
1159 ISOWeekDate::new(self.year(), self.weeks_in_year(), Weekday::Sunday)
1160 }
1161
1162 #[inline]
1169 pub const fn days_in_year(self) -> i16 {
1170 if self.in_long_year() {
1171 371
1172 } else {
1173 364
1174 }
1175 }
1176
1177 #[inline]
1184 pub const fn weeks_in_year(self) -> i8 {
1185 civil::weeks_in_iso_week_year(self.year())
1186 }
1187
1188 #[inline]
1194 pub const fn in_long_year(self) -> bool {
1195 civil::is_long_iso_week_year(self.year())
1196 }
1197
1198 #[inline]
1204 pub const fn tomorrow(self) -> Result<ISOWeekDate, RangeError> {
1205 if self.year() == ISOWeekDate::MAX.year()
1209 && self.week() == ISOWeekDate::MAX.week()
1210 && matches!(self.weekday(), Weekday::Friday)
1211 {
1212 rbail!(b::ISOYear::error());
1213 }
1214 if matches!(self.weekday(), Weekday::Sunday) {
1218 if self.week() >= 52 && self.week() == self.weeks_in_year() {
1219 let year = self.year() + 1;
1220 return Ok(ISOWeekDate {
1221 year,
1222 week: 1,
1223 weekday: Weekday::Monday,
1224 });
1225 }
1226 let week = self.week() + 1;
1227 return Ok(ISOWeekDate { week, weekday: Weekday::Monday, ..self });
1228 }
1229 Ok(ISOWeekDate { weekday: self.weekday().next(), ..self })
1230 }
1231
1232 #[inline]
1238 pub fn yesterday(self) -> Result<ISOWeekDate, RangeError> {
1239 if matches!(self.weekday(), Weekday::Monday) {
1240 if self.week() == 1 {
1241 let year = rtry!(b::ISOYear::checked_add(self.year(), -1));
1242 let week = civil::weeks_in_iso_week_year(year);
1243 return Ok(ISOWeekDate {
1244 year,
1245 week,
1246 weekday: Weekday::Sunday,
1247 });
1248 }
1249 let week = self.week() - 1;
1250 return Ok(ISOWeekDate { week, weekday: Weekday::Sunday, ..self });
1251 }
1252 Ok(ISOWeekDate { weekday: self.weekday().previous(), ..self })
1253 }
1254
1255 #[inline]
1257 pub const fn to_unix_epoch_day(self) -> UnixEpochDay {
1258 let epoch_day_year = iso_week_start_from_year(self.year());
1259 let week = self.week() as i32;
1260 let weekday = self.weekday().to_monday_zero_offset() as i32;
1261 unwrapr!(
1262 epoch_day_year.checked_add(((week - 1) * 7) + weekday),
1263 "all valid ISO 8601 dates convert to a valid Unix epoch day",
1264 )
1265 }
1266
1267 #[inline]
1269 pub const fn to_date(self) -> Date {
1270 self.to_unix_epoch_day().to_date()
1271 }
1272}
1273
1274impl core::fmt::Debug for ISOWeekDate {
1275 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1276 write!(
1277 f,
1278 "{:04}-W{:02}-{}",
1279 self.year,
1280 self.week,
1281 self.weekday.to_monday_one_offset()
1282 )
1283 }
1284}
1285
1286impl Ord for ISOWeekDate {
1287 #[inline]
1288 fn cmp(&self, other: &ISOWeekDate) -> core::cmp::Ordering {
1289 (self.year(), self.week(), self.weekday().to_monday_one_offset()).cmp(
1290 &(
1291 other.year(),
1292 other.week(),
1293 other.weekday().to_monday_one_offset(),
1294 ),
1295 )
1296 }
1297}
1298
1299impl PartialOrd for ISOWeekDate {
1300 #[inline]
1301 fn partial_cmp(&self, other: &ISOWeekDate) -> Option<core::cmp::Ordering> {
1302 Some(self.cmp(other))
1303 }
1304}
1305
1306const fn iso_week_start_from_year(year: i16) -> UnixEpochDay {
1314 debug_assert!(b::Year::checkc(year as i64).is_ok());
1315 let epoch_day_in_first_week =
1319 Date { year, month: 1, day: 4 }.to_unix_epoch_day();
1320 let diff_from_monday =
1324 epoch_day_in_first_week.weekday().since(Weekday::Monday);
1325 unwrapr!(
1331 epoch_day_in_first_week.checked_sub(diff_from_monday as i32),
1332 "valid Unix epoch day"
1333 )
1334}
1335
1336#[cfg(test)]
1337impl quickcheck::Arbitrary for ISOWeekDate {
1338 fn arbitrary(g: &mut quickcheck::Gen) -> ISOWeekDate {
1339 let year = b::ISOYear::arbitrary(g);
1340 let week = b::ISOWeek::arbitrary(g);
1341 let weekday = Weekday::arbitrary(g);
1342 ISOWeekDate::new_constrain(year, week, weekday).unwrap()
1343 }
1344
1345 fn shrink(&self) -> alloc::boxed::Box<dyn Iterator<Item = ISOWeekDate>> {
1346 alloc::boxed::Box::new(
1347 (self.year(), self.week(), self.weekday()).shrink().filter_map(
1348 |(year, week, weekday)| {
1349 ISOWeekDate::new_constrain(year, week, weekday).ok()
1350 },
1351 ),
1352 )
1353 }
1354}
1355
1356#[cfg(test)]
1357mod tests {
1358 use super::*;
1359
1360 fn date(year: i16, month: i8, day: i8) -> Date {
1361 Date::new(year, month, day).unwrap()
1362 }
1363
1364 fn week_date(year: i16, week: i8, weekday: Weekday) -> ISOWeekDate {
1365 ISOWeekDate::new(year, week, weekday).unwrap()
1366 }
1367
1368 #[test]
1369 fn date_min() {
1370 assert_eq!(Date::MIN, date(-9999, 1, 1));
1371 }
1372
1373 #[test]
1374 fn date_max() {
1375 assert_eq!(Date::MAX, date(9999, 12, 31));
1376 }
1377
1378 #[test]
1379 fn unix_epoch_to_date_and_back_again_min_to_max() {
1380 for i in 0.. {
1381 let Ok(epoch_day) = UnixEpochDay::MIN.checked_add(i) else {
1382 break;
1383 };
1384 let date = epoch_day.to_date();
1385 let got = date.to_unix_epoch_day();
1386 assert_eq!(epoch_day, got);
1387 }
1388 }
1389
1390 #[test]
1391 fn unix_epoch_to_date_and_back_again_max_to_min() {
1392 for i in 0.. {
1393 let Ok(epoch_day) = UnixEpochDay::MAX.checked_sub(i) else {
1394 break;
1395 };
1396 let date = epoch_day.to_date();
1397 let got = date.to_unix_epoch_day();
1398 assert_eq!(epoch_day, got);
1399 }
1400 }
1401
1402 #[test]
1403 fn date_to_unix_epoch_and_back_again() {
1404 for year in b::Year::MIN..=b::Year::MAX {
1405 for month in b::Month::MIN..=b::Month::MAX {
1406 for day in b::Day::MIN..=civil::days_in_month(year, month) {
1407 let d = date(year, month, day);
1408 let epoch_day = d.to_unix_epoch_day();
1409 let got = epoch_day.to_date();
1410 assert_eq!(d, got);
1411 }
1412 }
1413 }
1414 }
1415
1416 #[test]
1417 fn date_to_week_date_and_back_again() {
1418 for year in b::Year::MIN..=b::Year::MAX {
1419 for month in b::Month::MIN..=b::Month::MAX {
1420 for day in b::Day::MIN..=civil::days_in_month(year, month) {
1421 let d = date(year, month, day);
1422 let wd = d.to_iso_week_date();
1423 let got = wd.to_date();
1424 assert_eq!(d, got);
1425 }
1426 }
1427 }
1428 }
1429
1430 #[test]
1431 fn date_to_day_of_year_and_back_again() {
1432 for year in b::Year::MIN..=b::Year::MAX {
1433 for month in b::Month::MIN..=b::Month::MAX {
1434 for day in b::Day::MIN..=civil::days_in_month(year, month) {
1435 let d = date(year, month, day);
1436 let doy = d.day_of_year();
1437 let got = Date::from_day_of_year(year, doy);
1438 assert_eq!(Ok(d), got);
1439 }
1440 }
1441 }
1442 }
1443
1444 #[test]
1445 fn day_of_year_in_non_leap_year() {
1446 let year = 2026;
1447 let mut doy = 1;
1448 for month in b::Month::MIN..=b::Month::MAX {
1449 for day in b::Day::MIN..=civil::days_in_month(year, month) {
1450 let d = Date::from_day_of_year(year, doy).unwrap();
1451 assert_eq!(d, date(year, month, day));
1452 assert_eq!(d.day_of_year(), doy);
1453 doy += 1;
1454 }
1455 }
1456 }
1457
1458 #[test]
1459 fn day_of_year_in_leap_year() {
1460 let year = 2024;
1461 let mut doy = 1;
1462 for month in b::Month::MIN..=b::Month::MAX {
1463 for day in b::Day::MIN..=civil::days_in_month(year, month) {
1464 let d = Date::from_day_of_year(year, doy).unwrap();
1465 assert_eq!(d, date(year, month, day));
1466 assert_eq!(d.day_of_year(), doy);
1467 doy += 1;
1468 }
1469 }
1470 }
1471
1472 #[test]
1473 fn day_of_year_no_leap_in_non_leap_year() {
1474 let year = 2026;
1475 let mut doy = 1;
1476 for month in b::Month::MIN..=b::Month::MAX {
1477 for day in b::Day::MIN..=civil::days_in_month(year, month) {
1478 let d = Date::from_day_of_year_no_leap(year, doy).unwrap();
1479 assert_eq!(d, date(year, month, day));
1480 assert_eq!(d.day_of_year_no_leap(), Some(doy));
1481 doy += 1;
1482 }
1483 }
1484 }
1485
1486 #[test]
1487 fn day_of_year_no_leap_in_leap_year() {
1488 let year = 2024;
1489 let mut doy = 1;
1490 for month in b::Month::MIN..=b::Month::MAX {
1491 for day in b::Day::MIN..=civil::days_in_month(year, month) {
1492 if month == 2 && day == 29 {
1493 continue;
1494 }
1495 let d = Date::from_day_of_year_no_leap(year, doy).unwrap();
1496 assert_eq!(d, date(year, month, day));
1497 assert_eq!(d.day_of_year_no_leap(), Some(doy));
1498 doy += 1;
1499 }
1500 }
1501 }
1502
1503 #[test]
1504 fn date_to_day_of_year_no_leap_and_back_again() {
1505 for year in b::Year::MIN..=b::Year::MAX {
1506 for month in b::Month::MIN..=b::Month::MAX {
1507 for day in b::Day::MIN..=civil::days_in_month(year, month) {
1508 if month == 2 && day == 29 {
1509 continue;
1510 }
1511 let d = date(year, month, day);
1512 let doy = d.day_of_year_no_leap().unwrap();
1513 let got = Date::from_day_of_year_no_leap(year, doy);
1514 assert_eq!(Ok(d), got);
1515 }
1516 }
1517 }
1518 }
1519
1520 #[test]
1521 fn unix_epoch_day_weekday() {
1522 let mk = |day| date(2026, 2, day).weekday();
1523 assert_eq!(mk(14), Weekday::Saturday);
1524 assert_eq!(mk(15), Weekday::Sunday);
1525 assert_eq!(mk(16), Weekday::Monday);
1526 assert_eq!(mk(17), Weekday::Tuesday);
1527 assert_eq!(mk(18), Weekday::Wednesday);
1528 assert_eq!(mk(19), Weekday::Thursday);
1529 assert_eq!(mk(20), Weekday::Friday);
1530 assert_eq!(mk(21), Weekday::Saturday);
1531 }
1532
1533 #[test]
1534 fn first_of_last_of() {
1535 for year in b::Year::MIN..=b::Year::MAX {
1536 for month in b::Month::MIN..=b::Month::MAX {
1537 for day in b::Day::MIN..=civil::days_in_month(year, month) {
1538 let d = date(year, month, day);
1539
1540 assert_eq!(d.first_of_month(), date(year, month, 1));
1541 assert_eq!(
1542 d.last_of_month(),
1543 date(year, month, d.days_in_month())
1544 );
1545
1546 assert_eq!(d.first_of_year(), date(year, 1, 1));
1547 assert_eq!(d.last_of_year(), date(year, 12, 31));
1548 }
1549 }
1550 }
1551 }
1552
1553 #[test]
1554 fn days_in() {
1555 for year in b::Year::MIN..=b::Year::MAX {
1556 for month in b::Month::MIN..=b::Month::MAX {
1557 for day in b::Day::MIN..=civil::days_in_month(year, month) {
1558 let d = date(year, month, day);
1559
1560 assert!([365, 366].contains(&d.days_in_year()));
1561 assert!([28, 29, 30, 31].contains(&d.days_in_month()));
1562 }
1563 }
1564 }
1565 }
1566
1567 #[test]
1568 fn nth_weekday_of_month_various() {
1569 let d1 = date(2017, 3, 1);
1570 let wday = Weekday::Friday;
1571 assert_eq!(d1.nth_weekday_of_month(2, wday), Ok(date(2017, 3, 10)));
1572
1573 let d1 = date(2024, 3, 1);
1574 let wday = Weekday::Thursday;
1575 assert_eq!(d1.nth_weekday_of_month(-1, wday), Ok(date(2024, 3, 28)));
1576
1577 let d1 = date(2024, 3, 25);
1578 let wday = Weekday::Monday;
1579 assert!(d1.nth_weekday_of_month(5, wday).is_err());
1580 assert!(d1.nth_weekday_of_month(-5, wday).is_err());
1581
1582 let d1 = date(1998, 1, 1);
1583 let wday = Weekday::Saturday;
1584 assert_eq!(d1.nth_weekday_of_month(5, wday), Ok(date(1998, 1, 31)));
1585 }
1586
1587 #[test]
1588 fn nth_weekday_of_month_errors() {
1589 let d = date(2017, 3, 1);
1590 let wday = Weekday::Tuesday;
1591
1592 assert!(d.nth_weekday_of_month(0, wday).is_err());
1593 assert!(d.nth_weekday_of_month(5, wday).is_err());
1594 assert!(d.nth_weekday_of_month(-5, wday).is_err());
1595 assert!(d.nth_weekday_of_month(6, wday).is_err());
1596 assert!(d.nth_weekday_of_month(-6, wday).is_err());
1597 assert!(d.nth_weekday_of_month(i8::MIN, wday).is_err());
1598 assert!(d.nth_weekday_of_month(i8::MAX, wday).is_err());
1599
1600 assert_eq!(
1601 d.nth_weekday_of_month(5, Weekday::Friday),
1602 Ok(date(2017, 3, 31))
1603 );
1604 assert_eq!(
1605 d.nth_weekday_of_month(-5, Weekday::Friday),
1606 Ok(date(2017, 3, 3))
1607 );
1608 }
1609
1610 #[test]
1611 fn nth_weekday_of_month_near_minimum_date() {
1612 let d = date(-9999, 1, 1);
1613
1614 assert_eq!(
1615 d.nth_weekday_of_month(1, Weekday::Monday),
1616 Ok(date(-9999, 1, 1))
1617 );
1618 assert_eq!(
1619 d.nth_weekday_of_month(2, Weekday::Monday),
1620 Ok(date(-9999, 1, 8))
1621 );
1622 assert_eq!(
1623 d.nth_weekday_of_month(3, Weekday::Monday),
1624 Ok(date(-9999, 1, 15))
1625 );
1626 assert_eq!(
1627 d.nth_weekday_of_month(4, Weekday::Monday),
1628 Ok(date(-9999, 1, 22))
1629 );
1630 assert_eq!(
1631 d.nth_weekday_of_month(5, Weekday::Monday),
1632 Ok(date(-9999, 1, 29))
1633 );
1634
1635 assert_eq!(
1636 d.nth_weekday_of_month(-5, Weekday::Monday),
1637 Ok(date(-9999, 1, 1))
1638 );
1639 assert_eq!(
1640 d.nth_weekday_of_month(-4, Weekday::Monday),
1641 Ok(date(-9999, 1, 8))
1642 );
1643 assert_eq!(
1644 d.nth_weekday_of_month(-3, Weekday::Monday),
1645 Ok(date(-9999, 1, 15))
1646 );
1647 assert_eq!(
1648 d.nth_weekday_of_month(-2, Weekday::Monday),
1649 Ok(date(-9999, 1, 22))
1650 );
1651 assert_eq!(
1652 d.nth_weekday_of_month(-1, Weekday::Monday),
1653 Ok(date(-9999, 1, 29))
1654 );
1655 }
1656
1657 #[test]
1658 fn nth_weekday_of_month_near_maximum_date() {
1659 let d = date(9999, 12, 1);
1660
1661 assert_eq!(
1662 d.nth_weekday_of_month(1, Weekday::Friday),
1663 Ok(date(9999, 12, 3))
1664 );
1665 assert_eq!(
1666 d.nth_weekday_of_month(2, Weekday::Friday),
1667 Ok(date(9999, 12, 10))
1668 );
1669 assert_eq!(
1670 d.nth_weekday_of_month(3, Weekday::Friday),
1671 Ok(date(9999, 12, 17))
1672 );
1673 assert_eq!(
1674 d.nth_weekday_of_month(4, Weekday::Friday),
1675 Ok(date(9999, 12, 24))
1676 );
1677 assert_eq!(
1678 d.nth_weekday_of_month(5, Weekday::Friday),
1679 Ok(date(9999, 12, 31))
1680 );
1681
1682 assert_eq!(
1683 d.nth_weekday_of_month(-5, Weekday::Friday),
1684 Ok(date(9999, 12, 3))
1685 );
1686 assert_eq!(
1687 d.nth_weekday_of_month(-4, Weekday::Friday),
1688 Ok(date(9999, 12, 10))
1689 );
1690 assert_eq!(
1691 d.nth_weekday_of_month(-3, Weekday::Friday),
1692 Ok(date(9999, 12, 17))
1693 );
1694 assert_eq!(
1695 d.nth_weekday_of_month(-2, Weekday::Friday),
1696 Ok(date(9999, 12, 24))
1697 );
1698 assert_eq!(
1699 d.nth_weekday_of_month(-1, Weekday::Friday),
1700 Ok(date(9999, 12, 31))
1701 );
1702 }
1703
1704 #[test]
1705 fn nth_weekday_of_month_every_month_has_four_weekdays() {
1706 for year in b::Year::MIN..=b::Year::MAX {
1707 for month in b::Month::MIN..=b::Month::MAX {
1708 let d = date(year, month, 1);
1709 for weekday in Weekday::Sunday.cycle_forward().take(7) {
1710 for nth in [-4, -3, -2, -1, 1, 2, 3, 4] {
1711 assert!(d.nth_weekday_of_month(nth, weekday).is_ok());
1712 assert!(d.nth_weekday_of_month(0, weekday).is_err());
1714 assert!(d.nth_weekday_of_month(6, weekday).is_err());
1715 assert!(d.nth_weekday_of_month(-6, weekday).is_err());
1716 }
1717 }
1718 }
1719 }
1720 }
1721
1722 #[test]
1723 fn nth_weekday_various() {
1724 let d = date(2024, 3, 10);
1725
1726 assert_eq!(d.nth_weekday(1, Weekday::Monday), Ok(date(2024, 3, 11)));
1727 assert_eq!(d.nth_weekday(1, Weekday::Sunday), Ok(date(2024, 3, 17)));
1728 assert_eq!(d.nth_weekday(2, Weekday::Thursday), Ok(date(2024, 3, 21)));
1729
1730 assert_eq!(d.nth_weekday(-1, Weekday::Monday), Ok(date(2024, 3, 4)));
1731 assert_eq!(d.nth_weekday(-1, Weekday::Sunday), Ok(date(2024, 3, 3)));
1732 assert_eq!(
1733 d.nth_weekday(-2, Weekday::Thursday),
1734 Ok(date(2024, 2, 29))
1735 );
1736
1737 let d = date(9999, 12, 24);
1738 assert_eq!(d.nth_weekday(1, Weekday::Friday), Ok(date(9999, 12, 31)));
1739 let d = date(9999, 12, 30);
1740 assert_eq!(d.nth_weekday(1, Weekday::Friday), Ok(date(9999, 12, 31)));
1741 let d = date(9999, 12, 31);
1742 assert_eq!(d.nth_weekday(-1, Weekday::Friday), Ok(date(9999, 12, 24)));
1743 assert!(d.nth_weekday(1, Weekday::Friday).is_err());
1744
1745 let d = date(-9999, 1, 8);
1746 assert_eq!(d.nth_weekday(-1, Weekday::Monday), Ok(date(-9999, 1, 1)));
1747 let d = date(-9999, 1, 2);
1748 assert_eq!(d.nth_weekday(-1, Weekday::Monday), Ok(date(-9999, 1, 1)));
1749 let d = date(-9999, 1, 1);
1750 assert_eq!(d.nth_weekday(1, Weekday::Monday), Ok(date(-9999, 1, 8)));
1751 assert!(d.nth_weekday(-1, Weekday::Monday).is_err());
1752 }
1753
1754 #[test]
1755 fn nth_weekday_errors() {
1756 let d = date(2024, 3, 10);
1757 assert!(d.nth_weekday(0, Weekday::Monday).is_err());
1758 }
1759
1760 #[test]
1761 fn nth_weekday_extreme() {
1762 let weeks = 1_043_497;
1763
1764 let d1 = date(-9999, 1, 1);
1765 let d2 = d1.nth_weekday(weeks, Weekday::Monday).unwrap();
1766 assert_eq!(d2, date(9999, 12, 27));
1767 assert!(d1.nth_weekday(weeks + 1, Weekday::Monday).is_err());
1768 assert!(d1.nth_weekday(i32::MIN, Weekday::Monday).is_err());
1769 assert!(d1.nth_weekday(i32::MAX, Weekday::Monday).is_err());
1770
1771 let d1 = date(9999, 12, 31);
1772 let d2 = d1.nth_weekday(-weeks, Weekday::Friday).unwrap();
1773 assert_eq!(d2, date(-9999, 1, 5));
1774 assert!(d1.nth_weekday(weeks - 1, Weekday::Friday).is_err());
1775 assert!(d1.nth_weekday(i32::MIN, Weekday::Friday).is_err());
1776 assert!(d1.nth_weekday(i32::MAX, Weekday::Friday).is_err());
1777 }
1778
1779 #[test]
1780 fn yesterday() {
1781 assert_eq!(date(2024, 7, 3).yesterday(), Ok(date(2024, 7, 2)));
1782 assert_eq!(date(2024, 7, 1).yesterday(), Ok(date(2024, 6, 30)));
1783 assert_eq!(date(2024, 6, 1).yesterday(), Ok(date(2024, 5, 31)));
1784 assert_eq!(date(2024, 3, 1).yesterday(), Ok(date(2024, 2, 29)));
1785 assert_eq!(date(2023, 3, 1).yesterday(), Ok(date(2023, 2, 28)));
1786 assert_eq!(date(2023, 1, 1).yesterday(), Ok(date(2022, 12, 31)));
1787 assert_eq!(date(-9999, 1, 2).yesterday(), Ok(date(-9999, 1, 1)));
1788 assert_eq!(date(9999, 12, 31).yesterday(), Ok(date(9999, 12, 30)));
1789
1790 assert!(date(-9999, 1, 1).yesterday().is_err());
1791 }
1792
1793 #[test]
1794 fn tomorrow() {
1795 assert_eq!(date(2024, 7, 3).tomorrow(), Ok(date(2024, 7, 4)));
1796 assert_eq!(date(2024, 6, 30).tomorrow(), Ok(date(2024, 7, 1)));
1797 assert_eq!(date(2024, 5, 30).tomorrow(), Ok(date(2024, 5, 31)));
1798 assert_eq!(date(2024, 5, 31).tomorrow(), Ok(date(2024, 6, 1)));
1799 assert_eq!(date(2024, 2, 28).tomorrow(), Ok(date(2024, 2, 29)));
1800 assert_eq!(date(2024, 2, 29).tomorrow(), Ok(date(2024, 3, 1)));
1801 assert_eq!(date(2023, 2, 28).tomorrow(), Ok(date(2023, 3, 1)));
1802 assert_eq!(date(2023, 12, 31).tomorrow(), Ok(date(2024, 1, 1)));
1803 assert_eq!(date(-9999, 1, 1).tomorrow(), Ok(date(-9999, 1, 2)));
1804 assert_eq!(date(9999, 12, 30).tomorrow(), Ok(date(9999, 12, 31)));
1805
1806 assert!(date(9999, 12, 31).tomorrow().is_err());
1807 }
1808
1809 #[test]
1810 fn iso_week_date_tomorrow() {
1811 assert_eq!(
1812 week_date(2024, 27, Weekday::Wednesday).tomorrow(),
1813 Ok(week_date(2024, 27, Weekday::Thursday)),
1814 );
1815 assert_eq!(
1816 week_date(2024, 27, Weekday::Sunday).tomorrow(),
1817 Ok(week_date(2024, 28, Weekday::Monday)),
1818 );
1819 assert_eq!(
1820 week_date(2024, 52, Weekday::Sunday).tomorrow(),
1821 Ok(week_date(2025, 1, Weekday::Monday)),
1822 );
1823 assert_eq!(
1824 week_date(2025, 1, Weekday::Monday).tomorrow(),
1825 Ok(week_date(2025, 1, Weekday::Tuesday)),
1826 );
1827 assert_eq!(
1828 week_date(2025, 1, Weekday::Tuesday).tomorrow(),
1829 Ok(week_date(2025, 1, Weekday::Wednesday)),
1830 );
1831 assert_eq!(
1832 week_date(2026, 52, Weekday::Sunday).tomorrow(),
1833 Ok(week_date(2026, 53, Weekday::Monday)),
1834 );
1835 assert_eq!(
1836 week_date(2026, 53, Weekday::Sunday).tomorrow(),
1837 Ok(week_date(2027, 1, Weekday::Monday)),
1838 );
1839 assert_eq!(
1840 week_date(-9999, 1, Weekday::Monday).tomorrow(),
1841 Ok(week_date(-9999, 1, Weekday::Tuesday)),
1842 );
1843 assert_eq!(
1844 week_date(9999, 52, Weekday::Thursday).tomorrow(),
1845 Ok(week_date(9999, 52, Weekday::Friday)),
1846 );
1847
1848 assert!(week_date(9999, 52, Weekday::Friday).tomorrow().is_err());
1849 }
1850
1851 #[test]
1852 fn iso_week_date_yesterday() {
1853 assert_eq!(
1854 week_date(2024, 27, Weekday::Thursday).yesterday(),
1855 Ok(week_date(2024, 27, Weekday::Wednesday)),
1856 );
1857 assert_eq!(
1858 week_date(2024, 28, Weekday::Monday).yesterday(),
1859 Ok(week_date(2024, 27, Weekday::Sunday)),
1860 );
1861 assert_eq!(
1862 week_date(2025, 1, Weekday::Monday).yesterday(),
1863 Ok(week_date(2024, 52, Weekday::Sunday)),
1864 );
1865 assert_eq!(
1866 week_date(2025, 1, Weekday::Tuesday).yesterday(),
1867 Ok(week_date(2025, 1, Weekday::Monday)),
1868 );
1869 assert_eq!(
1870 week_date(2025, 1, Weekday::Wednesday).yesterday(),
1871 Ok(week_date(2025, 1, Weekday::Tuesday)),
1872 );
1873 assert_eq!(
1874 week_date(2026, 53, Weekday::Monday).yesterday(),
1875 Ok(week_date(2026, 52, Weekday::Sunday)),
1876 );
1877 assert_eq!(
1878 week_date(2027, 1, Weekday::Monday).yesterday(),
1879 Ok(week_date(2026, 53, Weekday::Sunday)),
1880 );
1881 assert_eq!(
1882 week_date(9999, 12, Weekday::Friday).yesterday(),
1883 Ok(week_date(9999, 12, Weekday::Thursday)),
1884 );
1885 assert_eq!(
1886 week_date(-9999, 1, Weekday::Tuesday).yesterday(),
1887 Ok(week_date(-9999, 1, Weekday::Monday)),
1888 );
1889
1890 assert!(week_date(-9999, 1, Weekday::Monday).yesterday().is_err());
1891 }
1892
1893 #[test]
1894 fn add() {
1895 assert_eq!(date(2024, 7, 3).checked_add(-1), Ok(date(2024, 7, 2)));
1896 assert_eq!(date(2024, 7, 1).checked_add(-1), Ok(date(2024, 6, 30)));
1897 assert_eq!(date(2024, 6, 1).checked_add(-1), Ok(date(2024, 5, 31)));
1898 assert_eq!(date(2024, 3, 1).checked_add(-1), Ok(date(2024, 2, 29)));
1899 assert_eq!(date(2023, 3, 1).checked_add(-1), Ok(date(2023, 2, 28)));
1900 assert_eq!(date(2023, 1, 1).checked_add(-1), Ok(date(2022, 12, 31)));
1901 assert_eq!(date(-9999, 1, 2).checked_add(-1), Ok(date(-9999, 1, 1)));
1902 assert_eq!(date(9999, 12, 31).checked_add(-1), Ok(date(9999, 12, 30)));
1903
1904 assert_eq!(date(2024, 7, 3).checked_add(1), Ok(date(2024, 7, 4)));
1905 assert_eq!(date(2024, 6, 30).checked_add(1), Ok(date(2024, 7, 1)));
1906 assert_eq!(date(2024, 5, 30).checked_add(1), Ok(date(2024, 5, 31)));
1907 assert_eq!(date(2024, 5, 31).checked_add(1), Ok(date(2024, 6, 1)));
1908 assert_eq!(date(2024, 2, 28).checked_add(1), Ok(date(2024, 2, 29)));
1909 assert_eq!(date(2024, 2, 29).checked_add(1), Ok(date(2024, 3, 1)));
1910 assert_eq!(date(2023, 2, 28).checked_add(1), Ok(date(2023, 3, 1)));
1911 assert_eq!(date(2023, 12, 31).checked_add(1), Ok(date(2024, 1, 1)));
1912 assert_eq!(date(-9999, 1, 1).checked_add(1), Ok(date(-9999, 1, 2)));
1913 assert_eq!(date(9999, 12, 30).checked_add(1), Ok(date(9999, 12, 31)));
1914
1915 assert_eq!(date(2024, 7, 3).checked_add(2), Ok(date(2024, 7, 5)));
1916 assert_eq!(date(2024, 6, 29).checked_add(2), Ok(date(2024, 7, 1)));
1917 assert_eq!(date(2024, 5, 29).checked_add(2), Ok(date(2024, 5, 31)));
1918 assert_eq!(date(2024, 5, 30).checked_add(2), Ok(date(2024, 6, 1)));
1919 assert_eq!(date(2024, 2, 27).checked_add(2), Ok(date(2024, 2, 29)));
1920 assert_eq!(date(2024, 2, 28).checked_add(2), Ok(date(2024, 3, 1)));
1921 assert_eq!(date(2023, 2, 27).checked_add(2), Ok(date(2023, 3, 1)));
1922 assert_eq!(date(2023, 12, 30).checked_add(2), Ok(date(2024, 1, 1)));
1923 assert_eq!(date(-9999, 1, 1).checked_add(2), Ok(date(-9999, 1, 3)));
1924 assert_eq!(date(9999, 12, 29).checked_add(2), Ok(date(9999, 12, 31)));
1925
1926 let max_days = (b::UnixEpochDays::LEN - 1) as i32;
1927
1928 assert_eq!(
1929 date(-9999, 1, 1).checked_add(max_days),
1930 Ok(date(9999, 12, 31))
1931 );
1932 assert_eq!(
1933 date(9999, 12, 31).checked_add(-max_days),
1934 Ok(date(-9999, 1, 1))
1935 );
1936
1937 assert!(date(-9999, 1, 1).checked_add(-1).is_err());
1938 assert!(date(9999, 12, 31).checked_add(1).is_err());
1939 assert!(date(-9999, 1, 1).checked_add(max_days + 1).is_err());
1940 assert!(date(9999, 12, 31).checked_add(-(max_days + 1)).is_err());
1941 }
1942
1943 #[test]
1944 fn sub() {
1945 assert_eq!(date(-9999, 1, 1).checked_sub(-1), Ok(date(-9999, 1, 2)));
1946 assert_eq!(date(-9999, 1, 1).checked_sub(-2), Ok(date(-9999, 1, 3)));
1947
1948 assert!(date(-9999, 1, 1).checked_sub(1).is_err());
1949 assert!(date(-9999, 1, 1).checked_sub(i32::MIN).is_err());
1950 assert!(date(9999, 12, 31).checked_sub(i32::MAX).is_err());
1951 }
1952
1953 #[test]
1954 fn prev_year() {
1955 assert_eq!(date(2024, 2, 29).prev_year(), Ok(2023));
1956 assert_eq!(date(2024, 1, 1).prev_year(), Ok(2023));
1957 assert_eq!(date(2023, 12, 31).prev_year(), Ok(2022));
1958 assert_eq!(date(9999, 12, 31).prev_year(), Ok(9998));
1959
1960 assert!(date(-9999, 12, 31).prev_year().is_err());
1961 assert!(date(-9999, 1, 1).prev_year().is_err());
1962 }
1963
1964 #[test]
1965 fn next_year() {
1966 assert_eq!(date(2024, 2, 29).next_year(), Ok(2025));
1967 assert_eq!(date(2024, 1, 1).next_year(), Ok(2025));
1968 assert_eq!(date(2023, 12, 31).next_year(), Ok(2024));
1969 assert_eq!(date(-9999, 12, 31).next_year(), Ok(-9998));
1970
1971 assert!(date(9999, 12, 31).next_year().is_err());
1972 assert!(date(9999, 1, 1).next_year().is_err());
1973 }
1974
1975 #[test]
1976 fn to_iso_week_date_various() {
1977 assert_eq!(
1978 date(1995, 1, 1).to_iso_week_date(),
1979 week_date(1994, 52, Weekday::Sunday),
1980 );
1981 assert_eq!(
1982 date(1996, 12, 31).to_iso_week_date(),
1983 week_date(1997, 1, Weekday::Tuesday),
1984 );
1985 assert_eq!(
1986 date(2019, 12, 30).to_iso_week_date(),
1987 week_date(2020, 1, Weekday::Monday),
1988 );
1989 assert_eq!(
1990 date(2031, 12, 29).to_iso_week_date(),
1991 week_date(2032, 1, Weekday::Monday),
1992 );
1993 assert_eq!(
1994 date(2024, 3, 9).to_iso_week_date(),
1995 week_date(2024, 10, Weekday::Saturday),
1996 );
1997 assert_eq!(
1998 Date::MIN.to_iso_week_date(),
1999 week_date(-9999, 1, Weekday::Monday),
2000 );
2001 assert_eq!(
2002 Date::MAX.to_iso_week_date(),
2003 week_date(9999, 52, Weekday::Friday),
2004 );
2005 }
2006
2007 quickcheck::quickcheck! {
2008 fn prop_tomorrow_yesterday_is_identity(d: Date) -> quickcheck::TestResult {
2009 let Ok(yesterday) = d.yesterday() else {
2010 return quickcheck::TestResult::discard()
2011 };
2012 quickcheck::TestResult::from_bool(yesterday.tomorrow() == Ok(d))
2013 }
2014
2015 fn prop_yesterday_tomorrow_is_identity(d: Date) -> quickcheck::TestResult {
2016 let Ok(tomorrow) = d.tomorrow() else {
2017 return quickcheck::TestResult::discard()
2018 };
2019 quickcheck::TestResult::from_bool(tomorrow.yesterday() == Ok(d))
2020 }
2021
2022 fn prop_add_equals_sub(d1: Date, days: i32) -> quickcheck::TestResult {
2023 let Ok(d2) = d1.checked_add(days) else {
2024 return quickcheck::TestResult::discard();
2025 };
2026 quickcheck::TestResult::from_bool(d2.checked_sub(days) == Ok(d1))
2027 }
2028
2029 fn prop_all_long_years_have_53rd_week(year: i16) -> quickcheck::TestResult {
2030 if b::ISOYear::check(year).is_err() {
2031 return quickcheck::TestResult::discard();
2032 }
2033 quickcheck::TestResult::from_bool(
2034 !civil::is_long_iso_week_year(year)
2035 || ISOWeekDate::new(year, 53, Weekday::Sunday).is_ok()
2036 )
2037 }
2038
2039 fn prop_prev_day_is_less(wd: ISOWeekDate) -> quickcheck::TestResult {
2040 let Ok(prev_date) = wd.to_date().yesterday() else {
2041 return quickcheck::TestResult::discard();
2042 };
2043 quickcheck::TestResult::from_bool(
2044 prev_date.to_iso_week_date() < wd,
2045 )
2046 }
2047
2048 fn prop_next_day_is_greater(wd: ISOWeekDate) -> quickcheck::TestResult {
2049 let Ok(next_date) = wd.to_date().tomorrow() else {
2050 return quickcheck::TestResult::discard();
2051 };
2052 quickcheck::TestResult::from_bool(
2053 wd < next_date.to_iso_week_date(),
2054 )
2055 }
2056
2057 fn prop_iso_tomorrow_yesterday_is_identity(
2058 wd: ISOWeekDate
2059 ) -> quickcheck::TestResult {
2060 let Ok(yesterday) = wd.yesterday() else {
2061 return quickcheck::TestResult::discard()
2062 };
2063 quickcheck::TestResult::from_bool(yesterday.tomorrow() == Ok(wd))
2064 }
2065
2066 fn prop_iso_yesterday_tomorrow_is_identity(
2067 wd: ISOWeekDate
2068 ) -> quickcheck::TestResult {
2069 let Ok(tomorrow) = wd.tomorrow() else {
2070 return quickcheck::TestResult::discard()
2071 };
2072 quickcheck::TestResult::from_bool(tomorrow.yesterday() == Ok(wd))
2073 }
2074 }
2075}