1 | //! Internal helper types for working with dates. |
2 | |
3 | #![cfg_attr (feature = "__internal_bench" , allow(missing_docs))] |
4 | |
5 | use 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)] |
19 | pub 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. |
23 | const YEAR_STARTS_AFTER_MONDAY: u8 = 7; // non-zero to allow use with `NonZero*`. |
24 | const YEAR_STARTS_AFTER_THUESDAY: u8 = 1; |
25 | const YEAR_STARTS_AFTER_WEDNESDAY: u8 = 2; |
26 | const YEAR_STARTS_AFTER_THURSDAY: u8 = 3; |
27 | const YEAR_STARTS_AFTER_FRIDAY: u8 = 4; |
28 | const YEAR_STARTS_AFTER_SATURDAY: u8 = 5; |
29 | const YEAR_STARTS_AFTER_SUNDAY: u8 = 6; |
30 | |
31 | const COMMON_YEAR: u8 = 1 << 3; |
32 | const LEAP_YEAR: u8 = 0 << 3; |
33 | |
34 | pub(super) const A: YearFlags = YearFlags(COMMON_YEAR | YEAR_STARTS_AFTER_SATURDAY); |
35 | pub(super) const AG: YearFlags = YearFlags(LEAP_YEAR | YEAR_STARTS_AFTER_SATURDAY); |
36 | pub(super) const B: YearFlags = YearFlags(COMMON_YEAR | YEAR_STARTS_AFTER_FRIDAY); |
37 | pub(super) const BA: YearFlags = YearFlags(LEAP_YEAR | YEAR_STARTS_AFTER_FRIDAY); |
38 | pub(super) const C: YearFlags = YearFlags(COMMON_YEAR | YEAR_STARTS_AFTER_THURSDAY); |
39 | pub(super) const CB: YearFlags = YearFlags(LEAP_YEAR | YEAR_STARTS_AFTER_THURSDAY); |
40 | pub(super) const D: YearFlags = YearFlags(COMMON_YEAR | YEAR_STARTS_AFTER_WEDNESDAY); |
41 | pub(super) const DC: YearFlags = YearFlags(LEAP_YEAR | YEAR_STARTS_AFTER_WEDNESDAY); |
42 | pub(super) const E: YearFlags = YearFlags(COMMON_YEAR | YEAR_STARTS_AFTER_THUESDAY); |
43 | pub(super) const ED: YearFlags = YearFlags(LEAP_YEAR | YEAR_STARTS_AFTER_THUESDAY); |
44 | pub(super) const F: YearFlags = YearFlags(COMMON_YEAR | YEAR_STARTS_AFTER_MONDAY); |
45 | pub(super) const FE: YearFlags = YearFlags(LEAP_YEAR | YEAR_STARTS_AFTER_MONDAY); |
46 | pub(super) const G: YearFlags = YearFlags(COMMON_YEAR | YEAR_STARTS_AFTER_SUNDAY); |
47 | pub(super) const GF: YearFlags = YearFlags(LEAP_YEAR | YEAR_STARTS_AFTER_SUNDAY); |
48 | |
49 | const 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 | |
68 | impl 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 | |
106 | impl 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 |
132 | const MAX_OL: u32 = 366 << 1; // `(366 << 1) | 1` would be day 366 in a non-leap year |
133 | const 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`. |
138 | const XX: i8 = 0; |
139 | const 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 | |
182 | const 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)] |
237 | pub(super) struct Mdf(u32); |
238 | |
239 | impl 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 | |
367 | impl 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)] |
382 | mod 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 | |