1#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
26pub(crate) struct ITimestamp {
27 pub(crate) second: i64,
28 pub(crate) nanosecond: i32,
29}
30
31impl ITimestamp {
32 const MIN: ITimestamp =
33 ITimestamp { second: -377705023201, nanosecond: 0 };
34 const MAX: ITimestamp =
35 ITimestamp { second: 253402207200, nanosecond: 999_999_999 };
36
37 #[inline]
39 pub(crate) const fn from_second(second: i64) -> ITimestamp {
40 ITimestamp { second, nanosecond: 0 }
41 }
42
43 #[cfg_attr(feature = "perf-inline", inline(always))]
48 pub(crate) const fn to_datetime(&self, offset: IOffset) -> IDateTime {
49 let ITimestamp { mut second, mut nanosecond } = *self;
50
51 const DAY_SHIFT: i32 = 30 * 146097;
57 const SEC_SHIFT: i64 = (DAY_SHIFT as i64) * 86_400;
58
59 let pos_sec = (second + (offset.second as i64) + SEC_SHIFT) as u64;
60 let mut epoch_day = (pos_sec / 86_400) as i32;
61 second = (pos_sec % 86_400) as i64;
62
63 if nanosecond < 0 {
64 if second > 0 {
65 second -= 1;
66 nanosecond += 1_000_000_000;
67 } else {
68 epoch_day -= 1;
69 second += 86_399;
70 nanosecond += 1_000_000_000;
71 }
72 }
73
74 epoch_day -= DAY_SHIFT;
75
76 let date = IEpochDay { epoch_day }.to_date();
77 let mut time = ITimeSecond { second: second as i32 }.to_time();
78 time.subsec_nanosecond = nanosecond;
79 IDateTime { date, time }
80 }
81}
82
83#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
84pub(crate) struct IOffset {
85 pub(crate) second: i32,
86}
87
88impl IOffset {
89 pub(crate) const UTC: IOffset = IOffset { second: 0 };
90}
91
92#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
93pub(crate) struct IDateTime {
94 pub(crate) date: IDate,
95 pub(crate) time: ITime,
96}
97
98impl IDateTime {
99 const MIN: IDateTime = IDateTime { date: IDate::MIN, time: ITime::MIN };
100 const MAX: IDateTime = IDateTime { date: IDate::MAX, time: ITime::MAX };
101
102 #[cfg_attr(feature = "perf-inline", inline(always))]
107 pub(crate) fn to_timestamp(&self, offset: IOffset) -> ITimestamp {
108 let epoch_day = self.date.to_epoch_day().epoch_day;
109 let mut second = (epoch_day as i64) * 86_400
110 + (self.time.to_second().second as i64);
111 let mut nanosecond = self.time.subsec_nanosecond;
112 second -= offset.second as i64;
113 if epoch_day < 0 && nanosecond != 0 {
114 second += 1;
115 nanosecond -= 1_000_000_000;
116 }
117 ITimestamp { second, nanosecond }
118 }
119
120 #[cfg_attr(feature = "perf-inline", inline(always))]
128 pub(crate) fn to_timestamp_checked(
129 &self,
130 offset: IOffset,
131 ) -> Option<ITimestamp> {
132 let ts = self.to_timestamp(offset);
133 if !(ITimestamp::MIN <= ts && ts <= ITimestamp::MAX) {
134 return None;
135 }
136 Some(ts)
137 }
138
139 #[cfg_attr(feature = "perf-inline", inline(always))]
140 pub(crate) fn saturating_add_seconds(&self, seconds: i32) -> IDateTime {
141 self.checked_add_seconds(seconds).unwrap_or_else(|_| {
142 if seconds < 0 {
143 IDateTime::MIN
144 } else {
145 IDateTime::MAX
146 }
147 })
148 }
149
150 #[cfg_attr(feature = "perf-inline", inline(always))]
151 pub(crate) fn checked_add_seconds(
152 &self,
153 seconds: i32,
154 ) -> Result<IDateTime, RangeError> {
155 let day_second = self
156 .time
157 .to_second()
158 .second
159 .checked_add(seconds)
160 .ok_or_else(|| RangeError::DateTimeSeconds)?;
161 let days = day_second.div_euclid(86400);
162 let second = day_second.rem_euclid(86400);
163 let date = self.date.checked_add_days(days)?;
164 let time = ITimeSecond { second }.to_time();
165 Ok(IDateTime { date, time })
166 }
167}
168
169#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
170pub(crate) struct IEpochDay {
171 pub(crate) epoch_day: i32,
172}
173
174impl IEpochDay {
175 pub(crate) const MIN: IEpochDay = IEpochDay { epoch_day: -4371587 };
176 pub(crate) const MAX: IEpochDay = IEpochDay { epoch_day: 2932896 };
177
178 #[cfg_attr(feature = "perf-inline", inline(always))]
184 #[allow(non_upper_case_globals, non_snake_case)] pub(crate) const fn to_date(&self) -> IDate {
186 const s: u32 = 82;
187 const K: u32 = 719468 + 146097 * s;
188 const L: u32 = 400 * s;
189
190 let N_U = self.epoch_day as u32;
191 let N = N_U.wrapping_add(K);
192
193 let N_1 = 4 * N + 3;
194 let C = N_1 / 146097;
195 let N_C = (N_1 % 146097) / 4;
196
197 let N_2 = 4 * N_C + 3;
198 let P_2 = 2939745 * (N_2 as u64);
199 let Z = (P_2 / 4294967296) as u32;
200 let N_Y = (P_2 % 4294967296) as u32 / 2939745 / 4;
201 let Y = 100 * C + Z;
202
203 let N_3 = 2141 * N_Y + 197913;
204 let M = N_3 / 65536;
205 let D = (N_3 % 65536) / 2141;
206
207 let J = N_Y >= 306;
208 let year = Y.wrapping_sub(L).wrapping_add(J as u32) as i16;
209 let month = (if J { M - 12 } else { M }) as i8;
210 let day = (D + 1) as i8;
211 IDate { year, month, day }
212 }
213
214 #[cfg_attr(feature = "perf-inline", inline(always))]
216 pub(crate) const fn weekday(&self) -> IWeekday {
217 IWeekday::from_monday_zero_offset(
223 (self.epoch_day + 3).rem_euclid(7) as i8
224 )
225 }
226
227 #[inline]
232 pub(crate) fn checked_add(
233 &self,
234 amount: i32,
235 ) -> Result<IEpochDay, RangeError> {
236 let epoch_day = self.epoch_day;
237 let sum = epoch_day
238 .checked_add(amount)
239 .ok_or_else(|| RangeError::EpochDayI32)?;
240 let ret = IEpochDay { epoch_day: sum };
241 if !(IEpochDay::MIN <= ret && ret <= IEpochDay::MAX) {
242 return Err(RangeError::EpochDayDays);
243 }
244 Ok(ret)
245 }
246}
247
248#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
249pub(crate) struct IDate {
250 pub(crate) year: i16,
251 pub(crate) month: i8,
252 pub(crate) day: i8,
253}
254
255impl IDate {
256 const MIN: IDate = IDate { year: -9999, month: 1, day: 1 };
257 const MAX: IDate = IDate { year: 9999, month: 12, day: 31 };
258
259 #[inline]
266 pub(crate) fn try_new(
267 year: i16,
268 month: i8,
269 day: i8,
270 ) -> Result<IDate, RangeError> {
271 if day > 28 {
272 let max_day = days_in_month(year, month);
273 if day > max_day {
274 return Err(RangeError::DateInvalidDays { year, month });
275 }
276 }
277 Ok(IDate { year, month, day })
278 }
279
280 #[inline]
287 pub(crate) fn from_day_of_year(
288 year: i16,
289 day: i16,
290 ) -> Result<IDate, RangeError> {
291 if !(1 <= day && day <= 366) {
292 return Err(RangeError::DateInvalidDayOfYear { year });
293 }
294 let start = IDate { year, month: 1, day: 1 }.to_epoch_day();
295 let end = start
296 .checked_add(i32::from(day) - 1)
297 .map_err(|_| RangeError::DayOfYear)?
299 .to_date();
300 if year != end.year {
302 debug_assert_eq!(day, 366);
304 debug_assert!(!is_leap_year(year));
305 return Err(RangeError::DateInvalidDayOfYear { year });
306 }
307 Ok(end)
308 }
309
310 #[inline]
318 pub(crate) fn from_day_of_year_no_leap(
319 year: i16,
320 mut day: i16,
321 ) -> Result<IDate, RangeError> {
322 if !(1 <= day && day <= 365) {
323 return Err(RangeError::DateInvalidDayOfYearNoLeap);
324 }
325 if day >= 60 && is_leap_year(year) {
326 day += 1;
327 }
328 Ok(IDate::from_day_of_year(year, day).unwrap())
330 }
331
332 #[cfg_attr(feature = "perf-inline", inline(always))]
338 #[allow(non_upper_case_globals, non_snake_case)] pub(crate) const fn to_epoch_day(&self) -> IEpochDay {
340 const s: u32 = 82;
341 const K: u32 = 719468 + 146097 * s;
342 const L: u32 = 400 * s;
343
344 let year = self.year as u32;
345 let month = self.month as u32;
346 let day = self.day as u32;
347
348 let J = month <= 2;
349 let Y = year.wrapping_add(L).wrapping_sub(J as u32);
350 let M = if J { month + 12 } else { month };
351 let D = day - 1;
352 let C = Y / 100;
353
354 let y_star = 1461 * Y / 4 - C + C / 4;
355 let m_star = (979 * M - 2919) / 32;
356 let N = y_star + m_star + D;
357
358 let N_U = N.wrapping_sub(K);
359 let epoch_day = N_U as i32;
360 IEpochDay { epoch_day }
361 }
362
363 #[inline]
365 pub(crate) const fn weekday(&self) -> IWeekday {
366 self.to_epoch_day().weekday()
367 }
368
369 #[inline]
377 pub(crate) fn nth_weekday_of_month(
378 &self,
379 nth: i8,
380 weekday: IWeekday,
381 ) -> Result<IDate, RangeError> {
382 if nth == 0 || !(-5 <= nth && nth <= 5) {
383 return Err(RangeError::NthWeekdayOfMonth);
384 }
385 if nth > 0 {
386 let first_weekday = self.first_of_month().weekday();
387 let diff = weekday.since(first_weekday);
388 let day = diff + 1 + (nth - 1) * 7;
389 IDate::try_new(self.year, self.month, day)
390 } else {
391 let last = self.last_of_month();
392 let last_weekday = last.weekday();
393 let diff = last_weekday.since(weekday);
394 let day = last.day - diff - (nth.abs() - 1) * 7;
395 if day < 1 {
400 return Err(RangeError::DateInvalidDays {
401 year: self.year,
402 month: self.month,
403 });
404 }
405 IDate::try_new(self.year, self.month, day)
406 }
407 }
408
409 #[inline]
411 pub(crate) fn yesterday(self) -> Result<IDate, RangeError> {
412 if self.day == 1 {
413 if self.month == 1 {
414 let year = self.year - 1;
415 if year <= -10000 {
416 return Err(RangeError::Yesterday);
417 }
418 return Ok(IDate { year, month: 12, day: 31 });
419 }
420 let month = self.month - 1;
421 let day = days_in_month(self.year, month);
422 return Ok(IDate { month, day, ..self });
423 }
424 Ok(IDate { day: self.day - 1, ..self })
425 }
426
427 #[inline]
429 pub(crate) fn tomorrow(self) -> Result<IDate, RangeError> {
430 if self.day >= 28 && self.day == days_in_month(self.year, self.month) {
431 if self.month == 12 {
432 let year = self.year + 1;
433 if year >= 10000 {
434 return Err(RangeError::Tomorrow);
435 }
436 return Ok(IDate { year, month: 1, day: 1 });
437 }
438 let month = self.month + 1;
439 return Ok(IDate { month, day: 1, ..self });
440 }
441 Ok(IDate { day: self.day + 1, ..self })
442 }
443
444 #[inline]
446 pub(crate) fn prev_year(self) -> Result<i16, RangeError> {
447 let year = self.year - 1;
448 if year <= -10_000 {
449 return Err(RangeError::YearPrevious);
450 }
451 Ok(year)
452 }
453
454 #[inline]
456 pub(crate) fn next_year(self) -> Result<i16, RangeError> {
457 let year = self.year + 1;
458 if year >= 10_000 {
459 return Err(RangeError::YearNext);
460 }
461 Ok(year)
462 }
463
464 #[inline]
466 pub(crate) fn checked_add_days(
467 &self,
468 amount: i32,
469 ) -> Result<IDate, RangeError> {
470 match amount {
471 0 => Ok(*self),
472 -1 => self.yesterday(),
473 1 => self.tomorrow(),
474 n => self.to_epoch_day().checked_add(n).map(|d| d.to_date()),
475 }
476 }
477
478 #[inline]
479 fn first_of_month(&self) -> IDate {
480 IDate { day: 1, ..*self }
481 }
482
483 #[inline]
484 fn last_of_month(&self) -> IDate {
485 IDate { day: days_in_month(self.year, self.month), ..*self }
486 }
487
488 #[cfg(test)]
489 pub(crate) fn at(
490 &self,
491 hour: i8,
492 minute: i8,
493 second: i8,
494 subsec_nanosecond: i32,
495 ) -> IDateTime {
496 let time = ITime { hour, minute, second, subsec_nanosecond };
497 IDateTime { date: *self, time }
498 }
499}
500
501#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
506pub(crate) struct ITime {
507 pub(crate) hour: i8,
508 pub(crate) minute: i8,
509 pub(crate) second: i8,
510 pub(crate) subsec_nanosecond: i32,
511}
512
513impl ITime {
514 pub(crate) const ZERO: ITime =
515 ITime { hour: 0, minute: 0, second: 0, subsec_nanosecond: 0 };
516 pub(crate) const MIN: ITime =
517 ITime { hour: 0, minute: 0, second: 0, subsec_nanosecond: 0 };
518 pub(crate) const MAX: ITime = ITime {
519 hour: 23,
520 minute: 59,
521 second: 59,
522 subsec_nanosecond: 999_999_999,
523 };
524
525 #[cfg_attr(feature = "perf-inline", inline(always))]
526 pub(crate) const fn to_second(&self) -> ITimeSecond {
527 let mut second: i32 = 0;
528 second += (self.hour as i32) * 3600;
529 second += (self.minute as i32) * 60;
530 second += self.second as i32;
531 ITimeSecond { second }
532 }
533
534 #[cfg_attr(feature = "perf-inline", inline(always))]
535 pub(crate) const fn to_nanosecond(&self) -> ITimeNanosecond {
536 let mut nanosecond: i64 = 0;
537 nanosecond += (self.hour as i64) * 3_600_000_000_000;
538 nanosecond += (self.minute as i64) * 60_000_000_000;
539 nanosecond += (self.second as i64) * 1_000_000_000;
540 nanosecond += self.subsec_nanosecond as i64;
541 ITimeNanosecond { nanosecond }
542 }
543}
544
545#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
547pub(crate) struct ITimeSecond {
548 pub(crate) second: i32,
549}
550
551impl ITimeSecond {
552 #[cfg_attr(feature = "perf-inline", inline(always))]
553 pub(crate) const fn to_time(&self) -> ITime {
554 let mut second = self.second;
555 let mut time = ITime::ZERO;
556 if second != 0 {
557 time.hour = (second / 3600) as i8;
558 second %= 3600;
559 if second != 0 {
560 time.minute = (second / 60) as i8;
561 time.second = (second % 60) as i8;
562 }
563 }
564 time
565 }
566}
567
568#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
570pub(crate) struct ITimeNanosecond {
571 pub(crate) nanosecond: i64,
572}
573
574impl ITimeNanosecond {
575 #[cfg_attr(feature = "perf-inline", inline(always))]
576 pub(crate) const fn to_time(&self) -> ITime {
577 let mut nanosecond = self.nanosecond;
578 let mut time = ITime::ZERO;
579 if nanosecond != 0 {
580 time.hour = (nanosecond / 3_600_000_000_000) as i8;
581 nanosecond %= 3_600_000_000_000;
582 if nanosecond != 0 {
583 time.minute = (nanosecond / 60_000_000_000) as i8;
584 nanosecond %= 60_000_000_000;
585 if nanosecond != 0 {
586 time.second = (nanosecond / 1_000_000_000) as i8;
587 time.subsec_nanosecond =
588 (nanosecond % 1_000_000_000) as i32;
589 }
590 }
591 }
592 time
593 }
594}
595
596#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
598pub(crate) struct IWeekday {
599 offset: i8,
601}
602
603impl IWeekday {
604 #[inline]
607 pub(crate) const fn from_monday_zero_offset(offset: i8) -> IWeekday {
608 assert!(0 <= offset && offset <= 6);
609 IWeekday::from_monday_one_offset(offset + 1)
610 }
611
612 #[inline]
615 pub(crate) const fn from_monday_one_offset(offset: i8) -> IWeekday {
616 assert!(1 <= offset && offset <= 7);
617 IWeekday { offset }
618 }
619
620 #[inline]
623 pub(crate) const fn from_sunday_zero_offset(offset: i8) -> IWeekday {
624 assert!(0 <= offset && offset <= 6);
625 IWeekday::from_monday_zero_offset((offset - 1).rem_euclid(7))
626 }
627
628 #[cfg(test)] #[inline]
632 pub(crate) const fn from_sunday_one_offset(offset: i8) -> IWeekday {
633 assert!(1 <= offset && offset <= 7);
634 IWeekday::from_sunday_zero_offset(offset - 1)
635 }
636
637 #[inline]
640 pub(crate) const fn to_monday_zero_offset(self) -> i8 {
641 self.to_monday_one_offset() - 1
642 }
643
644 #[inline]
647 pub(crate) const fn to_monday_one_offset(self) -> i8 {
648 self.offset
649 }
650
651 #[cfg(test)] #[inline]
655 pub(crate) const fn to_sunday_zero_offset(self) -> i8 {
656 (self.to_monday_zero_offset() + 1) % 7
657 }
658
659 #[cfg(test)] #[inline]
663 pub(crate) const fn to_sunday_one_offset(self) -> i8 {
664 self.to_sunday_zero_offset() + 1
665 }
666
667 #[inline]
668 pub(crate) const fn since(self, other: IWeekday) -> i8 {
669 (self.to_monday_zero_offset() - other.to_monday_zero_offset())
670 .rem_euclid(7)
671 }
672}
673
674#[derive(Clone, Copy, Debug, Eq, PartialEq)]
675pub(crate) enum IAmbiguousOffset {
676 Unambiguous { offset: IOffset },
677 Gap { before: IOffset, after: IOffset },
678 Fold { before: IOffset, after: IOffset },
679}
680
681#[derive(Clone, Debug, Eq, PartialEq)]
682pub(crate) enum RangeError {
683 DateInvalidDayOfYear { year: i16 },
684 DateInvalidDayOfYearNoLeap,
685 DateInvalidDays { year: i16, month: i8 },
686 DateTimeSeconds,
687 DayOfYear,
688 EpochDayDays,
689 EpochDayI32,
690 NthWeekdayOfMonth,
691 Tomorrow,
692 YearNext,
693 YearPrevious,
694 Yesterday,
695}
696
697impl core::fmt::Display for RangeError {
698 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
699 use self::RangeError::*;
700
701 match *self {
702 DateInvalidDayOfYear { year } => write!(
703 f,
704 "number of days for `{year:04}` is invalid, \
705 must be in range `1..={max_day}`",
706 max_day = days_in_year(year),
707 ),
708 DateInvalidDayOfYearNoLeap => f.write_str(
709 "number of days is invalid, must be in range `1..=365`",
710 ),
711 DateInvalidDays { year, month } => write!(
712 f,
713 "parameter 'day' for `{year:04}-{month:02}` is invalid, \
714 must be in range `1..={max_day}`",
715 max_day = days_in_month(year, month),
716 ),
717 DateTimeSeconds => {
718 f.write_str("adding seconds to datetime overflowed")
719 }
720 DayOfYear => f.write_str("day of year is invalid"),
721 EpochDayDays => write!(
722 f,
723 "adding to epoch day resulted in a value outside \
724 the allowed range of `{min}..={max}`",
725 min = IEpochDay::MIN.epoch_day,
726 max = IEpochDay::MAX.epoch_day,
727 ),
728 EpochDayI32 => f.write_str(
729 "adding to epoch day overflowed 32-bit signed integer",
730 ),
731 NthWeekdayOfMonth => f.write_str(
732 "invalid nth weekday of month, \
733 must be non-zero and in range `-5..=5`",
734 ),
735 Tomorrow => f.write_str(
736 "returning tomorrow for `9999-12-31` is not \
737 possible because it is greater than Jiff's supported
738 maximum date",
739 ),
740 YearNext => f.write_str(
741 "creating a date for a year following `9999` is \
742 not possible because it is greater than Jiff's supported \
743 maximum date",
744 ),
745 YearPrevious => f.write_str(
746 "creating a date for a year preceding `-9999` is \
747 not possible because it is less than Jiff's supported \
748 minimum date",
749 ),
750 Yesterday => f.write_str(
751 "returning yesterday for `-9999-01-01` is not \
752 possible because it is less than Jiff's supported
753 minimum date",
754 ),
755 }
756 }
757}
758
759#[inline]
763pub(crate) const fn is_leap_year(year: i16) -> bool {
764 let d = if year % 25 != 0 { 4 } else { 16 };
766 (year % d) == 0
767}
768
769#[inline]
771pub(crate) const fn days_in_year(year: i16) -> i16 {
772 if is_leap_year(year) {
773 366
774 } else {
775 365
776 }
777}
778
779#[inline]
781pub(crate) const fn days_in_month(year: i16, month: i8) -> i8 {
782 if month == 2 {
784 if is_leap_year(year) {
785 29
786 } else {
787 28
788 }
789 } else {
790 30 | (month ^ month >> 3)
791 }
792}
793
794#[cfg(test)]
795mod tests {
796 use super::*;
797
798 #[test]
799 fn roundtrip_epochday_date() {
800 for year in -9999..=9999 {
801 for month in 1..=12 {
802 for day in 1..=days_in_month(year, month) {
803 let date = IDate { year, month, day };
804 let epoch_day = date.to_epoch_day();
805 let date_roundtrip = epoch_day.to_date();
806 assert_eq!(date, date_roundtrip);
807 }
808 }
809 }
810 }
811
812 #[test]
813 fn roundtrip_second_time() {
814 for second in 0..=86_399 {
815 let second = ITimeSecond { second };
816 let time = second.to_time();
817 let second_roundtrip = time.to_second();
818 assert_eq!(second, second_roundtrip);
819 }
820 }
821
822 #[test]
823 fn roundtrip_nanosecond_time() {
824 for second in 0..=86_399 {
825 for nanosecond in
826 [0, 250_000_000, 500_000_000, 750_000_000, 900_000_000]
827 {
828 let nanosecond = ITimeNanosecond {
829 nanosecond: (second * 1_000_000_000 + nanosecond),
830 };
831 let time = nanosecond.to_time();
832 let nanosecond_roundtrip = time.to_nanosecond();
833 assert_eq!(nanosecond, nanosecond_roundtrip);
834 }
835 }
836 }
837
838 #[test]
839 fn nth_weekday() {
840 let d1 = IDate { year: 2017, month: 3, day: 1 };
841 let wday = IWeekday::from_sunday_zero_offset(5);
842 let d2 = d1.nth_weekday_of_month(2, wday).unwrap();
843 assert_eq!(d2, IDate { year: 2017, month: 3, day: 10 });
844
845 let d1 = IDate { year: 2024, month: 3, day: 1 };
846 let wday = IWeekday::from_sunday_zero_offset(4);
847 let d2 = d1.nth_weekday_of_month(-1, wday).unwrap();
848 assert_eq!(d2, IDate { year: 2024, month: 3, day: 28 });
849
850 let d1 = IDate { year: 2024, month: 3, day: 25 };
851 let wday = IWeekday::from_sunday_zero_offset(1);
852 assert!(d1.nth_weekday_of_month(5, wday).is_err());
853 assert!(d1.nth_weekday_of_month(-5, wday).is_err());
854
855 let d1 = IDate { year: 1998, month: 1, day: 1 };
856 let wday = IWeekday::from_sunday_zero_offset(6);
857 let d2 = d1.nth_weekday_of_month(5, wday).unwrap();
858 assert_eq!(d2, IDate { year: 1998, month: 1, day: 31 });
859 }
860
861 #[test]
862 fn weekday() {
863 let wday = IWeekday::from_sunday_zero_offset(0);
864 assert_eq!(wday.to_monday_one_offset(), 7);
865
866 let wday = IWeekday::from_monday_one_offset(7);
867 assert_eq!(wday.to_sunday_zero_offset(), 0);
868
869 let wday = IWeekday::from_sunday_one_offset(1);
870 assert_eq!(wday.to_monday_zero_offset(), 6);
871
872 let wday = IWeekday::from_monday_zero_offset(6);
873 assert_eq!(wday.to_sunday_one_offset(), 1);
874 }
875
876 #[test]
877 fn weekday_since() {
878 let wday1 = IWeekday::from_sunday_zero_offset(0);
879 let wday2 = IWeekday::from_sunday_zero_offset(6);
880 assert_eq!(wday2.since(wday1), 6);
881 assert_eq!(wday1.since(wday2), 1);
882 }
883
884 #[test]
885 fn leap_year() {
886 assert!(!is_leap_year(1900));
887 assert!(is_leap_year(2000));
888 assert!(!is_leap_year(2001));
889 assert!(!is_leap_year(2002));
890 assert!(!is_leap_year(2003));
891 assert!(is_leap_year(2004));
892 }
893
894 #[test]
895 fn number_of_days_in_month() {
896 assert_eq!(days_in_month(2024, 1), 31);
897 assert_eq!(days_in_month(2024, 2), 29);
898 assert_eq!(days_in_month(2024, 3), 31);
899 assert_eq!(days_in_month(2024, 4), 30);
900 assert_eq!(days_in_month(2024, 5), 31);
901 assert_eq!(days_in_month(2024, 6), 30);
902 assert_eq!(days_in_month(2024, 7), 31);
903 assert_eq!(days_in_month(2024, 8), 31);
904 assert_eq!(days_in_month(2024, 9), 30);
905 assert_eq!(days_in_month(2024, 10), 31);
906 assert_eq!(days_in_month(2024, 11), 30);
907 assert_eq!(days_in_month(2024, 12), 31);
908
909 assert_eq!(days_in_month(2025, 1), 31);
910 assert_eq!(days_in_month(2025, 2), 28);
911 assert_eq!(days_in_month(2025, 3), 31);
912 assert_eq!(days_in_month(2025, 4), 30);
913 assert_eq!(days_in_month(2025, 5), 31);
914 assert_eq!(days_in_month(2025, 6), 30);
915 assert_eq!(days_in_month(2025, 7), 31);
916 assert_eq!(days_in_month(2025, 8), 31);
917 assert_eq!(days_in_month(2025, 9), 30);
918 assert_eq!(days_in_month(2025, 10), 31);
919 assert_eq!(days_in_month(2025, 11), 30);
920 assert_eq!(days_in_month(2025, 12), 31);
921
922 assert_eq!(days_in_month(1900, 2), 28);
923 assert_eq!(days_in_month(2000, 2), 29);
924 }
925
926 #[test]
927 fn yesterday() {
928 let d1 = IDate { year: 2025, month: 4, day: 7 };
929 let d2 = d1.yesterday().unwrap();
930 assert_eq!(d2, IDate { year: 2025, month: 4, day: 6 });
931
932 let d1 = IDate { year: 2025, month: 4, day: 1 };
933 let d2 = d1.yesterday().unwrap();
934 assert_eq!(d2, IDate { year: 2025, month: 3, day: 31 });
935
936 let d1 = IDate { year: 2025, month: 1, day: 1 };
937 let d2 = d1.yesterday().unwrap();
938 assert_eq!(d2, IDate { year: 2024, month: 12, day: 31 });
939
940 let d1 = IDate { year: -9999, month: 1, day: 1 };
941 assert_eq!(d1.yesterday().ok(), None);
942 }
943
944 #[test]
945 fn tomorrow() {
946 let d1 = IDate { year: 2025, month: 4, day: 7 };
947 let d2 = d1.tomorrow().unwrap();
948 assert_eq!(d2, IDate { year: 2025, month: 4, day: 8 });
949
950 let d1 = IDate { year: 2025, month: 3, day: 31 };
951 let d2 = d1.tomorrow().unwrap();
952 assert_eq!(d2, IDate { year: 2025, month: 4, day: 1 });
953
954 let d1 = IDate { year: 2025, month: 12, day: 31 };
955 let d2 = d1.tomorrow().unwrap();
956 assert_eq!(d2, IDate { year: 2026, month: 1, day: 1 });
957
958 let d1 = IDate { year: 9999, month: 12, day: 31 };
959 assert_eq!(d1.tomorrow().ok(), None);
960 }
961
962 #[test]
963 fn from_day_of_year() {
964 assert_eq!(
965 IDate::from_day_of_year(9999, 365),
966 Ok(IDate { year: 9999, month: 12, day: 31 }),
967 );
968 assert_eq!(
969 IDate::from_day_of_year(9998, 366),
970 Err(RangeError::DateInvalidDayOfYear { year: 9998 }),
971 );
972 assert_eq!(
973 IDate::from_day_of_year(9999, 366),
974 Err(RangeError::DayOfYear),
975 );
976 }
977
978 #[test]
979 fn timestamp_to_datetime() {
980 let ts = ITimestamp { second: 0, nanosecond: 1 };
981 let dt = ts.to_datetime(IOffset { second: 1 });
982 assert_eq!(
983 dt,
984 IDateTime {
985 date: IDate { year: 1970, month: 1, day: 1 },
986 time: ITime {
987 hour: 0,
988 minute: 0,
989 second: 1,
990 subsec_nanosecond: 1
991 },
992 }
993 );
994
995 let ts = ITimestamp { second: 0, nanosecond: 1 };
996 let dt = ts.to_datetime(IOffset { second: -1 });
997 assert_eq!(
998 dt,
999 IDateTime {
1000 date: IDate { year: 1969, month: 12, day: 31 },
1001 time: ITime {
1002 hour: 23,
1003 minute: 59,
1004 second: 59,
1005 subsec_nanosecond: 1,
1006 },
1007 }
1008 );
1009
1010 let ts = ITimestamp { second: 0, nanosecond: -1 };
1011 let dt = ts.to_datetime(IOffset { second: 1 });
1012 assert_eq!(
1013 dt,
1014 IDateTime {
1015 date: IDate { year: 1970, month: 1, day: 1 },
1016 time: ITime {
1017 hour: 0,
1018 minute: 0,
1019 second: 0,
1020 subsec_nanosecond: 999_999_999
1021 },
1022 }
1023 );
1024
1025 let ts = ITimestamp { second: 0, nanosecond: -1 };
1026 let dt = ts.to_datetime(IOffset { second: -1 });
1027 assert_eq!(
1028 dt,
1029 IDateTime {
1030 date: IDate { year: 1969, month: 12, day: 31 },
1031 time: ITime {
1032 hour: 23,
1033 minute: 59,
1034 second: 58,
1035 subsec_nanosecond: 999_999_999,
1036 },
1037 }
1038 );
1039 }
1040}