use std::ops::Range;
use chrono::{NaiveDate, NaiveDateTime, NaiveTime};
pub fn try_parse_monotonic_iso8601_timestamp<'a>(a: &'a str) -> Option<NaiveDateTime> {
const YYYY: Range<usize> = 0..0 + "YYYY".len();
const LIT_DASH_0: Range<usize> = YYYY.end..YYYY.end + "-".len();
const MM: Range<usize> = LIT_DASH_0.end..LIT_DASH_0.end + "MM".len();
const LIT_DASH_1: Range<usize> = MM.end..MM.end + "-".len();
const DD: Range<usize> = LIT_DASH_1.end..LIT_DASH_1.end + "DD".len();
const LIT_T: Range<usize> = DD.end..DD.end + "T".len();
const HH: Range<usize> = LIT_T.end..LIT_T.end + "HH".len();
const LIT_COLON_0: Range<usize> = HH.end..HH.end + ":".len();
const MI: Range<usize> = LIT_COLON_0.end..LIT_COLON_0.end + "MI".len();
const LIT_COLON_1: Range<usize> = MI.end..MI.end + ":".len();
const SS: Range<usize> = LIT_COLON_1.end..LIT_COLON_1.end + "SS".len();
const LIT_DOT: Range<usize> = SS.end..SS.end + ".".len();
const MS: Range<usize> = LIT_DOT.end..LIT_DOT.end + 3;
const LIT_Z: Range<usize> = MS.end..MS.end + "Z".len();
if !a.is_ascii() {
return None;
}
if a.len() != LIT_Z.end {
return None;
}
if &a[LIT_DASH_0] != "-"
|| &a[LIT_DASH_1] != "-"
|| &a[LIT_T] != "T"
|| &a[LIT_COLON_0] != ":"
|| &a[LIT_COLON_1] != ":"
|| &a[LIT_DOT] != "."
|| &a[LIT_Z] != "Z"
{
return None;
}
let yyyy = a[YYYY].parse().ok()?;
let mm = a[MM].parse().ok()?;
let dd = a[DD].parse().ok()?;
let hh = a[HH].parse().ok()?;
let mi = a[MI].parse().ok()?;
let ss = a[SS].parse().ok()?;
let ms = a[MS].parse().ok()?;
let date = NaiveDate::from_ymd_opt(yyyy, mm, dd)?;
let time = NaiveTime::from_hms_milli_opt(hh, mi, ss, ms)?;
Some(NaiveDateTime::new(date, time))
}
#[cfg(test)]
mod tests {
use super::*;
#[mz_ore::test]
fn monotonic_iso8601() {
let mut inputs = vec![
"0000-01-01T00:00:00.000Z",
"0001-01-01T00:00:00.000Z",
"2015-00-00T00:00:00.000Z",
"2015-09-00T00:00:00.000Z",
"2015-09-18T00:00:00.000Z",
"2015-09-18T23:00:00.000Z",
"2015-09-18T23:56:00.000Z",
"2015-09-18T23:56:04.000Z",
"2015-09-18T23:56:04.123Z",
"2015-09-18T23:56:04.1234Z",
"2015-09-18T23:56:04.124Z",
"2015-09-18T23:56:05.000Z",
"2015-09-18T23:57:00.000Z",
"2015-09-18T24:00:00.000Z",
"2015-09-19T00:00:00.000Z",
"2015-10-00T00:00:00.000Z",
"2016-10-00T00:00:00.000Z",
"9999-12-31T23:59:59.999Z",
];
inputs.sort();
let outputs = inputs
.into_iter()
.flat_map(try_parse_monotonic_iso8601_timestamp)
.collect::<Vec<_>>();
assert!(!outputs.is_empty());
let mut outputs_sorted = outputs.clone();
outputs_sorted.sort();
assert_eq!(outputs, outputs_sorted);
}
}