1use super::error::{err, Error};
26
27#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
28pub(crate) struct ITimestamp {
29 pub(crate) second: i64,
30 pub(crate) nanosecond: i32,
31}
32
33impl ITimestamp {
34 const MIN: ITimestamp =
35 ITimestamp { second: -377705023201, nanosecond: 0 };
36 const MAX: ITimestamp =
37 ITimestamp { second: 253402207200, nanosecond: 999_999_999 };
38
39 #[inline]
41 pub(crate) const fn from_second(second: i64) -> ITimestamp {
42 ITimestamp { second, nanosecond: 0 }
43 }
44
45 #[cfg_attr(feature = "perf-inline", inline(always))]
50 pub(crate) const fn to_datetime(&self, offset: IOffset) -> IDateTime {
51 let ITimestamp { mut second, mut nanosecond } = *self;
52 second += offset.second as i64;
53 let mut epoch_day = second.div_euclid(86_400) as i32;
54 second = second.rem_euclid(86_400);
55 if nanosecond < 0 {
56 if second > 0 {
57 second -= 1;
58 nanosecond += 1_000_000_000;
59 } else {
60 epoch_day -= 1;
61 second += 86_399;
62 nanosecond += 1_000_000_000;
63 }
64 }
65
66 let date = IEpochDay { epoch_day }.to_date();
67 let mut time = ITimeSecond { second: second as i32 }.to_time();
68 time.subsec_nanosecond = nanosecond;
69 IDateTime { date, time }
70 }
71}
72
73#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
74pub(crate) struct IOffset {
75 pub(crate) second: i32,
76}
77
78impl IOffset {
79 pub(crate) const UTC: IOffset = IOffset { second: 0 };
80}
81
82#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
83pub(crate) struct IDateTime {
84 pub(crate) date: IDate,
85 pub(crate) time: ITime,
86}
87
88impl IDateTime {
89 const MIN: IDateTime = IDateTime { date: IDate::MIN, time: ITime::MIN };
90 const MAX: IDateTime = IDateTime { date: IDate::MAX, time: ITime::MAX };
91
92 #[cfg_attr(feature = "perf-inline", inline(always))]
97 pub(crate) fn to_timestamp(&self, offset: IOffset) -> ITimestamp {
98 let epoch_day = self.date.to_epoch_day().epoch_day;
99 let mut second = (epoch_day as i64) * 86_400
100 + (self.time.to_second().second as i64);
101 let mut nanosecond = self.time.subsec_nanosecond;
102 second -= offset.second as i64;
103 if epoch_day < 0 && nanosecond != 0 {
104 second += 1;
105 nanosecond -= 1_000_000_000;
106 }
107 ITimestamp { second, nanosecond }
108 }
109
110 #[cfg_attr(feature = "perf-inline", inline(always))]
118 pub(crate) fn to_timestamp_checked(
119 &self,
120 offset: IOffset,
121 ) -> Option<ITimestamp> {
122 let ts = self.to_timestamp(offset);
123 if !(ITimestamp::MIN <= ts && ts <= ITimestamp::MAX) {
124 return None;
125 }
126 Some(ts)
127 }
128
129 #[cfg_attr(feature = "perf-inline", inline(always))]
130 pub(crate) fn saturating_add_seconds(&self, seconds: i32) -> IDateTime {
131 self.checked_add_seconds(seconds).unwrap_or_else(|_| {
132 if seconds < 0 {
133 IDateTime::MIN
134 } else {
135 IDateTime::MAX
136 }
137 })
138 }
139
140 #[cfg_attr(feature = "perf-inline", inline(always))]
141 pub(crate) fn checked_add_seconds(
142 &self,
143 seconds: i32,
144 ) -> Result<IDateTime, Error> {
145 let day_second =
146 self.time.to_second().second.checked_add(seconds).ok_or_else(
147 || err!("adding `{seconds}s` to datetime overflowed"),
148 )?;
149 let days = day_second.div_euclid(86400);
150 let second = day_second.rem_euclid(86400);
151 let date = self.date.checked_add_days(days)?;
152 let time = ITimeSecond { second }.to_time();
153 Ok(IDateTime { date, time })
154 }
155}
156
157#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
158pub(crate) struct IEpochDay {
159 pub(crate) epoch_day: i32,
160}
161
162impl IEpochDay {
163 const MIN: IEpochDay = IEpochDay { epoch_day: -4371587 };
164 const MAX: IEpochDay = IEpochDay { epoch_day: 2932896 };
165
166 #[cfg_attr(feature = "perf-inline", inline(always))]
172 #[allow(non_upper_case_globals, non_snake_case)] pub(crate) const fn to_date(&self) -> IDate {
174 const s: u32 = 82;
175 const K: u32 = 719468 + 146097 * s;
176 const L: u32 = 400 * s;
177
178 let N_U = self.epoch_day as u32;
179 let N = N_U.wrapping_add(K);
180
181 let N_1 = 4 * N + 3;
182 let C = N_1 / 146097;
183 let N_C = (N_1 % 146097) / 4;
184
185 let N_2 = 4 * N_C + 3;
186 let P_2 = 2939745 * (N_2 as u64);
187 let Z = (P_2 / 4294967296) as u32;
188 let N_Y = (P_2 % 4294967296) as u32 / 2939745 / 4;
189 let Y = 100 * C + Z;
190
191 let N_3 = 2141 * N_Y + 197913;
192 let M = N_3 / 65536;
193 let D = (N_3 % 65536) / 2141;
194
195 let J = N_Y >= 306;
196 let year = Y.wrapping_sub(L).wrapping_add(J as u32) as i16;
197 let month = (if J { M - 12 } else { M }) as i8;
198 let day = (D + 1) as i8;
199 IDate { year, month, day }
200 }
201
202 #[cfg_attr(feature = "perf-inline", inline(always))]
204 pub(crate) const fn weekday(&self) -> IWeekday {
205 IWeekday::from_monday_zero_offset(
211 (self.epoch_day + 3).rem_euclid(7) as i8
212 )
213 }
214
215 #[inline]
220 pub(crate) fn checked_add(&self, amount: i32) -> Result<IEpochDay, Error> {
221 let epoch_day = self.epoch_day;
222 let sum = epoch_day.checked_add(amount).ok_or_else(|| {
223 err!("adding `{amount}` to epoch day `{epoch_day}` overflowed i32")
224 })?;
225 let ret = IEpochDay { epoch_day: sum };
226 if !(IEpochDay::MIN <= ret && ret <= IEpochDay::MAX) {
227 return Err(err!(
228 "adding `{amount}` to epoch day `{epoch_day}` \
229 resulted in `{sum}`, which is not in the required \
230 epoch day range of `{min}..={max}`",
231 min = IEpochDay::MIN.epoch_day,
232 max = IEpochDay::MAX.epoch_day,
233 ));
234 }
235 Ok(ret)
236 }
237}
238
239#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
240pub(crate) struct IDate {
241 pub(crate) year: i16,
242 pub(crate) month: i8,
243 pub(crate) day: i8,
244}
245
246impl IDate {
247 const MIN: IDate = IDate { year: -9999, month: 1, day: 1 };
248 const MAX: IDate = IDate { year: 9999, month: 12, day: 31 };
249
250 #[inline]
257 pub(crate) fn try_new(
258 year: i16,
259 month: i8,
260 day: i8,
261 ) -> Result<IDate, Error> {
262 if day > 28 {
263 let max_day = days_in_month(year, month);
264 if day > max_day {
265 return Err(err!(
266 "day={day} is out of range for year={year} \
267 and month={month}, must be in range 1..={max_day}",
268 ));
269 }
270 }
271 Ok(IDate { year, month, day })
272 }
273
274 #[inline]
281 pub(crate) fn from_day_of_year(
282 year: i16,
283 day: i16,
284 ) -> Result<IDate, Error> {
285 if !(1 <= day && day <= 366) {
286 return Err(err!(
287 "day-of-year={day} is out of range for year={year}, \
288 must be in range 1..={max_day}",
289 max_day = days_in_year(year),
290 ));
291 }
292 let start = IDate { year, month: 1, day: 1 }.to_epoch_day();
293 let end = start
294 .checked_add(i32::from(day) - 1)
295 .map_err(|_| {
296 err!(
297 "failed to find date for \
298 year={year} and day-of-year={day}: \
299 adding `{day}` to `{start}` overflows \
300 Jiff's range",
301 start = start.epoch_day,
302 )
303 })?
304 .to_date();
305 if year != end.year {
307 debug_assert_eq!(day, 366);
309 debug_assert!(!is_leap_year(year));
310 return Err(err!(
311 "day-of-year={day} is out of range for year={year}, \
312 must be in range 1..={max_day}",
313 max_day = days_in_year(year),
314 ));
315 }
316 Ok(end)
317 }
318
319 #[inline]
327 pub(crate) fn from_day_of_year_no_leap(
328 year: i16,
329 mut day: i16,
330 ) -> Result<IDate, Error> {
331 if !(1 <= day && day <= 365) {
332 return Err(err!(
333 "day-of-year={day} is out of range for year={year}, \
334 must be in range 1..=365",
335 ));
336 }
337 if day >= 60 && is_leap_year(year) {
338 day += 1;
339 }
340 Ok(IDate::from_day_of_year(year, day).unwrap())
342 }
343
344 #[cfg_attr(feature = "perf-inline", inline(always))]
350 #[allow(non_upper_case_globals, non_snake_case)] pub(crate) const fn to_epoch_day(&self) -> IEpochDay {
352 const s: u32 = 82;
353 const K: u32 = 719468 + 146097 * s;
354 const L: u32 = 400 * s;
355
356 let year = self.year as u32;
357 let month = self.month as u32;
358 let day = self.day as u32;
359
360 let J = month <= 2;
361 let Y = year.wrapping_add(L).wrapping_sub(J as u32);
362 let M = if J { month + 12 } else { month };
363 let D = day - 1;
364 let C = Y / 100;
365
366 let y_star = 1461 * Y / 4 - C + C / 4;
367 let m_star = (979 * M - 2919) / 32;
368 let N = y_star + m_star + D;
369
370 let N_U = N.wrapping_sub(K);
371 let epoch_day = N_U as i32;
372 IEpochDay { epoch_day }
373 }
374
375 #[inline]
377 pub(crate) const fn weekday(&self) -> IWeekday {
378 self.to_epoch_day().weekday()
379 }
380
381 #[inline]
389 pub(crate) fn nth_weekday_of_month(
390 &self,
391 nth: i8,
392 weekday: IWeekday,
393 ) -> Result<IDate, Error> {
394 if nth == 0 || !(-5 <= nth && nth <= 5) {
395 return Err(err!(
396 "got nth weekday of `{nth}`, but \
397 must be non-zero and in range `-5..=5`",
398 ));
399 }
400 if nth > 0 {
401 let first_weekday = self.first_of_month().weekday();
402 let diff = weekday.since(first_weekday);
403 let day = diff + 1 + (nth - 1) * 7;
404 IDate::try_new(self.year, self.month, day)
405 } else {
406 let last = self.last_of_month();
407 let last_weekday = last.weekday();
408 let diff = last_weekday.since(weekday);
409 let day = last.day - diff - (nth.abs() - 1) * 7;
410 if day < 1 {
415 return Err(err!(
416 "day={day} is out of range for year={year} \
417 and month={month}, must be in range 1..={max_day}",
418 year = self.year,
419 month = self.month,
420 max_day = days_in_month(self.year, self.month),
421 ));
422 }
423 IDate::try_new(self.year, self.month, day)
424 }
425 }
426
427 #[inline]
429 pub(crate) fn yesterday(self) -> Result<IDate, Error> {
430 if self.day == 1 {
431 if self.month == 1 {
432 let year = self.year - 1;
433 if year <= -10000 {
434 return Err(err!(
435 "returning yesterday for -9999-01-01 is not \
436 possible because it is less than Jiff's supported
437 minimum date",
438 ));
439 }
440 return Ok(IDate { year, month: 12, day: 31 });
441 }
442 let month = self.month - 1;
443 let day = days_in_month(self.year, month);
444 return Ok(IDate { month, day, ..self });
445 }
446 Ok(IDate { day: self.day - 1, ..self })
447 }
448
449 #[inline]
451 pub(crate) fn tomorrow(self) -> Result<IDate, Error> {
452 if self.day >= 28 && self.day == days_in_month(self.year, self.month) {
453 if self.month == 12 {
454 let year = self.year + 1;
455 if year >= 10000 {
456 return Err(err!(
457 "returning tomorrow for 9999-12-31 is not \
458 possible because it is greater than Jiff's supported
459 maximum date",
460 ));
461 }
462 return Ok(IDate { year, month: 1, day: 1 });
463 }
464 let month = self.month + 1;
465 return Ok(IDate { month, day: 1, ..self });
466 }
467 Ok(IDate { day: self.day + 1, ..self })
468 }
469
470 #[inline]
472 pub(crate) fn prev_year(self) -> Result<i16, Error> {
473 let year = self.year - 1;
474 if year <= -10_000 {
475 return Err(err!(
476 "returning previous year for {year:04}-{month:02}-{day:02} is \
477 not possible because it is less than Jiff's supported \
478 minimum date",
479 year = self.year,
480 month = self.month,
481 day = self.day,
482 ));
483 }
484 Ok(year)
485 }
486
487 #[inline]
489 pub(crate) fn next_year(self) -> Result<i16, Error> {
490 let year = self.year + 1;
491 if year >= 10_000 {
492 return Err(err!(
493 "returning next year for {year:04}-{month:02}-{day:02} is \
494 not possible because it is greater than Jiff's supported \
495 maximum date",
496 year = self.year,
497 month = self.month,
498 day = self.day,
499 ));
500 }
501 Ok(year)
502 }
503
504 #[inline]
506 pub(crate) fn checked_add_days(
507 &self,
508 amount: i32,
509 ) -> Result<IDate, Error> {
510 match amount {
511 0 => Ok(*self),
512 -1 => self.yesterday(),
513 1 => self.tomorrow(),
514 n => self.to_epoch_day().checked_add(n).map(|d| d.to_date()),
515 }
516 }
517
518 #[inline]
519 fn first_of_month(&self) -> IDate {
520 IDate { day: 1, ..*self }
521 }
522
523 #[inline]
524 fn last_of_month(&self) -> IDate {
525 IDate { day: days_in_month(self.year, self.month), ..*self }
526 }
527
528 #[cfg(test)]
529 pub(crate) fn at(
530 &self,
531 hour: i8,
532 minute: i8,
533 second: i8,
534 subsec_nanosecond: i32,
535 ) -> IDateTime {
536 let time = ITime { hour, minute, second, subsec_nanosecond };
537 IDateTime { date: *self, time }
538 }
539}
540
541#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
546pub(crate) struct ITime {
547 pub(crate) hour: i8,
548 pub(crate) minute: i8,
549 pub(crate) second: i8,
550 pub(crate) subsec_nanosecond: i32,
551}
552
553impl ITime {
554 pub(crate) const ZERO: ITime =
555 ITime { hour: 0, minute: 0, second: 0, subsec_nanosecond: 0 };
556 pub(crate) const MIN: ITime =
557 ITime { hour: 0, minute: 0, second: 0, subsec_nanosecond: 0 };
558 pub(crate) const MAX: ITime = ITime {
559 hour: 23,
560 minute: 59,
561 second: 59,
562 subsec_nanosecond: 999_999_999,
563 };
564
565 #[cfg_attr(feature = "perf-inline", inline(always))]
566 pub(crate) const fn to_second(&self) -> ITimeSecond {
567 let mut second: i32 = 0;
568 second += (self.hour as i32) * 3600;
569 second += (self.minute as i32) * 60;
570 second += self.second as i32;
571 ITimeSecond { second }
572 }
573
574 #[cfg_attr(feature = "perf-inline", inline(always))]
575 pub(crate) const fn to_nanosecond(&self) -> ITimeNanosecond {
576 let mut nanosecond: i64 = 0;
577 nanosecond += (self.hour as i64) * 3_600_000_000_000;
578 nanosecond += (self.minute as i64) * 60_000_000_000;
579 nanosecond += (self.second as i64) * 1_000_000_000;
580 nanosecond += self.subsec_nanosecond as i64;
581 ITimeNanosecond { nanosecond }
582 }
583}
584
585#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
587pub(crate) struct ITimeSecond {
588 pub(crate) second: i32,
589}
590
591impl ITimeSecond {
592 #[cfg_attr(feature = "perf-inline", inline(always))]
593 pub(crate) const fn to_time(&self) -> ITime {
594 let mut second = self.second;
595 let mut time = ITime::ZERO;
596 if second != 0 {
597 time.hour = (second / 3600) as i8;
598 second %= 3600;
599 if second != 0 {
600 time.minute = (second / 60) as i8;
601 time.second = (second % 60) as i8;
602 }
603 }
604 time
605 }
606}
607
608#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
610pub(crate) struct ITimeNanosecond {
611 pub(crate) nanosecond: i64,
612}
613
614impl ITimeNanosecond {
615 #[cfg_attr(feature = "perf-inline", inline(always))]
616 pub(crate) const fn to_time(&self) -> ITime {
617 let mut nanosecond = self.nanosecond;
618 let mut time = ITime::ZERO;
619 if nanosecond != 0 {
620 time.hour = (nanosecond / 3_600_000_000_000) as i8;
621 nanosecond %= 3_600_000_000_000;
622 if nanosecond != 0 {
623 time.minute = (nanosecond / 60_000_000_000) as i8;
624 nanosecond %= 60_000_000_000;
625 if nanosecond != 0 {
626 time.second = (nanosecond / 1_000_000_000) as i8;
627 time.subsec_nanosecond =
628 (nanosecond % 1_000_000_000) as i32;
629 }
630 }
631 }
632 time
633 }
634}
635
636#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
638pub(crate) struct IWeekday {
639 offset: i8,
641}
642
643impl IWeekday {
644 #[inline]
647 pub(crate) const fn from_monday_zero_offset(offset: i8) -> IWeekday {
648 assert!(0 <= offset && offset <= 6);
649 IWeekday::from_monday_one_offset(offset + 1)
650 }
651
652 #[inline]
655 pub(crate) const fn from_monday_one_offset(offset: i8) -> IWeekday {
656 assert!(1 <= offset && offset <= 7);
657 IWeekday { offset }
658 }
659
660 #[inline]
663 pub(crate) const fn from_sunday_zero_offset(offset: i8) -> IWeekday {
664 assert!(0 <= offset && offset <= 6);
665 IWeekday::from_monday_zero_offset((offset - 1).rem_euclid(7))
666 }
667
668 #[cfg(test)] #[inline]
672 pub(crate) const fn from_sunday_one_offset(offset: i8) -> IWeekday {
673 assert!(1 <= offset && offset <= 7);
674 IWeekday::from_sunday_zero_offset(offset - 1)
675 }
676
677 #[inline]
680 pub(crate) const fn to_monday_zero_offset(self) -> i8 {
681 self.to_monday_one_offset() - 1
682 }
683
684 #[inline]
687 pub(crate) const fn to_monday_one_offset(self) -> i8 {
688 self.offset
689 }
690
691 #[cfg(test)] #[inline]
695 pub(crate) const fn to_sunday_zero_offset(self) -> i8 {
696 (self.to_monday_zero_offset() + 1) % 7
697 }
698
699 #[cfg(test)] #[inline]
703 pub(crate) const fn to_sunday_one_offset(self) -> i8 {
704 self.to_sunday_zero_offset() + 1
705 }
706
707 #[inline]
708 pub(crate) const fn since(self, other: IWeekday) -> i8 {
709 (self.to_monday_zero_offset() - other.to_monday_zero_offset())
710 .rem_euclid(7)
711 }
712}
713
714#[derive(Clone, Copy, Debug, Eq, PartialEq)]
715pub(crate) enum IAmbiguousOffset {
716 Unambiguous { offset: IOffset },
717 Gap { before: IOffset, after: IOffset },
718 Fold { before: IOffset, after: IOffset },
719}
720
721#[inline]
725pub(crate) const fn is_leap_year(year: i16) -> bool {
726 let d = if year % 25 != 0 { 4 } else { 16 };
728 (year % d) == 0
729}
730
731#[inline]
733pub(crate) const fn days_in_year(year: i16) -> i16 {
734 if is_leap_year(year) {
735 366
736 } else {
737 365
738 }
739}
740
741#[inline]
743pub(crate) const fn days_in_month(year: i16, month: i8) -> i8 {
744 if month == 2 {
746 if is_leap_year(year) {
747 29
748 } else {
749 28
750 }
751 } else {
752 30 | (month ^ month >> 3)
753 }
754}
755
756#[cfg(test)]
757mod tests {
758 use super::*;
759
760 #[test]
761 fn roundtrip_epochday_date() {
762 for year in -9999..=9999 {
763 for month in 1..=12 {
764 for day in 1..=days_in_month(year, month) {
765 let date = IDate { year, month, day };
766 let epoch_day = date.to_epoch_day();
767 let date_roundtrip = epoch_day.to_date();
768 assert_eq!(date, date_roundtrip);
769 }
770 }
771 }
772 }
773
774 #[test]
775 fn roundtrip_second_time() {
776 for second in 0..=86_399 {
777 let second = ITimeSecond { second };
778 let time = second.to_time();
779 let second_roundtrip = time.to_second();
780 assert_eq!(second, second_roundtrip);
781 }
782 }
783
784 #[test]
785 fn roundtrip_nanosecond_time() {
786 for second in 0..=86_399 {
787 for nanosecond in
788 [0, 250_000_000, 500_000_000, 750_000_000, 900_000_000]
789 {
790 let nanosecond = ITimeNanosecond {
791 nanosecond: (second * 1_000_000_000 + nanosecond),
792 };
793 let time = nanosecond.to_time();
794 let nanosecond_roundtrip = time.to_nanosecond();
795 assert_eq!(nanosecond, nanosecond_roundtrip);
796 }
797 }
798 }
799
800 #[test]
801 fn nth_weekday() {
802 let d1 = IDate { year: 2017, month: 3, day: 1 };
803 let wday = IWeekday::from_sunday_zero_offset(5);
804 let d2 = d1.nth_weekday_of_month(2, wday).unwrap();
805 assert_eq!(d2, IDate { year: 2017, month: 3, day: 10 });
806
807 let d1 = IDate { year: 2024, month: 3, day: 1 };
808 let wday = IWeekday::from_sunday_zero_offset(4);
809 let d2 = d1.nth_weekday_of_month(-1, wday).unwrap();
810 assert_eq!(d2, IDate { year: 2024, month: 3, day: 28 });
811
812 let d1 = IDate { year: 2024, month: 3, day: 25 };
813 let wday = IWeekday::from_sunday_zero_offset(1);
814 assert!(d1.nth_weekday_of_month(5, wday).is_err());
815 assert!(d1.nth_weekday_of_month(-5, wday).is_err());
816
817 let d1 = IDate { year: 1998, month: 1, day: 1 };
818 let wday = IWeekday::from_sunday_zero_offset(6);
819 let d2 = d1.nth_weekday_of_month(5, wday).unwrap();
820 assert_eq!(d2, IDate { year: 1998, month: 1, day: 31 });
821 }
822
823 #[test]
824 fn weekday() {
825 let wday = IWeekday::from_sunday_zero_offset(0);
826 assert_eq!(wday.to_monday_one_offset(), 7);
827
828 let wday = IWeekday::from_monday_one_offset(7);
829 assert_eq!(wday.to_sunday_zero_offset(), 0);
830
831 let wday = IWeekday::from_sunday_one_offset(1);
832 assert_eq!(wday.to_monday_zero_offset(), 6);
833
834 let wday = IWeekday::from_monday_zero_offset(6);
835 assert_eq!(wday.to_sunday_one_offset(), 1);
836 }
837
838 #[test]
839 fn weekday_since() {
840 let wday1 = IWeekday::from_sunday_zero_offset(0);
841 let wday2 = IWeekday::from_sunday_zero_offset(6);
842 assert_eq!(wday2.since(wday1), 6);
843 assert_eq!(wday1.since(wday2), 1);
844 }
845
846 #[test]
847 fn leap_year() {
848 assert!(!is_leap_year(1900));
849 assert!(is_leap_year(2000));
850 assert!(!is_leap_year(2001));
851 assert!(!is_leap_year(2002));
852 assert!(!is_leap_year(2003));
853 assert!(is_leap_year(2004));
854 }
855
856 #[test]
857 fn number_of_days_in_month() {
858 assert_eq!(days_in_month(2024, 1), 31);
859 assert_eq!(days_in_month(2024, 2), 29);
860 assert_eq!(days_in_month(2024, 3), 31);
861 assert_eq!(days_in_month(2024, 4), 30);
862 assert_eq!(days_in_month(2024, 5), 31);
863 assert_eq!(days_in_month(2024, 6), 30);
864 assert_eq!(days_in_month(2024, 7), 31);
865 assert_eq!(days_in_month(2024, 8), 31);
866 assert_eq!(days_in_month(2024, 9), 30);
867 assert_eq!(days_in_month(2024, 10), 31);
868 assert_eq!(days_in_month(2024, 11), 30);
869 assert_eq!(days_in_month(2024, 12), 31);
870
871 assert_eq!(days_in_month(2025, 1), 31);
872 assert_eq!(days_in_month(2025, 2), 28);
873 assert_eq!(days_in_month(2025, 3), 31);
874 assert_eq!(days_in_month(2025, 4), 30);
875 assert_eq!(days_in_month(2025, 5), 31);
876 assert_eq!(days_in_month(2025, 6), 30);
877 assert_eq!(days_in_month(2025, 7), 31);
878 assert_eq!(days_in_month(2025, 8), 31);
879 assert_eq!(days_in_month(2025, 9), 30);
880 assert_eq!(days_in_month(2025, 10), 31);
881 assert_eq!(days_in_month(2025, 11), 30);
882 assert_eq!(days_in_month(2025, 12), 31);
883
884 assert_eq!(days_in_month(1900, 2), 28);
885 assert_eq!(days_in_month(2000, 2), 29);
886 }
887
888 #[test]
889 fn yesterday() {
890 let d1 = IDate { year: 2025, month: 4, day: 7 };
891 let d2 = d1.yesterday().unwrap();
892 assert_eq!(d2, IDate { year: 2025, month: 4, day: 6 });
893
894 let d1 = IDate { year: 2025, month: 4, day: 1 };
895 let d2 = d1.yesterday().unwrap();
896 assert_eq!(d2, IDate { year: 2025, month: 3, day: 31 });
897
898 let d1 = IDate { year: 2025, month: 1, day: 1 };
899 let d2 = d1.yesterday().unwrap();
900 assert_eq!(d2, IDate { year: 2024, month: 12, day: 31 });
901
902 let d1 = IDate { year: -9999, month: 1, day: 1 };
903 assert_eq!(d1.yesterday().ok(), None);
904 }
905
906 #[test]
907 fn tomorrow() {
908 let d1 = IDate { year: 2025, month: 4, day: 7 };
909 let d2 = d1.tomorrow().unwrap();
910 assert_eq!(d2, IDate { year: 2025, month: 4, day: 8 });
911
912 let d1 = IDate { year: 2025, month: 3, day: 31 };
913 let d2 = d1.tomorrow().unwrap();
914 assert_eq!(d2, IDate { year: 2025, month: 4, day: 1 });
915
916 let d1 = IDate { year: 2025, month: 12, day: 31 };
917 let d2 = d1.tomorrow().unwrap();
918 assert_eq!(d2, IDate { year: 2026, month: 1, day: 1 });
919
920 let d1 = IDate { year: 9999, month: 12, day: 31 };
921 assert_eq!(d1.tomorrow().ok(), None);
922 }
923}