Merge pull request #886 from gwenn/date_format

Fix DateTime format
This commit is contained in:
gwenn 2021-01-30 09:17:10 +01:00 committed by GitHub
commit 7def43fe7b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 45 additions and 28 deletions

View File

@ -1,7 +1,5 @@
//! Convert most of the [Time Strings](http://sqlite.org/lang_datefunc.html) to chrono types. //! Convert most of the [Time Strings](http://sqlite.org/lang_datefunc.html) to chrono types.
use std::borrow::Cow;
use chrono::{DateTime, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc}; use chrono::{DateTime, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc};
use crate::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef}; use crate::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
@ -11,7 +9,7 @@ use crate::Result;
impl ToSql for NaiveDate { impl ToSql for NaiveDate {
#[inline] #[inline]
fn to_sql(&self) -> Result<ToSqlOutput<'_>> { fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
let date_str = self.format("%Y-%m-%d").to_string(); let date_str = self.format("%F").to_string();
Ok(ToSqlOutput::from(date_str)) Ok(ToSqlOutput::from(date_str))
} }
} }
@ -22,7 +20,7 @@ impl FromSql for NaiveDate {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> { fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
value value
.as_str() .as_str()
.and_then(|s| match NaiveDate::parse_from_str(s, "%Y-%m-%d") { .and_then(|s| match NaiveDate::parse_from_str(s, "%F") {
Ok(dt) => Ok(dt), Ok(dt) => Ok(dt),
Err(err) => Err(FromSqlError::Other(Box::new(err))), Err(err) => Err(FromSqlError::Other(Box::new(err))),
}) })
@ -33,7 +31,7 @@ impl FromSql for NaiveDate {
impl ToSql for NaiveTime { impl ToSql for NaiveTime {
#[inline] #[inline]
fn to_sql(&self) -> Result<ToSqlOutput<'_>> { fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
let date_str = self.format("%H:%M:%S%.f").to_string(); let date_str = self.format("%T%.f").to_string();
Ok(ToSqlOutput::from(date_str)) Ok(ToSqlOutput::from(date_str))
} }
} }
@ -44,8 +42,8 @@ impl FromSql for NaiveTime {
value.as_str().and_then(|s| { value.as_str().and_then(|s| {
let fmt = match s.len() { let fmt = match s.len() {
5 => "%H:%M", 5 => "%H:%M",
8 => "%H:%M:%S", 8 => "%T",
_ => "%H:%M:%S%.f", _ => "%T%.f",
}; };
match NaiveTime::parse_from_str(s, fmt) { match NaiveTime::parse_from_str(s, fmt) {
Ok(dt) => Ok(dt), Ok(dt) => Ok(dt),
@ -56,11 +54,11 @@ impl FromSql for NaiveTime {
} }
/// ISO 8601 combined date and time without timezone => /// ISO 8601 combined date and time without timezone =>
/// "YYYY-MM-DDTHH:MM:SS.SSS" /// "YYYY-MM-DD HH:MM:SS.SSS"
impl ToSql for NaiveDateTime { impl ToSql for NaiveDateTime {
#[inline] #[inline]
fn to_sql(&self) -> Result<ToSqlOutput<'_>> { fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
let date_str = self.format("%Y-%m-%dT%H:%M:%S%.f").to_string(); let date_str = self.format("%F %T%.f").to_string();
Ok(ToSqlOutput::from(date_str)) Ok(ToSqlOutput::from(date_str))
} }
} }
@ -72,9 +70,9 @@ impl FromSql for NaiveDateTime {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> { fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
value.as_str().and_then(|s| { value.as_str().and_then(|s| {
let fmt = if s.len() >= 11 && s.as_bytes()[10] == b'T' { let fmt = if s.len() >= 11 && s.as_bytes()[10] == b'T' {
"%Y-%m-%dT%H:%M:%S%.f" "%FT%T%.f"
} else { } else {
"%Y-%m-%d %H:%M:%S%.f" "%F %T%.f"
}; };
match NaiveDateTime::parse_from_str(s, fmt) { match NaiveDateTime::parse_from_str(s, fmt) {
@ -86,34 +84,29 @@ impl FromSql for NaiveDateTime {
} }
/// Date and time with time zone => UTC RFC3339 timestamp /// Date and time with time zone => UTC RFC3339 timestamp
/// ("YYYY-MM-DDTHH:MM:SS.SSS+00:00"). /// ("YYYY-MM-DD HH:MM:SS.SSS+00:00").
impl<Tz: TimeZone> ToSql for DateTime<Tz> { impl<Tz: TimeZone> ToSql for DateTime<Tz> {
#[inline] #[inline]
fn to_sql(&self) -> Result<ToSqlOutput<'_>> { fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
Ok(ToSqlOutput::from(self.with_timezone(&Utc).to_rfc3339())) let date_str = self.with_timezone(&Utc).format("%F %T%.f%:z").to_string();
Ok(ToSqlOutput::from(date_str))
} }
} }
/// RFC3339 ("YYYY-MM-DDTHH:MM:SS.SSS[+-]HH:MM") into `DateTime<Utc>`. /// RFC3339 ("YYYY-MM-DD HH:MM:SS.SSS[+-]HH:MM") into `DateTime<Utc>`.
impl FromSql for DateTime<Utc> { impl FromSql for DateTime<Utc> {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> { fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
{ {
// Try to parse value as rfc3339 first. // Try to parse value as rfc3339 first.
let s = value.as_str()?; let s = value.as_str()?;
// If timestamp looks space-separated, make a copy and replace it with 'T'. let fmt = if s.len() >= 11 && s.as_bytes()[10] == b'T' {
let s = if s.len() >= 11 && s.as_bytes()[10] == b' ' { "%FT%T%.f%:z"
let mut s = s.to_string();
unsafe {
let sbytes = s.as_mut_vec();
sbytes[10] = b'T';
}
Cow::Owned(s)
} else { } else {
Cow::Borrowed(s) "%F %T%.f%:z"
}; };
if let Ok(dt) = DateTime::parse_from_rfc3339(&s) { if let Ok(dt) = DateTime::parse_from_str(s, fmt) {
return Ok(dt.with_timezone(&Utc)); return Ok(dt.with_timezone(&Utc));
} }
} }
@ -123,7 +116,7 @@ impl FromSql for DateTime<Utc> {
} }
} }
/// RFC3339 ("YYYY-MM-DDTHH:MM:SS.SSS[+-]HH:MM") into `DateTime<Local>`. /// RFC3339 ("YYYY-MM-DD HH:MM:SS.SSS[+-]HH:MM") into `DateTime<Local>`.
impl FromSql for DateTime<Local> { impl FromSql for DateTime<Local> {
#[inline] #[inline]
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> { fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
@ -179,7 +172,7 @@ mod test {
db.execute("INSERT INTO foo (t) VALUES (?)", [dt])?; db.execute("INSERT INTO foo (t) VALUES (?)", [dt])?;
let s: String = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?; let s: String = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
assert_eq!("2016-02-23T23:56:04", s); assert_eq!("2016-02-23 23:56:04", s);
let v: NaiveDateTime = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?; let v: NaiveDateTime = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
assert_eq!(dt, v); assert_eq!(dt, v);
@ -200,7 +193,7 @@ mod test {
db.execute("INSERT INTO foo (t) VALUES (?)", [utc])?; db.execute("INSERT INTO foo (t) VALUES (?)", [utc])?;
let s: String = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?; let s: String = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
assert_eq!("2016-02-23T23:56:04.789+00:00", s); assert_eq!("2016-02-23 23:56:04.789+00:00", s);
let v1: DateTime<Utc> = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?; let v1: DateTime<Utc> = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
assert_eq!(utc, v1); assert_eq!(utc, v1);
@ -252,4 +245,20 @@ mod test {
assert!(result.is_ok()); assert!(result.is_ok());
Ok(()) Ok(())
} }
#[test]
fn test_naive_date_time_param() -> Result<()> {
let db = checked_memory_handle()?;
let result: Result<bool> = db.query_row("SELECT 1 WHERE ? BETWEEN datetime('now', '-1 seconds') AND datetime('now', '+1 seconds')", [Utc::now().naive_utc()], |r| r.get(0));
assert!(result.is_ok());
Ok(())
}
#[test]
fn test_date_time_param() -> Result<()> {
let db = checked_memory_handle()?;
let result: Result<bool> = db.query_row("SELECT 1 WHERE ? BETWEEN datetime('now', '-1 seconds') AND datetime('now', '+1 seconds')", [Utc::now()], |r| r.get(0));
assert!(result.is_ok());
Ok(())
}
} }

View File

@ -4,7 +4,7 @@ use crate::Result;
use time::{OffsetDateTime, PrimitiveDateTime, UtcOffset}; use time::{OffsetDateTime, PrimitiveDateTime, UtcOffset};
const CURRENT_TIMESTAMP_FMT: &str = "%Y-%m-%d %H:%M:%S"; const CURRENT_TIMESTAMP_FMT: &str = "%Y-%m-%d %H:%M:%S";
const SQLITE_DATETIME_FMT: &str = "%Y-%m-%dT%H:%M:%S.%NZ"; const SQLITE_DATETIME_FMT: &str = "%Y-%m-%d %H:%M:%S.%NZ";
const SQLITE_DATETIME_FMT_LEGACY: &str = "%Y-%m-%d %H:%M:%S:%N %z"; const SQLITE_DATETIME_FMT_LEGACY: &str = "%Y-%m-%d %H:%M:%S:%N %z";
impl ToSql for OffsetDateTime { impl ToSql for OffsetDateTime {
@ -79,4 +79,12 @@ mod test {
assert!(result.is_ok()); assert!(result.is_ok());
Ok(()) Ok(())
} }
#[test]
fn test_param() -> Result<()> {
let db = checked_memory_handle()?;
let result: Result<bool> = db.query_row("SELECT 1 WHERE ? BETWEEN datetime('now', '-1 seconds') AND datetime('now', '+1 seconds')", [OffsetDateTime::now_utc()], |r| r.get(0));
assert!(result.is_ok());
Ok(())
}
} }