1use std::{convert::Infallible, num::NonZero};
4
5use thiserror::Error;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
9#[repr(u8)]
10pub enum Weekday {
11 Monday,
12 Tuesday,
13 Wednesday,
14 Thursday,
15 Friday,
16 Saturday,
17 Sunday,
18}
19
20impl Weekday {
21 pub const fn from_repr(repr: u8) -> Option<Self> {
23 match repr {
24 0..=6 => {
25 Some(unsafe { std::mem::transmute::<u8, Self>(repr) })
28 }
29 _ => None,
30 }
31 }
32
33 pub fn iter() -> impl ExactSizeIterator<Item = Self> {
35 const VARIANTS: [Weekday; 7] = [
36 Weekday::Monday,
37 Weekday::Tuesday,
38 Weekday::Wednesday,
39 Weekday::Thursday,
40 Weekday::Friday,
41 Weekday::Saturday,
42 Weekday::Sunday,
43 ];
44
45 VARIANTS.iter().copied()
46 }
47}
48
49#[repr(u8)]
51#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
52pub enum IsoWeek {
53 W1 = 1,
54 W2,
55 W3,
56 W4,
57 W5,
58 W6,
59 W7,
60 W8,
61 W9,
62 W10,
63 W11,
64 W12,
65 W13,
66 W14,
67 W15,
68 W16,
69 W17,
70 W18,
71 W19,
72 W20,
73 W21,
74 W22,
75 W23,
76 W24,
77 W25,
78 W26,
79 W27,
80 W28,
81 W29,
82 W30,
83 W31,
84 W32,
85 W33,
86 W34,
87 W35,
88 W36,
89 W37,
90 W38,
91 W39,
92 W40,
93 W41,
94 W42,
95 W43,
96 W44,
97 W45,
98 W46,
99 W47,
100 W48,
101 W49,
102 W50,
103 W51,
104 W52,
105 W53,
106}
107
108impl IsoWeek {
109 pub const fn index(&self) -> NonZero<u8> {
111 NonZero::new(*self as u8).unwrap()
112 }
113
114 pub const fn from_index(index: u8) -> Option<Self> {
116 match index {
117 1..=53 => {
118 let week: Self = unsafe { std::mem::transmute(index) };
119 Some(week)
120 }
121 _ => None,
122 }
123 }
124}
125
126#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
128pub struct Utc;
129
130#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
132pub struct Local;
133
134#[derive(Debug, Clone, Copy, Error, PartialEq, Eq)]
136pub enum InvalidDateTimeError {
137 #[error("invalid date: {0}")]
139 Date(#[from] InvalidDateError),
140 #[error("invalid time: {0}")]
142 Time(#[from] InvalidTimeError),
143}
144
145#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
155pub struct DateTime<M> {
156 pub date: Date,
158 pub time: Time,
160 pub marker: M,
162}
163
164#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
166pub struct Date {
167 year: Year,
168 month: Month,
169 day: Day,
170}
171
172impl Date {
173 #[inline(always)]
175 pub const fn new(year: Year, month: Month, day: Day) -> Result<Self, ImpossibleDateError> {
176 if (day as u8) <= (Date::maximum_day(year, month) as u8) {
177 Ok(Self { year, month, day })
178 } else {
179 Err(ImpossibleDateError { year, month, day })
180 }
181 }
182
183 #[inline(always)]
185 pub const fn year(&self) -> Year {
186 self.year
187 }
188
189 #[inline(always)]
191 pub const fn month(&self) -> Month {
192 self.month
193 }
194
195 #[inline(always)]
197 pub const fn day(&self) -> Day {
198 self.day
199 }
200
201 pub const fn maximum_day(year: Year, month: Month) -> Day {
203 match month {
204 Month::Feb if year.is_leap_year() => Day::D29,
205 Month::Feb => Day::D28,
206 Month::Jan
207 | Month::Mar
208 | Month::May
209 | Month::Jul
210 | Month::Aug
211 | Month::Oct
212 | Month::Dec => Day::D31,
213 Month::Apr | Month::Jun | Month::Sep | Month::Nov => Day::D30,
214 }
215 }
216}
217
218#[derive(Debug, Clone, Copy, Error, PartialEq, Eq)]
220pub enum InvalidDateError {
221 #[error("invalid year: {0}")]
223 Year(#[from] InvalidYearError),
224 #[error("invalid month: {0}")]
226 Month(#[from] InvalidMonthError),
227 #[error("invalid day: {0}")]
229 Day(#[from] InvalidDayError),
230 #[error(transparent)]
232 ImpossibleDate(#[from] ImpossibleDateError),
233}
234
235#[derive(Debug, Clone, Copy, Error, PartialEq, Eq)]
238#[error("the given date is impossible")]
239pub struct ImpossibleDateError {
240 year: Year,
241 month: Month,
242 day: Day,
243}
244
245#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
247pub struct Year(u16);
248
249impl std::fmt::Debug for Year {
250 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
251 assert!(self.0 <= 9999);
252 write!(f, "{:04} CE", self.0)
253 }
254}
255
256impl Year {
257 pub const MIN: Self = Self(0);
259 pub const MAX: Self = Self(9999);
261
262 pub const fn is_leap_year(self) -> bool {
264 let year = self.0;
265 year.is_multiple_of(4) && (!year.is_multiple_of(100) || year.is_multiple_of(400))
267 }
268
269 #[inline(always)]
271 pub const fn new(value: u16) -> Result<Self, InvalidYearError> {
272 if value <= 9999 {
273 Ok(Year(value))
274 } else {
275 Err(InvalidYearError(value))
276 }
277 }
278
279 #[inline(always)]
281 pub const fn get(self) -> u16 {
282 self.0
283 }
284}
285
286impl std::fmt::Display for Year {
287 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
288 write!(f, "{:04}", self.0)
289 }
290}
291
292impl std::fmt::Display for Month {
293 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
294 write!(f, "{:02}", *self as u8)
295 }
296}
297
298impl std::fmt::Display for Day {
299 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
300 write!(f, "{:02}", *self as u8)
301 }
302}
303
304impl std::fmt::Display for Date {
305 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
306 write!(f, "{}-{}-{}", self.year, self.month, self.day)
307 }
308}
309
310impl std::fmt::Display for Hour {
311 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
312 write!(f, "{:02}", *self as u8)
313 }
314}
315
316impl std::fmt::Display for Minute {
317 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
318 write!(f, "{:02}", *self as u8)
319 }
320}
321
322impl std::fmt::Display for Second {
323 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
324 write!(f, "{:02}", *self as u8)
325 }
326}
327
328impl std::fmt::Display for Time {
329 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
330 write!(f, "{}:{}:{}", self.hour, self.minute, self.second)?;
331 if let Some(frac) = self.frac {
332 let nanos = frac.get().get();
334 let mut s = format!("{nanos:09}");
335 let trimmed = s.trim_end_matches('0');
336 s.truncate(trimmed.len());
337 write!(f, ".{s}")?;
338 }
339 Ok(())
340 }
341}
342
343impl<M> std::fmt::Display for DateTime<M>
344where
345 M: DateTimeMarker,
346{
347 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
348 write!(f, "{}T{}{}", self.date, self.time, M::SUFFIX)
349 }
350}
351
352pub trait DateTimeMarker {
354 const SUFFIX: &'static str;
356}
357
358impl DateTimeMarker for Utc {
359 const SUFFIX: &'static str = "Z";
360}
361
362impl DateTimeMarker for Local {
363 const SUFFIX: &'static str = "";
364}
365
366impl DateTimeMarker for () {
367 const SUFFIX: &'static str = "";
368}
369
370#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
375pub enum TimeFormat {
376 #[default]
377 Local,
378 Utc,
379}
380
381impl From<Utc> for TimeFormat {
382 fn from(_: Utc) -> Self {
383 Self::Utc
384 }
385}
386
387impl From<Local> for TimeFormat {
388 fn from(_: Local) -> Self {
389 Self::Local
390 }
391}
392
393impl std::fmt::Display for DateTime<TimeFormat> {
394 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
395 let suffix = match self.marker {
396 TimeFormat::Utc => "Z",
397 TimeFormat::Local => "",
398 };
399 write!(f, "{}T{}{}", self.date, self.time, suffix)
400 }
401}
402
403#[derive(Debug, Clone, Copy, Error, PartialEq, Eq)]
405#[error("expected an integer of at most 9999 but received {0} instead")]
406pub struct InvalidYearError(u16);
407
408#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
410#[repr(u8)]
411pub enum Month {
412 Jan = 1,
413 Feb,
414 Mar,
415 Apr,
416 May,
417 Jun,
418 Jul,
419 Aug,
420 Sep,
421 Oct,
422 Nov,
423 Dec,
424}
425
426impl Month {
427 pub const fn new(value: u8) -> Result<Self, InvalidMonthError> {
429 match value {
430 1..=12 => Ok({
431 unsafe { std::mem::transmute::<u8, Month>(value) }
434 }),
435 _ => Err(InvalidMonthError(value)),
436 }
437 }
438
439 pub const fn number(self) -> NonZero<u8> {
441 unsafe { NonZero::new_unchecked(self as u8) }
443 }
444
445 pub fn iter() -> impl ExactSizeIterator<Item = Month> {
447 [
448 Self::Jan,
449 Self::Feb,
450 Self::Mar,
451 Self::Apr,
452 Self::May,
453 Self::Jun,
454 Self::Jul,
455 Self::Aug,
456 Self::Sep,
457 Self::Oct,
458 Self::Nov,
459 Self::Dec,
460 ]
461 .into_iter()
462 }
463}
464
465#[derive(Debug, Clone, Copy, Error, PartialEq, Eq)]
467#[error("expected an integer between 1 and 12 but received {0} instead")]
468pub struct InvalidMonthError(u8);
469
470#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
472#[repr(u8)]
473pub enum Day {
474 D01 = 1,
475 D02,
476 D03,
477 D04,
478 D05,
479 D06,
480 D07,
481 D08,
482 D09,
483 D10,
484 D11,
485 D12,
486 D13,
487 D14,
488 D15,
489 D16,
490 D17,
491 D18,
492 D19,
493 D20,
494 D21,
495 D22,
496 D23,
497 D24,
498 D25,
499 D26,
500 D27,
501 D28,
502 D29,
503 D30,
504 D31,
505}
506
507impl Day {
508 #[inline(always)]
510 pub const fn new(value: u8) -> Result<Self, InvalidDayError> {
511 match value {
512 1..=31 => Ok({
513 unsafe { std::mem::transmute::<u8, Self>(value) }
516 }),
517 _ => Err(InvalidDayError(value)),
518 }
519 }
520}
521
522#[derive(Debug, Clone, Copy, Error, PartialEq, Eq)]
524#[error("expected an integer between 1 and 31 but received {0} instead")]
525pub struct InvalidDayError(u8);
526
527#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
529pub struct Time {
530 hour: Hour,
531 minute: Minute,
532 second: Second,
533 frac: Option<FractionalSecond>,
534}
535
536impl Time {
537 pub const fn new(
539 hour: Hour,
540 minute: Minute,
541 second: Second,
542 frac: Option<FractionalSecond>,
543 ) -> Result<Self, InvalidTimeError> {
544 Ok(Self {
548 hour,
549 minute,
550 second,
551 frac,
552 })
553 }
554
555 #[inline(always)]
557 pub const fn hour(&self) -> Hour {
558 self.hour
559 }
560
561 #[inline(always)]
563 pub const fn minute(&self) -> Minute {
564 self.minute
565 }
566
567 #[inline(always)]
569 pub const fn second(&self) -> Second {
570 self.second
571 }
572
573 #[inline(always)]
575 pub const fn frac(&self) -> Option<FractionalSecond> {
576 self.frac
577 }
578}
579
580#[derive(Debug, Clone, Copy, Error, PartialEq, Eq)]
582pub enum InvalidTimeError {
583 #[error("invalid hour: {0}")]
585 Hour(#[from] InvalidHourError),
586 #[error("invalid minute: {0}")]
588 Minute(#[from] InvalidMinuteError),
589 #[error("invalid second: {0}")]
591 Second(#[from] InvalidSecondError),
592 #[error("invalid fractional second: {0}")]
594 FractionalSecond(#[from] InvalidFractionalSecondError),
595}
596
597impl From<Infallible> for InvalidTimeError {
598 fn from(value: Infallible) -> Self {
599 match value {}
600 }
601}
602
603#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
605#[repr(u8)]
606pub enum Hour {
607 #[default]
608 H00,
609 H01,
610 H02,
611 H03,
612 H04,
613 H05,
614 H06,
615 H07,
616 H08,
617 H09,
618 H10,
619 H11,
620 H12,
621 H13,
622 H14,
623 H15,
624 H16,
625 H17,
626 H18,
627 H19,
628 H20,
629 H21,
630 H22,
631 H23,
632}
633
634impl Hour {
635 pub const fn new(value: u8) -> Result<Self, InvalidHourError> {
637 match NonZero::new(value) {
638 None => Ok(Self::H00),
639 Some(value) => match value.get() <= 23 {
640 false => Err(InvalidHourError(value)),
641 true => Ok({
642 unsafe { std::mem::transmute::<u8, Hour>(value.get()) }
645 }),
646 },
647 }
648 }
649}
650
651#[derive(Debug, Clone, Copy, Error, PartialEq, Eq)]
653#[error("expected an integer between 0 and 23 but received {0}")]
654pub struct InvalidHourError(NonZero<u8>);
655
656#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
658#[repr(u8)]
659pub enum Minute {
660 #[default]
661 M00,
662 M01,
663 M02,
664 M03,
665 M04,
666 M05,
667 M06,
668 M07,
669 M08,
670 M09,
671 M10,
672 M11,
673 M12,
674 M13,
675 M14,
676 M15,
677 M16,
678 M17,
679 M18,
680 M19,
681 M20,
682 M21,
683 M22,
684 M23,
685 M24,
686 M25,
687 M26,
688 M27,
689 M28,
690 M29,
691 M30,
692 M31,
693 M32,
694 M33,
695 M34,
696 M35,
697 M36,
698 M37,
699 M38,
700 M39,
701 M40,
702 M41,
703 M42,
704 M43,
705 M44,
706 M45,
707 M46,
708 M47,
709 M48,
710 M49,
711 M50,
712 M51,
713 M52,
714 M53,
715 M54,
716 M55,
717 M56,
718 M57,
719 M58,
720 M59,
721}
722
723impl Minute {
724 pub const fn new(value: u8) -> Result<Self, InvalidMinuteError> {
726 match NonZero::new(value) {
727 None => Ok(Self::M00),
728 Some(value) => match value.get() <= 59 {
729 false => Err(InvalidMinuteError(value)),
730 true => Ok({
731 unsafe { std::mem::transmute::<u8, Minute>(value.get()) }
734 }),
735 },
736 }
737 }
738}
739
740#[derive(Debug, Clone, Copy, Error, PartialEq, Eq)]
742#[error("expected an integer between 0 and 59 but received {0}")]
743pub struct InvalidMinuteError(NonZero<u8>);
744
745#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
747#[repr(u8)]
748pub enum Second {
749 #[default]
750 S00,
751 S01,
752 S02,
753 S03,
754 S04,
755 S05,
756 S06,
757 S07,
758 S08,
759 S09,
760 S10,
761 S11,
762 S12,
763 S13,
764 S14,
765 S15,
766 S16,
767 S17,
768 S18,
769 S19,
770 S20,
771 S21,
772 S22,
773 S23,
774 S24,
775 S25,
776 S26,
777 S27,
778 S28,
779 S29,
780 S30,
781 S31,
782 S32,
783 S33,
784 S34,
785 S35,
786 S36,
787 S37,
788 S38,
789 S39,
790 S40,
791 S41,
792 S42,
793 S43,
794 S44,
795 S45,
796 S46,
797 S47,
798 S48,
799 S49,
800 S50,
801 S51,
802 S52,
803 S53,
804 S54,
805 S55,
806 S56,
807 S57,
808 S58,
809 S59,
810 S60,
811}
812
813impl Second {
814 pub const fn new(value: u8) -> Result<Self, InvalidSecondError> {
816 match NonZero::new(value) {
817 None => Ok(Self::S00),
818 Some(value) => match value.get() <= 60 {
819 false => Err(InvalidSecondError(value)),
820 true => Ok({
821 unsafe { std::mem::transmute::<u8, Second>(value.get()) }
824 }),
825 },
826 }
827 }
828}
829
830#[derive(Debug, Clone, Copy, Error, PartialEq, Eq)]
832#[error("expected an integer between 0 and 60 but received {0}")]
833pub struct InvalidSecondError(NonZero<u8>);
834
835#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
837#[repr(u8)]
838pub enum NonLeapSecond {
839 #[default]
840 S00,
841 S01,
842 S02,
843 S03,
844 S04,
845 S05,
846 S06,
847 S07,
848 S08,
849 S09,
850 S10,
851 S11,
852 S12,
853 S13,
854 S14,
855 S15,
856 S16,
857 S17,
858 S18,
859 S19,
860 S20,
861 S21,
862 S22,
863 S23,
864 S24,
865 S25,
866 S26,
867 S27,
868 S28,
869 S29,
870 S30,
871 S31,
872 S32,
873 S33,
874 S34,
875 S35,
876 S36,
877 S37,
878 S38,
879 S39,
880 S40,
881 S41,
882 S42,
883 S43,
884 S44,
885 S45,
886 S46,
887 S47,
888 S48,
889 S49,
890 S50,
891 S51,
892 S52,
893 S53,
894 S54,
895 S55,
896 S56,
897 S57,
898 S58,
899 S59,
900}
901
902impl NonLeapSecond {
903 pub const fn new(value: u8) -> Result<Self, InvalidNonLeapSecondError> {
905 match NonZero::new(value) {
906 None => Ok(Self::S00),
907 Some(value) => match value.get() < 60 {
908 false => Err(InvalidNonLeapSecondError(value)),
909 true => Ok({
910 unsafe { std::mem::transmute::<u8, NonLeapSecond>(value.get()) }
913 }),
914 },
915 }
916 }
917
918 #[inline(always)]
920 pub const fn to_second(self) -> Second {
921 match Second::new(self as u8) {
922 Ok(second) => second,
923 Err(_) => unreachable!(),
924 }
925 }
926}
927
928#[derive(Debug, Clone, Copy, Error, PartialEq, Eq)]
930#[error("expected an integer between 0 and 59 but received {0}")]
931pub struct InvalidNonLeapSecondError(NonZero<u8>);
932
933#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
936pub struct FractionalSecond(NonZero<u32>);
937
938impl std::fmt::Debug for FractionalSecond {
939 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
940 write!(f, "{}ns", self.0.get())
941 }
942}
943
944impl FractionalSecond {
945 pub const MIN: Self = Self(NonZero::new(1).unwrap());
947 pub const MAX: Self = Self(NonZero::new(10u32.pow(9) - 1).unwrap());
949
950 #[inline(always)]
952 pub const fn get(self) -> NonZero<u32> {
953 self.0
954 }
955
956 pub const fn new(value: u32) -> Result<Self, InvalidFractionalSecondError> {
959 match NonZero::new(value) {
960 None => Err(InvalidFractionalSecondError::AllZero),
961 Some(value) => match value.get() <= Self::MAX.0.get() {
962 true => Ok(Self(value)),
963 false => Err(InvalidFractionalSecondError::TooManyDigits(value)),
964 },
965 }
966 }
967}
968
969#[derive(Debug, Clone, Copy, Error, PartialEq, Eq)]
971pub enum InvalidFractionalSecondError {
972 #[error("at least one fractional second digit must be non-zero")]
974 AllZero,
975 #[error("{0} has more than nine decimal digits")]
977 TooManyDigits(NonZero<u32>),
978}
979
980#[cfg(test)]
981mod tests {
982 use super::*;
983
984 #[test]
985 fn iso_week_from_index() {
986 assert_eq!(IsoWeek::from_index(0), None);
987 assert_eq!(IsoWeek::from_index(1), Some(IsoWeek::W1));
988 assert_eq!(IsoWeek::from_index(2), Some(IsoWeek::W2));
989 assert_eq!(IsoWeek::from_index(3), Some(IsoWeek::W3));
990 assert_eq!(IsoWeek::from_index(4), Some(IsoWeek::W4));
991 assert_eq!(IsoWeek::from_index(5), Some(IsoWeek::W5));
992 assert_eq!(IsoWeek::from_index(25), Some(IsoWeek::W25));
994 assert_eq!(IsoWeek::from_index(26), Some(IsoWeek::W26));
995 assert_eq!(IsoWeek::from_index(27), Some(IsoWeek::W27));
996 assert_eq!(IsoWeek::from_index(51), Some(IsoWeek::W51));
998 assert_eq!(IsoWeek::from_index(52), Some(IsoWeek::W52));
999 assert_eq!(IsoWeek::from_index(53), Some(IsoWeek::W53));
1000 assert_eq!(IsoWeek::from_index(54), None);
1001 assert_eq!(IsoWeek::from_index(55), None);
1002 assert_eq!(IsoWeek::from_index(254), None);
1004 assert_eq!(IsoWeek::from_index(255), None);
1005 }
1006}