use ::chrono::{DateTime, Utc};
use serde::de::Error as _;
use serde::ser::Error as _;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
pub mod datetime {
use super::*;
pub fn serialize<S>(dt: &DateTime<Utc>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let ts = dt.timestamp();
u32::try_from(ts)
.map_err(|_| S::Error::custom(format!("{dt} cannot be represented as DateTime")))?
.serialize(serializer)
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<DateTime<Utc>, D::Error>
where
D: Deserializer<'de>,
{
let ts = u32::deserialize(deserializer)?;
DateTime::<Utc>::from_timestamp(i64::from(ts), 0)
.ok_or_else(|| D::Error::custom(format!("{ts} cannot be converted to DateTime<Utc>")))
}
crate::serde::option_module!(::chrono::DateTime<::chrono::Utc>);
}
pub mod datetime64 {
use super::*;
macro_rules! datetime64_precision {
($(#[$doc:meta])* $mod_name:ident, to = $to:expr, from = $from:expr) => {
$(#[$doc])*
pub mod $mod_name {
use super::*;
pub fn serialize<S>(dt: &DateTime<Utc>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
#[allow(clippy::redundant_closure_call)]
let ts: i64 = ($to)(dt).map_err(S::Error::custom)?;
ts.serialize(serializer)
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<DateTime<Utc>, D::Error>
where
D: Deserializer<'de>,
{
let ts = i64::deserialize(deserializer)?;
#[allow(clippy::redundant_closure_call)]
($from)(ts).ok_or_else(|| {
D::Error::custom(format!("can't create DateTime<Utc> from {ts}"))
})
}
crate::serde::option_module!(::chrono::DateTime<::chrono::Utc>);
}
};
}
datetime64_precision! {
secs,
to = |dt: &DateTime<Utc>| Ok::<_, String>(dt.timestamp()),
from = |ts| DateTime::<Utc>::from_timestamp(ts, 0)
}
datetime64_precision! {
millis,
to = |dt: &DateTime<Utc>| Ok::<_, String>(dt.timestamp_millis()),
from = DateTime::<Utc>::from_timestamp_millis
}
datetime64_precision! {
micros,
to = |dt: &DateTime<Utc>| Ok::<_, String>(dt.timestamp_micros()),
from = DateTime::<Utc>::from_timestamp_micros
}
datetime64_precision! {
nanos,
to = |dt: &DateTime<Utc>| dt
.timestamp_nanos_opt()
.ok_or_else(|| format!("{dt} cannot be represented as DateTime64(9)")),
from = |ts| Some(DateTime::<Utc>::from_timestamp_nanos(ts))
}
}
pub mod date {
use super::*;
use ::chrono::{Duration, NaiveDate};
const ORIGIN: Option<NaiveDate> = NaiveDate::from_yo_opt(1970, 1);
pub fn serialize<S>(d: &NaiveDate, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let origin = ORIGIN.unwrap();
if *d < origin {
return Err(S::Error::custom(format!(
"{d} cannot be represented as Date"
)));
}
let days = (*d - origin).num_days();
u16::try_from(days)
.map_err(|_| S::Error::custom(format!("{d} cannot be represented as Date")))?
.serialize(serializer)
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<NaiveDate, D::Error>
where
D: Deserializer<'de>,
{
let days = u16::deserialize(deserializer)?;
Ok(ORIGIN.unwrap() + Duration::days(i64::from(days)))
}
crate::serde::option_module!(::chrono::NaiveDate);
}
pub mod date32 {
use super::*;
use ::chrono::{Duration, NaiveDate};
const ORIGIN: Option<NaiveDate> = NaiveDate::from_yo_opt(1970, 1);
const MIN: Option<NaiveDate> = NaiveDate::from_yo_opt(1900, 1);
const MAX: Option<NaiveDate> = NaiveDate::from_yo_opt(2299, 365);
pub fn serialize<S>(d: &NaiveDate, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
if *d < MIN.unwrap() || *d > MAX.unwrap() {
return Err(S::Error::custom(format!(
"{d} cannot be represented as Date32"
)));
}
let days = (*d - ORIGIN.unwrap()).num_days();
i32::try_from(days)
.map_err(|_| S::Error::custom(format!("{d} cannot be represented as Date32")))?
.serialize(serializer)
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<NaiveDate, D::Error>
where
D: Deserializer<'de>,
{
let days = i32::deserialize(deserializer)?;
Ok(ORIGIN.unwrap() + Duration::days(i64::from(days)))
}
crate::serde::option_module!(::chrono::NaiveDate);
}
pub mod time {
use super::*;
use ::chrono::Duration;
pub fn serialize<S>(t: &Duration, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
i32::try_from(t.num_seconds())
.map_err(|_| S::Error::custom(format!("{t} cannot be represented as Time")))?
.serialize(serializer)
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Duration, D::Error>
where
D: Deserializer<'de>,
{
let seconds = i32::deserialize(deserializer)?;
Ok(Duration::seconds(i64::from(seconds)))
}
crate::serde::option_module!(::chrono::Duration);
}
pub mod time64 {
use super::*;
use ::chrono::Duration;
macro_rules! time64_precision {
($(#[$doc:meta])* $mod_name:ident, to = $to:expr, from = $from:expr) => {
$(#[$doc])*
pub mod $mod_name {
use super::*;
pub fn serialize<S>(t: &Duration, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
#[allow(clippy::redundant_closure_call)]
let ticks: i64 = ($to)(t).map_err(S::Error::custom)?;
ticks.serialize(serializer)
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Duration, D::Error>
where
D: Deserializer<'de>,
{
let ticks = i64::deserialize(deserializer)?;
#[allow(clippy::redundant_closure_call)]
Ok(($from)(ticks))
}
crate::serde::option_module!(::chrono::Duration);
}
};
}
time64_precision! {
secs,
to = |t: &Duration| Ok::<_, String>(t.num_seconds()),
from = Duration::seconds
}
time64_precision! {
millis,
to = |t: &Duration| Ok::<_, String>(t.num_milliseconds()),
from = Duration::milliseconds
}
time64_precision! {
micros,
to = |t: &Duration| t
.num_microseconds()
.ok_or_else(|| format!("{t} has too many microseconds for Time64(6)")),
from = Duration::microseconds
}
time64_precision! {
nanos,
to = |t: &Duration| t
.num_nanoseconds()
.ok_or_else(|| format!("{t} has too many nanoseconds for Time64(9)")),
from = Duration::nanoseconds
}
}
#[cfg(test)]
mod tests {
use crate::rowbinary::{RowBinaryError, serialize_row};
use ::chrono::{DateTime, Duration, NaiveDate, TimeZone, Utc};
use bytes::BytesMut;
use serde::Serialize;
fn enc<T: Serialize>(v: &T) -> Vec<u8> {
let mut buf = BytesMut::new();
serialize_row(v, &mut buf).expect("serialize");
buf.to_vec()
}
fn enc_err<T: Serialize>(v: &T) -> RowBinaryError {
serialize_row(v, &mut BytesMut::new()).unwrap_err()
}
#[derive(Serialize)]
struct DateRow(#[serde(with = "crate::serde::chrono::date")] NaiveDate);
#[derive(Serialize)]
struct Date32Row(#[serde(with = "crate::serde::chrono::date32")] NaiveDate);
#[derive(Serialize)]
struct DtRow(#[serde(with = "crate::serde::chrono::datetime")] DateTime<Utc>);
#[derive(Serialize)]
struct Dt64MillisRow(#[serde(with = "crate::serde::chrono::datetime64::millis")] DateTime<Utc>);
#[derive(Serialize)]
struct Dt64NanosRow(#[serde(with = "crate::serde::chrono::datetime64::nanos")] DateTime<Utc>);
#[derive(Serialize)]
struct TimeRow(#[serde(with = "crate::serde::chrono::time")] Duration);
#[derive(Serialize)]
struct Time64MicrosRow(#[serde(with = "crate::serde::chrono::time64::micros")] Duration);
#[derive(Serialize)]
struct OptDateRow(#[serde(with = "crate::serde::chrono::date::option")] Option<NaiveDate>);
#[test]
fn dates_are_days_since_epoch() {
let d = NaiveDate::from_ymd_opt(1970, 1, 2).unwrap();
assert_eq!(enc(&DateRow(d)), 1u16.to_le_bytes());
let old = NaiveDate::from_ymd_opt(1900, 1, 1).unwrap();
assert_eq!(enc(&Date32Row(old)), (-25567i32).to_le_bytes());
assert!(matches!(enc_err(&DateRow(old)), RowBinaryError::Custom(_)));
let ancient = NaiveDate::from_ymd_opt(1899, 12, 31).unwrap();
assert!(matches!(
enc_err(&Date32Row(ancient)),
RowBinaryError::Custom(_)
));
}
#[test]
fn datetimes_are_epoch_ticks() {
let dt = Utc.timestamp_opt(1_700_000_000, 0).unwrap();
assert_eq!(enc(&DtRow(dt)), 1_700_000_000u32.to_le_bytes());
assert_eq!(enc(&Dt64MillisRow(dt)), 1_700_000_000_000i64.to_le_bytes());
assert_eq!(
enc(&Dt64NanosRow(dt)),
1_700_000_000_000_000_000i64.to_le_bytes()
);
let pre = Utc.timestamp_opt(-1, 0).unwrap();
assert_eq!(enc(&Dt64MillisRow(pre)), (-1_000i64).to_le_bytes());
assert!(matches!(enc_err(&DtRow(pre)), RowBinaryError::Custom(_)));
}
#[test]
fn durations_are_time_ticks() {
assert_eq!(
enc(&TimeRow(Duration::seconds(-3599))),
(-3599i32).to_le_bytes()
);
assert_eq!(
enc(&Time64MicrosRow(Duration::milliseconds(1))),
1_000i64.to_le_bytes()
);
}
#[test]
fn nullable_chrono_columns_compose_with_the_option_guard() {
let d = NaiveDate::from_ymd_opt(1970, 1, 2).unwrap();
assert_eq!(enc(&OptDateRow(Some(d))), [0, 1, 0]);
assert_eq!(enc(&OptDateRow(None)), [1]);
}
}