diff --git a/src/types/chrono.rs b/src/types/chrono.rs index 78acdf7..c1f4a6d 100644 --- a/src/types/chrono.rs +++ b/src/types/chrono.rs @@ -1,7 +1,5 @@ //! 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 crate::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef}; @@ -11,7 +9,7 @@ use crate::Result; impl ToSql for NaiveDate { #[inline] fn to_sql(&self) -> Result> { - let date_str = self.format("%Y-%m-%d").to_string(); + let date_str = self.format("%F").to_string(); Ok(ToSqlOutput::from(date_str)) } } @@ -22,7 +20,7 @@ impl FromSql for NaiveDate { fn column_result(value: ValueRef<'_>) -> FromSqlResult { value .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), Err(err) => Err(FromSqlError::Other(Box::new(err))), }) @@ -33,7 +31,7 @@ impl FromSql for NaiveDate { impl ToSql for NaiveTime { #[inline] fn to_sql(&self) -> Result> { - 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)) } } @@ -44,8 +42,8 @@ impl FromSql for NaiveTime { value.as_str().and_then(|s| { let fmt = match s.len() { 5 => "%H:%M", - 8 => "%H:%M:%S", - _ => "%H:%M:%S%.f", + 8 => "%T", + _ => "%T%.f", }; match NaiveTime::parse_from_str(s, fmt) { Ok(dt) => Ok(dt), @@ -56,11 +54,11 @@ impl FromSql for NaiveTime { } /// 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 { #[inline] fn to_sql(&self) -> Result> { - 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)) } } @@ -72,9 +70,9 @@ impl FromSql for NaiveDateTime { fn column_result(value: ValueRef<'_>) -> FromSqlResult { value.as_str().and_then(|s| { let fmt = if s.len() >= 11 && s.as_bytes()[10] == b'T' { - "%Y-%m-%dT%H:%M:%S%.f" + "%FT%T%.f" } else { - "%Y-%m-%d %H:%M:%S%.f" + "%F %T%.f" }; match NaiveDateTime::parse_from_str(s, fmt) { @@ -86,34 +84,29 @@ impl FromSql for NaiveDateTime { } /// 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 ToSql for DateTime { #[inline] fn to_sql(&self) -> Result> { - 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`. +/// RFC3339 ("YYYY-MM-DD HH:MM:SS.SSS[+-]HH:MM") into `DateTime`. impl FromSql for DateTime { fn column_result(value: ValueRef<'_>) -> FromSqlResult { { // Try to parse value as rfc3339 first. let s = value.as_str()?; - // If timestamp looks space-separated, make a copy and replace it with 'T'. - let s = if s.len() >= 11 && s.as_bytes()[10] == b' ' { - let mut s = s.to_string(); - unsafe { - let sbytes = s.as_mut_vec(); - sbytes[10] = b'T'; - } - Cow::Owned(s) + let fmt = if s.len() >= 11 && s.as_bytes()[10] == b'T' { + "%FT%T%.f%:z" } 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)); } } @@ -123,7 +116,7 @@ impl FromSql for DateTime { } } -/// RFC3339 ("YYYY-MM-DDTHH:MM:SS.SSS[+-]HH:MM") into `DateTime`. +/// RFC3339 ("YYYY-MM-DD HH:MM:SS.SSS[+-]HH:MM") into `DateTime`. impl FromSql for DateTime { #[inline] fn column_result(value: ValueRef<'_>) -> FromSqlResult { @@ -179,7 +172,7 @@ mod test { db.execute("INSERT INTO foo (t) VALUES (?)", [dt])?; 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))?; assert_eq!(dt, v); @@ -200,7 +193,7 @@ mod test { db.execute("INSERT INTO foo (t) VALUES (?)", [utc])?; 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 = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?; assert_eq!(utc, v1); @@ -252,4 +245,20 @@ mod test { assert!(result.is_ok()); Ok(()) } + + #[test] + fn test_naive_date_time_param() -> Result<()> { + let db = checked_memory_handle()?; + let result: Result = 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 = 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(()) + } } diff --git a/src/types/time.rs b/src/types/time.rs index 54bdda5..a56bd05 100644 --- a/src/types/time.rs +++ b/src/types/time.rs @@ -4,7 +4,7 @@ use crate::Result; use time::{OffsetDateTime, PrimitiveDateTime, UtcOffset}; 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"; impl ToSql for OffsetDateTime { @@ -79,4 +79,12 @@ mod test { assert!(result.is_ok()); Ok(()) } + + #[test] + fn test_param() -> Result<()> { + let db = checked_memory_handle()?; + let result: Result = 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(()) + } }