diff --git a/src/backend/query_builder.rs b/src/backend/query_builder.rs index ab6906b655..6a7da927f9 100644 --- a/src/backend/query_builder.rs +++ b/src/backend/query_builder.rs @@ -760,6 +760,8 @@ pub trait QueryBuilder: QuotedBuilder { Value::DateTime(None) => write!(s, "NULL").unwrap(), #[cfg(feature = "with-chrono")] Value::DateTimeWithTimeZone(None) => write!(s, "NULL").unwrap(), + #[cfg(feature = "with-chrono")] + Value::DateTimeUtc(None) => write!(s, "NULL").unwrap(), #[cfg(feature = "with-rust_decimal")] Value::Decimal(None) => write!(s, "NULL").unwrap(), #[cfg(feature = "with-bigdecimal")] @@ -800,6 +802,10 @@ pub trait QueryBuilder: QuotedBuilder { Value::DateTimeWithTimeZone(Some(v)) => { write!(s, "\'{}\'", v.format("%Y-%m-%d %H:%M:%S %:z").to_string()).unwrap() } + #[cfg(feature = "with-chrono")] + Value::DateTimeUtc(Some(v)) => { + write!(s, "\'{}\'", v.format("%Y-%m-%d %H:%M:%S %:z").to_string()).unwrap() + } #[cfg(feature = "with-rust_decimal")] Value::Decimal(Some(v)) => write!(s, "{}", v).unwrap(), #[cfg(feature = "with-bigdecimal")] diff --git a/src/driver/postgres.rs b/src/driver/postgres.rs index ee1daef875..67dfc3a017 100644 --- a/src/driver/postgres.rs +++ b/src/driver/postgres.rs @@ -53,6 +53,8 @@ impl ToSql for Value { Value::DateTime(v) => box_to_sql!(v, chrono::NaiveDateTime), #[cfg(feature = "postgres-chrono")] Value::DateTimeWithTimeZone(v) => box_to_sql!(v, chrono::DateTime), + #[cfg(feature = "postgres-chrono")] + Value::DateTimeUtc(v) => box_to_sql!(v, chrono::DateTime), #[cfg(feature = "postgres-rust_decimal")] Value::Decimal(v) => box_to_sql!(v, rust_decimal::Decimal), #[cfg(feature = "postgres-bigdecimal")] diff --git a/src/driver/sqlx_mysql.rs b/src/driver/sqlx_mysql.rs index 4615b62786..87b6b76dbb 100644 --- a/src/driver/sqlx_mysql.rs +++ b/src/driver/sqlx_mysql.rs @@ -42,6 +42,8 @@ macro_rules! bind_params_sqlx_mysql { query.bind(value.as_ref_time()) } else if value.is_date_time() { query.bind(value.as_ref_date_time()) + } else if value.is_date_time_utc() { + query.bind(value.as_ref_date_time_utc()) } else if value.is_date_time_with_time_zone() { query.bind(value.as_ref_date_time_with_time_zone_in_naive_utc()) } else if value.is_decimal() { diff --git a/src/driver/sqlx_postgres.rs b/src/driver/sqlx_postgres.rs index 18f5ed24cf..9db7394931 100644 --- a/src/driver/sqlx_postgres.rs +++ b/src/driver/sqlx_postgres.rs @@ -42,6 +42,8 @@ macro_rules! bind_params_sqlx_postgres { query.bind(value.as_ref_time()) } else if value.is_date_time() { query.bind(value.as_ref_date_time()) + } else if value.is_date_time_utc() { + query.bind(value.as_ref_date_time_utc()) } else if value.is_date_time_with_time_zone() { query.bind(value.as_ref_date_time_with_time_zone()) } else if value.is_decimal() { diff --git a/src/driver/sqlx_sqlite.rs b/src/driver/sqlx_sqlite.rs index 9be73d5ed7..c09cd2dd88 100644 --- a/src/driver/sqlx_sqlite.rs +++ b/src/driver/sqlx_sqlite.rs @@ -42,6 +42,8 @@ macro_rules! bind_params_sqlx_sqlite { query.bind(value.as_ref_time()) } else if value.is_date_time() { query.bind(value.as_ref_date_time()) + } else if value.is_date_time_utc() { + query.bind(value.as_ref_date_time_utc()) } else if value.is_date_time_with_time_zone() { query.bind(value.as_ref_date_time_with_time_zone_in_naive_utc()) } else if value.is_decimal() { diff --git a/src/value.rs b/src/value.rs index d969a4b649..337461c3a4 100644 --- a/src/value.rs +++ b/src/value.rs @@ -7,7 +7,7 @@ use serde_json::Value as Json; use std::str::from_utf8; #[cfg(feature = "with-chrono")] -use chrono::{DateTime, FixedOffset, NaiveDate, NaiveDateTime, NaiveTime}; +use chrono::{DateTime, FixedOffset, NaiveDate, NaiveDateTime, NaiveTime, Utc}; #[cfg(feature = "with-rust_decimal")] use rust_decimal::Decimal; @@ -61,6 +61,33 @@ pub enum Value { #[cfg_attr(docsrs, doc(cfg(feature = "with-chrono")))] DateTimeWithTimeZone(Option>>), + /// Support for `chrono::DateTime` + /// + /// #### Example + /// ``` + /// use sea_orm::entity::prelude::*; + /// + /// #[derive(Clone, Debug, PartialEq, DeriveEntityModel)] + /// #[sea_orm(table_name = "satellites")] + /// pub struct Model { + /// #[sea_orm(primary_key)] + /// pub id: i32, + /// pub satellite_name: String, + /// // For older mysql databases like 5.7 + /// // use `Option, + /// pub deployment_date: DateTimeUtc, + /// } + /// + /// #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] + /// pub enum Relation {} + /// + /// impl ActiveModelBehavior for ActiveModel {} + /// ``` + #[cfg(feature = "with-chrono")] + #[cfg_attr(docsrs, doc(cfg(feature = "with-chrono")))] + DateTimeUtc(Option>>), + #[cfg(feature = "with-uuid")] #[cfg_attr(docsrs, doc(cfg(feature = "with-uuid")))] Uuid(Option>), @@ -286,22 +313,26 @@ mod with_json { #[cfg_attr(docsrs, doc(cfg(feature = "with-chrono")))] mod with_chrono { use super::*; - use chrono::{Offset, TimeZone}; + use chrono::{Offset, Utc}; type_to_box_value!(NaiveDate, Date, Date); type_to_box_value!(NaiveTime, Time, Time(None)); type_to_box_value!(NaiveDateTime, DateTime, DateTime(None)); - impl From> for Value - where - Tz: TimeZone, - { - fn from(x: DateTime) -> Value { + impl From> for Value { + fn from(x: DateTime) -> Value { let v = DateTime::::from_utc(x.naive_utc(), x.offset().fix()); Value::DateTimeWithTimeZone(Some(Box::new(v))) } } + impl From> for Value { + fn from(x: DateTime) -> Value { + let v = DateTime::::from_utc(x.naive_utc(), Utc); + Value::DateTimeUtc(Some(Box::new(v))) + } + } + impl Nullable for DateTime { fn null() -> Value { Value::DateTimeWithTimeZone(None) @@ -324,6 +355,29 @@ mod with_chrono { ColumnType::TimestampWithTimeZone(None) } } + + impl Nullable for DateTime { + fn null() -> Value { + Value::DateTimeUtc(None) + } + } + + impl ValueType for DateTime { + fn try_from(v: Value) -> Result { + match v { + Value::DateTimeUtc(Some(x)) => Ok(*x), + _ => Err(ValueTypeErr), + } + } + + fn type_name() -> String { + stringify!(DateTime).to_owned() + } + + fn column_type() -> ColumnType { + ColumnType::TimestampWithTimeZone(None) + } + } } #[cfg(feature = "with-rust_decimal")] @@ -533,6 +587,12 @@ impl Value { #[cfg(not(feature = "with-chrono"))] return false; } + pub fn is_date_time_utc(&self) -> bool { + #[cfg(feature = "with-chrono")] + return matches!(self, Self::DateTimeUtc(_)); + #[cfg(not(feature = "with-chrono"))] + return false; + } #[cfg(feature = "with-chrono")] pub fn as_ref_date_time_with_time_zone(&self) -> Option<&DateTime> { match self { @@ -555,6 +615,29 @@ impl Value { pub fn as_ref_date_time_with_time_zone_in_naive_utc(&self) -> Option<&bool> { panic!("not Value::DateTimeWithTimeZone") } + + #[cfg(feature = "with-chrono")] + pub fn as_ref_date_time_utc(&self) -> Option<&DateTime> { + match self { + Self::DateTimeUtc(v) => v.as_ref().map(|v| v.as_ref()), + _ => panic!("not Value::DateTimeUtc"), + } + } + #[cfg(not(feature = "with-chrono"))] + pub fn as_ref_date_time_utc(&self) -> Option<&bool> { + panic!("not Value::DateTimeUtc") + } + #[cfg(feature = "with-chrono")] + pub fn as_ref_date_time_naive_utc(&self) -> Option { + match self { + Self::DateTimeWithTimeZone(v) => v.as_ref().map(|v| format! {"{:?}", v.as_ref()}), + _ => panic!("not Value::DateTimeWithTimeZone"), + } + } + #[cfg(not(feature = "with-chrono"))] + pub fn as_ref_date_time_naive_utc(&self) -> Option<&bool> { + panic!("not Value::DateTimeWithTimeZone") + } } impl Value { @@ -970,6 +1053,8 @@ pub fn sea_value_to_json_value(value: &Value) -> Json { Value::DateTime(_) => CommonSqlQueryBuilder.value_to_string(value).into(), #[cfg(feature = "with-chrono")] Value::DateTimeWithTimeZone(_) => CommonSqlQueryBuilder.value_to_string(value).into(), + #[cfg(feature = "with-chrono")] + Value::DateTimeUtc(_) => CommonSqlQueryBuilder.value_to_string(value).into(), #[cfg(feature = "with-rust_decimal")] Value::Decimal(Some(v)) => { use rust_decimal::prelude::ToPrimitive; @@ -1275,6 +1360,17 @@ mod tests { assert_eq!(out, timestamp); } + #[test] + #[cfg(feature = "with-chrono")] + fn test_chrono_utc_value() { + use chrono::{DateTime, NaiveDateTime, Utc}; + + let timestamp = DateTime::::from_utc(NaiveDateTime::from_timestamp(61, 0), Utc); + let value: Value = timestamp.into(); + let out: DateTime = value.unwrap(); + assert_eq!(out, timestamp); + } + #[test] #[cfg(feature = "with-chrono")] fn test_chrono_query() {