| 1 | //! Hackery to work around not being able to use ADTs in const generics on stable. |
| 2 | |
| 3 | use core::num::NonZeroU8; |
| 4 | |
| 5 | #[cfg (feature = "formatting" )] |
| 6 | use super::Iso8601; |
| 7 | use super::{Config, DateKind, FormattedComponents as FC, OffsetPrecision, TimePrecision}; |
| 8 | |
| 9 | // This provides a way to include `EncodedConfig` in documentation without displaying the type it is |
| 10 | // aliased to. |
| 11 | #[doc (hidden)] |
| 12 | pub type DoNotRelyOnWhatThisIs = u128; |
| 13 | |
| 14 | /// An encoded [`Config`] that can be used as a const parameter to [`Iso8601`](super::Iso8601). |
| 15 | /// |
| 16 | /// The type this is aliased to must not be relied upon. It can change in any release without |
| 17 | /// notice. |
| 18 | pub type EncodedConfig = DoNotRelyOnWhatThisIs; |
| 19 | |
| 20 | #[cfg (feature = "formatting" )] |
| 21 | impl<const CONFIG: EncodedConfig> Iso8601<CONFIG> { |
| 22 | /// The user-provided configuration for the ISO 8601 format. |
| 23 | const CONFIG: Config = Config::decode(CONFIG); |
| 24 | /// Whether the date should be formatted. |
| 25 | pub(crate) const FORMAT_DATE: bool = matches!( |
| 26 | Self::CONFIG.formatted_components, |
| 27 | FC::Date | FC::DateTime | FC::DateTimeOffset |
| 28 | ); |
| 29 | /// Whether the time should be formatted. |
| 30 | pub(crate) const FORMAT_TIME: bool = matches!( |
| 31 | Self::CONFIG.formatted_components, |
| 32 | FC::Time | FC::DateTime | FC::DateTimeOffset | FC::TimeOffset |
| 33 | ); |
| 34 | /// Whether the UTC offset should be formatted. |
| 35 | pub(crate) const FORMAT_OFFSET: bool = matches!( |
| 36 | Self::CONFIG.formatted_components, |
| 37 | FC::Offset | FC::DateTimeOffset | FC::TimeOffset |
| 38 | ); |
| 39 | /// Whether the year is six digits. |
| 40 | pub(crate) const YEAR_IS_SIX_DIGITS: bool = Self::CONFIG.year_is_six_digits; |
| 41 | /// Whether the format contains separators (such as `-` or `:`). |
| 42 | pub(crate) const USE_SEPARATORS: bool = Self::CONFIG.use_separators; |
| 43 | /// Which format to use for the date. |
| 44 | pub(crate) const DATE_KIND: DateKind = Self::CONFIG.date_kind; |
| 45 | /// The precision and number of decimal digits to use for the time. |
| 46 | pub(crate) const TIME_PRECISION: TimePrecision = Self::CONFIG.time_precision; |
| 47 | /// The precision for the UTC offset. |
| 48 | pub(crate) const OFFSET_PRECISION: OffsetPrecision = Self::CONFIG.offset_precision; |
| 49 | } |
| 50 | |
| 51 | impl Config { |
| 52 | /// Encode the configuration, permitting it to be used as a const parameter of [`Iso8601`]. |
| 53 | /// |
| 54 | /// The value returned by this method must only be used as a const parameter to [`Iso8601`]. Any |
| 55 | /// other usage is unspecified behavior. |
| 56 | pub const fn encode(&self) -> EncodedConfig { |
| 57 | let mut bytes = [0; EncodedConfig::BITS as usize / 8]; |
| 58 | |
| 59 | bytes[0] = match self.formatted_components { |
| 60 | FC::None => 0, |
| 61 | FC::Date => 1, |
| 62 | FC::Time => 2, |
| 63 | FC::Offset => 3, |
| 64 | FC::DateTime => 4, |
| 65 | FC::DateTimeOffset => 5, |
| 66 | FC::TimeOffset => 6, |
| 67 | }; |
| 68 | bytes[1] = self.use_separators as _; |
| 69 | bytes[2] = self.year_is_six_digits as _; |
| 70 | bytes[3] = match self.date_kind { |
| 71 | DateKind::Calendar => 0, |
| 72 | DateKind::Week => 1, |
| 73 | DateKind::Ordinal => 2, |
| 74 | }; |
| 75 | bytes[4] = match self.time_precision { |
| 76 | TimePrecision::Hour { .. } => 0, |
| 77 | TimePrecision::Minute { .. } => 1, |
| 78 | TimePrecision::Second { .. } => 2, |
| 79 | }; |
| 80 | bytes[5] = match self.time_precision { |
| 81 | TimePrecision::Hour { decimal_digits } |
| 82 | | TimePrecision::Minute { decimal_digits } |
| 83 | | TimePrecision::Second { decimal_digits } => match decimal_digits { |
| 84 | None => 0, |
| 85 | Some(decimal_digits) => decimal_digits.get(), |
| 86 | }, |
| 87 | }; |
| 88 | bytes[6] = match self.offset_precision { |
| 89 | OffsetPrecision::Hour => 0, |
| 90 | OffsetPrecision::Minute => 1, |
| 91 | }; |
| 92 | |
| 93 | EncodedConfig::from_be_bytes(bytes) |
| 94 | } |
| 95 | |
| 96 | /// Decode the configuration. The configuration must have been generated from |
| 97 | /// [`Config::encode`]. |
| 98 | pub(super) const fn decode(encoded: EncodedConfig) -> Self { |
| 99 | let bytes = encoded.to_be_bytes(); |
| 100 | |
| 101 | let formatted_components = match bytes[0] { |
| 102 | 0 => FC::None, |
| 103 | 1 => FC::Date, |
| 104 | 2 => FC::Time, |
| 105 | 3 => FC::Offset, |
| 106 | 4 => FC::DateTime, |
| 107 | 5 => FC::DateTimeOffset, |
| 108 | 6 => FC::TimeOffset, |
| 109 | _ => panic!("invalid configuration" ), |
| 110 | }; |
| 111 | let use_separators = match bytes[1] { |
| 112 | 0 => false, |
| 113 | 1 => true, |
| 114 | _ => panic!("invalid configuration" ), |
| 115 | }; |
| 116 | let year_is_six_digits = match bytes[2] { |
| 117 | 0 => false, |
| 118 | 1 => true, |
| 119 | _ => panic!("invalid configuration" ), |
| 120 | }; |
| 121 | let date_kind = match bytes[3] { |
| 122 | 0 => DateKind::Calendar, |
| 123 | 1 => DateKind::Week, |
| 124 | 2 => DateKind::Ordinal, |
| 125 | _ => panic!("invalid configuration" ), |
| 126 | }; |
| 127 | let time_precision = match bytes[4] { |
| 128 | 0 => TimePrecision::Hour { |
| 129 | decimal_digits: NonZeroU8::new(bytes[5]), |
| 130 | }, |
| 131 | 1 => TimePrecision::Minute { |
| 132 | decimal_digits: NonZeroU8::new(bytes[5]), |
| 133 | }, |
| 134 | 2 => TimePrecision::Second { |
| 135 | decimal_digits: NonZeroU8::new(bytes[5]), |
| 136 | }, |
| 137 | _ => panic!("invalid configuration" ), |
| 138 | }; |
| 139 | let offset_precision = match bytes[6] { |
| 140 | 0 => OffsetPrecision::Hour, |
| 141 | 1 => OffsetPrecision::Minute, |
| 142 | _ => panic!("invalid configuration" ), |
| 143 | }; |
| 144 | |
| 145 | // No `for` loops in `const fn`. |
| 146 | let mut idx = 7; // first unused byte |
| 147 | while idx < EncodedConfig::BITS as usize / 8 { |
| 148 | assert!(bytes[idx] == 0, "invalid configuration" ); |
| 149 | idx += 1; |
| 150 | } |
| 151 | |
| 152 | Self { |
| 153 | formatted_components, |
| 154 | use_separators, |
| 155 | year_is_six_digits, |
| 156 | date_kind, |
| 157 | time_precision, |
| 158 | offset_precision, |
| 159 | } |
| 160 | } |
| 161 | } |
| 162 | |
| 163 | #[cfg (test)] |
| 164 | mod tests { |
| 165 | use super::*; |
| 166 | |
| 167 | macro_rules! eq { |
| 168 | ($a:expr, $b:expr) => {{ |
| 169 | let a = $a; |
| 170 | let b = $b; |
| 171 | a.formatted_components == b.formatted_components |
| 172 | && a.use_separators == b.use_separators |
| 173 | && a.year_is_six_digits == b.year_is_six_digits |
| 174 | && a.date_kind == b.date_kind |
| 175 | && a.time_precision == b.time_precision |
| 176 | && a.offset_precision == b.offset_precision |
| 177 | }}; |
| 178 | } |
| 179 | |
| 180 | #[test ] |
| 181 | fn encoding_roundtrip() { |
| 182 | macro_rules! assert_roundtrip { |
| 183 | ($config:expr) => { |
| 184 | let config = $config; |
| 185 | let encoded = config.encode(); |
| 186 | let decoded = Config::decode(encoded); |
| 187 | assert!(eq!(config, decoded)); |
| 188 | }; |
| 189 | } |
| 190 | |
| 191 | assert_roundtrip!(Config::DEFAULT); |
| 192 | assert_roundtrip!(Config::DEFAULT.set_formatted_components(FC::None)); |
| 193 | assert_roundtrip!(Config::DEFAULT.set_formatted_components(FC::Date)); |
| 194 | assert_roundtrip!(Config::DEFAULT.set_formatted_components(FC::Time)); |
| 195 | assert_roundtrip!(Config::DEFAULT.set_formatted_components(FC::Offset)); |
| 196 | assert_roundtrip!(Config::DEFAULT.set_formatted_components(FC::DateTime)); |
| 197 | assert_roundtrip!(Config::DEFAULT.set_formatted_components(FC::DateTimeOffset)); |
| 198 | assert_roundtrip!(Config::DEFAULT.set_formatted_components(FC::TimeOffset)); |
| 199 | assert_roundtrip!(Config::DEFAULT.set_use_separators(false)); |
| 200 | assert_roundtrip!(Config::DEFAULT.set_use_separators(true)); |
| 201 | assert_roundtrip!(Config::DEFAULT.set_year_is_six_digits(false)); |
| 202 | assert_roundtrip!(Config::DEFAULT.set_year_is_six_digits(true)); |
| 203 | assert_roundtrip!(Config::DEFAULT.set_date_kind(DateKind::Calendar)); |
| 204 | assert_roundtrip!(Config::DEFAULT.set_date_kind(DateKind::Week)); |
| 205 | assert_roundtrip!(Config::DEFAULT.set_date_kind(DateKind::Ordinal)); |
| 206 | assert_roundtrip!(Config::DEFAULT.set_time_precision(TimePrecision::Hour { |
| 207 | decimal_digits: None, |
| 208 | })); |
| 209 | assert_roundtrip!(Config::DEFAULT.set_time_precision(TimePrecision::Minute { |
| 210 | decimal_digits: None, |
| 211 | })); |
| 212 | assert_roundtrip!(Config::DEFAULT.set_time_precision(TimePrecision::Second { |
| 213 | decimal_digits: None, |
| 214 | })); |
| 215 | assert_roundtrip!(Config::DEFAULT.set_time_precision(TimePrecision::Hour { |
| 216 | decimal_digits: NonZeroU8::new(1), |
| 217 | })); |
| 218 | assert_roundtrip!(Config::DEFAULT.set_time_precision(TimePrecision::Minute { |
| 219 | decimal_digits: NonZeroU8::new(1), |
| 220 | })); |
| 221 | assert_roundtrip!(Config::DEFAULT.set_time_precision(TimePrecision::Second { |
| 222 | decimal_digits: NonZeroU8::new(1), |
| 223 | })); |
| 224 | assert_roundtrip!(Config::DEFAULT.set_offset_precision(OffsetPrecision::Hour)); |
| 225 | assert_roundtrip!(Config::DEFAULT.set_offset_precision(OffsetPrecision::Minute)); |
| 226 | } |
| 227 | |
| 228 | macro_rules! assert_decode_fail { |
| 229 | ($encoding:expr) => { |
| 230 | assert!(std::panic::catch_unwind(|| { |
| 231 | Config::decode($encoding); |
| 232 | }) |
| 233 | .is_err()); |
| 234 | }; |
| 235 | } |
| 236 | |
| 237 | #[test ] |
| 238 | fn decode_fail() { |
| 239 | assert_decode_fail!(0x07_00_00_00_00_00_00_00_00_00_00_00_00_00_00_00); |
| 240 | assert_decode_fail!(0x00_02_00_00_00_00_00_00_00_00_00_00_00_00_00_00); |
| 241 | assert_decode_fail!(0x00_00_02_00_00_00_00_00_00_00_00_00_00_00_00_00); |
| 242 | assert_decode_fail!(0x00_00_00_03_00_00_00_00_00_00_00_00_00_00_00_00); |
| 243 | assert_decode_fail!(0x00_00_00_00_03_00_00_00_00_00_00_00_00_00_00_00); |
| 244 | assert_decode_fail!(0x00_00_00_00_00_00_02_00_00_00_00_00_00_00_00_00); |
| 245 | assert_decode_fail!(0x00_00_00_00_00_00_00_01_00_00_00_00_00_00_00_00); |
| 246 | } |
| 247 | } |
| 248 | |