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};
8use crate::panic;
9
10// This provides a way to include `EncodedConfig` in documentation without displaying the type it is
11// aliased to.
12#[doc(hidden)]
13pub type DoNotRelyOnWhatThisIs = u128;
14
15/// An encoded [`Config`] that can be used as a const parameter to [`Iso8601`](super::Iso8601).
16///
17/// The type this is aliased to must not be relied upon. It can change in any release without
18/// notice.
19pub type EncodedConfig = DoNotRelyOnWhatThisIs;
20
21#[cfg(feature = "formatting")]
22impl<const CONFIG: EncodedConfig> Iso8601<CONFIG> {
23    /// The user-provided configuration for the ISO 8601 format.
24    const CONFIG: Config = Config::decode(CONFIG);
25    /// Whether the date should be formatted.
26    pub(crate) const FORMAT_DATE: bool = matches!(
27        Self::CONFIG.formatted_components,
28        FC::Date | FC::DateTime | FC::DateTimeOffset
29    );
30    /// Whether the time should be formatted.
31    pub(crate) const FORMAT_TIME: bool = matches!(
32        Self::CONFIG.formatted_components,
33        FC::Time | FC::DateTime | FC::DateTimeOffset | FC::TimeOffset
34    );
35    /// Whether the UTC offset should be formatted.
36    pub(crate) const FORMAT_OFFSET: bool = matches!(
37        Self::CONFIG.formatted_components,
38        FC::Offset | FC::DateTimeOffset | FC::TimeOffset
39    );
40    /// Whether the year is six digits.
41    pub(crate) const YEAR_IS_SIX_DIGITS: bool = Self::CONFIG.year_is_six_digits;
42    /// Whether the format contains separators (such as `-` or `:`).
43    pub(crate) const USE_SEPARATORS: bool = Self::CONFIG.use_separators;
44    /// Which format to use for the date.
45    pub(crate) const DATE_KIND: DateKind = Self::CONFIG.date_kind;
46    /// The precision and number of decimal digits to use for the time.
47    pub(crate) const TIME_PRECISION: TimePrecision = Self::CONFIG.time_precision;
48    /// The precision for the UTC offset.
49    pub(crate) const OFFSET_PRECISION: OffsetPrecision = Self::CONFIG.offset_precision;
50}
51
52impl Config {
53    /// Encode the configuration, permitting it to be used as a const parameter of [`Iso8601`].
54    ///
55    /// The value returned by this method must only be used as a const parameter to [`Iso8601`]. Any
56    /// 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 u8;
70        bytes[2] = self.year_is_six_digits as u8;
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: NonZero::new(bytes[5]),
131            },
132            1 => TimePrecision::Minute {
133                decimal_digits: NonZero::new(bytes[5]),
134            },
135            2 => TimePrecision::Second {
136                decimal_digits: NonZero::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            if bytes[idx] != 0 {
150                panic("invalid configuration");
151            }
152            idx += 1;
153        }
154
155        Self {
156            formatted_components,
157            use_separators,
158            year_is_six_digits,
159            date_kind,
160            time_precision,
161            offset_precision,
162        }
163    }
164}
165
166#[cfg(test)]
167mod tests {
168    use super::*;
169
170    macro_rules! eq {
171        ($a:expr, $b:expr) => {{
172            let a = $a;
173            let b = $b;
174            a.formatted_components == b.formatted_components
175                && a.use_separators == b.use_separators
176                && a.year_is_six_digits == b.year_is_six_digits
177                && a.date_kind == b.date_kind
178                && a.time_precision == b.time_precision
179                && a.offset_precision == b.offset_precision
180        }};
181    }
182
183    #[test]
184    fn encoding_roundtrip() {
185        macro_rules! assert_roundtrip {
186            ($config:expr) => {
187                let config = $config;
188                let encoded = config.encode();
189                let decoded = Config::decode(encoded);
190                assert!(eq!(config, decoded));
191            };
192        }
193
194        assert_roundtrip!(Config::DEFAULT);
195        assert_roundtrip!(Config::DEFAULT.set_formatted_components(FC::None));
196        assert_roundtrip!(Config::DEFAULT.set_formatted_components(FC::Date));
197        assert_roundtrip!(Config::DEFAULT.set_formatted_components(FC::Time));
198        assert_roundtrip!(Config::DEFAULT.set_formatted_components(FC::Offset));
199        assert_roundtrip!(Config::DEFAULT.set_formatted_components(FC::DateTime));
200        assert_roundtrip!(Config::DEFAULT.set_formatted_components(FC::DateTimeOffset));
201        assert_roundtrip!(Config::DEFAULT.set_formatted_components(FC::TimeOffset));
202        assert_roundtrip!(Config::DEFAULT.set_use_separators(false));
203        assert_roundtrip!(Config::DEFAULT.set_use_separators(true));
204        assert_roundtrip!(Config::DEFAULT.set_year_is_six_digits(false));
205        assert_roundtrip!(Config::DEFAULT.set_year_is_six_digits(true));
206        assert_roundtrip!(Config::DEFAULT.set_date_kind(DateKind::Calendar));
207        assert_roundtrip!(Config::DEFAULT.set_date_kind(DateKind::Week));
208        assert_roundtrip!(Config::DEFAULT.set_date_kind(DateKind::Ordinal));
209        assert_roundtrip!(Config::DEFAULT.set_time_precision(TimePrecision::Hour {
210            decimal_digits: None,
211        }));
212        assert_roundtrip!(Config::DEFAULT.set_time_precision(TimePrecision::Minute {
213            decimal_digits: None,
214        }));
215        assert_roundtrip!(Config::DEFAULT.set_time_precision(TimePrecision::Second {
216            decimal_digits: None,
217        }));
218        assert_roundtrip!(Config::DEFAULT.set_time_precision(TimePrecision::Hour {
219            decimal_digits: NonZero::new(1),
220        }));
221        assert_roundtrip!(Config::DEFAULT.set_time_precision(TimePrecision::Minute {
222            decimal_digits: NonZero::new(1),
223        }));
224        assert_roundtrip!(Config::DEFAULT.set_time_precision(TimePrecision::Second {
225            decimal_digits: NonZero::new(1),
226        }));
227        assert_roundtrip!(Config::DEFAULT.set_offset_precision(OffsetPrecision::Hour));
228        assert_roundtrip!(Config::DEFAULT.set_offset_precision(OffsetPrecision::Minute));
229    }
230
231    macro_rules! assert_decode_fail {
232        ($encoding:expr) => {
233            assert!(std::panic::catch_unwind(|| {
234                Config::decode($encoding);
235            })
236            .is_err());
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}