mz_persist_types/
timestamp.rs1use chrono::{NaiveDate, NaiveDateTime, NaiveTime};
13use mz_ore::cast::CastFrom;
14
15pub fn try_parse_monotonic_iso8601_timestamp(a: &str) -> Option<NaiveDateTime> {
21 fn parse_lit(str: &mut &[u8], byte: u8) -> Option<()> {
22 if *str.split_off_first()? == byte {
23 Some(())
24 } else {
25 None
26 }
27 }
28
29 fn parse_int(str: &mut &[u8], digits: usize) -> Option<u32> {
30 let mut acc = 0u32;
31 for digit in str.split_off(..digits)? {
32 if !digit.is_ascii_digit() {
33 return None;
34 }
35 acc = acc * 10 + u32::cast_from(*digit - b'0');
36 }
37 Some(acc)
38 }
39
40 if !a.is_ascii() {
42 return None;
43 }
44 let bytes = &mut a.as_bytes();
45
46 let yyyy = parse_int(bytes, 4)?;
47 parse_lit(bytes, b'-')?;
48 let mm = parse_int(bytes, 2)?;
49 parse_lit(bytes, b'-')?;
50 let dd = parse_int(bytes, 2)?;
51 parse_lit(bytes, b'T')?;
52 let hh = parse_int(bytes, 2)?;
53 parse_lit(bytes, b':')?;
54 let mi = parse_int(bytes, 2)?;
55 parse_lit(bytes, b':')?;
56 let ss = parse_int(bytes, 2)?;
57 parse_lit(bytes, b'.')?;
58 let ms = parse_int(bytes, 3)?;
59 parse_lit(bytes, b'Z')?;
60
61 if !bytes.is_empty() {
62 return None;
63 }
64
65 let date = NaiveDate::from_ymd_opt(i32::try_from(yyyy).ok()?, mm, dd)?;
67 let time = NaiveTime::from_hms_milli_opt(hh, mi, ss, ms)?;
68 Some(NaiveDateTime::new(date, time))
69}
70
71#[cfg(test)]
72mod tests {
73 use super::*;
74
75 #[mz_ore::test]
76 fn monotonic_iso8601() {
77 let mut inputs = vec![
80 "-005-01-01T00:00:00.000Z",
81 "-002-01-01T00:00:00.000Z",
82 "0000-01-01T00:00:00.000Z",
83 "0001-01-01T00:00:00.000Z",
84 "+000-01-01T00:00:00.000Z",
85 "+001-01-01T00:00:00.000Z",
86 "2015-00-00T00:00:00.000Z",
87 "2015-09-00T00:00:00.000Z",
88 "2015-09-18T00:00:00.000Z",
89 "2015-09-18T23:00:00.000Z",
90 "2015-09-18T23:56:00.000Z",
91 "2015-09-18T23:56:04.000Z",
92 "2015-09-18T23:56:04.123Z",
93 "2015-09-18T23:56:04.1234Z",
94 "2015-09-18T23:56:04.124Z",
95 "2015-09-18T23:56:05.000Z",
96 "2015-09-18T23:57:00.000Z",
97 "2015-09-18T23:57:00.000Zextra",
98 "2015-09-18T24:00:00.000Z",
99 "2015-09-19T00:00:00.000Z",
100 "2015-10-00T00:00:00.000Z",
101 "2016-10-00T00:00:00.000Z",
102 "9999-12-31T23:59:59.999Z",
103 ];
104 inputs.sort();
107 let outputs = inputs
108 .into_iter()
109 .flat_map(try_parse_monotonic_iso8601_timestamp)
110 .collect::<Vec<_>>();
111 assert!(!outputs.is_empty());
113 let mut outputs_sorted = outputs.clone();
114 outputs_sorted.sort();
115 assert_eq!(outputs, outputs_sorted);
116 }
117}