use crate::error::{Error, Result};
use crate::protocol::types::{Oid, oid};
use super::{FromWireValue, PG_EPOCH_JULIAN_DAY, ToWireValue};
impl FromWireValue<'_> for time::Date {
fn from_text(oid: Oid, bytes: &[u8]) -> Result<Self> {
if oid != oid::DATE {
return Err(Error::Decode(format!("cannot decode oid {} as Date", oid)));
}
let s = simdutf8::compat::from_utf8(bytes)
.map_err(|e| Error::Decode(format!("invalid UTF-8: {}", e)))?;
let format = time::macros::format_description!("[year]-[month]-[day]");
time::Date::parse(s, &format).map_err(|e| Error::Decode(format!("invalid date: {}", e)))
}
fn from_binary(oid: Oid, bytes: &[u8]) -> Result<Self> {
if oid != oid::DATE {
return Err(Error::Decode(format!("cannot decode oid {} as Date", oid)));
}
let arr: [u8; 4] = bytes.try_into().map_err(|_unhelpful_err| {
Error::Decode(format!("invalid Date length: {}", bytes.len()))
})?;
let pg_days = i32::from_be_bytes(arr);
time::Date::from_julian_day(pg_days + PG_EPOCH_JULIAN_DAY)
.map_err(|e| Error::Decode(format!("invalid date: {}", e)))
}
}
impl ToWireValue for time::Date {
fn natural_oid(&self) -> Oid {
oid::DATE
}
fn encode(&self, target_oid: Oid, buf: &mut Vec<u8>) -> Result<()> {
match target_oid {
oid::DATE => {
let pg_days = self.to_julian_day() - PG_EPOCH_JULIAN_DAY;
buf.extend_from_slice(&4_i32.to_be_bytes());
buf.extend_from_slice(&pg_days.to_be_bytes());
Ok(())
}
_ => Err(Error::type_mismatch(self.natural_oid(), target_oid)),
}
}
}
impl FromWireValue<'_> for time::Time {
fn from_text(oid: Oid, bytes: &[u8]) -> Result<Self> {
if oid != oid::TIME {
return Err(Error::Decode(format!("cannot decode oid {} as Time", oid)));
}
let s = simdutf8::compat::from_utf8(bytes)
.map_err(|e| Error::Decode(format!("invalid UTF-8: {}", e)))?;
let format_with_micro =
time::macros::format_description!("[hour]:[minute]:[second].[subsecond]");
let format_without_micro = time::macros::format_description!("[hour]:[minute]:[second]");
time::Time::parse(s, &format_with_micro)
.or_else(|_| time::Time::parse(s, &format_without_micro))
.map_err(|e| Error::Decode(format!("invalid time: {}", e)))
}
fn from_binary(oid: Oid, bytes: &[u8]) -> Result<Self> {
if oid != oid::TIME {
return Err(Error::Decode(format!("cannot decode oid {} as Time", oid)));
}
let arr: [u8; 8] = bytes.try_into().map_err(|_unhelpful_err| {
Error::Decode(format!("invalid Time length: {}", bytes.len()))
})?;
let usecs = i64::from_be_bytes(arr);
let hours = (usecs / 3_600_000_000) as u8;
let remaining = usecs % 3_600_000_000;
let minutes = (remaining / 60_000_000) as u8;
let remaining = remaining % 60_000_000;
let seconds = (remaining / 1_000_000) as u8;
let micros = (remaining % 1_000_000) as u32;
time::Time::from_hms_micro(hours, minutes, seconds, micros)
.map_err(|e| Error::Decode(format!("invalid time: {}", e)))
}
}
impl ToWireValue for time::Time {
fn natural_oid(&self) -> Oid {
oid::TIME
}
fn encode(&self, target_oid: Oid, buf: &mut Vec<u8>) -> Result<()> {
match target_oid {
oid::TIME => {
let (hour, minute, second, nano) = self.as_hms_nano();
let usecs = (hour as i64) * 3_600_000_000
+ (minute as i64) * 60_000_000
+ (second as i64) * 1_000_000
+ (nano as i64) / 1000;
buf.extend_from_slice(&8_i32.to_be_bytes());
buf.extend_from_slice(&usecs.to_be_bytes());
Ok(())
}
_ => Err(Error::type_mismatch(self.natural_oid(), target_oid)),
}
}
}
impl FromWireValue<'_> for time::PrimitiveDateTime {
fn from_text(oid: Oid, bytes: &[u8]) -> Result<Self> {
if !matches!(oid, oid::TIMESTAMP | oid::TIMESTAMPTZ) {
return Err(Error::Decode(format!(
"cannot decode oid {} as Timestamp",
oid
)));
}
let s = simdutf8::compat::from_utf8(bytes)
.map_err(|e| Error::Decode(format!("invalid UTF-8: {}", e)))?;
let s = s
.find(['+', '-'])
.filter(|&pos| pos > 10) .map(|pos| &s[..pos])
.unwrap_or(s);
let format_with_micro = time::macros::format_description!(
"[year]-[month]-[day] [hour]:[minute]:[second].[subsecond]"
);
let format_without_micro =
time::macros::format_description!("[year]-[month]-[day] [hour]:[minute]:[second]");
time::PrimitiveDateTime::parse(s, &format_with_micro)
.or_else(|_| time::PrimitiveDateTime::parse(s, &format_without_micro))
.map_err(|e| Error::Decode(format!("invalid timestamp: {}", e)))
}
fn from_binary(oid: Oid, bytes: &[u8]) -> Result<Self> {
if !matches!(oid, oid::TIMESTAMP | oid::TIMESTAMPTZ) {
return Err(Error::Decode(format!(
"cannot decode oid {} as Timestamp",
oid
)));
}
let arr: [u8; 8] = bytes.try_into().map_err(|_unhelpful_err| {
Error::Decode(format!("invalid Timestamp length: {}", bytes.len()))
})?;
let usecs = i64::from_be_bytes(arr);
const PG_EPOCH: time::PrimitiveDateTime = time::macros::datetime!(2000-01-01 00:00:00);
PG_EPOCH
.checked_add(time::Duration::microseconds(usecs))
.ok_or_else(|| Error::Decode("timestamp overflow".into()))
}
}
impl ToWireValue for time::PrimitiveDateTime {
fn natural_oid(&self) -> Oid {
oid::TIMESTAMP
}
fn encode(&self, target_oid: Oid, buf: &mut Vec<u8>) -> Result<()> {
match target_oid {
oid::TIMESTAMP | oid::TIMESTAMPTZ => {
const PG_EPOCH: time::PrimitiveDateTime =
time::macros::datetime!(2000-01-01 00:00:00);
let duration = *self - PG_EPOCH;
let usecs = duration.whole_microseconds() as i64;
buf.extend_from_slice(&8_i32.to_be_bytes());
buf.extend_from_slice(&usecs.to_be_bytes());
Ok(())
}
_ => Err(Error::type_mismatch(self.natural_oid(), target_oid)),
}
}
}
impl FromWireValue<'_> for time::OffsetDateTime {
fn from_text(oid: Oid, bytes: &[u8]) -> Result<Self> {
if oid != oid::TIMESTAMPTZ {
return Err(Error::Decode(format!(
"cannot decode oid {} as OffsetDateTime",
oid
)));
}
let s = simdutf8::compat::from_utf8(bytes)
.map_err(|e| Error::Decode(format!("invalid UTF-8: {}", e)))?;
let formats = [
"[year]-[month]-[day] [hour]:[minute]:[second].[subsecond][offset_hour]:[offset_minute]",
"[year]-[month]-[day] [hour]:[minute]:[second][offset_hour]:[offset_minute]",
"[year]-[month]-[day] [hour]:[minute]:[second].[subsecond][offset_hour]",
"[year]-[month]-[day] [hour]:[minute]:[second][offset_hour]",
];
for fmt in &formats {
if let Ok(format) = time::format_description::parse(fmt)
&& let Ok(dt) = time::OffsetDateTime::parse(s, &format)
{
return Ok(dt);
}
}
Err(Error::Decode(format!("invalid timestamptz: {}", s)))
}
fn from_binary(oid: Oid, bytes: &[u8]) -> Result<Self> {
if oid != oid::TIMESTAMPTZ {
return Err(Error::Decode(format!(
"cannot decode oid {} as OffsetDateTime",
oid
)));
}
let arr: [u8; 8] = bytes.try_into().map_err(|_unhelpful_err| {
Error::Decode(format!("invalid Timestamp length: {}", bytes.len()))
})?;
let usecs = i64::from_be_bytes(arr);
const PG_EPOCH: time::OffsetDateTime = time::macros::datetime!(2000-01-01 00:00:00 UTC);
PG_EPOCH
.checked_add(time::Duration::microseconds(usecs))
.ok_or_else(|| Error::Decode("timestamp overflow".into()))
}
}
impl ToWireValue for time::OffsetDateTime {
fn natural_oid(&self) -> Oid {
oid::TIMESTAMPTZ
}
fn encode(&self, target_oid: Oid, buf: &mut Vec<u8>) -> Result<()> {
match target_oid {
oid::TIMESTAMP | oid::TIMESTAMPTZ => {
let utc = self.to_offset(time::UtcOffset::UTC);
const PG_EPOCH: time::OffsetDateTime =
time::macros::datetime!(2000-01-01 00:00:00 UTC);
let duration = utc - PG_EPOCH;
let usecs = duration.whole_microseconds() as i64;
buf.extend_from_slice(&8_i32.to_be_bytes());
buf.extend_from_slice(&usecs.to_be_bytes());
Ok(())
}
_ => Err(Error::type_mismatch(self.natural_oid(), target_oid)),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn date_text() {
let date = time::Date::from_text(oid::DATE, b"2024-01-15").unwrap();
assert_eq!(date.year(), 2024);
assert_eq!(date.month() as u8, 1);
assert_eq!(date.day(), 15);
}
#[test]
fn date_binary() {
let days: i32 = 8780;
let bytes = days.to_be_bytes();
let date = time::Date::from_binary(oid::DATE, &bytes).unwrap();
assert_eq!(date.year(), 2024);
assert_eq!(date.month() as u8, 1);
assert_eq!(date.day(), 15);
}
#[test]
fn date_roundtrip() {
let original = time::Date::from_calendar_date(2024, time::Month::January, 15).unwrap();
let mut buf = Vec::new();
original.encode(original.natural_oid(), &mut buf).unwrap();
let decoded = time::Date::from_binary(oid::DATE, &buf[4..]).unwrap();
assert_eq!(original, decoded);
}
#[test]
fn time_text() {
let time = time::Time::from_text(oid::TIME, b"10:30:45").unwrap();
assert_eq!(time.hour(), 10);
assert_eq!(time.minute(), 30);
assert_eq!(time.second(), 45);
}
#[test]
fn time_text_with_micros() {
let time = time::Time::from_text(oid::TIME, b"10:30:45.123456").unwrap();
assert_eq!(time.hour(), 10);
assert_eq!(time.minute(), 30);
assert_eq!(time.second(), 45);
assert_eq!(time.microsecond(), 123456);
}
#[test]
fn time_binary() {
let usecs: i64 = (10 * 3600 + 30 * 60 + 45) * 1_000_000;
let bytes = usecs.to_be_bytes();
let time = time::Time::from_binary(oid::TIME, &bytes).unwrap();
assert_eq!(time.hour(), 10);
assert_eq!(time.minute(), 30);
assert_eq!(time.second(), 45);
}
#[test]
fn time_roundtrip() {
let original = time::Time::from_hms_micro(10, 30, 45, 123456).unwrap();
let mut buf = Vec::new();
original.encode(original.natural_oid(), &mut buf).unwrap();
let decoded = time::Time::from_binary(oid::TIME, &buf[4..]).unwrap();
assert_eq!(original, decoded);
}
#[test]
fn timestamp_text() {
let ts =
time::PrimitiveDateTime::from_text(oid::TIMESTAMP, b"2024-01-15 10:30:45").unwrap();
assert_eq!(ts.year(), 2024);
assert_eq!(ts.month() as u8, 1);
assert_eq!(ts.day(), 15);
assert_eq!(ts.hour(), 10);
assert_eq!(ts.minute(), 30);
assert_eq!(ts.second(), 45);
}
#[test]
fn timestamp_binary() {
let day_usecs: i64 = 8780 * 24 * 3600 * 1_000_000;
let time_usecs: i64 = (10 * 3600 + 30 * 60 + 45) * 1_000_000;
let total_usecs = day_usecs + time_usecs;
let bytes = total_usecs.to_be_bytes();
let ts = time::PrimitiveDateTime::from_binary(oid::TIMESTAMP, &bytes).unwrap();
assert_eq!(ts.year(), 2024);
assert_eq!(ts.month() as u8, 1);
assert_eq!(ts.day(), 15);
assert_eq!(ts.hour(), 10);
assert_eq!(ts.minute(), 30);
assert_eq!(ts.second(), 45);
}
#[test]
fn timestamp_roundtrip() {
let date = time::Date::from_calendar_date(2024, time::Month::January, 15).unwrap();
let time = time::Time::from_hms_micro(10, 30, 45, 123456).unwrap();
let original = time::PrimitiveDateTime::new(date, time);
let mut buf = Vec::new();
original.encode(original.natural_oid(), &mut buf).unwrap();
let decoded = time::PrimitiveDateTime::from_binary(oid::TIMESTAMP, &buf[4..]).unwrap();
assert_eq!(original, decoded);
}
#[test]
fn timestamptz_binary() {
let day_usecs: i64 = 8780 * 24 * 3600 * 1_000_000;
let time_usecs: i64 = (10 * 3600 + 30 * 60 + 45) * 1_000_000;
let total_usecs = day_usecs + time_usecs;
let bytes = total_usecs.to_be_bytes();
let ts = time::OffsetDateTime::from_binary(oid::TIMESTAMPTZ, &bytes).unwrap();
assert_eq!(ts.year(), 2024);
assert_eq!(ts.month() as u8, 1);
assert_eq!(ts.day(), 15);
assert_eq!(ts.hour(), 10);
assert_eq!(ts.minute(), 30);
assert_eq!(ts.second(), 45);
}
#[test]
fn timestamptz_roundtrip() {
let original = time::OffsetDateTime::now_utc();
let original = original
.replace_nanosecond((original.nanosecond() / 1000) * 1000)
.unwrap();
let mut buf = Vec::new();
original.encode(original.natural_oid(), &mut buf).unwrap();
let decoded = time::OffsetDateTime::from_binary(oid::TIMESTAMPTZ, &buf[4..]).unwrap();
assert_eq!(original, decoded);
}
}