1//! Hackery to work around not being able to use ADTs in const generics on stable.
2
3use core::num::NonZeroU8;
4
5#[cfg(feature = "formatting")]
6use super::Iso8601;
7use 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)]
12pub 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.
18pub type EncodedConfig = DoNotRelyOnWhatThisIs;
19
20#[cfg(feature = "formatting")]
21impl<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
51impl 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)]
165mod 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