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: u8) = *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: u32) = *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}
592