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 |
53 | /// [`Iso8601`](super::Iso8601). |
54 | /// |
55 | /// The value returned by this method must only be used as a const parameter to |
56 | /// [`Iso8601`](super::Iso8601). Any other usage is unspecified behavior. |
57 | pub const fn encode(&self) -> EncodedConfig { |
58 | let mut bytes = [0; EncodedConfig::BITS as usize / 8]; |
59 | |
60 | bytes[0] = match self.formatted_components { |
61 | FC::None => 0, |
62 | FC::Date => 1, |
63 | FC::Time => 2, |
64 | FC::Offset => 3, |
65 | FC::DateTime => 4, |
66 | FC::DateTimeOffset => 5, |
67 | FC::TimeOffset => 6, |
68 | }; |
69 | bytes[1] = self.use_separators as _; |
70 | bytes[2] = self.year_is_six_digits as _; |
71 | bytes[3] = match self.date_kind { |
72 | DateKind::Calendar => 0, |
73 | DateKind::Week => 1, |
74 | DateKind::Ordinal => 2, |
75 | }; |
76 | bytes[4] = match self.time_precision { |
77 | TimePrecision::Hour { .. } => 0, |
78 | TimePrecision::Minute { .. } => 1, |
79 | TimePrecision::Second { .. } => 2, |
80 | }; |
81 | bytes[5] = match self.time_precision { |
82 | TimePrecision::Hour { decimal_digits } |
83 | | TimePrecision::Minute { decimal_digits } |
84 | | TimePrecision::Second { decimal_digits } => match decimal_digits { |
85 | None => 0, |
86 | Some(decimal_digits) => decimal_digits.get(), |
87 | }, |
88 | }; |
89 | bytes[6] = match self.offset_precision { |
90 | OffsetPrecision::Hour => 0, |
91 | OffsetPrecision::Minute => 1, |
92 | }; |
93 | |
94 | EncodedConfig::from_be_bytes(bytes) |
95 | } |
96 | |
97 | /// Decode the configuration. The configuration must have been generated from |
98 | /// [`Config::encode`]. |
99 | pub(super) const fn decode(encoded: EncodedConfig) -> Self { |
100 | let bytes = encoded.to_be_bytes(); |
101 | |
102 | let formatted_components = match bytes[0] { |
103 | 0 => FC::None, |
104 | 1 => FC::Date, |
105 | 2 => FC::Time, |
106 | 3 => FC::Offset, |
107 | 4 => FC::DateTime, |
108 | 5 => FC::DateTimeOffset, |
109 | 6 => FC::TimeOffset, |
110 | _ => panic!("invalid configuration" ), |
111 | }; |
112 | let use_separators = match bytes[1] { |
113 | 0 => false, |
114 | 1 => true, |
115 | _ => panic!("invalid configuration" ), |
116 | }; |
117 | let year_is_six_digits = match bytes[2] { |
118 | 0 => false, |
119 | 1 => true, |
120 | _ => panic!("invalid configuration" ), |
121 | }; |
122 | let date_kind = match bytes[3] { |
123 | 0 => DateKind::Calendar, |
124 | 1 => DateKind::Week, |
125 | 2 => DateKind::Ordinal, |
126 | _ => panic!("invalid configuration" ), |
127 | }; |
128 | let time_precision = match bytes[4] { |
129 | 0 => TimePrecision::Hour { |
130 | decimal_digits: NonZeroU8::new(bytes[5]), |
131 | }, |
132 | 1 => TimePrecision::Minute { |
133 | decimal_digits: NonZeroU8::new(bytes[5]), |
134 | }, |
135 | 2 => TimePrecision::Second { |
136 | decimal_digits: NonZeroU8::new(bytes[5]), |
137 | }, |
138 | _ => panic!("invalid configuration" ), |
139 | }; |
140 | let offset_precision = match bytes[6] { |
141 | 0 => OffsetPrecision::Hour, |
142 | 1 => OffsetPrecision::Minute, |
143 | _ => panic!("invalid configuration" ), |
144 | }; |
145 | |
146 | // No `for` loops in `const fn`. |
147 | let mut idx = 7; // first unused byte |
148 | while idx < EncodedConfig::BITS as usize / 8 { |
149 | assert!(bytes[idx] == 0, "invalid configuration" ); |
150 | idx += 1; |
151 | } |
152 | |
153 | Self { |
154 | formatted_components, |
155 | use_separators, |
156 | year_is_six_digits, |
157 | date_kind, |
158 | time_precision, |
159 | offset_precision, |
160 | } |
161 | } |
162 | } |
163 | |
164 | #[cfg (test)] |
165 | mod tests { |
166 | use super::*; |
167 | |
168 | macro_rules! eq { |
169 | ($a:expr, $b:expr) => {{ |
170 | let a = $a; |
171 | let b = $b; |
172 | a.formatted_components == b.formatted_components |
173 | && a.use_separators == b.use_separators |
174 | && a.year_is_six_digits == b.year_is_six_digits |
175 | && a.date_kind == b.date_kind |
176 | && a.time_precision == b.time_precision |
177 | && a.offset_precision == b.offset_precision |
178 | }}; |
179 | } |
180 | |
181 | #[test ] |
182 | fn encoding_roundtrip() { |
183 | macro_rules! assert_roundtrip { |
184 | ($config:expr) => { |
185 | let config = $config; |
186 | let encoded = config.encode(); |
187 | let decoded = Config::decode(encoded); |
188 | assert!(eq!(config, decoded)); |
189 | }; |
190 | } |
191 | |
192 | assert_roundtrip!(Config::DEFAULT); |
193 | assert_roundtrip!(Config::DEFAULT.set_formatted_components(FC::None)); |
194 | assert_roundtrip!(Config::DEFAULT.set_formatted_components(FC::Date)); |
195 | assert_roundtrip!(Config::DEFAULT.set_formatted_components(FC::Time)); |
196 | assert_roundtrip!(Config::DEFAULT.set_formatted_components(FC::Offset)); |
197 | assert_roundtrip!(Config::DEFAULT.set_formatted_components(FC::DateTime)); |
198 | assert_roundtrip!(Config::DEFAULT.set_formatted_components(FC::DateTimeOffset)); |
199 | assert_roundtrip!(Config::DEFAULT.set_formatted_components(FC::TimeOffset)); |
200 | assert_roundtrip!(Config::DEFAULT.set_use_separators(false)); |
201 | assert_roundtrip!(Config::DEFAULT.set_use_separators(true)); |
202 | assert_roundtrip!(Config::DEFAULT.set_year_is_six_digits(false)); |
203 | assert_roundtrip!(Config::DEFAULT.set_year_is_six_digits(true)); |
204 | assert_roundtrip!(Config::DEFAULT.set_date_kind(DateKind::Calendar)); |
205 | assert_roundtrip!(Config::DEFAULT.set_date_kind(DateKind::Week)); |
206 | assert_roundtrip!(Config::DEFAULT.set_date_kind(DateKind::Ordinal)); |
207 | assert_roundtrip!(Config::DEFAULT.set_time_precision(TimePrecision::Hour { |
208 | decimal_digits: None, |
209 | })); |
210 | assert_roundtrip!(Config::DEFAULT.set_time_precision(TimePrecision::Minute { |
211 | decimal_digits: None, |
212 | })); |
213 | assert_roundtrip!(Config::DEFAULT.set_time_precision(TimePrecision::Second { |
214 | decimal_digits: None, |
215 | })); |
216 | assert_roundtrip!(Config::DEFAULT.set_time_precision(TimePrecision::Hour { |
217 | decimal_digits: NonZeroU8::new(1), |
218 | })); |
219 | assert_roundtrip!(Config::DEFAULT.set_time_precision(TimePrecision::Minute { |
220 | decimal_digits: NonZeroU8::new(1), |
221 | })); |
222 | assert_roundtrip!(Config::DEFAULT.set_time_precision(TimePrecision::Second { |
223 | decimal_digits: NonZeroU8::new(1), |
224 | })); |
225 | assert_roundtrip!(Config::DEFAULT.set_offset_precision(OffsetPrecision::Hour)); |
226 | assert_roundtrip!(Config::DEFAULT.set_offset_precision(OffsetPrecision::Minute)); |
227 | } |
228 | |
229 | macro_rules! assert_decode_fail { |
230 | ($encoding:expr) => { |
231 | assert!( |
232 | std::panic::catch_unwind(|| { |
233 | Config::decode($encoding); |
234 | }) |
235 | .is_err() |
236 | ); |
237 | }; |
238 | } |
239 | |
240 | #[test ] |
241 | fn decode_fail() { |
242 | assert_decode_fail!(0x07_00_00_00_00_00_00_00_00_00_00_00_00_00_00_00); |
243 | assert_decode_fail!(0x00_02_00_00_00_00_00_00_00_00_00_00_00_00_00_00); |
244 | assert_decode_fail!(0x00_00_02_00_00_00_00_00_00_00_00_00_00_00_00_00); |
245 | assert_decode_fail!(0x00_00_00_03_00_00_00_00_00_00_00_00_00_00_00_00); |
246 | assert_decode_fail!(0x00_00_00_00_03_00_00_00_00_00_00_00_00_00_00_00); |
247 | assert_decode_fail!(0x00_00_00_00_00_00_02_00_00_00_00_00_00_00_00_00); |
248 | assert_decode_fail!(0x00_00_00_00_00_00_00_01_00_00_00_00_00_00_00_00); |
249 | } |
250 | } |
251 | |