chrono/naive/
internals.rs

1//! Internal helper types for working with dates.
2
3#![cfg_attr(feature = "__internal_bench", allow(missing_docs))]
4
5use core::fmt;
6
7/// Year flags (aka the dominical letter).
8///
9/// `YearFlags` are used as the last four bits of `NaiveDate`, `Mdf` and `IsoWeek`.
10///
11/// There are 14 possible classes of year in the Gregorian calendar:
12/// common and leap years starting with Monday through Sunday.
13///
14/// The `YearFlags` stores this information into 4 bits `LWWW`. `L` is the leap year flag, with `1`
15/// for the common year (this simplifies validating an ordinal in `NaiveDate`). `WWW` is a non-zero
16/// `Weekday` of the last day in the preceding year.
17#[allow(unreachable_pub)] // public as an alias for benchmarks only
18#[derive(PartialEq, Eq, Copy, Clone, Hash)]
19pub struct YearFlags(pub(super) u8);
20
21// Weekday of the last day in the preceding year.
22// Allows for quick day of week calculation from the 1-based ordinal.
23const YEAR_STARTS_AFTER_MONDAY: u8 = 7; // non-zero to allow use with `NonZero*`.
24const YEAR_STARTS_AFTER_THUESDAY: u8 = 1;
25const YEAR_STARTS_AFTER_WEDNESDAY: u8 = 2;
26const YEAR_STARTS_AFTER_THURSDAY: u8 = 3;
27const YEAR_STARTS_AFTER_FRIDAY: u8 = 4;
28const YEAR_STARTS_AFTER_SATURDAY: u8 = 5;
29const YEAR_STARTS_AFTER_SUNDAY: u8 = 6;
30
31const COMMON_YEAR: u8 = 1 << 3;
32const LEAP_YEAR: u8 = 0 << 3;
33
34pub(super) const A: YearFlags = YearFlags(COMMON_YEAR | YEAR_STARTS_AFTER_SATURDAY);
35pub(super) const AG: YearFlags = YearFlags(LEAP_YEAR | YEAR_STARTS_AFTER_SATURDAY);
36pub(super) const B: YearFlags = YearFlags(COMMON_YEAR | YEAR_STARTS_AFTER_FRIDAY);
37pub(super) const BA: YearFlags = YearFlags(LEAP_YEAR | YEAR_STARTS_AFTER_FRIDAY);
38pub(super) const C: YearFlags = YearFlags(COMMON_YEAR | YEAR_STARTS_AFTER_THURSDAY);
39pub(super) const CB: YearFlags = YearFlags(LEAP_YEAR | YEAR_STARTS_AFTER_THURSDAY);
40pub(super) const D: YearFlags = YearFlags(COMMON_YEAR | YEAR_STARTS_AFTER_WEDNESDAY);
41pub(super) const DC: YearFlags = YearFlags(LEAP_YEAR | YEAR_STARTS_AFTER_WEDNESDAY);
42pub(super) const E: YearFlags = YearFlags(COMMON_YEAR | YEAR_STARTS_AFTER_THUESDAY);
43pub(super) const ED: YearFlags = YearFlags(LEAP_YEAR | YEAR_STARTS_AFTER_THUESDAY);
44pub(super) const F: YearFlags = YearFlags(COMMON_YEAR | YEAR_STARTS_AFTER_MONDAY);
45pub(super) const FE: YearFlags = YearFlags(LEAP_YEAR | YEAR_STARTS_AFTER_MONDAY);
46pub(super) const G: YearFlags = YearFlags(COMMON_YEAR | YEAR_STARTS_AFTER_SUNDAY);
47pub(super) const GF: YearFlags = YearFlags(LEAP_YEAR | YEAR_STARTS_AFTER_SUNDAY);
48
49const YEAR_TO_FLAGS: &[YearFlags; 400] = &[
50    BA, G, F, E, DC, B, A, G, FE, D, C, B, AG, F, E, D, CB, A, G, F, ED, C, B, A, GF, E, D, C, BA,
51    G, F, E, DC, B, A, G, FE, D, C, B, AG, F, E, D, CB, A, G, F, ED, C, B, A, GF, E, D, C, BA, G,
52    F, E, DC, B, A, G, FE, D, C, B, AG, F, E, D, CB, A, G, F, ED, C, B, A, GF, E, D, C, BA, G, F,
53    E, DC, B, A, G, FE, D, C, B, AG, F, E, D, // 100
54    C, B, A, G, FE, D, C, B, AG, F, E, D, CB, A, G, F, ED, C, B, A, GF, E, D, C, BA, G, F, E, DC,
55    B, A, G, FE, D, C, B, AG, F, E, D, CB, A, G, F, ED, C, B, A, GF, E, D, C, BA, G, F, E, DC, B,
56    A, G, FE, D, C, B, AG, F, E, D, CB, A, G, F, ED, C, B, A, GF, E, D, C, BA, G, F, E, DC, B, A,
57    G, FE, D, C, B, AG, F, E, D, CB, A, G, F, // 200
58    E, D, C, B, AG, F, E, D, CB, A, G, F, ED, C, B, A, GF, E, D, C, BA, G, F, E, DC, B, A, G, FE,
59    D, C, B, AG, F, E, D, CB, A, G, F, ED, C, B, A, GF, E, D, C, BA, G, F, E, DC, B, A, G, FE, D,
60    C, B, AG, F, E, D, CB, A, G, F, ED, C, B, A, GF, E, D, C, BA, G, F, E, DC, B, A, G, FE, D, C,
61    B, AG, F, E, D, CB, A, G, F, ED, C, B, A, // 300
62    G, F, E, D, CB, A, G, F, ED, C, B, A, GF, E, D, C, BA, G, F, E, DC, B, A, G, FE, D, C, B, AG,
63    F, E, D, CB, A, G, F, ED, C, B, A, GF, E, D, C, BA, G, F, E, DC, B, A, G, FE, D, C, B, AG, F,
64    E, D, CB, A, G, F, ED, C, B, A, GF, E, D, C, BA, G, F, E, DC, B, A, G, FE, D, C, B, AG, F, E,
65    D, CB, A, G, F, ED, C, B, A, GF, E, D, C, // 400
66];
67
68impl YearFlags {
69    #[allow(unreachable_pub)] // public as an alias for benchmarks only
70    #[doc(hidden)] // for benchmarks only
71    #[inline]
72    #[must_use]
73    pub const fn from_year(year: i32) -> YearFlags {
74        let year = year.rem_euclid(400);
75        YearFlags::from_year_mod_400(year)
76    }
77
78    #[inline]
79    pub(super) const fn from_year_mod_400(year: i32) -> YearFlags {
80        YEAR_TO_FLAGS[year as usize]
81    }
82
83    #[inline]
84    pub(super) const fn ndays(&self) -> u32 {
85        let YearFlags(flags) = *self;
86        366 - (flags >> 3) as u32
87    }
88
89    #[inline]
90    pub(super) const fn isoweek_delta(&self) -> u32 {
91        let YearFlags(flags) = *self;
92        let mut delta = (flags & 0b0111) as u32;
93        if delta < 3 {
94            delta += 7;
95        }
96        delta
97    }
98
99    #[inline]
100    pub(super) const fn nisoweeks(&self) -> u32 {
101        let YearFlags(flags) = *self;
102        52 + ((0b0000_0100_0000_0110 >> flags as usize) & 1)
103    }
104}
105
106impl fmt::Debug for YearFlags {
107    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
108        let YearFlags(flags) = *self;
109        match flags {
110            0o15 => "A".fmt(f),
111            0o05 => "AG".fmt(f),
112            0o14 => "B".fmt(f),
113            0o04 => "BA".fmt(f),
114            0o13 => "C".fmt(f),
115            0o03 => "CB".fmt(f),
116            0o12 => "D".fmt(f),
117            0o02 => "DC".fmt(f),
118            0o11 => "E".fmt(f),
119            0o01 => "ED".fmt(f),
120            0o10 => "F?".fmt(f),
121            0o00 => "FE?".fmt(f), // non-canonical
122            0o17 => "F".fmt(f),
123            0o07 => "FE".fmt(f),
124            0o16 => "G".fmt(f),
125            0o06 => "GF".fmt(f),
126            _ => write!(f, "YearFlags({})", flags),
127        }
128    }
129}
130
131// OL: (ordinal << 1) | leap year flag
132const MAX_OL: u32 = 366 << 1; // `(366 << 1) | 1` would be day 366 in a non-leap year
133const MAX_MDL: u32 = (12 << 6) | (31 << 1) | 1;
134
135// The next table are adjustment values to convert a date encoded as month-day-leapyear to
136// ordinal-leapyear. OL = MDL - adjustment.
137// Dates that do not exist are encoded as `XX`.
138const XX: i8 = 0;
139const MDL_TO_OL: &[i8; MAX_MDL as usize + 1] = &[
140    XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX,
141    XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX,
142    XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, // 0
143    XX, XX, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
144    64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
145    64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, // 1
146    XX, XX, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66,
147    66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66,
148    66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, XX, XX, XX, XX, XX, // 2
149    XX, XX, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74,
150    72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74,
151    72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, // 3
152    XX, XX, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76,
153    74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76,
154    74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, XX, XX, // 4
155    XX, XX, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80,
156    78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80,
157    78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, // 5
158    XX, XX, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82,
159    80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82,
160    80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, XX, XX, // 6
161    XX, XX, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86,
162    84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86,
163    84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, // 7
164    XX, XX, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88,
165    86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88,
166    86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, // 8
167    XX, XX, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90,
168    88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90,
169    88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, XX, XX, // 9
170    XX, XX, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94,
171    92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94,
172    92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, // 10
173    XX, XX, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96,
174    94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96,
175    94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, XX, XX, // 11
176    XX, XX, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98,
177    100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100,
178    98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98,
179    100, // 12
180];
181
182const OL_TO_MDL: &[u8; MAX_OL as usize + 1] = &[
183    0, 0, // 0
184    64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
185    64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
186    64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, // 1
187    66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66,
188    66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66,
189    66, 66, 66, 66, 66, 66, 66, 66, 66, // 2
190    74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72,
191    74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72,
192    74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, // 3
193    76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74,
194    76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74,
195    76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, // 4
196    80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78,
197    80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78,
198    80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, // 5
199    82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80,
200    82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80,
201    82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, // 6
202    86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84,
203    86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84,
204    86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, // 7
205    88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86,
206    88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86,
207    88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, // 8
208    90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88,
209    90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88,
210    90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, // 9
211    94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92,
212    94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92,
213    94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, // 10
214    96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94,
215    96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94,
216    96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, // 11
217    100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100,
218    98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98,
219    100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100, 98, 100,
220    98, // 12
221];
222
223/// Month, day of month and year flags: `(month << 9) | (day << 4) | flags`
224/// `M_MMMD_DDDD_LFFF`
225///
226/// The whole bits except for the least 3 bits are referred as `Mdl` (month, day of month, and leap
227/// year flag), which is an index to the `MDL_TO_OL` lookup table.
228///
229/// The conversion between the packed calendar date (`Mdf`) and the ordinal date (`NaiveDate`) is
230/// based on the moderately-sized lookup table (~1.5KB) and the packed representation is chosen for
231/// efficient lookup.
232///
233/// The methods of `Mdf` validate their inputs as late as possible. Dates that can't exist, like
234/// February 30, can still be represented. This allows the validation to be combined with the final
235/// table lookup, which is good for performance.
236#[derive(PartialEq, PartialOrd, Copy, Clone)]
237pub(super) struct Mdf(u32);
238
239impl Mdf {
240    /// Makes a new `Mdf` value from month, day and `YearFlags`.
241    ///
242    /// This method doesn't fully validate the range of the `month` and `day` parameters, only as
243    /// much as what can't be deferred until later. The year `flags` are trusted to be correct.
244    ///
245    /// # Errors
246    ///
247    /// Returns `None` if `month > 12` or `day > 31`.
248    #[inline]
249    pub(super) const fn new(month: u32, day: u32, YearFlags(flags): YearFlags) -> Option<Mdf> {
250        match month <= 12 && day <= 31 {
251            true => Some(Mdf((month << 9) | (day << 4) | flags as u32)),
252            false => None,
253        }
254    }
255
256    /// Makes a new `Mdf` value from an `i32` with an ordinal and a leap year flag, and year
257    /// `flags`.
258    ///
259    /// The `ol` is trusted to be valid, and the `flags` are trusted to match it.
260    #[inline]
261    pub(super) const fn from_ol(ol: i32, YearFlags(flags): YearFlags) -> Mdf {
262        debug_assert!(ol > 1 && ol <= MAX_OL as i32);
263        // Array is indexed from `[2..=MAX_OL]`, with a `0` index having a meaningless value.
264        Mdf(((ol as u32 + OL_TO_MDL[ol as usize] as u32) << 3) | flags as u32)
265    }
266
267    /// Returns the month of this `Mdf`.
268    #[inline]
269    pub(super) const fn month(&self) -> u32 {
270        let Mdf(mdf) = *self;
271        mdf >> 9
272    }
273
274    /// Replaces the month of this `Mdf`, keeping the day and flags.
275    ///
276    /// # Errors
277    ///
278    /// Returns `None` if `month > 12`.
279    #[inline]
280    pub(super) const fn with_month(&self, month: u32) -> Option<Mdf> {
281        if month > 12 {
282            return None;
283        }
284
285        let Mdf(mdf) = *self;
286        Some(Mdf((mdf & 0b1_1111_1111) | (month << 9)))
287    }
288
289    /// Returns the day of this `Mdf`.
290    #[inline]
291    pub(super) const fn day(&self) -> u32 {
292        let Mdf(mdf) = *self;
293        (mdf >> 4) & 0b1_1111
294    }
295
296    /// Replaces the day of this `Mdf`, keeping the month and flags.
297    ///
298    /// # Errors
299    ///
300    /// Returns `None` if `day > 31`.
301    #[inline]
302    pub(super) const fn with_day(&self, day: u32) -> Option<Mdf> {
303        if day > 31 {
304            return None;
305        }
306
307        let Mdf(mdf) = *self;
308        Some(Mdf((mdf & !0b1_1111_0000) | (day << 4)))
309    }
310
311    /// Replaces the flags of this `Mdf`, keeping the month and day.
312    #[inline]
313    pub(super) const fn with_flags(&self, YearFlags(flags): YearFlags) -> Mdf {
314        let Mdf(mdf) = *self;
315        Mdf((mdf & !0b1111) | flags as u32)
316    }
317
318    /// Returns the ordinal that corresponds to this `Mdf`.
319    ///
320    /// This does a table lookup to calculate the corresponding ordinal. It will return an error if
321    /// the `Mdl` turns out not to be a valid date.
322    ///
323    /// # Errors
324    ///
325    /// Returns `None` if `month == 0` or `day == 0`, or if a the given day does not exist in the
326    /// given month.
327    #[inline]
328    pub(super) const fn ordinal(&self) -> Option<u32> {
329        let mdl = self.0 >> 3;
330        match MDL_TO_OL[mdl as usize] {
331            XX => None,
332            v => Some((mdl - v as u8 as u32) >> 1),
333        }
334    }
335
336    /// Returns the year flags of this `Mdf`.
337    #[inline]
338    pub(super) const fn year_flags(&self) -> YearFlags {
339        YearFlags((self.0 & 0b1111) as u8)
340    }
341
342    /// Returns the ordinal that corresponds to this `Mdf`, encoded as a value including year flags.
343    ///
344    /// This does a table lookup to calculate the corresponding ordinal. It will return an error if
345    /// the `Mdl` turns out not to be a valid date.
346    ///
347    /// # Errors
348    ///
349    /// Returns `None` if `month == 0` or `day == 0`, or if a the given day does not exist in the
350    /// given month.
351    #[inline]
352    pub(super) const fn ordinal_and_flags(&self) -> Option<i32> {
353        let mdl = self.0 >> 3;
354        match MDL_TO_OL[mdl as usize] {
355            XX => None,
356            v => Some(self.0 as i32 - ((v as i32) << 3)),
357        }
358    }
359
360    #[cfg(test)]
361    fn valid(&self) -> bool {
362        let mdl = self.0 >> 3;
363        MDL_TO_OL[mdl as usize] > 0
364    }
365}
366
367impl fmt::Debug for Mdf {
368    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
369        let Mdf(mdf) = *self;
370        write!(
371            f,
372            "Mdf(({} << 9) | ({} << 4) | {:#04o} /*{:?}*/)",
373            mdf >> 9,
374            (mdf >> 4) & 0b1_1111,
375            mdf & 0b1111,
376            YearFlags((mdf & 0b1111) as u8)
377        )
378    }
379}
380
381#[cfg(test)]
382mod tests {
383    use super::Mdf;
384    use super::{YearFlags, A, AG, B, BA, C, CB, D, DC, E, ED, F, FE, G, GF};
385
386    const NONLEAP_FLAGS: [YearFlags; 7] = [A, B, C, D, E, F, G];
387    const LEAP_FLAGS: [YearFlags; 7] = [AG, BA, CB, DC, ED, FE, GF];
388    const FLAGS: [YearFlags; 14] = [A, B, C, D, E, F, G, AG, BA, CB, DC, ED, FE, GF];
389
390    #[test]
391    fn test_year_flags_ndays_from_year() {
392        assert_eq!(YearFlags::from_year(2014).ndays(), 365);
393        assert_eq!(YearFlags::from_year(2012).ndays(), 366);
394        assert_eq!(YearFlags::from_year(2000).ndays(), 366);
395        assert_eq!(YearFlags::from_year(1900).ndays(), 365);
396        assert_eq!(YearFlags::from_year(1600).ndays(), 366);
397        assert_eq!(YearFlags::from_year(1).ndays(), 365);
398        assert_eq!(YearFlags::from_year(0).ndays(), 366); // 1 BCE (proleptic Gregorian)
399        assert_eq!(YearFlags::from_year(-1).ndays(), 365); // 2 BCE
400        assert_eq!(YearFlags::from_year(-4).ndays(), 366); // 5 BCE
401        assert_eq!(YearFlags::from_year(-99).ndays(), 365); // 100 BCE
402        assert_eq!(YearFlags::from_year(-100).ndays(), 365); // 101 BCE
403        assert_eq!(YearFlags::from_year(-399).ndays(), 365); // 400 BCE
404        assert_eq!(YearFlags::from_year(-400).ndays(), 366); // 401 BCE
405    }
406
407    #[test]
408    fn test_year_flags_nisoweeks() {
409        assert_eq!(A.nisoweeks(), 52);
410        assert_eq!(B.nisoweeks(), 52);
411        assert_eq!(C.nisoweeks(), 52);
412        assert_eq!(D.nisoweeks(), 53);
413        assert_eq!(E.nisoweeks(), 52);
414        assert_eq!(F.nisoweeks(), 52);
415        assert_eq!(G.nisoweeks(), 52);
416        assert_eq!(AG.nisoweeks(), 52);
417        assert_eq!(BA.nisoweeks(), 52);
418        assert_eq!(CB.nisoweeks(), 52);
419        assert_eq!(DC.nisoweeks(), 53);
420        assert_eq!(ED.nisoweeks(), 53);
421        assert_eq!(FE.nisoweeks(), 52);
422        assert_eq!(GF.nisoweeks(), 52);
423    }
424
425    #[test]
426    fn test_mdf_valid() {
427        fn check(expected: bool, flags: YearFlags, month1: u32, day1: u32, month2: u32, day2: u32) {
428            for month in month1..=month2 {
429                for day in day1..=day2 {
430                    let mdf = match Mdf::new(month, day, flags) {
431                        Some(mdf) => mdf,
432                        None if !expected => continue,
433                        None => panic!("Mdf::new({}, {}, {:?}) returned None", month, day, flags),
434                    };
435
436                    assert!(
437                        mdf.valid() == expected,
438                        "month {} day {} = {:?} should be {} for dominical year {:?}",
439                        month,
440                        day,
441                        mdf,
442                        if expected { "valid" } else { "invalid" },
443                        flags
444                    );
445                }
446            }
447        }
448
449        for &flags in NONLEAP_FLAGS.iter() {
450            check(false, flags, 0, 0, 0, 1024);
451            check(false, flags, 0, 0, 16, 0);
452            check(true, flags, 1, 1, 1, 31);
453            check(false, flags, 1, 32, 1, 1024);
454            check(true, flags, 2, 1, 2, 28);
455            check(false, flags, 2, 29, 2, 1024);
456            check(true, flags, 3, 1, 3, 31);
457            check(false, flags, 3, 32, 3, 1024);
458            check(true, flags, 4, 1, 4, 30);
459            check(false, flags, 4, 31, 4, 1024);
460            check(true, flags, 5, 1, 5, 31);
461            check(false, flags, 5, 32, 5, 1024);
462            check(true, flags, 6, 1, 6, 30);
463            check(false, flags, 6, 31, 6, 1024);
464            check(true, flags, 7, 1, 7, 31);
465            check(false, flags, 7, 32, 7, 1024);
466            check(true, flags, 8, 1, 8, 31);
467            check(false, flags, 8, 32, 8, 1024);
468            check(true, flags, 9, 1, 9, 30);
469            check(false, flags, 9, 31, 9, 1024);
470            check(true, flags, 10, 1, 10, 31);
471            check(false, flags, 10, 32, 10, 1024);
472            check(true, flags, 11, 1, 11, 30);
473            check(false, flags, 11, 31, 11, 1024);
474            check(true, flags, 12, 1, 12, 31);
475            check(false, flags, 12, 32, 12, 1024);
476            check(false, flags, 13, 0, 16, 1024);
477            check(false, flags, u32::MAX, 0, u32::MAX, 1024);
478            check(false, flags, 0, u32::MAX, 16, u32::MAX);
479            check(false, flags, u32::MAX, u32::MAX, u32::MAX, u32::MAX);
480        }
481
482        for &flags in LEAP_FLAGS.iter() {
483            check(false, flags, 0, 0, 0, 1024);
484            check(false, flags, 0, 0, 16, 0);
485            check(true, flags, 1, 1, 1, 31);
486            check(false, flags, 1, 32, 1, 1024);
487            check(true, flags, 2, 1, 2, 29);
488            check(false, flags, 2, 30, 2, 1024);
489            check(true, flags, 3, 1, 3, 31);
490            check(false, flags, 3, 32, 3, 1024);
491            check(true, flags, 4, 1, 4, 30);
492            check(false, flags, 4, 31, 4, 1024);
493            check(true, flags, 5, 1, 5, 31);
494            check(false, flags, 5, 32, 5, 1024);
495            check(true, flags, 6, 1, 6, 30);
496            check(false, flags, 6, 31, 6, 1024);
497            check(true, flags, 7, 1, 7, 31);
498            check(false, flags, 7, 32, 7, 1024);
499            check(true, flags, 8, 1, 8, 31);
500            check(false, flags, 8, 32, 8, 1024);
501            check(true, flags, 9, 1, 9, 30);
502            check(false, flags, 9, 31, 9, 1024);
503            check(true, flags, 10, 1, 10, 31);
504            check(false, flags, 10, 32, 10, 1024);
505            check(true, flags, 11, 1, 11, 30);
506            check(false, flags, 11, 31, 11, 1024);
507            check(true, flags, 12, 1, 12, 31);
508            check(false, flags, 12, 32, 12, 1024);
509            check(false, flags, 13, 0, 16, 1024);
510            check(false, flags, u32::MAX, 0, u32::MAX, 1024);
511            check(false, flags, 0, u32::MAX, 16, u32::MAX);
512            check(false, flags, u32::MAX, u32::MAX, u32::MAX, u32::MAX);
513        }
514    }
515
516    #[test]
517    fn test_mdf_fields() {
518        for &flags in FLAGS.iter() {
519            for month in 1u32..=12 {
520                for day in 1u32..31 {
521                    let mdf = match Mdf::new(month, day, flags) {
522                        Some(mdf) => mdf,
523                        None => continue,
524                    };
525
526                    if mdf.valid() {
527                        assert_eq!(mdf.month(), month);
528                        assert_eq!(mdf.day(), day);
529                    }
530                }
531            }
532        }
533    }
534
535    #[test]
536    fn test_mdf_with_fields() {
537        fn check(flags: YearFlags, month: u32, day: u32) {
538            let mdf = Mdf::new(month, day, flags).unwrap();
539
540            for month in 0u32..=16 {
541                let mdf = match mdf.with_month(month) {
542                    Some(mdf) => mdf,
543                    None if month > 12 => continue,
544                    None => panic!("failed to create Mdf with month {}", month),
545                };
546
547                if mdf.valid() {
548                    assert_eq!(mdf.month(), month);
549                    assert_eq!(mdf.day(), day);
550                }
551            }
552
553            for day in 0u32..=1024 {
554                let mdf = match mdf.with_day(day) {
555                    Some(mdf) => mdf,
556                    None if day > 31 => continue,
557                    None => panic!("failed to create Mdf with month {}", month),
558                };
559
560                if mdf.valid() {
561                    assert_eq!(mdf.month(), month);
562                    assert_eq!(mdf.day(), day);
563                }
564            }
565        }
566
567        for &flags in NONLEAP_FLAGS.iter() {
568            check(flags, 1, 1);
569            check(flags, 1, 31);
570            check(flags, 2, 1);
571            check(flags, 2, 28);
572            check(flags, 2, 29);
573            check(flags, 12, 31);
574        }
575        for &flags in LEAP_FLAGS.iter() {
576            check(flags, 1, 1);
577            check(flags, 1, 31);
578            check(flags, 2, 1);
579            check(flags, 2, 29);
580            check(flags, 2, 30);
581            check(flags, 12, 31);
582        }
583    }
584
585    #[test]
586    fn test_mdf_new_range() {
587        let flags = YearFlags::from_year(2023);
588        assert!(Mdf::new(13, 1, flags).is_none());
589        assert!(Mdf::new(1, 32, flags).is_none());
590    }
591}