| 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 | |