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#[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#[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 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}