use crate::{
calendar::{Date, DateTime, Day, Month, Nanosecond, SECONDS_PER_DAY, Time, Utc, Year},
codec::{Decode, Encode},
database::{
DatabaseError, Typed,
client::postgres::{DecodeWrapper, EncodeWrapper, Postgres, PostgresError, Ty},
},
};
const PG_EPOCH: DateTime<Utc> = DateTime::new(
if let Ok(date) = Date::from_ymd(
if let Ok(year) = Year::from_num(2000) {
year
} else {
panic!();
},
Month::January,
Day::N1,
) {
date
} else {
panic!();
},
Time::ZERO,
Utc,
);
const PG_MIN: DateTime<Utc> = DateTime::new(
if let Ok(date) = Date::from_ymd(
if let Ok(year) = Year::from_num(-4713) {
year
} else {
panic!();
},
Month::January,
Day::N1,
) {
date
} else {
panic!();
},
Time::ZERO,
Utc,
);
impl<E> Decode<'_, Postgres<E>> for DateTime<Utc>
where
E: From<crate::Error>,
{
#[inline]
fn decode(dw: &mut DecodeWrapper<'_, '_>) -> Result<Self, E> {
let micros: i64 = Decode::<Postgres<E>>::decode(dw)?;
let (epoch_ts, _) = PG_EPOCH.timestamp_secs_and_ns();
let this_ts = micros.div_euclid(1_000_000);
let this_ns = ((micros.rem_euclid(1_000_000)) as u32).wrapping_mul(1_000);
let ts_diff = epoch_ts.wrapping_add(this_ts);
Ok(DateTime::from_timestamp_secs_and_ns(
ts_diff,
Nanosecond::from_num(this_ns).map_err(crate::Error::from)?,
)?)
}
}
impl<E> Encode<Postgres<E>> for DateTime<Utc>
where
E: From<crate::Error>,
{
#[inline]
fn encode(&self, ew: &mut EncodeWrapper<'_, '_>) -> Result<(), E> {
if self < &PG_MIN || self > &DateTime::MAX {
return Err(E::from(PostgresError::TimeStructureOverflow.into()));
}
let (this_ts, this_ns) = self.timestamp_secs_and_ns();
if !this_ns.num().is_multiple_of(1_000) {
return Err(E::from(PostgresError::TimeStructureWithGreaterPrecision.into()));
}
let this_us = this_ns.num() / 1_000;
let (epoch_ts, _) = PG_EPOCH.timestamp_secs_and_ns();
let ts_diff = this_ts.wrapping_sub(epoch_ts).wrapping_mul(1_000_000);
let rslt = ts_diff.wrapping_add(this_us.into());
Encode::<Postgres<E>>::encode(&rslt, ew)
}
}
impl<E> Typed<Postgres<E>> for DateTime<Utc>
where
E: From<crate::Error>,
{
#[inline]
fn runtime_ty(&self) -> Option<Ty> {
<Self as Typed<Postgres<E>>>::static_ty()
}
#[inline]
fn static_ty() -> Option<Ty> {
Some(Ty::Timestamptz)
}
}
impl<E> Decode<'_, Postgres<E>> for Date
where
E: From<crate::Error>,
{
#[inline]
fn decode(dw: &mut DecodeWrapper<'_, '_>) -> Result<Self, E> {
let days: i32 = Decode::<Postgres<E>>::decode(dw)?;
let days_in_secs = i64::from(SECONDS_PER_DAY).wrapping_mul(days.into());
let timestamp = days_in_secs.wrapping_add(PG_EPOCH.timestamp_secs_and_ns().0);
Ok(DateTime::from_timestamp_secs(timestamp)?.date())
}
}
impl<E> Encode<Postgres<E>> for Date
where
E: From<crate::Error>,
{
#[inline]
fn encode(&self, ew: &mut EncodeWrapper<'_, '_>) -> Result<(), E> {
if self < &PG_MIN.date() || self > &Date::MAX {
return Err(E::from(
DatabaseError::UnexpectedValueFromBytes { expected: "date".into() }.into(),
));
}
let this_timestamp = DateTime::new(*self, Time::ZERO, Utc).timestamp_secs_and_ns().0;
let diff = this_timestamp.wrapping_sub(PG_EPOCH.timestamp_secs_and_ns().0);
let days = i32::try_from(diff / i64::from(SECONDS_PER_DAY)).map_err(crate::Error::from)?;
Encode::<Postgres<E>>::encode(&days, ew)
}
}
impl<E> Typed<Postgres<E>> for Date
where
E: From<crate::Error>,
{
#[inline]
fn runtime_ty(&self) -> Option<Ty> {
<Self as Typed<Postgres<E>>>::static_ty()
}
#[inline]
fn static_ty() -> Option<Ty> {
Some(Ty::Date)
}
}
test!(date, Date, Date::from_ymd(4.try_into().unwrap(), Month::January, Day::N6).unwrap());
test!(
datetime,
DateTime<Utc>,
DateTime::from_timestamp_secs_and_ns(123456789, Nanosecond::from_num(12000).unwrap()).unwrap()
);