Skip to content

Commit

Permalink
Support the use of chrono::DateTime<Utc> using the type alias DateTim… (
Browse files Browse the repository at this point in the history
SeaQL#222)

* Support the use of chrono::DateTime<Utc> using the type alias DateTimeUtc

Add a test to ensure the DateTimeUtc is parsed and converted to SQL equivalent of TIMESTAMP

* Ensure DateTime formatting is consistent

* Implement `From` for Utc

Move FixedOffset to its own `From` implementation

Implement test for Utc

* Return a DateTime<Utc> instead of String

* Return timestamp as a String

* Add example documentation for DateTimeUtc
  • Loading branch information
charleschege committed Jan 16, 2022
1 parent a85be76 commit 44aa114
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 7 deletions.
6 changes: 6 additions & 0 deletions src/backend/query_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand Down Expand Up @@ -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")]
Expand Down
2 changes: 2 additions & 0 deletions src/driver/postgres.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<chrono::FixedOffset>),
#[cfg(feature = "postgres-chrono")]
Value::DateTimeUtc(v) => box_to_sql!(v, chrono::DateTime<chrono::Utc>),
#[cfg(feature = "postgres-rust_decimal")]
Value::Decimal(v) => box_to_sql!(v, rust_decimal::Decimal),
#[cfg(feature = "postgres-bigdecimal")]
Expand Down
2 changes: 2 additions & 0 deletions src/driver/sqlx_mysql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
2 changes: 2 additions & 0 deletions src/driver/sqlx_postgres.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
2 changes: 2 additions & 0 deletions src/driver/sqlx_sqlite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
110 changes: 103 additions & 7 deletions src/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -61,6 +61,33 @@ pub enum Value {
#[cfg_attr(docsrs, doc(cfg(feature = "with-chrono")))]
DateTimeWithTimeZone(Option<Box<DateTime<FixedOffset>>>),

/// Support for `chrono::DateTime<Utc>`
///
/// #### 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<DateTimeUtc` to ensure the value is nullable
/// pub launch_date: Option<DateTimeUtc>,
/// 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<Box<DateTime<Utc>>>),

#[cfg(feature = "with-uuid")]
#[cfg_attr(docsrs, doc(cfg(feature = "with-uuid")))]
Uuid(Option<Box<Uuid>>),
Expand Down Expand Up @@ -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<Tz> From<DateTime<Tz>> for Value
where
Tz: TimeZone,
{
fn from(x: DateTime<Tz>) -> Value {
impl From<DateTime<FixedOffset>> for Value {
fn from(x: DateTime<FixedOffset>) -> Value {
let v = DateTime::<FixedOffset>::from_utc(x.naive_utc(), x.offset().fix());
Value::DateTimeWithTimeZone(Some(Box::new(v)))
}
}

impl From<DateTime<Utc>> for Value {
fn from(x: DateTime<Utc>) -> Value {
let v = DateTime::<Utc>::from_utc(x.naive_utc(), Utc);
Value::DateTimeUtc(Some(Box::new(v)))
}
}

impl Nullable for DateTime<FixedOffset> {
fn null() -> Value {
Value::DateTimeWithTimeZone(None)
Expand All @@ -324,6 +355,29 @@ mod with_chrono {
ColumnType::TimestampWithTimeZone(None)
}
}

impl Nullable for DateTime<Utc> {
fn null() -> Value {
Value::DateTimeUtc(None)
}
}

impl ValueType for DateTime<Utc> {
fn try_from(v: Value) -> Result<Self, ValueTypeErr> {
match v {
Value::DateTimeUtc(Some(x)) => Ok(*x),
_ => Err(ValueTypeErr),
}
}

fn type_name() -> String {
stringify!(DateTime<Utc>).to_owned()
}

fn column_type() -> ColumnType {
ColumnType::TimestampWithTimeZone(None)
}
}
}

#[cfg(feature = "with-rust_decimal")]
Expand Down Expand Up @@ -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<FixedOffset>> {
match self {
Expand All @@ -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<Utc>> {
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<String> {
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 {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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::<Utc>::from_utc(NaiveDateTime::from_timestamp(61, 0), Utc);
let value: Value = timestamp.into();
let out: DateTime<Utc> = value.unwrap();
assert_eq!(out, timestamp);
}

#[test]
#[cfg(feature = "with-chrono")]
fn test_chrono_query() {
Expand Down

0 comments on commit 44aa114

Please sign in to comment.