Skip to content

Commit

Permalink
bridge Date and Time
Browse files Browse the repository at this point in the history
Signed-off-by: tison <[email protected]>
  • Loading branch information
tisonkun committed Oct 26, 2024
1 parent d8472eb commit ab4f1af
Show file tree
Hide file tree
Showing 4 changed files with 195 additions and 1 deletion.
63 changes: 63 additions & 0 deletions jiff-sqlx/src/postgres/date.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
use crate::{Date, ToDate};
use sqlx::encode::IsNull;
use sqlx::error::BoxDynError;
use sqlx::postgres::types::Oid;
use sqlx::postgres::{
PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef,
};
use sqlx::{Decode, Encode, Postgres, Type};

impl Type<Postgres> for Date {
fn type_info() -> PgTypeInfo {
// 1082 => PgType::Date
PgTypeInfo::with_oid(Oid(1082))
}
}

impl PgHasArrayType for Date {
fn array_type_info() -> PgTypeInfo {
// 1182 => PgType::DateArray
PgTypeInfo::with_oid(Oid(1182))
}
}

impl Encode<'_, Postgres> for Date {
fn encode_by_ref(
&self,
buf: &mut PgArgumentBuffer,
) -> Result<IsNull, BoxDynError> {
let date = self.to_jiff();

// DATE is encoded as the days since epoch
let days = date.since(postgres_epoch_date())?.get_days();
Encode::<Postgres>::encode(days, buf)
}

fn size_hint(&self) -> usize {
size_of::<i32>()
}
}

impl<'r> Decode<'r, Postgres> for Date {
fn decode(value: PgValueRef<'r>) -> Result<Self, BoxDynError> {
Ok(match value.format() {
PgValueFormat::Binary => {
// DATE is encoded as the days since epoch
let days: i32 = Decode::<Postgres>::decode(value)?;
let date = jiff::Span::new()
.try_days(days)
.and_then(|s| postgres_epoch_date().checked_add(s))?;
date.to_sqlx()
}
PgValueFormat::Text => {
let s = value.as_str()?;
let date = jiff::civil::Date::strptime("%Y-%m-%d", s)?;
date.to_sqlx()
}
})
}
}

const fn postgres_epoch_date() -> jiff::civil::Date {
jiff::civil::Date::constant(2000, 1, 1)
}
68 changes: 68 additions & 0 deletions jiff-sqlx/src/postgres/datetime.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
use crate::{DateTime, ToDateTime};
use jiff::SignedDuration;
use sqlx::encode::IsNull;
use sqlx::error::BoxDynError;
use sqlx::postgres::types::Oid;
use sqlx::postgres::{
PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef,
};
use sqlx::{Decode, Encode, Postgres, Type};
use std::str::FromStr;

impl Type<Postgres> for DateTime {
fn type_info() -> PgTypeInfo {
// 1114 => PgType::Timestamp
PgTypeInfo::with_oid(Oid(1114))
}
}

impl PgHasArrayType for DateTime {
fn array_type_info() -> PgTypeInfo {
// 1115 => PgType::TimestampArray
PgTypeInfo::with_oid(Oid(1115))
}
}

impl Encode<'_, Postgres> for DateTime {
fn encode_by_ref(
&self,
buf: &mut PgArgumentBuffer,
) -> Result<IsNull, BoxDynError> {
let datetime = self.to_jiff();

// TIMESTAMP is encoded as the microseconds since the epoch
let micros =
datetime.duration_since(postgres_epoch_datetime()).as_micros();
let micros = i64::try_from(micros).map_err(|_| {
format!("DateTime {datetime} out of range for Postgres: {micros}")
})?;
Encode::<Postgres>::encode(micros, buf)
}

fn size_hint(&self) -> usize {
size_of::<i64>()
}
}

impl<'r> Decode<'r, Postgres> for DateTime {
fn decode(value: PgValueRef<'r>) -> Result<Self, BoxDynError> {
Ok(match value.format() {
PgValueFormat::Binary => {
// TIMESTAMP is encoded as the microseconds since the epoch
let us = Decode::<Postgres>::decode(value)?;
let datetime = postgres_epoch_datetime()
.checked_add(SignedDuration::from_micros(us))?;
datetime.to_sqlx()
}
PgValueFormat::Text => {
let s = value.as_str()?;
let datetime = jiff::civil::DateTime::from_str(s)?;
datetime.to_sqlx()
}
})
}
}

const fn postgres_epoch_datetime() -> jiff::civil::DateTime {
jiff::civil::DateTime::constant(2000, 1, 1, 0, 0, 0, 0)
}
2 changes: 1 addition & 1 deletion jiff-sqlx/src/postgres/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ mod date;
mod datetime;
mod interval;
mod time;
mod timestamp;
mod timestamp;
63 changes: 63 additions & 0 deletions jiff-sqlx/src/postgres/time.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
use crate::{Time, ToTime};
use jiff::SignedDuration;
use sqlx::encode::IsNull;
use sqlx::error::BoxDynError;
use sqlx::postgres::types::Oid;
use sqlx::postgres::{
PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef,
};
use sqlx::{Decode, Encode, Postgres, Type};

impl Type<Postgres> for Time {
fn type_info() -> PgTypeInfo {
// 1083 => PgType::Time
PgTypeInfo::with_oid(Oid(1083))
}
}

impl PgHasArrayType for Time {
fn array_type_info() -> PgTypeInfo {
// 1183 => PgType::TimeArray
PgTypeInfo::with_oid(Oid(1183))
}
}

impl Encode<'_, Postgres> for Time {
fn encode_by_ref(
&self,
buf: &mut PgArgumentBuffer,
) -> Result<IsNull, BoxDynError> {
let time = self.to_jiff();

// TIME is encoded as the microseconds since midnight
let micros =
time.duration_since(jiff::civil::Time::midnight()).as_micros();
let micros = i64::try_from(micros).map_err(|_| {
format!("Time {time} out of range for Postgres: {micros}")
})?;
Encode::<Postgres>::encode(micros, buf)
}

fn size_hint(&self) -> usize {
size_of::<i64>()
}
}

impl<'r> Decode<'r, Postgres> for Time {
fn decode(value: PgValueRef<'r>) -> Result<Self, BoxDynError> {
Ok(match value.format() {
PgValueFormat::Binary => {
// TIME is encoded as the microseconds since midnight
let us: i64 = Decode::<Postgres>::decode(value)?;
let time = jiff::civil::Time::midnight()
.checked_add(SignedDuration::from_micros(us))?;
time.to_sqlx()
}
PgValueFormat::Text => {
let s = value.as_str()?;
let time = jiff::civil::Time::strptime("%H:%M:%S%.f", s)?;
time.to_sqlx()
}
})
}
}

0 comments on commit ab4f1af

Please sign in to comment.