diff --git a/postgres-types/Cargo.toml b/postgres-types/Cargo.toml index 4add1381..a17829ce 100644 --- a/postgres-types/Cargo.toml +++ b/postgres-types/Cargo.toml @@ -18,6 +18,7 @@ with-eui48-0_4 = ["eui48-04"] with-geo-types-0_4 = ["geo-types-04"] with-serde_json-1 = ["serde-1", "serde_json-1"] with-uuid-0_8 = ["uuid-08"] +with-time-0_2 = ["time-02"] [dependencies] bytes = "0.5" @@ -32,3 +33,4 @@ geo-types-04 = { version = "0.4", package = "geo-types", optional = true } serde-1 = { version = "1.0", package = "serde", optional = true } serde_json-1 = { version = "1.0", package = "serde_json", optional = true } uuid-08 = { version = "0.8", package = "uuid", optional = true } +time-02 = { version = "0.2", package = "time", optional = true } diff --git a/postgres-types/src/lib.rs b/postgres-types/src/lib.rs index f9876e68..88ab298d 100644 --- a/postgres-types/src/lib.rs +++ b/postgres-types/src/lib.rs @@ -198,9 +198,15 @@ mod eui48_04; mod geo_types_04; #[cfg(feature = "with-serde_json-1")] mod serde_json_1; +#[cfg(feature = "with-time-0_2")] +mod time_02; #[cfg(feature = "with-uuid-0_8")] mod uuid_08; +// The time::{date, time} macros produce compile errors if the crate package is renamed. +#[cfg(feature = "with-time-0_2")] +extern crate time_02 as time; + #[doc(hidden)] pub mod private; mod special; @@ -391,6 +397,10 @@ impl WrongType { /// | `chrono::DateTime` | TIMESTAMP WITH TIME ZONE | /// | `chrono::NaiveDate` | DATE | /// | `chrono::NaiveTime` | TIME | +/// | `time::PrimitiveDateTime` | TIMESTAMP | +/// | `time::OffsetDateTime` | TIMESTAMP, TIMESTAMP WITH TIME ZONE | +/// | `time::Date` | DATE | +/// | `time::Time` | TIME | /// | `eui48::MacAddress` | MACADDR | /// | `geo_types::Point` | POINT | /// | `geo_types::Rect` | BOX | @@ -650,6 +660,10 @@ pub enum IsNull { /// | `chrono::DateTime` | TIMESTAMP WITH TIME ZONE | /// | `chrono::NaiveDate` | DATE | /// | `chrono::NaiveTime` | TIME | +/// | `time::PrimitiveDateTime` | TIMESTAMP | +/// | `time::OffsetDateTime` | TIMESTAMP, TIMESTAMP WITH TIME ZONE | +/// | `time::Date` | DATE | +/// | `time::Time` | TIME | /// | `eui48::MacAddress` | MACADDR | /// | `geo_types::Point` | POINT | /// | `geo_types::Rect` | BOX | diff --git a/postgres-types/src/time_02.rs b/postgres-types/src/time_02.rs new file mode 100644 index 00000000..ce80267c --- /dev/null +++ b/postgres-types/src/time_02.rs @@ -0,0 +1,109 @@ +use bytes::BytesMut; +use postgres_protocol::types; +use std::convert::TryFrom; +use std::error::Error; +use time_02::{date, time, Date, Duration, OffsetDateTime, PrimitiveDateTime, Time, UtcOffset}; + +use crate::{FromSql, IsNull, ToSql, Type}; + +#[rustfmt::skip] +const fn base() -> PrimitiveDateTime { + PrimitiveDateTime::new(date!(2000-01-01), time!(00:00:00)) +} + +impl<'a> FromSql<'a> for PrimitiveDateTime { + fn from_sql(_: &Type, raw: &[u8]) -> Result> { + let t = types::timestamp_from_sql(raw)?; + Ok(base() + Duration::microseconds(t)) + } + + accepts!(TIMESTAMP); +} + +impl ToSql for PrimitiveDateTime { + fn to_sql(&self, _: &Type, w: &mut BytesMut) -> Result> { + let time = match i64::try_from((*self - base()).whole_microseconds()) { + Ok(time) => time, + Err(_) => return Err("value too large to transmit".into()), + }; + types::timestamp_to_sql(time, w); + Ok(IsNull::No) + } + + accepts!(TIMESTAMP); + to_sql_checked!(); +} + +impl<'a> FromSql<'a> for OffsetDateTime { + fn from_sql(type_: &Type, raw: &[u8]) -> Result> { + let primitive = PrimitiveDateTime::from_sql(type_, raw)?; + Ok(primitive.assume_utc()) + } + + accepts!(TIMESTAMP, TIMESTAMPTZ); +} + +impl ToSql for OffsetDateTime { + fn to_sql( + &self, + type_: &Type, + w: &mut BytesMut, + ) -> Result> { + let utc_datetime = self.to_offset(UtcOffset::UTC); + let date = utc_datetime.date(); + let time = utc_datetime.time(); + let primitive = PrimitiveDateTime::new(date, time); + primitive.to_sql(type_, w) + } + + accepts!(TIMESTAMP, TIMESTAMPTZ); + to_sql_checked!(); +} + +impl<'a> FromSql<'a> for Date { + fn from_sql(_: &Type, raw: &[u8]) -> Result> { + let jd = types::date_from_sql(raw)?; + Ok(base().date() + Duration::days(i64::from(jd))) + } + + accepts!(DATE); +} + +impl ToSql for Date { + fn to_sql(&self, _: &Type, w: &mut BytesMut) -> Result> { + let jd = (*self - base().date()).whole_days(); + if jd > i64::from(i32::max_value()) || jd < i64::from(i32::min_value()) { + return Err("value too large to transmit".into()); + } + + types::date_to_sql(jd as i32, w); + Ok(IsNull::No) + } + + accepts!(DATE); + to_sql_checked!(); +} + +impl<'a> FromSql<'a> for Time { + fn from_sql(_: &Type, raw: &[u8]) -> Result> { + let usec = types::time_from_sql(raw)?; + Ok(time!(00:00:00) + Duration::microseconds(usec)) + } + + accepts!(TIME); +} + +impl ToSql for Time { + fn to_sql(&self, _: &Type, w: &mut BytesMut) -> Result> { + let delta = *self - time!(00:00:00); + let time = match i64::try_from(delta.whole_microseconds()) { + Ok(time) => time, + Err(_) => return Err("value too large to transmit".into()), + }; + types::time_to_sql(time, w); + Ok(IsNull::No) + } + + accepts!(TIME); + to_sql_checked!(); +} diff --git a/postgres/Cargo.toml b/postgres/Cargo.toml index 6ba57b11..9bfdd9a3 100644 --- a/postgres/Cargo.toml +++ b/postgres/Cargo.toml @@ -27,6 +27,7 @@ with-eui48-0_4 = ["tokio-postgres/with-eui48-0_4"] with-geo-types-0_4 = ["tokio-postgres/with-geo-types-0_4"] with-serde_json-1 = ["tokio-postgres/with-serde_json-1"] with-uuid-0_8 = ["tokio-postgres/with-uuid-0_8"] +with-time-0_2 = ["tokio-postgres/with-time-0_2"] [dependencies] bytes = "0.5" diff --git a/tokio-postgres/Cargo.toml b/tokio-postgres/Cargo.toml index 2f3dd40c..f3f8df73 100644 --- a/tokio-postgres/Cargo.toml +++ b/tokio-postgres/Cargo.toml @@ -33,6 +33,7 @@ with-eui48-0_4 = ["postgres-types/with-eui48-0_4"] with-geo-types-0_4 = ["postgres-types/with-geo-types-0_4"] with-serde_json-1 = ["postgres-types/with-serde_json-1"] with-uuid-0_8 = ["postgres-types/with-uuid-0_8"] +with-time-0_2 = ["postgres-types/with-time-0_2"] [dependencies] async-trait = "0.1"