2022-09-22 01:25:45 +00:00
|
|
|
use crate::sql::common::{take_digits, take_digits_range, take_u32_len};
|
2022-09-22 23:48:49 +00:00
|
|
|
use crate::sql::duration::Duration;
|
2022-01-16 20:31:50 +00:00
|
|
|
use crate::sql::error::IResult;
|
2023-05-31 07:36:29 +00:00
|
|
|
use crate::sql::escape::quote_str;
|
2023-04-25 10:13:04 +00:00
|
|
|
use crate::sql::strand::Strand;
|
2023-06-13 21:13:10 +00:00
|
|
|
use chrono::{
|
|
|
|
DateTime, FixedOffset, NaiveDate, NaiveDateTime, NaiveTime, Offset, SecondsFormat, TimeZone,
|
|
|
|
Utc,
|
|
|
|
};
|
2020-06-29 15:36:01 +00:00
|
|
|
use nom::branch::alt;
|
2022-03-16 23:52:25 +00:00
|
|
|
use nom::character::complete::char;
|
2020-06-29 15:36:01 +00:00
|
|
|
use nom::combinator::map;
|
2023-01-15 09:29:46 +00:00
|
|
|
use nom::error::ErrorKind;
|
2020-06-29 15:36:01 +00:00
|
|
|
use nom::sequence::delimited;
|
2023-01-15 09:29:46 +00:00
|
|
|
use nom::{error_position, Err};
|
2023-08-17 18:03:46 +00:00
|
|
|
use revision::revisioned;
|
2020-06-29 15:36:01 +00:00
|
|
|
use serde::{Deserialize, Serialize};
|
2022-10-16 16:19:36 +00:00
|
|
|
use std::fmt::{self, Display, Formatter};
|
|
|
|
use std::ops;
|
2022-05-05 05:01:00 +00:00
|
|
|
use std::ops::Deref;
|
2020-06-29 15:36:01 +00:00
|
|
|
use std::str;
|
2023-04-25 10:13:04 +00:00
|
|
|
use std::str::FromStr;
|
2020-06-29 15:36:01 +00:00
|
|
|
|
2023-09-08 11:28:36 +00:00
|
|
|
use super::error::expected;
|
|
|
|
|
2023-03-30 10:41:44 +00:00
|
|
|
pub(crate) const TOKEN: &str = "$surrealdb::private::sql::Datetime";
|
|
|
|
|
2023-07-23 08:19:28 +00:00
|
|
|
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize, Hash)]
|
2023-04-29 15:58:22 +00:00
|
|
|
#[serde(rename = "$surrealdb::private::sql::Datetime")]
|
2023-08-17 18:03:46 +00:00
|
|
|
#[revisioned(revision = 1)]
|
2023-09-04 07:54:04 +00:00
|
|
|
pub struct Datetime(pub DateTime<Utc>);
|
2020-06-29 15:36:01 +00:00
|
|
|
|
|
|
|
impl Default for Datetime {
|
|
|
|
fn default() -> Self {
|
2022-10-04 21:51:18 +00:00
|
|
|
Self(Utc::now())
|
2020-06-29 15:36:01 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-13 17:36:41 +00:00
|
|
|
impl From<DateTime<Utc>> for Datetime {
|
|
|
|
fn from(v: DateTime<Utc>) -> Self {
|
2022-10-04 21:51:18 +00:00
|
|
|
Self(v)
|
2022-01-13 17:36:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-25 10:13:04 +00:00
|
|
|
impl From<Datetime> for DateTime<Utc> {
|
|
|
|
fn from(x: Datetime) -> Self {
|
|
|
|
x.0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl FromStr for Datetime {
|
|
|
|
type Err = ();
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
|
|
Self::try_from(s)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl TryFrom<String> for Datetime {
|
|
|
|
type Error = ();
|
|
|
|
fn try_from(v: String) -> Result<Self, Self::Error> {
|
|
|
|
Self::try_from(v.as_str())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl TryFrom<Strand> for Datetime {
|
|
|
|
type Error = ();
|
|
|
|
fn try_from(v: Strand) -> Result<Self, Self::Error> {
|
|
|
|
Self::try_from(v.as_str())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl TryFrom<&str> for Datetime {
|
|
|
|
type Error = ();
|
|
|
|
fn try_from(v: &str) -> Result<Self, Self::Error> {
|
|
|
|
match datetime_all_raw(v) {
|
|
|
|
Ok((_, v)) => Ok(v),
|
|
|
|
_ => Err(()),
|
2021-03-29 15:43:37 +00:00
|
|
|
}
|
2020-06-29 15:36:01 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-05 05:01:00 +00:00
|
|
|
impl Deref for Datetime {
|
|
|
|
type Target = DateTime<Utc>;
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
|
|
&self.0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-16 16:19:36 +00:00
|
|
|
impl Datetime {
|
2022-10-19 09:55:19 +00:00
|
|
|
/// Convert the Datetime to a raw String
|
2022-10-16 16:19:36 +00:00
|
|
|
pub fn to_raw(&self) -> String {
|
2022-10-17 02:25:58 +00:00
|
|
|
self.0.to_rfc3339_opts(SecondsFormat::AutoSi, true)
|
2022-10-16 16:19:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Display for Datetime {
|
|
|
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
2023-06-26 11:02:26 +00:00
|
|
|
Display::fmt("e_str(&self.to_raw()), f)
|
2021-03-29 15:43:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-04 21:51:18 +00:00
|
|
|
impl ops::Sub<Self> for Datetime {
|
2022-09-22 23:48:49 +00:00
|
|
|
type Output = Duration;
|
2022-10-04 21:51:18 +00:00
|
|
|
fn sub(self, other: Self) -> Duration {
|
2022-09-22 23:48:49 +00:00
|
|
|
match (self.0 - other.0).to_std() {
|
|
|
|
Ok(d) => Duration::from(d),
|
|
|
|
Err(_) => Duration::default(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-29 15:36:01 +00:00
|
|
|
pub fn datetime(i: &str) -> IResult<&str, Datetime> {
|
2023-09-08 11:28:36 +00:00
|
|
|
expected("a datetime", alt((datetime_single, datetime_double)))(i)
|
2020-06-29 15:36:01 +00:00
|
|
|
}
|
|
|
|
|
2022-10-17 02:26:32 +00:00
|
|
|
fn datetime_single(i: &str) -> IResult<&str, Datetime> {
|
|
|
|
delimited(char('\''), datetime_raw, char('\''))(i)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn datetime_double(i: &str) -> IResult<&str, Datetime> {
|
|
|
|
delimited(char('\"'), datetime_raw, char('\"'))(i)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn datetime_all_raw(i: &str) -> IResult<&str, Datetime> {
|
2020-06-29 15:36:01 +00:00
|
|
|
alt((nano, time, date))(i)
|
|
|
|
}
|
|
|
|
|
2022-10-17 02:26:32 +00:00
|
|
|
fn datetime_raw(i: &str) -> IResult<&str, Datetime> {
|
|
|
|
alt((nano, time))(i)
|
|
|
|
}
|
|
|
|
|
2020-06-29 15:36:01 +00:00
|
|
|
fn date(i: &str) -> IResult<&str, Datetime> {
|
|
|
|
let (i, year) = year(i)?;
|
2022-03-16 23:52:25 +00:00
|
|
|
let (i, _) = char('-')(i)?;
|
2020-06-29 15:36:01 +00:00
|
|
|
let (i, mon) = month(i)?;
|
2022-03-16 23:52:25 +00:00
|
|
|
let (i, _) = char('-')(i)?;
|
2020-06-29 15:36:01 +00:00
|
|
|
let (i, day) = day(i)?;
|
2023-01-15 09:29:46 +00:00
|
|
|
convert(i, (year, mon, day), (0, 0, 0, 0), Utc.fix())
|
2020-06-29 15:36:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn time(i: &str) -> IResult<&str, Datetime> {
|
|
|
|
let (i, year) = year(i)?;
|
2022-03-16 23:52:25 +00:00
|
|
|
let (i, _) = char('-')(i)?;
|
2020-06-29 15:36:01 +00:00
|
|
|
let (i, mon) = month(i)?;
|
2022-03-16 23:52:25 +00:00
|
|
|
let (i, _) = char('-')(i)?;
|
2020-06-29 15:36:01 +00:00
|
|
|
let (i, day) = day(i)?;
|
2022-03-16 23:52:25 +00:00
|
|
|
let (i, _) = char('T')(i)?;
|
2020-06-29 15:36:01 +00:00
|
|
|
let (i, hour) = hour(i)?;
|
2022-03-16 23:52:25 +00:00
|
|
|
let (i, _) = char(':')(i)?;
|
2020-06-29 15:36:01 +00:00
|
|
|
let (i, min) = minute(i)?;
|
2022-03-16 23:52:25 +00:00
|
|
|
let (i, _) = char(':')(i)?;
|
2020-06-29 15:36:01 +00:00
|
|
|
let (i, sec) = second(i)?;
|
|
|
|
let (i, zone) = zone(i)?;
|
2023-01-15 09:29:46 +00:00
|
|
|
convert(i, (year, mon, day), (hour, min, sec, 0), zone)
|
2020-06-29 15:36:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn nano(i: &str) -> IResult<&str, Datetime> {
|
|
|
|
let (i, year) = year(i)?;
|
2022-03-16 23:52:25 +00:00
|
|
|
let (i, _) = char('-')(i)?;
|
2020-06-29 15:36:01 +00:00
|
|
|
let (i, mon) = month(i)?;
|
2022-03-16 23:52:25 +00:00
|
|
|
let (i, _) = char('-')(i)?;
|
2020-06-29 15:36:01 +00:00
|
|
|
let (i, day) = day(i)?;
|
2022-03-16 23:52:25 +00:00
|
|
|
let (i, _) = char('T')(i)?;
|
2020-06-29 15:36:01 +00:00
|
|
|
let (i, hour) = hour(i)?;
|
2022-03-16 23:52:25 +00:00
|
|
|
let (i, _) = char(':')(i)?;
|
2020-06-29 15:36:01 +00:00
|
|
|
let (i, min) = minute(i)?;
|
2022-03-16 23:52:25 +00:00
|
|
|
let (i, _) = char(':')(i)?;
|
2020-06-29 15:36:01 +00:00
|
|
|
let (i, sec) = second(i)?;
|
|
|
|
let (i, nano) = nanosecond(i)?;
|
|
|
|
let (i, zone) = zone(i)?;
|
2023-01-15 09:29:46 +00:00
|
|
|
convert(i, (year, mon, day), (hour, min, sec, nano), zone)
|
|
|
|
}
|
2020-06-29 15:36:01 +00:00
|
|
|
|
2023-01-15 09:29:46 +00:00
|
|
|
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
|
2023-01-14 19:28:33 +00:00
|
|
|
let d = NaiveDate::from_ymd_opt(year, mon, day)
|
2023-01-15 09:29:46 +00:00
|
|
|
.ok_or_else(|| Err::Error(error_position!(i, ErrorKind::Verify)))?;
|
|
|
|
// Attempt to create time
|
2023-01-14 19:28:33 +00:00
|
|
|
let t = NaiveTime::from_hms_nano_opt(hour, min, sec, nano)
|
|
|
|
.ok_or_else(|| Err::Error(error_position!(i, ErrorKind::Verify)))?;
|
|
|
|
//
|
|
|
|
let v = NaiveDateTime::new(d, t);
|
|
|
|
// Attempt to create time
|
2023-01-15 09:29:46 +00:00
|
|
|
let d = zone
|
2023-01-14 19:28:33 +00:00
|
|
|
.from_local_datetime(&v)
|
|
|
|
.earliest()
|
2023-01-15 09:29:46 +00:00
|
|
|
.ok_or_else(|| Err::Error(error_position!(i, ErrorKind::Verify)))?
|
|
|
|
.with_timezone(&Utc);
|
|
|
|
// This is a valid datetime
|
|
|
|
Ok((i, Datetime(d)))
|
2020-06-29 15:36:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn year(i: &str) -> IResult<&str, i32> {
|
|
|
|
let (i, s) = sign(i).unwrap_or((i, 1));
|
|
|
|
let (i, y) = take_digits(i, 4)?;
|
|
|
|
let v = s * y as i32;
|
|
|
|
Ok((i, v))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn month(i: &str) -> IResult<&str, u32> {
|
|
|
|
take_digits_range(i, 2, 1..=12)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn day(i: &str) -> IResult<&str, u32> {
|
|
|
|
take_digits_range(i, 2, 1..=31)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn hour(i: &str) -> IResult<&str, u32> {
|
|
|
|
take_digits_range(i, 2, 0..=24)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn minute(i: &str) -> IResult<&str, u32> {
|
|
|
|
take_digits_range(i, 2, 0..=59)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn second(i: &str) -> IResult<&str, u32> {
|
2023-01-15 09:29:46 +00:00
|
|
|
take_digits_range(i, 2, 0..=60)
|
2020-06-29 15:36:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn nanosecond(i: &str) -> IResult<&str, u32> {
|
2022-03-16 23:52:25 +00:00
|
|
|
let (i, _) = char('.')(i)?;
|
2022-09-22 01:25:45 +00:00
|
|
|
let (i, (v, l)) = take_u32_len(i)?;
|
|
|
|
let v = match l {
|
|
|
|
l if l <= 2 => v * 10000000,
|
|
|
|
l if l <= 3 => v * 1000000,
|
|
|
|
l if l <= 4 => v * 100000,
|
|
|
|
l if l <= 5 => v * 10000,
|
|
|
|
l if l <= 6 => v * 1000,
|
|
|
|
l if l <= 7 => v * 100,
|
|
|
|
l if l <= 8 => v * 10,
|
|
|
|
_ => v,
|
|
|
|
};
|
2020-06-29 15:36:01 +00:00
|
|
|
Ok((i, v))
|
|
|
|
}
|
|
|
|
|
2023-01-15 09:29:46 +00:00
|
|
|
fn zone(i: &str) -> IResult<&str, FixedOffset> {
|
2020-06-29 15:36:01 +00:00
|
|
|
alt((zone_utc, zone_all))(i)
|
|
|
|
}
|
|
|
|
|
2023-01-15 09:29:46 +00:00
|
|
|
fn zone_utc(i: &str) -> IResult<&str, FixedOffset> {
|
2022-03-16 23:52:25 +00:00
|
|
|
let (i, _) = char('Z')(i)?;
|
2023-01-15 09:29:46 +00:00
|
|
|
Ok((i, Utc.fix()))
|
2020-06-29 15:36:01 +00:00
|
|
|
}
|
|
|
|
|
2023-01-15 09:29:46 +00:00
|
|
|
fn zone_all(i: &str) -> IResult<&str, FixedOffset> {
|
2020-06-29 15:36:01 +00:00
|
|
|
let (i, s) = sign(i)?;
|
|
|
|
let (i, h) = hour(i)?;
|
2022-03-16 23:52:25 +00:00
|
|
|
let (i, _) = char(':')(i)?;
|
2020-06-29 15:36:01 +00:00
|
|
|
let (i, m) = minute(i)?;
|
|
|
|
if h == 0 && m == 0 {
|
2023-01-15 09:29:46 +00:00
|
|
|
Ok((i, Utc.fix()))
|
2020-06-29 15:36:01 +00:00
|
|
|
} else if s < 0 {
|
2023-01-14 19:28:33 +00:00
|
|
|
match FixedOffset::west_opt((h * 3600 + m * 60) as i32) {
|
|
|
|
Some(v) => Ok((i, v)),
|
|
|
|
None => Err(Err::Error(error_position!(i, ErrorKind::Verify))),
|
|
|
|
}
|
2020-06-29 15:36:01 +00:00
|
|
|
} else if s > 0 {
|
2023-01-14 19:28:33 +00:00
|
|
|
match FixedOffset::east_opt((h * 3600 + m * 60) as i32) {
|
|
|
|
Some(v) => Ok((i, v)),
|
|
|
|
None => Err(Err::Error(error_position!(i, ErrorKind::Verify))),
|
|
|
|
}
|
2020-06-29 15:36:01 +00:00
|
|
|
} else {
|
2023-01-15 09:29:46 +00:00
|
|
|
Ok((i, Utc.fix()))
|
2020-06-29 15:36:01 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn sign(i: &str) -> IResult<&str, i32> {
|
2022-03-16 23:52:25 +00:00
|
|
|
map(alt((char('-'), char('+'))), |s: char| match s {
|
|
|
|
'-' => -1,
|
2020-06-29 15:36:01 +00:00
|
|
|
_ => 1,
|
|
|
|
})(i)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
|
2023-04-25 10:13:04 +00:00
|
|
|
// use chrono::Date;
|
|
|
|
|
2020-06-29 15:36:01 +00:00
|
|
|
use super::*;
|
|
|
|
|
2023-04-25 10:13:04 +00:00
|
|
|
#[test]
|
|
|
|
fn date_zone() {
|
|
|
|
let sql = "2020-01-01T00:00:00Z";
|
|
|
|
let res = datetime_all_raw(sql);
|
|
|
|
let out = res.unwrap().1;
|
|
|
|
assert_eq!("'2020-01-01T00:00:00Z'", format!("{}", out));
|
|
|
|
assert_eq!(out, Datetime::try_from("2020-01-01T00:00:00Z").unwrap());
|
|
|
|
}
|
|
|
|
|
2020-06-29 15:36:01 +00:00
|
|
|
#[test]
|
|
|
|
fn date_time() {
|
|
|
|
let sql = "2012-04-23T18:25:43Z";
|
|
|
|
let res = datetime_raw(sql);
|
|
|
|
let out = res.unwrap().1;
|
2022-10-19 14:48:50 +00:00
|
|
|
assert_eq!("'2012-04-23T18:25:43Z'", format!("{}", out));
|
2023-04-25 10:13:04 +00:00
|
|
|
assert_eq!(out, Datetime::try_from("2012-04-23T18:25:43Z").unwrap());
|
2020-06-29 15:36:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn date_time_nanos() {
|
|
|
|
let sql = "2012-04-23T18:25:43.5631Z";
|
|
|
|
let res = datetime_raw(sql);
|
|
|
|
let out = res.unwrap().1;
|
2022-10-19 14:48:50 +00:00
|
|
|
assert_eq!("'2012-04-23T18:25:43.563100Z'", format!("{}", out));
|
2023-04-25 10:13:04 +00:00
|
|
|
assert_eq!(out, Datetime::try_from("2012-04-23T18:25:43.563100Z").unwrap());
|
2020-06-29 15:36:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn date_time_timezone_utc() {
|
2022-09-22 01:25:45 +00:00
|
|
|
let sql = "2012-04-23T18:25:43.0000511Z";
|
2020-06-29 15:36:01 +00:00
|
|
|
let res = datetime_raw(sql);
|
|
|
|
let out = res.unwrap().1;
|
2022-10-19 14:48:50 +00:00
|
|
|
assert_eq!("'2012-04-23T18:25:43.000051100Z'", format!("{}", out));
|
2023-04-25 10:13:04 +00:00
|
|
|
assert_eq!(out, Datetime::try_from("2012-04-23T18:25:43.000051100Z").unwrap());
|
2020-06-29 15:36:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn date_time_timezone_pacific() {
|
|
|
|
let sql = "2012-04-23T18:25:43.511-08:00";
|
|
|
|
let res = datetime_raw(sql);
|
|
|
|
let out = res.unwrap().1;
|
2022-10-19 14:48:50 +00:00
|
|
|
assert_eq!("'2012-04-24T02:25:43.511Z'", format!("{}", out));
|
2023-04-25 10:13:04 +00:00
|
|
|
assert_eq!(out, Datetime::try_from("2012-04-24T02:25:43.511Z").unwrap());
|
2020-06-29 15:36:01 +00:00
|
|
|
}
|
2022-09-26 10:31:13 +00:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn date_time_timezone_pacific_partial() {
|
|
|
|
let sql = "2012-04-23T18:25:43.511-08:30";
|
|
|
|
let res = datetime_raw(sql);
|
|
|
|
let out = res.unwrap().1;
|
2022-10-19 14:48:50 +00:00
|
|
|
assert_eq!("'2012-04-24T02:55:43.511Z'", format!("{}", out));
|
2023-04-25 10:13:04 +00:00
|
|
|
assert_eq!(out, Datetime::try_from("2012-04-24T02:55:43.511Z").unwrap());
|
2022-09-26 10:31:13 +00:00
|
|
|
}
|
2023-01-15 09:29:46 +00:00
|
|
|
|
2023-03-05 21:59:42 +00:00
|
|
|
#[test]
|
|
|
|
fn date_time_timezone_utc_nanoseconds() {
|
|
|
|
let sql = "2012-04-23T18:25:43.5110000Z";
|
|
|
|
let res = datetime_raw(sql);
|
|
|
|
let out = res.unwrap().1;
|
|
|
|
assert_eq!("'2012-04-23T18:25:43.511Z'", format!("{}", out));
|
2023-04-25 10:13:04 +00:00
|
|
|
assert_eq!(out, Datetime::try_from("2012-04-23T18:25:43.511Z").unwrap());
|
2023-03-05 21:59:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn date_time_timezone_utc_sub_nanoseconds() {
|
|
|
|
let sql = "2012-04-23T18:25:43.0000511Z";
|
|
|
|
let res = datetime_raw(sql);
|
|
|
|
let out = res.unwrap().1;
|
|
|
|
assert_eq!("'2012-04-23T18:25:43.000051100Z'", format!("{}", out));
|
2023-04-25 10:13:04 +00:00
|
|
|
assert_eq!(out, Datetime::try_from("2012-04-23T18:25:43.000051100Z").unwrap());
|
2023-03-05 21:59:42 +00:00
|
|
|
}
|
|
|
|
|
2023-01-15 09:29:46 +00:00
|
|
|
#[test]
|
|
|
|
fn date_time_illegal_date() {
|
|
|
|
// Hey! There's not a 31st of November!
|
|
|
|
let sql = "2022-11-31T12:00:00.000Z";
|
2023-08-29 10:50:00 +00:00
|
|
|
datetime_raw(sql).unwrap_err();
|
2023-01-15 09:29:46 +00:00
|
|
|
}
|
2020-06-29 15:36:01 +00:00
|
|
|
}
|