packedtime_rs/
parse.rs

1use crate::datetime::DateTimeComponents;
2use crate::error::*;
3use crate::{EpochDays, PackedTimestamp};
4
5#[repr(C)]
6#[derive(PartialEq, Clone, Debug, Default)]
7struct SimdTimestamp {
8    year_hi: u16,
9    year_lo: u16,
10    month: u16,
11    day: u16,
12    hour: u16,
13    minute: u16,
14    pad1: u16,
15    pad2: u16,
16}
17
18const _: () = {
19    assert!(std::mem::size_of::<SimdTimestamp>() == 16);
20};
21
22impl SimdTimestamp {
23    fn new(year: u16, month: u16, day: u16, hour: u16, minute: u16) -> Self {
24        Self {
25            year_hi: year / 100,
26            year_lo: year % 100,
27            month,
28            day,
29            hour,
30            minute,
31            pad1: 0,
32            pad2: 0,
33        }
34    }
35}
36
37#[inline(always)]
38fn ts_to_epoch_millis(ts: &DateTimeComponents) -> i64 {
39    let epoch_day = EpochDays::from_ymd(ts.year, ts.month as i32, ts.day as i32).days() as i64;
40
41    let h = ts.hour as i64;
42    let m = ts.minute as i64;
43    let s = ts.second as i64;
44    let offset_minute = ts.offset_minute as i64;
45    let seconds = epoch_day * 24 * 60 * 60 + h * 60 * 60 + m * 60 + s - offset_minute * 60;
46
47    seconds * 1000 + ts.millisecond as i64
48}
49
50#[doc(hidden)]
51pub fn parse_to_epoch_millis_scalar(input: &str) -> ParseResult<i64> {
52    let ts = parse_scalar(input.as_bytes())?;
53    Ok(ts_to_epoch_millis(&ts))
54}
55
56#[doc(hidden)]
57pub fn parse_to_packed_timestamp_scalar(input: &str) -> ParseResult<PackedTimestamp> {
58    let ts = parse_scalar(input.as_bytes())?;
59    Ok(PackedTimestamp::new(
60        ts.year,
61        ts.month as u32,
62        ts.day as u32,
63        ts.hour as u32,
64        ts.minute as u32,
65        ts.second as u32,
66        ts.millisecond,
67        ts.offset_minute,
68    ))
69}
70
71pub(crate) fn parse_scalar(bytes: &[u8]) -> ParseResult<DateTimeComponents> {
72    if bytes.len() < 16 {
73        return Err(ParseError::InvalidLen(bytes.len()));
74    }
75
76    let mut timestamp = DateTimeComponents::default();
77    let mut index = 0;
78
79    let year = parse_num4(bytes, &mut index)?;
80    expect(bytes, &mut index, b'-')?;
81    let month = parse_num2(bytes, &mut index)?;
82    expect(bytes, &mut index, b'-')?;
83    let day = parse_num2(bytes, &mut index)?;
84    expect2(bytes, &mut index, b'T', b' ')?;
85    let hour = parse_num2(bytes, &mut index)?;
86    expect(bytes, &mut index, b':')?;
87    let minute = parse_num2(bytes, &mut index)?;
88
89    let (second, nano) = parse_seconds_and_nanos(bytes, &mut index)?;
90
91    let offset = parse_utc_or_offset_minutes(bytes, &mut index)?;
92
93    timestamp.year = year as i32;
94    timestamp.month = month as u8;
95    timestamp.day = day as u8;
96    timestamp.hour = hour as u8;
97    timestamp.minute = minute as u8;
98    timestamp.second = second as u8;
99    timestamp.millisecond = nano / 1_000_000;
100    timestamp.offset_minute = offset;
101
102    Ok(timestamp)
103}
104
105#[inline(always)]
106fn parse_seconds_and_nanos(bytes: &[u8], index: &mut usize) -> ParseResult<(u32, u32)> {
107    let mut second = 0;
108    let mut nano = 0;
109    if *index < bytes.len() {
110        let ch = bytes[*index];
111        if ch == b'.' {
112            *index += 1;
113            nano = parse_nano(bytes, index)?;
114        } else if ch == b':' {
115            *index += 1;
116            second = parse_num2(bytes, index)?;
117            if *index < bytes.len() && bytes[*index] == b'.' {
118                *index += 1;
119                nano = parse_nano(bytes, index)?;
120            }
121        }
122    }
123
124    Ok((second, nano))
125}
126
127#[inline(never)]
128fn parse_seconds_and_nanos_and_offset_minutes_slow_path(bytes: &[u8], index: &mut usize) -> ParseResult<(u32, u32, i32)> {
129    let (seconds, nanos) = parse_seconds_and_nanos(bytes, index)?;
130    let offset_minutes = parse_utc_or_offset_minutes(bytes, index)?;
131    Ok((seconds, nanos, offset_minutes))
132}
133
134#[inline(never)]
135fn skip_nanos_and_parse_offset_minutes_slow_path(bytes: &[u8], index: &mut usize) -> ParseResult<i32> {
136    skip_fractional_millis(bytes, index);
137    let offset_minutes = parse_utc_or_offset_minutes(bytes, index)?;
138    Ok(offset_minutes)
139}
140
141#[inline(always)]
142fn parse_utc_or_offset_minutes(bytes: &[u8], index: &mut usize) -> ParseResult<i32> {
143    if *index >= bytes.len() {
144        return Err(ParseError::InvalidLen(*index));
145    }
146    let first = bytes[*index];
147    if first == b'Z' {
148        *index += 1;
149        if *index != bytes.len() {
150            Err(ParseError::TrailingChar(*index))
151        } else {
152            Ok(0)
153        }
154    } else if first == b'+' {
155        *index += 1;
156        Ok(parse_offset_minutes(bytes, index)? as i32)
157    } else if first == b'-' {
158        *index += 1;
159        Ok(-(parse_offset_minutes(bytes, index)? as i32))
160    } else {
161        Err(ParseError::InvalidChar(*index))
162    }
163}
164
165#[inline(always)]
166fn parse_offset_minutes(bytes: &[u8], index: &mut usize) -> ParseResult<u32> {
167    let offset_hour = parse_num2(bytes, index)?;
168    expect(bytes, index, b':')?;
169    let offset_minute = parse_num2(bytes, index)?;
170
171    Ok(offset_hour * 60 + offset_minute)
172}
173
174#[inline(always)]
175fn parse_num2(bytes: &[u8], i: &mut usize) -> ParseResult<u32> {
176    let d1 = digit(bytes, i)?;
177    let d2 = digit(bytes, i)?;
178    Ok(d1 * 10 + d2)
179}
180
181#[inline(always)]
182fn parse_num4(bytes: &[u8], i: &mut usize) -> ParseResult<u32> {
183    let d1 = digit(bytes, i)?;
184    let d2 = digit(bytes, i)?;
185    let d3 = digit(bytes, i)?;
186    let d4 = digit(bytes, i)?;
187    Ok(d1 * 1000 + d2 * 100 + d3 * 10 + d4)
188}
189
190const NANO_MULTIPLIER: [u32; 9] = [1, 10, 100, 1_000, 10_000, 100_000, 1_000_000, 10_000_000, 100_000_000];
191
192#[inline(always)]
193fn parse_nano(bytes: &[u8], i: &mut usize) -> ParseResult<u32> {
194    let mut r = digit(bytes, i)?;
195    let mut j = 1;
196
197    while *i < bytes.len() && j < 9 {
198        let ch = bytes[*i];
199        if ch >= b'0' && ch <= b'9' {
200            r = r * 10 + (ch - b'0') as u32;
201            j += 1;
202            *i += 1;
203        } else {
204            break;
205        }
206    }
207
208    Ok(r * NANO_MULTIPLIER[9 - j])
209}
210
211#[inline(always)]
212fn skip_fractional_millis(bytes: &[u8], i: &mut usize) {
213    let mut j = 0;
214
215    while *i < bytes.len() && j < 6 {
216        let ch = bytes[*i];
217        if ch >= b'0' && ch <= b'9' {
218            j += 1;
219            *i += 1;
220        } else {
221            break;
222        }
223    }
224}
225
226#[inline(always)]
227fn expect(bytes: &[u8], i: &mut usize, expected: u8) -> ParseResult<()> {
228    if *i >= bytes.len() {
229        return Err(ParseError::InvalidLen(*i));
230    }
231    let ch = bytes[*i];
232    if ch == expected {
233        *i += 1;
234        Ok(())
235    } else {
236        Err(ParseError::InvalidChar(*i))
237    }
238}
239
240#[inline(always)]
241fn expect2(bytes: &[u8], i: &mut usize, expected1: u8, expected2: u8) -> ParseResult<u8> {
242    if *i >= bytes.len() {
243        return Err(ParseError::InvalidLen(*i));
244    }
245    let ch = bytes[*i];
246    if ch == expected1 || ch == expected2 {
247        *i += 1;
248        Ok(ch)
249    } else {
250        Err(ParseError::InvalidChar(*i))
251    }
252}
253
254#[inline(always)]
255fn digit(bytes: &[u8], i: &mut usize) -> ParseResult<u32> {
256    if *i >= bytes.len() {
257        return Err(ParseError::InvalidLen(*i));
258    }
259    let ch = bytes[*i];
260    if ch >= b'0' && ch <= b'9' {
261        *i += 1;
262        Ok((ch - b'0') as u32)
263    } else {
264        Err(ParseError::InvalidChar(*i))
265    }
266}
267
268// only public for benchmarks
269#[doc(hidden)]
270#[inline]
271#[cfg(all(target_arch = "x86_64", target_feature = "sse2", target_feature = "ssse3"))]
272pub fn parse_to_epoch_millis_simd(input: &str) -> ParseResult<i64> {
273    let ts = parse_simd(input.as_bytes())?;
274    Ok(ts_to_epoch_millis(&ts))
275}
276
277// only public for benchmarks
278#[doc(hidden)]
279#[inline]
280#[cfg(all(target_arch = "x86_64", target_feature = "sse2", target_feature = "ssse3"))]
281pub fn parse_to_packed_timestamp_simd(input: &str) -> ParseResult<PackedTimestamp> {
282    let ts = parse_simd(input.as_bytes())?;
283    Ok(PackedTimestamp::new(
284        ts.year,
285        ts.month as u32,
286        ts.day as u32,
287        ts.hour as u32,
288        ts.minute as u32,
289        ts.second as u32,
290        ts.millisecond,
291        ts.offset_minute,
292    ))
293}
294
295#[inline]
296#[cfg(target_arch = "x86_64")]
297#[cfg(all(target_arch = "x86_64", target_feature = "sse2", target_feature = "ssse3"))]
298unsafe fn parse_simd_yyyy_mm_dd_hh_mm(bytes: *const u8) -> ParseResult<SimdTimestamp> {
299    use std::arch::x86_64::*;
300
301    const MIN_BYTES: &[u8] = "))))-)0-)0S))9))9))".as_bytes();
302    const MAX_BYTES: &[u8] = "@@@@-2@-4@U3@;6@;6@".as_bytes();
303    const SPACE_SEP_BYTES: &[u8] = "0000-00-00 00:00:00".as_bytes();
304    const REM_MIN_BYTES: &[u8] = "9-)Y*9))))))))))".as_bytes();
305    const REM_MAX_BYTES: &[u8] = ";/@[.;@@@@@@@@@@".as_bytes();
306
307    let mut timestamp = SimdTimestamp::default();
308    let ts_without_seconds = _mm_loadu_si128(bytes as *const __m128i);
309    let min = _mm_loadu_si128(MIN_BYTES.as_ptr() as *const __m128i);
310    let max = _mm_loadu_si128(MAX_BYTES.as_ptr() as *const __m128i);
311    let space = _mm_loadu_si128(SPACE_SEP_BYTES.as_ptr() as *const __m128i);
312
313    let gt = _mm_cmpgt_epi8(ts_without_seconds, min);
314    let lt = _mm_cmplt_epi8(ts_without_seconds, max);
315
316    let space_sep = _mm_cmpeq_epi8(ts_without_seconds, space);
317    let mask = _mm_or_si128(_mm_and_si128(gt, lt), space_sep);
318    let mask = _mm_movemask_epi8(mask);
319
320    if mask != 0xFFFF {
321        return Err(ParseError::InvalidChar((!mask).trailing_zeros() as usize));
322    }
323
324    let nums = _mm_sub_epi8(ts_without_seconds, space);
325    let nums = _mm_shuffle_epi8(nums, _mm_set_epi8(-1, -1, -1, -1, 15, 14, 12, 11, 9, 8, 6, 5, 3, 2, 1, 0));
326
327    let hundreds = _mm_and_si128(nums, _mm_set1_epi16(0x00FF));
328    let hundreds = _mm_mullo_epi16(hundreds, _mm_set1_epi16(10));
329
330    let ones = _mm_srli_epi16::<8>(nums);
331
332    let res = _mm_add_epi16(ones, hundreds);
333
334    let timestamp_ptr: *mut SimdTimestamp = &mut timestamp;
335    _mm_storeu_si128(timestamp_ptr as *mut __m128i, res);
336
337    Ok(timestamp)
338}
339
340#[inline]
341#[cfg(all(target_arch = "x86_64", target_feature = "sse2", target_feature = "ssse3"))]
342pub(crate) fn parse_simd(bytes: &[u8]) -> ParseResult<DateTimeComponents> {
343    if bytes.len() < 16 {
344        return Err(ParseError::InvalidLen(bytes.len()));
345    }
346
347    let timestamp = unsafe { parse_simd_yyyy_mm_dd_hh_mm(bytes.as_ptr())? };
348
349    let (seconds, millis, offset_minutes) = parse_seconds_and_millis_simd(bytes)?;
350
351    Ok(DateTimeComponents {
352        year: timestamp.year_hi as i32 * 100 + timestamp.year_lo as i32,
353        month: timestamp.month as u8,
354        day: timestamp.day as u8,
355        hour: timestamp.hour as u8,
356        minute: timestamp.minute as u8,
357        second: seconds as u8,
358        millisecond: millis,
359        offset_minute: offset_minutes,
360    })
361}
362
363#[inline(always)]
364#[cfg(all(target_arch = "x86_64", target_feature = "sse2", target_feature = "ssse3"))]
365fn parse_seconds_and_millis_simd(bytes: &[u8]) -> ParseResult<(u32, u32, i32)> {
366    if let Some((seconds, millis, offset_sign)) = try_parse_seconds_and_millis_simd(bytes) {
367        match offset_sign {
368            b'Z' => return Ok((seconds, millis, 0)),
369            b'+' | b'-' => {
370                let mut index = 24;
371                let offset_minutes = parse_offset_minutes(bytes, &mut index)? as i32;
372                let offset_minutes = if offset_sign == b'-' {
373                    -offset_minutes
374                } else {
375                    offset_minutes
376                };
377                return Ok((seconds, millis, offset_minutes));
378            }
379            digit @ b'0'..=b'9' => {
380                let mut i = 24 - 1;
381                let offset_minutes = skip_nanos_and_parse_offset_minutes_slow_path(bytes, &mut i)?;
382                return Ok((seconds, millis, offset_minutes));
383            }
384            _ => return Err(ParseError::InvalidChar(23)),
385        }
386    }
387
388    let mut index = 16;
389    let (second, nano, offset_minutes) = parse_seconds_and_nanos_and_offset_minutes_slow_path(bytes, &mut index)?;
390    Ok((second, nano / 1_000_000, offset_minutes))
391}
392
393#[inline(always)]
394#[cfg(all(target_arch = "x86_64", target_feature = "sse2", target_feature = "ssse3"))]
395fn try_parse_seconds_and_millis_simd(input: &[u8]) -> Option<(u32, u32, u8)> {
396    use std::arch::x86_64::*;
397    if input.len() >= 24 {
398        let buf = unsafe { std::ptr::read_unaligned(input.as_ptr().add(16) as *const u64) };
399
400        unsafe {
401            let min = _mm_sub_epi8(
402                _mm_set_epi64x(0, i64::from_le_bytes(*b":00.000+")),
403                _mm_set1_epi64x(0x0101_0101_0101_0101),
404            );
405            let max = _mm_add_epi8(
406                _mm_set_epi64x(0, i64::from_le_bytes(*b":99.999Z")),
407                _mm_set1_epi64x(0x0101_0101_0101_0101),
408            );
409            let reg = _mm_set1_epi64x(buf as _);
410
411            let gt = _mm_cmpgt_epi8(reg, min);
412            let lt = _mm_cmplt_epi8(reg, max);
413
414            let mask = _mm_movemask_epi8(_mm_and_si128(gt, lt));
415
416            if mask != 0xFF {
417                return None;
418            }
419        }
420
421        let buf = buf.to_le_bytes();
422
423        let second = (buf[1] - b'0') as u32 * 10 + (buf[2] - b'0') as u32;
424        let milli = (buf[4] - b'0') as u32 * 100 + (buf[5] - b'0') as u32 * 10 + (buf[6] - b'0') as u32;
425
426        Some((second, milli, buf[7]))
427    } else {
428        None
429    }
430}
431
432pub fn parse_to_timestamp_millis(bytes: &[u8]) -> ParseResult<i64> {
433    #[cfg(target_feature = "sse4.1")]
434    {
435        let ts = parse_simd(bytes)?;
436        Ok(ts_to_epoch_millis(&ts))
437    }
438    #[cfg(not(target_feature = "sse4.1"))]
439    {
440        let ts = parse_scalar(bytes)?;
441        Ok(ts_to_epoch_millis(&ts))
442    }
443}
444
445#[cfg(test)]
446#[cfg(all(not(miri), target_arch = "x86_64", target_feature = "sse2", target_feature = "ssse3"))]
447pub mod simd_tests {
448    use crate::error::ParseError;
449    use crate::parse::{parse_simd, try_parse_seconds_and_millis_simd, DateTimeComponents};
450    use crate::parse_to_epoch_millis_simd;
451
452    #[test]
453    fn test_valid() {
454        assert!(parse_simd(b"1970-01-01T00:00Z").is_ok());
455        assert!(parse_simd(b"1970-01-01T00:00:00Z").is_ok());
456        assert!(parse_simd(b"1970-01-01T00:00:00.000Z").is_ok());
457
458        assert!(parse_simd(b"1970-01-01 00:00Z").is_ok());
459        assert!(parse_simd(b"1970-01-01 00:00:00Z").is_ok());
460        assert!(parse_simd(b"1970-01-01 00:00:00.000Z").is_ok());
461    }
462
463    #[test]
464    fn test_invalid_len() {
465        assert_eq!(Err(ParseError::InvalidLen(0)), parse_simd(b""));
466        assert_eq!(Err(ParseError::InvalidLen(1)), parse_simd(b"X"));
467        assert_eq!(Err(ParseError::InvalidLen(4)), parse_simd(b"2020"));
468    }
469
470    #[test]
471    fn test_invalid_char() {
472        assert_eq!(Err(ParseError::InvalidChar(0)), parse_simd(b"X020-09-10T12:00:00Z"));
473        assert_eq!(Err(ParseError::InvalidChar(1)), parse_simd(b"2X20-09-10T12:00:00Z"));
474        assert_eq!(Err(ParseError::InvalidChar(2)), parse_simd(b"20X0-09-10T12:00:00Z"));
475        assert_eq!(Err(ParseError::InvalidChar(10)), parse_simd(b"2020-09-10X12:00:00Z"));
476        assert_eq!(Err(ParseError::InvalidChar(10)), parse_simd(b"2020-09-10X12:00/"));
477        assert_eq!(Err(ParseError::InvalidChar(15)), parse_simd(b"2020-09-10T12:0X/"));
478    }
479
480    #[test]
481    fn test_parse_simd() {
482        assert_eq!(
483            DateTimeComponents::new(2345, 12, 24, 17, 30, 15, 100),
484            parse_simd(b"2345-12-24T17:30:15.1Z").unwrap()
485        );
486        assert_eq!(
487            DateTimeComponents::new(2345, 12, 24, 17, 30, 15, 120),
488            parse_simd(b"2345-12-24T17:30:15.12Z").unwrap()
489        );
490        assert_eq!(
491            DateTimeComponents::new(2345, 12, 24, 17, 30, 15, 123),
492            parse_simd(b"2345-12-24T17:30:15.123Z").unwrap()
493        );
494        assert_eq!(
495            DateTimeComponents::new(2345, 12, 24, 17, 30, 15, 123),
496            parse_simd(b"2345-12-24T17:30:15.1234Z").unwrap()
497        );
498        assert_eq!(
499            DateTimeComponents::new(2345, 12, 24, 17, 30, 15, 123),
500            parse_simd(b"2345-12-24T17:30:15.12345Z").unwrap()
501        );
502        assert_eq!(
503            DateTimeComponents::new(2345, 12, 24, 17, 30, 15, 123),
504            parse_simd(b"2345-12-24T17:30:15.123456Z").unwrap()
505        );
506        assert_eq!(
507            DateTimeComponents::new(2345, 12, 24, 17, 30, 15, 123),
508            parse_simd(b"2345-12-24T17:30:15.123457Z").unwrap()
509        );
510        assert_eq!(
511            DateTimeComponents::new(2345, 12, 24, 17, 30, 15, 123),
512            parse_simd(b"2345-12-24T17:30:15.12345678Z").unwrap()
513        );
514        assert_eq!(
515            DateTimeComponents::new(2345, 12, 24, 17, 30, 15, 123),
516            parse_simd(b"2345-12-24T17:30:15.123456789Z").unwrap()
517        );
518        assert_eq!(
519            DateTimeComponents::new_with_offset_minute(2345, 12, 24, 17, 30, 15, 123, -60),
520            parse_simd(b"2345-12-24T17:30:15.123456789-01:00").unwrap()
521        );
522    }
523
524    #[test]
525    fn test_parse_with_offset_simd() {
526        assert_eq!(
527            DateTimeComponents::new_with_offset_minute(2020, 9, 19, 11, 40, 20, 123, 2 * 60),
528            parse_simd(b"2020-09-19T11:40:20.123+02:00").unwrap()
529        );
530    }
531
532    #[test]
533    fn test_parse_with_zero_offset_simd() {
534        assert_eq!(
535            DateTimeComponents::new_with_offset_minute(2020, 9, 19, 11, 40, 20, 123, 0),
536            parse_simd(b"2020-09-19T11:40:20.123-00:00").unwrap()
537        );
538    }
539
540    #[test]
541    fn test_parse_with_negative_offset_simd() {
542        assert_eq!(
543            DateTimeComponents::new_with_offset_minute(2020, 9, 19, 11, 40, 20, 123, -2 * 60),
544            parse_simd(b"2020-09-19T11:40:20.123-02:00").unwrap()
545        );
546    }
547
548    #[test]
549    fn test_parse_millis_simd() {
550        let input = "2020-09-18T23:30:15Z";
551        let expected = chrono::DateTime::parse_from_rfc3339(input).unwrap().timestamp_millis();
552        let actual = parse_to_epoch_millis_simd(input).unwrap();
553        assert_eq!(expected, actual);
554    }
555
556    #[test]
557    fn test_parse_millis_simd_masked() {
558        let input = "2020-09-18T23:30:15Z--::ZZ";
559        let input = unsafe { input.get_unchecked(0..20) };
560        let expected = chrono::DateTime::parse_from_rfc3339(input).unwrap().timestamp_millis();
561        let actual = parse_to_epoch_millis_simd(input).unwrap();
562        assert_eq!(expected, actual);
563    }
564
565    #[test]
566    fn test_try_parse_seconds_and_millis_simd() {
567        let input = b"2020-09-08T13:42:29+00:00";
568        // fast path should require milliseconds
569        assert!(try_parse_seconds_and_millis_simd(input).is_none());
570
571        let input = b"2020-09-08T13:42:29.123Z";
572        assert_eq!(try_parse_seconds_and_millis_simd(input), Some((29, 123, b'Z')));
573
574        let input = b"2020-09-08T13:42:29.123+01:00";
575        assert_eq!(try_parse_seconds_and_millis_simd(input), Some((29, 123, b'+')));
576
577        let input = b"2020-09-08T13:42:29.123-01:00";
578        assert_eq!(try_parse_seconds_and_millis_simd(input), Some((29, 123, b'-')));
579
580        let input = b"2020-09-08T13:42:29.123456Z";
581        assert_eq!(try_parse_seconds_and_millis_simd(input), Some((29, 123, b'4')));
582
583        let input = b"2020-09-08T13:42:29.123456+01:00";
584        assert_eq!(try_parse_seconds_and_millis_simd(input), Some((29, 123, b'4')));
585
586        let input = b"2020-09-08T13:42:29.1234567Z";
587        assert_eq!(try_parse_seconds_and_millis_simd(input), Some((29, 123, b'4')));
588        let input = b"2020-09-08T13:42:29.1234567-01:00";
589        assert_eq!(try_parse_seconds_and_millis_simd(input), Some((29, 123, b'4')));
590
591        let input = b"2020-09-08T13:42:29.12345678Z";
592        assert_eq!(try_parse_seconds_and_millis_simd(input), Some((29, 123, b'4')));
593        let input = b"2020-09-08T13:42:29.123456789Z";
594        assert_eq!(try_parse_seconds_and_millis_simd(input), Some((29, 123, b'4')));
595        let input = b"2020-09-08T13:42:29.123456789Z";
596        assert_eq!(try_parse_seconds_and_millis_simd(input), Some((29, 123, b'4')));
597        let input = b"2020-09-08T13:42:29.123456789-02:00";
598        assert_eq!(try_parse_seconds_and_millis_simd(input), Some((29, 123, b'4')));
599    }
600
601    #[test]
602    fn test_parse_leap_seconds_simd() {
603        assert_eq!(
604            DateTimeComponents::new(2023, 1, 3, 9, 30, 60, 123),
605            parse_simd(b"2023-01-03T09:30:60.123Z").unwrap()
606        );
607    }
608}
609
610#[cfg(test)]
611mod scalar_tests {
612    use crate::datetime::DateTimeComponents;
613    use crate::{parse_scalar, parse_to_epoch_millis_scalar};
614
615    #[test]
616    fn test_parse_scalar() {
617        assert_eq!(
618            DateTimeComponents::new(2345, 12, 24, 17, 30, 15, 123),
619            parse_scalar(b"2345-12-24T17:30:15.123Z").unwrap()
620        );
621    }
622
623    #[test]
624    fn test_parse_leap_seconds_scalar() {
625        assert_eq!(
626            DateTimeComponents::new(2023, 1, 3, 9, 30, 60, 123),
627            parse_scalar(b"2023-01-03T09:30:60.123Z").unwrap()
628        );
629    }
630
631    #[test]
632    fn test_parse_millis_scalar() {
633        let input = "2020-09-18T23:30:15Z";
634        let expected = chrono::DateTime::parse_from_rfc3339(input).unwrap().timestamp_millis();
635        let actual = parse_to_epoch_millis_scalar(input).unwrap();
636        assert_eq!(expected, actual);
637    }
638}