Skip to main content

calendar_types/
time.rs

1//! Date and time types, largely from RFC 3339.
2
3use std::{convert::Infallible, num::NonZero};
4
5use thiserror::Error;
6
7/// One of the seven weekdays.
8#[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    /// Converts a `u8` discriminant (0=Monday through 6=Sunday) to a `Weekday`.
22    pub const fn from_repr(repr: u8) -> Option<Self> {
23        match repr {
24            0..=6 => {
25                // SAFETY: the valid discriminants of Self are exactly the
26                // values of the range 0..=6.
27                Some(unsafe { std::mem::transmute::<u8, Self>(repr) })
28            }
29            _ => None,
30        }
31    }
32
33    /// Returns an iterator over all seven weekdays, starting from Monday.
34    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/// An ISO week ranging from W1 to W53.
50#[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    /// Returns the 1-based week number.
110    pub const fn index(&self) -> NonZero<u8> {
111        NonZero::new(*self as u8).unwrap()
112    }
113
114    /// Converts a 1-based week number to an `IsoWeek`, returning `None` if out of range.
115    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/// A marker struct for the UTC timezone.
127#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
128pub struct Utc;
129
130/// A marker struct for the implicit local timezone.
131#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
132pub struct Local;
133
134/// An error arising from an invalid [`DateTime`] value.
135#[derive(Debug, Clone, Copy, Error, PartialEq, Eq)]
136pub enum InvalidDateTimeError {
137    /// The date component is invalid.
138    #[error("invalid date: {0}")]
139    Date(#[from] InvalidDateError),
140    /// The time component is invalid.
141    #[error("invalid time: {0}")]
142    Time(#[from] InvalidTimeError),
143}
144
145/// An ISO 8601 datetime with the timezone marker `M` (RFC 3339 §5.6).
146///
147/// This type makes no guarantees about the relationship between its fields, and in particular does
148/// not guarantee that the [`time`] field represents a time that actually occurred on the date
149/// represented by the [`date`] field; that is, it does not encode any information about leap
150/// seconds.
151///
152/// [`time`]: DateTime::time
153/// [`date`]: DateTime::date
154#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
155pub struct DateTime<M> {
156    /// The date component.
157    pub date: Date,
158    /// The time component.
159    pub time: Time,
160    /// The timezone marker.
161    pub marker: M,
162}
163
164/// An ISO 8601 date.
165#[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    /// Creates a new date, returning an error if the day is out of range for the given month and year.
174    #[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    /// Returns the year.
184    #[inline(always)]
185    pub const fn year(&self) -> Year {
186        self.year
187    }
188
189    /// Returns the month.
190    #[inline(always)]
191    pub const fn month(&self) -> Month {
192        self.month
193    }
194
195    /// Returns the day of the month.
196    #[inline(always)]
197    pub const fn day(&self) -> Day {
198        self.day
199    }
200
201    /// Returns the maximum day of `month` in `year`, based on the table given in RFC 3339 §5.7.
202    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/// An error arising from an invalid [`Date`] value.
219#[derive(Debug, Clone, Copy, Error, PartialEq, Eq)]
220pub enum InvalidDateError {
221    /// The year is out of range.
222    #[error("invalid year: {0}")]
223    Year(#[from] InvalidYearError),
224    /// The month is out of range.
225    #[error("invalid month: {0}")]
226    Month(#[from] InvalidMonthError),
227    /// The day is out of range.
228    #[error("invalid day: {0}")]
229    Day(#[from] InvalidDayError),
230    /// The day does not exist for the given month and year.
231    #[error(transparent)]
232    ImpossibleDate(#[from] ImpossibleDateError),
233}
234
235/// An error indicating that the day does not exist for the given month and year
236/// (e.g. February 30).
237#[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/// A four-digit year ranging from 0 CE through 9999 CE.
246#[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    /// The smallest representable year (0 CE).
258    pub const MIN: Self = Self(0);
259    /// The largest representable year (9999 CE).
260    pub const MAX: Self = Self(9999);
261
262    /// Returns `true` if this year is a leap year (RFC 3339 Appendix C).
263    pub const fn is_leap_year(self) -> bool {
264        let year = self.0;
265        // as given by RFC 3339, Appendix C
266        year.is_multiple_of(4) && (!year.is_multiple_of(100) || year.is_multiple_of(400))
267    }
268
269    /// Creates a `Year` from a raw `u16`, returning an error if greater than 9999.
270    #[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    /// Returns the numeric value of this year.
280    #[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            // Format as ".NNN..." with trailing zeros stripped
333            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
352/// Marker trait for timezone markers in [`DateTime`] formatting.
353pub trait DateTimeMarker {
354    /// The suffix appended when formatting (e.g. `"Z"` for UTC, `""` for local).
355    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/// Runtime discrimination between local time and UTC.
371///
372/// This is used as the timezone marker `M` in `DateTime<M>` when the format is
373/// not statically known.
374#[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/// An error indicating that a value exceeds the valid year range (0–9999).
404#[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/// One of the twelve Gregorian months.
409#[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    /// Creates a `Month` from a 1-based month number, returning an error if out of range.
428    pub const fn new(value: u8) -> Result<Self, InvalidMonthError> {
429        match value {
430            1..=12 => Ok({
431                // SAFETY: Month is repr(u8) and takes the values in the range 1..=12, which are
432                // the only possible values in this branch
433                unsafe { std::mem::transmute::<u8, Month>(value) }
434            }),
435            _ => Err(InvalidMonthError(value)),
436        }
437    }
438
439    /// Returns the month number of `self`, which lies in the range `1..=12`.
440    pub const fn number(self) -> NonZero<u8> {
441        // SAFETY: the value of (self as u8) can never be zero
442        unsafe { NonZero::new_unchecked(self as u8) }
443    }
444
445    /// Returns an iterator over all twelve months, starting from January.
446    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/// An error indicating that a value is not a valid month number (1–12).
466#[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/// One of the 31 days of the Gregorian month.
471#[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    /// Creates a `Day` from a 1-based day number, returning an error if out of range.
509    #[inline(always)]
510    pub const fn new(value: u8) -> Result<Self, InvalidDayError> {
511        match value {
512            1..=31 => Ok({
513                // SAFETY: Day is repr(u8) and takes the values in the range 1..=31, which are
514                // the only possible values in this branch
515                unsafe { std::mem::transmute::<u8, Self>(value) }
516            }),
517            _ => Err(InvalidDayError(value)),
518        }
519    }
520}
521
522/// An error indicating that a value is not a valid day number (1–31).
523#[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/// A time of day consisting of hour, minute, second, and optional fractional second.
528#[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    /// Creates a new `Time` from the given components.
538    pub const fn new(
539        hour: Hour,
540        minute: Minute,
541        second: Second,
542        frac: Option<FractionalSecond>,
543    ) -> Result<Self, InvalidTimeError> {
544        // refer to RFC 3339 §5.7 for details about when leap seconds are valid. for now, we're
545        // just going to unconditionally construct a Time
546
547        Ok(Self {
548            hour,
549            minute,
550            second,
551            frac,
552        })
553    }
554
555    /// Returns the hour component.
556    #[inline(always)]
557    pub const fn hour(&self) -> Hour {
558        self.hour
559    }
560
561    /// Returns the minute component.
562    #[inline(always)]
563    pub const fn minute(&self) -> Minute {
564        self.minute
565    }
566
567    /// Returns the second component.
568    #[inline(always)]
569    pub const fn second(&self) -> Second {
570        self.second
571    }
572
573    /// Returns the fractional second component, if present.
574    #[inline(always)]
575    pub const fn frac(&self) -> Option<FractionalSecond> {
576        self.frac
577    }
578}
579
580/// An error arising from an invalid [`Time`] value.
581#[derive(Debug, Clone, Copy, Error, PartialEq, Eq)]
582pub enum InvalidTimeError {
583    /// The hour is out of range.
584    #[error("invalid hour: {0}")]
585    Hour(#[from] InvalidHourError),
586    /// The minute is out of range.
587    #[error("invalid minute: {0}")]
588    Minute(#[from] InvalidMinuteError),
589    /// The second is out of range.
590    #[error("invalid second: {0}")]
591    Second(#[from] InvalidSecondError),
592    /// The fractional second is invalid.
593    #[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/// An hour of the day, ranging from 0 (midnight) through 23.
604#[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    /// Creates an `Hour` from a raw `u8`, returning an error if greater than 23.
636    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                    // SAFETY: `value` must be less than 24 in this branch, so it is a valid hour,
643                    // and Hour is repr(u8)
644                    unsafe { std::mem::transmute::<u8, Hour>(value.get()) }
645                }),
646            },
647        }
648    }
649}
650
651/// An error indicating that a value is not a valid hour (0–23).
652#[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/// A minute within an hour, ranging from 0 through 59.
657#[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    /// Creates a `Minute` from a raw `u8`, returning an error if greater than 59.
725    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                    // SAFETY: `value` must be less than 59 in this branch, so it is a valid minute,
732                    // and Minute is repr(u8)
733                    unsafe { std::mem::transmute::<u8, Minute>(value.get()) }
734                }),
735            },
736        }
737    }
738}
739
740/// An error indicating that a value is not a valid minute (0–59).
741#[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/// One of the 61 possible seconds in a minute.
746#[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    /// Creates a `Second` from a raw `u8`, returning an error if greater than 60.
815    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                    // SAFETY: `value` must be less than 60 in this branch, so it is a valid second,
822                    // and Second is repr(u8)
823                    unsafe { std::mem::transmute::<u8, Second>(value.get()) }
824                }),
825            },
826        }
827    }
828}
829
830/// An error indicating that a value is not a valid second (0–60).
831#[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/// One of the 60 seconds in a minute which are not leap seconds.
836#[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    /// Creates a `NonLeapSecond` from a raw `u8`, returning an error if 60 or greater.
904    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                    // SAFETY: `value` must be less than 60 in this branch, so it is a valid second
911                    // and not a leap second, and NonLeapSecond is repr(u8)
912                    unsafe { std::mem::transmute::<u8, NonLeapSecond>(value.get()) }
913                }),
914            },
915        }
916    }
917
918    /// Converts this non-leap second into a [`Second`].
919    #[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/// An error indicating that a value is not a valid non-leap second (0–59).
929#[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/// A non-zero fractional second, represented as an integer multiple of nanoseconds. This
934/// guarantees nine digits of decimal precision and a maximum error of 10^-9.
935#[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    /// The smallest fractional second; this value is exactly 1 nanosecond.
946    pub const MIN: Self = Self(NonZero::new(1).unwrap());
947    /// The largest fractional second; this value is 10^9 - 1 nanoseconds.
948    pub const MAX: Self = Self(NonZero::new(10u32.pow(9) - 1).unwrap());
949
950    /// Returns the value in nanoseconds.
951    #[inline(always)]
952    pub const fn get(self) -> NonZero<u32> {
953        self.0
954    }
955
956    /// Creates a `FractionalSecond` from a nanosecond count, returning an error if zero
957    /// or exceeding nine decimal digits.
958    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/// An error arising from an invalid [`FractionalSecond`] value.
970#[derive(Debug, Clone, Copy, Error, PartialEq, Eq)]
971pub enum InvalidFractionalSecondError {
972    /// The value is zero (fractional seconds must be non-zero).
973    #[error("at least one fractional second digit must be non-zero")]
974    AllZero,
975    /// The value exceeds nine decimal digits.
976    #[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        // ...
993        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        // ...
997        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        //...
1003        assert_eq!(IsoWeek::from_index(254), None);
1004        assert_eq!(IsoWeek::from_index(255), None);
1005    }
1006}