1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
/*!
# UTC2K: Printable Characters.
*/
/// # Helper: `DateChar` Definition.
macro_rules! date_chars {
($($k:ident $v:literal),+ $(,)*) => (
#[cfg_attr(not(feature = "local"), expect(dead_code, reason = "Macro made me do it."))]
#[repr(u8)]
#[derive(Debug, Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
/// # Date Characters.
///
/// This enum holds the small subset of ASCII characters comprising
/// datetime strings. (It's an alternative to unqualified `u8`.)
///
/// This adds some complication to the data population side of things,
/// but reduces the "unsafe" footprint to just two methods, both
/// located here and easy to verify.
///
/// TODO: replace this with `AsciiChar` once stable.
pub(crate) enum DateChar {
$($k = $v,)+
}
impl DateChar {
#[inline(always)]
#[must_use]
/// # As Char.
///
/// Return as a single char.
pub(crate) const fn as_char(self) -> char { (self as u8) as char }
#[inline(always)]
#[must_use]
/// # As Digit.
///
/// Convert the ASCII back to a real number.
pub(crate) const fn as_digit(self) -> u8 {
debug_assert!((self as u8).is_ascii_digit(), "BUG: trying to digit a non-digit!");
self as u8 & 0b0000_1111_u8
}
#[expect(unsafe_code, reason = "For transmute.")]
#[inline(always)]
#[must_use]
/// # As Bytes.
///
/// Transmute a slice of `DateChar` into a slice of bytes.
pub(crate) const fn as_bytes(src: &[Self]) -> &[u8] {
// This check is overly-paranoid, but the compiler should
// optimize it out.
const {
assert!(
align_of::<&[Self]>() == align_of::<&[u8]>() &&
size_of::<&[Self]>() == size_of::<&[u8]>(),
"BUG: DateChar and u8 slices have different layouts?!",
);
}
// Safety: `DateChar` is represented by `u8` so shares the
// same size and alignment.
unsafe { std::mem::transmute::<&[Self], &[u8]>(src) }
}
#[expect(unsafe_code, reason = "For transmute.")]
#[inline(always)]
#[must_use]
/// # As Str.
///
/// Transmute a slice of `DateChar` into a string slice.
pub(crate) const fn as_str(src: &[Self]) -> &str {
// Safety: all `DateChar` variants are valid ASCII, so no
// matter how they're sliced up, will always yield valid UTF-8
// sequences.
unsafe { std::str::from_utf8_unchecked(Self::as_bytes(src)) }
}
#[inline(always)]
/// # One Digit.
pub(crate) const fn from_digit(src: u8) -> Self {
match (src % 10) | b'0' {
b'0' => Self::Digit0,
b'1' => Self::Digit1,
b'2' => Self::Digit2,
b'3' => Self::Digit3,
b'4' => Self::Digit4,
b'5' => Self::Digit5,
b'6' => Self::Digit6,
b'7' => Self::Digit7,
b'8' => Self::Digit8,
b'9' => Self::Digit9,
_ => unreachable!(),
}
}
}
);
}
date_chars!(
Space b' ',
Plus b'+',
Dash b'-',
Digit0 b'0',
Digit1 b'1',
Digit2 b'2',
Digit3 b'3',
Digit4 b'4',
Digit5 b'5',
Digit6 b'6',
Digit7 b'7',
Digit8 b'8',
Digit9 b'9',
Colon b':',
);