mz_persist_types/
timestamp.rs

1// Copyright Materialize, Inc. and contributors. All rights reserved.
2//
3// Use of this software is governed by the Business Source License
4// included in the LICENSE file.
5//
6// As of the Change Date specified in that file, in accordance with
7// the Business Source License, use of this software will be governed
8// by the Apache License, Version 2.0.
9
10//! Stats-related timestamp code.
11
12use std::ops::Range;
13
14use chrono::{NaiveDate, NaiveDateTime, NaiveTime};
15
16/// Parses a specific subset of ISO8061 timestamps.
17///
18/// This has very specific semantics so that it can enable pushdown on string
19/// timestamps in JSON. See doc/user/content/sql/functions/pushdown.md for
20/// details.
21pub fn try_parse_monotonic_iso8601_timestamp<'a>(a: &'a str) -> Option<NaiveDateTime> {
22    const YYYY: Range<usize> = 0..0 + "YYYY".len();
23    const LIT_DASH_0: Range<usize> = YYYY.end..YYYY.end + "-".len();
24    const MM: Range<usize> = LIT_DASH_0.end..LIT_DASH_0.end + "MM".len();
25    const LIT_DASH_1: Range<usize> = MM.end..MM.end + "-".len();
26    const DD: Range<usize> = LIT_DASH_1.end..LIT_DASH_1.end + "DD".len();
27    const LIT_T: Range<usize> = DD.end..DD.end + "T".len();
28    const HH: Range<usize> = LIT_T.end..LIT_T.end + "HH".len();
29    const LIT_COLON_0: Range<usize> = HH.end..HH.end + ":".len();
30    const MI: Range<usize> = LIT_COLON_0.end..LIT_COLON_0.end + "MI".len();
31    const LIT_COLON_1: Range<usize> = MI.end..MI.end + ":".len();
32    const SS: Range<usize> = LIT_COLON_1.end..LIT_COLON_1.end + "SS".len();
33    const LIT_DOT: Range<usize> = SS.end..SS.end + ".".len();
34    // NB "MS" pattern is shorter than what it matches, so hardcode the 3.
35    const MS: Range<usize> = LIT_DOT.end..LIT_DOT.end + 3;
36    const LIT_Z: Range<usize> = MS.end..MS.end + "Z".len();
37
38    // The following assumes this is ASCII so do a quick check first.
39    if !a.is_ascii() {
40        return None;
41    }
42
43    if a.len() != LIT_Z.end {
44        return None;
45    }
46    if &a[LIT_DASH_0] != "-"
47        || &a[LIT_DASH_1] != "-"
48        || &a[LIT_T] != "T"
49        || &a[LIT_COLON_0] != ":"
50        || &a[LIT_COLON_1] != ":"
51        || &a[LIT_DOT] != "."
52        || &a[LIT_Z] != "Z"
53    {
54        return None;
55    }
56    let yyyy = a[YYYY].parse().ok()?;
57    let mm = a[MM].parse().ok()?;
58    let dd = a[DD].parse().ok()?;
59    let hh = a[HH].parse().ok()?;
60    let mi = a[MI].parse().ok()?;
61    let ss = a[SS].parse().ok()?;
62    let ms = a[MS].parse().ok()?;
63    let date = NaiveDate::from_ymd_opt(yyyy, mm, dd)?;
64    let time = NaiveTime::from_hms_milli_opt(hh, mi, ss, ms)?;
65    Some(NaiveDateTime::new(date, time))
66}
67
68#[cfg(test)]
69mod tests {
70    use super::*;
71
72    #[mz_ore::test]
73    fn monotonic_iso8601() {
74        // The entire point of this method is that the lexicographic order
75        // corresponds to chronological order (ignoring None/NULL). So, verify.
76        let mut inputs = vec![
77            "0000-01-01T00:00:00.000Z",
78            "0001-01-01T00:00:00.000Z",
79            "2015-00-00T00:00:00.000Z",
80            "2015-09-00T00:00:00.000Z",
81            "2015-09-18T00:00:00.000Z",
82            "2015-09-18T23:00:00.000Z",
83            "2015-09-18T23:56:00.000Z",
84            "2015-09-18T23:56:04.000Z",
85            "2015-09-18T23:56:04.123Z",
86            "2015-09-18T23:56:04.1234Z",
87            "2015-09-18T23:56:04.124Z",
88            "2015-09-18T23:56:05.000Z",
89            "2015-09-18T23:57:00.000Z",
90            "2015-09-18T24:00:00.000Z",
91            "2015-09-19T00:00:00.000Z",
92            "2015-10-00T00:00:00.000Z",
93            "2016-10-00T00:00:00.000Z",
94            "9999-12-31T23:59:59.999Z",
95        ];
96        // Sort the inputs so we can't accidentally pass by hardcoding them in
97        // the wrong order.
98        inputs.sort();
99        let outputs = inputs
100            .into_iter()
101            .flat_map(try_parse_monotonic_iso8601_timestamp)
102            .collect::<Vec<_>>();
103        // Sanity check that we don't trivially pass by always returning None.
104        assert!(!outputs.is_empty());
105        let mut outputs_sorted = outputs.clone();
106        outputs_sorted.sort();
107        assert_eq!(outputs, outputs_sorted);
108    }
109}