datetime_string/parse/
digits4.rs

1//! Parser functions for a string with 4 digits.
2
3/// Parses 4 digits effectively in little endian.
4///
5/// # Failures
6///
7/// Note that this returns meaningless value for strings with non-digit characters.
8/// This is safe in a sense that this never cause UB for any input, but callers
9/// should ensure that the bytes consists of only ASCII digits.
10#[cfg(any(not(target_endian = "big"), test))]
11#[must_use]
12fn parse_digits4_le(digits: [u8; 4]) -> u16 {
13    // Sample: digits == "1234" (i.e. digits == [0x31, 0x32, 0x33, 0x34]).
14
15    // Sample: chunk1 == 0x34_33_32_31.
16    let chunk1 = u32::from_le_bytes(digits);
17
18    /// Mask for 2nd and 4th significant digits.
19    const LOWER_MASK_1: u32 = 0x0f_00_0f_00;
20    /// Mask for 1st and 3rd significant digits.
21    const UPPER_MASK_1: u32 = 0x00_0f_00_0f;
22
23    // Sample: (chunk1 & LOWER_MASK_1) == 0x04_00_02_00.
24    // Sample: (chunk1 & LOWER_MASK_1) >> 8 == 0x00_04_00_02 (i.e. u32::from_be([0, 4, 0, 2]).
25    let chunk1_lower = (chunk1 & LOWER_MASK_1) >> 8;
26    // Sample: (chunk1 & UPPER_MASK_1) == 0x00_03_00_01 (i.e. u32::from_be([0x00, 0x03, 0x00, 0x01])).
27    // Sample: (chunk1 & UPPER_MASK_1) * 10 == u32::from_be([0, 30, 0, 10]).
28    let chunk1_upper = (chunk1 & UPPER_MASK_1) * 10;
29
30    // Sample: chunk1_lower + chunk1_upper == (4 + 30) << 16 + (2 + 10).
31    let chunk2: u32 = chunk1_lower + chunk1_upper;
32
33    // Sample: (chunk2 >> 16) as u16 == 4 + 30.
34    // Sample: chunk2 * 100 == (2 + 10) * 100 + (((4 + 30) << 16) * 100).
35    // Sample: (chunk2 * 100) as u16 == (2 + 10) * 100.
36    // Here `(chunk2 * 100) as u16` can be `chunk2 * 100`, because `99 * 100` is representable in
37    // 16-bits.
38    ((chunk2 >> 16) + (chunk2 * 100)) as u16
39}
40
41/// Parses 4 digits effectively in big endian.
42///
43/// # Failures
44///
45/// Note that this returns meaningless value for strings with non-digit characters.
46/// This is safe in a sense that this never cause UB for any input, but callers
47/// should ensure that the bytes consists of only ASCII digits.
48#[cfg(any(target_endian = "big", test))]
49#[must_use]
50fn parse_digits4_be(digits: [u8; 4]) -> u16 {
51    // Sample: digits == "1234" (i.e. digits == [0x31, 0x32, 0x33, 0x34]).
52
53    // Sample: chunk1 == 0x31_32_33_34.
54    let chunk1 = u32::from_be_bytes(digits);
55
56    /// Mask for 2nd and 4th significant digits.
57    const LOWER_MASK_1: u32 = 0x00_0f_00_0f;
58    /// Mask for 1st and 3rd significant digits.
59    const UPPER_MASK_1: u32 = 0x0f_00_0f_00;
60
61    // Sample: (chunk1 & LOWER_MASK_1) == 0x00_02_00_04 (i.e. u32::from_be([0, 2, 0, 4]).
62    let chunk1_lower = chunk1 & LOWER_MASK_1;
63    // Sample: (chunk1 & UPPER_MASK_1) == 0x01_00_03_00.
64    // Sample: (chunk1 & UPPER_MASK_1) >> 8 == 0x00_01_00_03.
65    // Sample: ((chunk1 & UPPER_MASK_1) >> 8) * 10 == u32::from_be([0, 10, 0, 30]).
66    let chunk1_upper = ((chunk1 & UPPER_MASK_1) >> 8) * 10;
67
68    // Sample: chunk1_lower + chunk1_upper == (2 + 10) << 16 + (4 + 30).
69    let chunk2: u32 = chunk1_lower + chunk1_upper;
70
71    // Sample: chunk2 = (2 + 10) << 16 + (4 + 30).
72    // Sample: (chunk2 >> 16) as u16 == 2 + 10.
73    // Sample: (chunk2 >> 16) * 100 as u16 == (2 + 10) * 100.
74    // Sample: chunk2 as u16 = 4 + 30.
75    ((chunk2 >> 16) * 100 + chunk2) as u16
76}
77
78/// Parses 4 digits effectively.
79///
80/// # Failures
81///
82/// Note that this returns meaningless value for strings with non-digit characters.
83/// This is safe in a sense that this never cause UB for any input, but callers
84/// should ensure that the bytes consists of only ASCII digits.
85#[inline]
86#[must_use]
87pub(crate) fn parse_digits4(digits: [u8; 4]) -> u16 {
88    #[cfg(not(target_endian = "big"))]
89    {
90        parse_digits4_le(digits)
91    }
92    #[cfg(target_endian = "big")]
93    {
94        parse_digits4_be(digits)
95    }
96}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101
102    use core::convert::TryFrom;
103
104    fn as_digit4(s: &str) -> [u8; 4] {
105        TryFrom::try_from(s.as_bytes()).unwrap()
106    }
107
108    #[test]
109    fn digits4_le() {
110        assert_eq!(parse_digits4_le(as_digit4("0000")), 0000);
111        assert_eq!(parse_digits4_le(as_digit4("1234")), 1234);
112        assert_eq!(parse_digits4_le(as_digit4("8765")), 8765);
113        assert_eq!(parse_digits4_le(as_digit4("9999")), 9999);
114    }
115
116    #[test]
117    fn digits4_be() {
118        assert_eq!(parse_digits4_be(as_digit4("0000")), 0000);
119        assert_eq!(parse_digits4_be(as_digit4("1234")), 1234);
120        assert_eq!(parse_digits4_be(as_digit4("8765")), 8765);
121        assert_eq!(parse_digits4_be(as_digit4("9999")), 9999);
122    }
123
124    #[test]
125    fn digits4() {
126        assert_eq!(parse_digits4(as_digit4("0000")), 0000);
127        assert_eq!(parse_digits4(as_digit4("1234")), 1234);
128        assert_eq!(parse_digits4(as_digit4("8765")), 8765);
129        assert_eq!(parse_digits4(as_digit4("9999")), 9999);
130    }
131}