iso8601_timestamp/
impls.rs

1use time::{Date, Month};
2
3use super::Timestamp;
4
5#[inline(always)]
6#[allow(dead_code)]
7const fn is_leap_year(y: i32) -> bool {
8    //(y % 4 == 0) & ((y % 25 != 0) | (y % 16 == 0)) // old version
9
10    // ternary compiles to cmov
11    y & (if y % 25 == 0 { 15 } else { 3 }) == 0
12}
13
14#[inline(always)]
15#[cfg(target_feature = "avx2")]
16unsafe fn to_calendar_date_avx2(date: Date) -> (i32, Month, u8) {
17    import_intrinsics!(x86::{
18        _mm256_set1_epi16, _mm256_set_epi16, _mm256_setzero_si256,
19        _mm256_cmpeq_epi16, _mm256_movemask_epi8, _mm256_subs_epu16, __m256i
20    });
21
22    let year = date.year();
23
24    #[rustfmt::skip]
25    let mut days = match is_leap_year(year) {
26        true => _mm256_set_epi16(i16::MAX, i16::MAX, i16::MAX, i16::MAX, 335, 305, 274, 244, 213, 182, 152, 121, 91, 60, 31, 0),
27        false => _mm256_set_epi16(i16::MAX, i16::MAX, i16::MAX, i16::MAX, 334, 304, 273, 243, 212, 181, 151, 120, 90, 59, 31, 0),
28    };
29
30    days = _mm256_subs_epu16(_mm256_set1_epi16(date.ordinal() as i16), days);
31
32    let mask = _mm256_movemask_epi8(_mm256_cmpeq_epi16(days, _mm256_setzero_si256()));
33    let month = mask.trailing_zeros() / 2;
34    let day = *core::mem::transmute::<__m256i, [u16; 16]>(days).get_unchecked(month as usize - 1);
35
36    (year, core::mem::transmute::<u8, Month>(month as u8), day as u8)
37}
38
39#[inline(always)]
40#[cfg(target_feature = "sse2")]
41unsafe fn to_calendar_date_sse2(date: Date) -> (i32, Month, u8) {
42    import_intrinsics!(x86::{
43        _mm_cmpeq_epi16, _mm_movemask_epi8, _mm_set1_epi16,
44        _mm_set_epi16, _mm_setzero_si128, _mm_subs_epu16, __m128i
45    });
46
47    let year = date.year();
48
49    #[rustfmt::skip]
50    let (mut hd, mut ld) = match is_leap_year(year) {
51        true => (
52            _mm_set_epi16(i16::MAX, i16::MAX, i16::MAX, i16::MAX, 335, 305, 274, 244),
53            _mm_set_epi16(213, 182, 152, 121, 91, 60, 31, 0)),
54        false => (
55            _mm_set_epi16(i16::MAX, i16::MAX, i16::MAX, i16::MAX, 334, 304, 273, 243),
56            _mm_set_epi16(212, 181, 151, 120, 90, 59, 31, 0))
57    };
58
59    let ordinals = _mm_set1_epi16(date.ordinal() as i16);
60
61    hd = _mm_subs_epu16(ordinals, hd);
62    ld = _mm_subs_epu16(ordinals, ld);
63
64    let z = _mm_setzero_si128();
65
66    let hm = _mm_movemask_epi8(_mm_cmpeq_epi16(hd, z));
67    let lm = _mm_movemask_epi8(_mm_cmpeq_epi16(ld, z));
68
69    let mask = (hm << 16) | lm;
70    let month = mask.trailing_zeros() / 2;
71
72    let day = *core::mem::transmute::<[__m128i; 2], [u16; 16]>([ld, hd]).get_unchecked(month as usize - 1);
73
74    (year, core::mem::transmute::<u8, Month>(month as u8), day as u8)
75}
76
77#[inline(always)]
78#[allow(unreachable_code)]
79pub(crate) fn to_calendar_date(date: Date) -> (i32, Month, u8) {
80    #[cfg(target_feature = "avx2")]
81    // SAFETY: Checked for AVX2 support
82    return unsafe { to_calendar_date_avx2(date) };
83
84    #[cfg(target_feature = "sse2")]
85    // SAFETY: Checked for SSE2 support
86    return unsafe { to_calendar_date_sse2(date) };
87
88    date.to_calendar_date()
89}
90
91impl Timestamp {
92    /// Get the year, month, and day.
93    ///
94    /// Like [`PrimitiveDateTime::to_calendar_date`](time::PrimitiveDateTime::to_calendar_date), but optimized for SSE2/AVX2 when available.
95    ///
96    /// ```rust
97    /// # use time::{Month, macros::datetime};
98    /// # use iso8601_timestamp::Timestamp;
99    /// assert_eq!(
100    ///     Timestamp::from(datetime!(2019-01-01 0:00)).to_calendar_date(),
101    ///     (2019, Month::January, 1)
102    /// );
103    /// ```
104    #[inline(always)]
105    #[must_use]
106    pub fn to_calendar_date(&self) -> (i32, Month, u8) {
107        to_calendar_date(self.date())
108    }
109}
110
111#[cfg(test)]
112mod tests {
113    use super::*;
114
115    #[cfg(target_feature = "avx2")]
116    #[test]
117    fn test_to_calendar_date() {
118        for year in &[2004, 2005, 2006] {
119            for ordinal in 0..367 {
120                let Ok(date) = Date::from_ordinal_date(*year, ordinal) else {
121                    continue;
122                };
123
124                // SAFETY: Only tested on x86_64 with AVX2 and SSE2
125                let (avx2, sse2, none) = unsafe {
126                    (
127                        to_calendar_date_avx2(date),
128                        to_calendar_date_sse2(date),
129                        date.to_calendar_date(),
130                    )
131                };
132
133                assert_eq!(none, avx2);
134                assert_eq!(none, sse2);
135            }
136        }
137    }
138}