time/format_description/well_known/iso8601/
adt_hack.rs

1//! Hackery to work around not being able to use ADTs in const generics on stable.
2
3use core::num::NonZero;
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 [`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 u8;
69        bytes[2] = self.year_is_six_digits as u8;
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: NonZero::new(bytes[5]),
130            },
131            1 => TimePrecision::Minute {
132                decimal_digits: NonZero::new(bytes[5]),
133            },
134            2 => TimePrecision::Second {
135                decimal_digits: NonZero::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            if bytes[idx] != 0 {
149                panic!("invalid configuration");
150            }
151            idx += 1;
152        }
153
154        Self {
155            formatted_components,
156            use_separators,
157            year_is_six_digits,
158            date_kind,
159            time_precision,
160            offset_precision,
161        }
162    }
163}
164
165#[cfg(test)]
166mod tests {
167    use super::*;
168
169    macro_rules! eq {
170        ($a:expr, $b:expr) => {{
171            let a = $a;
172            let b = $b;
173            a.formatted_components == b.formatted_components
174                && a.use_separators == b.use_separators
175                && a.year_is_six_digits == b.year_is_six_digits
176                && a.date_kind == b.date_kind
177                && a.time_precision == b.time_precision
178                && a.offset_precision == b.offset_precision
179        }};
180    }
181
182    #[test]
183    fn encoding_roundtrip() {
184        macro_rules! assert_roundtrip {
185            ($config:expr) => {
186                let config = $config;
187                let encoded = config.encode();
188                let decoded = Config::decode(encoded);
189                assert!(eq!(config, decoded));
190            };
191        }
192
193        assert_roundtrip!(Config::DEFAULT);
194        assert_roundtrip!(Config::DEFAULT.set_formatted_components(FC::None));
195        assert_roundtrip!(Config::DEFAULT.set_formatted_components(FC::Date));
196        assert_roundtrip!(Config::DEFAULT.set_formatted_components(FC::Time));
197        assert_roundtrip!(Config::DEFAULT.set_formatted_components(FC::Offset));
198        assert_roundtrip!(Config::DEFAULT.set_formatted_components(FC::DateTime));
199        assert_roundtrip!(Config::DEFAULT.set_formatted_components(FC::DateTimeOffset));
200        assert_roundtrip!(Config::DEFAULT.set_formatted_components(FC::TimeOffset));
201        assert_roundtrip!(Config::DEFAULT.set_use_separators(false));
202        assert_roundtrip!(Config::DEFAULT.set_use_separators(true));
203        assert_roundtrip!(Config::DEFAULT.set_year_is_six_digits(false));
204        assert_roundtrip!(Config::DEFAULT.set_year_is_six_digits(true));
205        assert_roundtrip!(Config::DEFAULT.set_date_kind(DateKind::Calendar));
206        assert_roundtrip!(Config::DEFAULT.set_date_kind(DateKind::Week));
207        assert_roundtrip!(Config::DEFAULT.set_date_kind(DateKind::Ordinal));
208        assert_roundtrip!(Config::DEFAULT.set_time_precision(TimePrecision::Hour {
209            decimal_digits: None,
210        }));
211        assert_roundtrip!(Config::DEFAULT.set_time_precision(TimePrecision::Minute {
212            decimal_digits: None,
213        }));
214        assert_roundtrip!(Config::DEFAULT.set_time_precision(TimePrecision::Second {
215            decimal_digits: None,
216        }));
217        assert_roundtrip!(Config::DEFAULT.set_time_precision(TimePrecision::Hour {
218            decimal_digits: NonZero::new(1),
219        }));
220        assert_roundtrip!(Config::DEFAULT.set_time_precision(TimePrecision::Minute {
221            decimal_digits: NonZero::new(1),
222        }));
223        assert_roundtrip!(Config::DEFAULT.set_time_precision(TimePrecision::Second {
224            decimal_digits: NonZero::new(1),
225        }));
226        assert_roundtrip!(Config::DEFAULT.set_offset_precision(OffsetPrecision::Hour));
227        assert_roundtrip!(Config::DEFAULT.set_offset_precision(OffsetPrecision::Minute));
228    }
229
230    macro_rules! assert_decode_fail {
231        ($encoding:expr) => {
232            assert!(
233                std::panic::catch_unwind(|| {
234                    Config::decode($encoding);
235                })
236                .is_err()
237            );
238        };
239    }
240
241    #[test]
242    fn decode_fail() {
243        assert_decode_fail!(0x07_00_00_00_00_00_00_00_00_00_00_00_00_00_00_00);
244        assert_decode_fail!(0x00_02_00_00_00_00_00_00_00_00_00_00_00_00_00_00);
245        assert_decode_fail!(0x00_00_02_00_00_00_00_00_00_00_00_00_00_00_00_00);
246        assert_decode_fail!(0x00_00_00_03_00_00_00_00_00_00_00_00_00_00_00_00);
247        assert_decode_fail!(0x00_00_00_00_03_00_00_00_00_00_00_00_00_00_00_00);
248        assert_decode_fail!(0x00_00_00_00_00_00_02_00_00_00_00_00_00_00_00_00);
249        assert_decode_fail!(0x00_00_00_00_00_00_00_01_00_00_00_00_00_00_00_00);
250    }
251}