Ensure invalid datetimes to not panic (#1550)
This commit is contained in:
parent
63ba510b09
commit
07a2e5f009
1 changed files with 42 additions and 40 deletions
|
@ -3,11 +3,13 @@ use crate::sql::duration::Duration;
|
||||||
use crate::sql::error::IResult;
|
use crate::sql::error::IResult;
|
||||||
use crate::sql::escape::escape_str;
|
use crate::sql::escape::escape_str;
|
||||||
use crate::sql::serde::is_internal_serialization;
|
use crate::sql::serde::is_internal_serialization;
|
||||||
use chrono::{DateTime, FixedOffset, SecondsFormat, TimeZone, Utc};
|
use chrono::{DateTime, FixedOffset, NaiveDate, Offset, SecondsFormat, TimeZone, Utc};
|
||||||
use nom::branch::alt;
|
use nom::branch::alt;
|
||||||
use nom::character::complete::char;
|
use nom::character::complete::char;
|
||||||
use nom::combinator::map;
|
use nom::combinator::map;
|
||||||
|
use nom::error::ErrorKind;
|
||||||
use nom::sequence::delimited;
|
use nom::sequence::delimited;
|
||||||
|
use nom::{error_position, Err};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::fmt::{self, Display, Formatter};
|
use std::fmt::{self, Display, Formatter};
|
||||||
use std::ops;
|
use std::ops;
|
||||||
|
@ -119,9 +121,7 @@ fn date(i: &str) -> IResult<&str, Datetime> {
|
||||||
let (i, mon) = month(i)?;
|
let (i, mon) = month(i)?;
|
||||||
let (i, _) = char('-')(i)?;
|
let (i, _) = char('-')(i)?;
|
||||||
let (i, day) = day(i)?;
|
let (i, day) = day(i)?;
|
||||||
|
convert(i, (year, mon, day), (0, 0, 0, 0), Utc.fix())
|
||||||
let d = Utc.ymd(year, mon, day).and_hms(0, 0, 0);
|
|
||||||
Ok((i, Datetime(d)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn time(i: &str) -> IResult<&str, Datetime> {
|
fn time(i: &str) -> IResult<&str, Datetime> {
|
||||||
|
@ -137,20 +137,7 @@ fn time(i: &str) -> IResult<&str, Datetime> {
|
||||||
let (i, _) = char(':')(i)?;
|
let (i, _) = char(':')(i)?;
|
||||||
let (i, sec) = second(i)?;
|
let (i, sec) = second(i)?;
|
||||||
let (i, zone) = zone(i)?;
|
let (i, zone) = zone(i)?;
|
||||||
|
convert(i, (year, mon, day), (hour, min, sec, 0), zone)
|
||||||
let v = match zone {
|
|
||||||
Some(z) => {
|
|
||||||
let d = z.ymd(year, mon, day).and_hms(hour, min, sec);
|
|
||||||
let d = d.with_timezone(&Utc);
|
|
||||||
Datetime(d)
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
let d = Utc.ymd(year, mon, day).and_hms(hour, min, sec);
|
|
||||||
Datetime(d)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok((i, v))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn nano(i: &str) -> IResult<&str, Datetime> {
|
fn nano(i: &str) -> IResult<&str, Datetime> {
|
||||||
|
@ -167,20 +154,27 @@ fn nano(i: &str) -> IResult<&str, Datetime> {
|
||||||
let (i, sec) = second(i)?;
|
let (i, sec) = second(i)?;
|
||||||
let (i, nano) = nanosecond(i)?;
|
let (i, nano) = nanosecond(i)?;
|
||||||
let (i, zone) = zone(i)?;
|
let (i, zone) = zone(i)?;
|
||||||
|
convert(i, (year, mon, day), (hour, min, sec, nano), zone)
|
||||||
let v = match zone {
|
|
||||||
Some(z) => {
|
|
||||||
let d = z.ymd(year, mon, day).and_hms_nano(hour, min, sec, nano);
|
|
||||||
let d = d.with_timezone(&Utc);
|
|
||||||
Datetime(d)
|
|
||||||
}
|
}
|
||||||
None => {
|
|
||||||
let d = Utc.ymd(year, mon, day).and_hms_nano(hour, min, sec, nano);
|
|
||||||
Datetime(d)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok((i, v))
|
fn convert(
|
||||||
|
i: &str,
|
||||||
|
(year, mon, day): (i32, u32, u32),
|
||||||
|
(hour, min, sec, nano): (u32, u32, u32, u32),
|
||||||
|
zone: FixedOffset,
|
||||||
|
) -> IResult<&str, Datetime> {
|
||||||
|
// Attempt to create date
|
||||||
|
let n = NaiveDate::from_ymd_opt(year, mon, day)
|
||||||
|
.ok_or_else(|| Err::Error(error_position!(i, ErrorKind::Verify)))?;
|
||||||
|
// Attempt to create time
|
||||||
|
let d = zone
|
||||||
|
.from_local_date(&n)
|
||||||
|
.unwrap()
|
||||||
|
.and_hms_nano_opt(hour, min, sec, nano)
|
||||||
|
.ok_or_else(|| Err::Error(error_position!(i, ErrorKind::Verify)))?
|
||||||
|
.with_timezone(&Utc);
|
||||||
|
// This is a valid datetime
|
||||||
|
Ok((i, Datetime(d)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn year(i: &str) -> IResult<&str, i32> {
|
fn year(i: &str) -> IResult<&str, i32> {
|
||||||
|
@ -207,7 +201,7 @@ fn minute(i: &str) -> IResult<&str, u32> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn second(i: &str) -> IResult<&str, u32> {
|
fn second(i: &str) -> IResult<&str, u32> {
|
||||||
take_digits_range(i, 2, 0..=59)
|
take_digits_range(i, 2, 0..=60)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn nanosecond(i: &str) -> IResult<&str, u32> {
|
fn nanosecond(i: &str) -> IResult<&str, u32> {
|
||||||
|
@ -226,28 +220,28 @@ fn nanosecond(i: &str) -> IResult<&str, u32> {
|
||||||
Ok((i, v))
|
Ok((i, v))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn zone(i: &str) -> IResult<&str, Option<FixedOffset>> {
|
fn zone(i: &str) -> IResult<&str, FixedOffset> {
|
||||||
alt((zone_utc, zone_all))(i)
|
alt((zone_utc, zone_all))(i)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn zone_utc(i: &str) -> IResult<&str, Option<FixedOffset>> {
|
fn zone_utc(i: &str) -> IResult<&str, FixedOffset> {
|
||||||
let (i, _) = char('Z')(i)?;
|
let (i, _) = char('Z')(i)?;
|
||||||
Ok((i, None))
|
Ok((i, Utc.fix()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn zone_all(i: &str) -> IResult<&str, Option<FixedOffset>> {
|
fn zone_all(i: &str) -> IResult<&str, FixedOffset> {
|
||||||
let (i, s) = sign(i)?;
|
let (i, s) = sign(i)?;
|
||||||
let (i, h) = hour(i)?;
|
let (i, h) = hour(i)?;
|
||||||
let (i, _) = char(':')(i)?;
|
let (i, _) = char(':')(i)?;
|
||||||
let (i, m) = minute(i)?;
|
let (i, m) = minute(i)?;
|
||||||
if h == 0 && m == 0 {
|
if h == 0 && m == 0 {
|
||||||
Ok((i, None))
|
Ok((i, Utc.fix()))
|
||||||
} else if s < 0 {
|
} else if s < 0 {
|
||||||
Ok((i, { Some(FixedOffset::west((h * 3600 + m * 60) as i32)) }))
|
Ok((i, FixedOffset::west((h * 3600 + m * 60) as i32)))
|
||||||
} else if s > 0 {
|
} else if s > 0 {
|
||||||
Ok((i, { Some(FixedOffset::east((h * 3600 + m * 60) as i32)) }))
|
Ok((i, FixedOffset::east((h * 3600 + m * 60) as i32)))
|
||||||
} else {
|
} else {
|
||||||
Ok((i, None))
|
Ok((i, Utc.fix()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -307,4 +301,12 @@ mod tests {
|
||||||
let out = res.unwrap().1;
|
let out = res.unwrap().1;
|
||||||
assert_eq!("'2012-04-24T02:55:43.511Z'", format!("{}", out));
|
assert_eq!("'2012-04-24T02:55:43.511Z'", format!("{}", out));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn date_time_illegal_date() {
|
||||||
|
// Hey! There's not a 31st of November!
|
||||||
|
let sql = "2022-11-31T12:00:00.000Z";
|
||||||
|
let res = datetime_raw(sql);
|
||||||
|
assert!(res.is_err());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue