Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 35 additions & 42 deletions src/datetime/serde.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ use serde::{de, ser};

use super::DateTime;
use crate::format::{write_rfc3339, SecondsFormat};
use crate::naive::datetime::serde::serde_from;
#[cfg(feature = "clock")]
use crate::offset::Local;
use crate::offset::{FixedOffset, Offset, TimeZone, Utc};
Expand Down Expand Up @@ -148,10 +147,10 @@ pub mod ts_nanoseconds {
use core::fmt;
use serde::{de, ser};

use crate::offset::TimeZone;
use crate::serde::invalid_ts;
use crate::{DateTime, Utc};

use super::{serde_from, NanoSecondsTimestampVisitor};
use super::NanoSecondsTimestampVisitor;

/// Serialize a UTC datetime into an integer number of nanoseconds since the epoch
///
Expand Down Expand Up @@ -240,24 +239,20 @@ pub mod ts_nanoseconds {
where
E: de::Error,
{
serde_from(
Utc.timestamp_opt(
value.div_euclid(1_000_000_000),
(value.rem_euclid(1_000_000_000)) as u32,
),
&value,
DateTime::from_timestamp(
value.div_euclid(1_000_000_000),
(value.rem_euclid(1_000_000_000)) as u32,
)
.ok_or_else(|| invalid_ts(value))
}

/// Deserialize a timestamp in nanoseconds since the epoch
fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
where
E: de::Error,
{
serde_from(
Utc.timestamp_opt((value / 1_000_000_000) as i64, (value % 1_000_000_000) as u32),
&value,
)
DateTime::from_timestamp((value / 1_000_000_000) as i64, (value % 1_000_000_000) as u32)
.ok_or_else(|| invalid_ts(value))
}
}
}
Expand Down Expand Up @@ -448,10 +443,11 @@ pub mod ts_microseconds {
use core::fmt;
use serde::{de, ser};

use super::{serde_from, MicroSecondsTimestampVisitor};
use crate::offset::TimeZone;
use crate::serde::invalid_ts;
use crate::{DateTime, Utc};

use super::MicroSecondsTimestampVisitor;

/// Serialize a UTC datetime into an integer number of microseconds since the epoch
///
/// Intended for use with `serde`s `serialize_with` attribute.
Expand Down Expand Up @@ -529,24 +525,23 @@ pub mod ts_microseconds {
where
E: de::Error,
{
serde_from(
Utc.timestamp_opt(
value.div_euclid(1_000_000),
(value.rem_euclid(1_000_000) * 1_000) as u32,
),
&value,
DateTime::from_timestamp(
value.div_euclid(1_000_000),
(value.rem_euclid(1_000_000) * 1000) as u32,
)
.ok_or_else(|| invalid_ts(value))
}

/// Deserialize a timestamp in milliseconds since the epoch
fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
where
E: de::Error,
{
serde_from(
Utc.timestamp_opt((value / 1_000_000) as i64, ((value % 1_000_000) * 1_000) as u32),
&value,
DateTime::from_timestamp(
(value / 1_000_000) as i64,
((value % 1_000_000) * 1_000) as u32,
)
.ok_or_else(|| invalid_ts(value))
}
}
}
Expand Down Expand Up @@ -726,10 +721,11 @@ pub mod ts_milliseconds {
use core::fmt;
use serde::{de, ser};

use super::{serde_from, MilliSecondsTimestampVisitor};
use crate::offset::TimeZone;
use crate::serde::invalid_ts;
use crate::{DateTime, Utc};

use super::MilliSecondsTimestampVisitor;

/// Serialize a UTC datetime into an integer number of milliseconds since the epoch
///
/// Intended for use with `serde`s `serialize_with` attribute.
Expand Down Expand Up @@ -807,18 +803,16 @@ pub mod ts_milliseconds {
where
E: de::Error,
{
serde_from(Utc.timestamp_millis_opt(value), &value)
DateTime::from_timestamp_millis(value).ok_or_else(|| invalid_ts(value))
}

/// Deserialize a timestamp in milliseconds since the epoch
fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
where
E: de::Error,
{
serde_from(
Utc.timestamp_opt((value / 1000) as i64, ((value % 1000) * 1_000_000) as u32),
&value,
)
DateTime::from_timestamp((value / 1000) as i64, ((value % 1000) * 1_000_000) as u32)
.ok_or_else(|| invalid_ts(value))
}
}
}
Expand Down Expand Up @@ -1005,8 +999,10 @@ pub mod ts_seconds {
use core::fmt;
use serde::{de, ser};

use super::{serde_from, SecondsTimestampVisitor};
use crate::{DateTime, LocalResult, TimeZone, Utc};
use crate::serde::invalid_ts;
use crate::{DateTime, Utc};

use super::SecondsTimestampVisitor;

/// Serialize a UTC datetime into an integer number of seconds since the epoch
///
Expand Down Expand Up @@ -1075,22 +1071,19 @@ pub mod ts_seconds {
where
E: de::Error,
{
serde_from(Utc.timestamp_opt(value, 0), &value)
DateTime::from_timestamp(value, 0).ok_or_else(|| invalid_ts(value))
}

/// Deserialize a timestamp in seconds since the epoch
fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
where
E: de::Error,
{
serde_from(
if value > i64::MAX as u64 {
LocalResult::None
} else {
Utc.timestamp_opt(value as i64, 0)
},
&value,
)
if value > i64::MAX as u64 {
Err(invalid_ts(value))
} else {
DateTime::from_timestamp(value as i64, 0).ok_or_else(|| invalid_ts(value))
}
}
}
}
Expand Down
26 changes: 26 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -624,7 +624,33 @@ pub use naive::__BenchYearFlags;
/// [2]: https://serde.rs/field-attrs.html#with
#[cfg(feature = "serde")]
pub mod serde {
use core::fmt;
use serde::de;

pub use super::datetime::serde::*;

/// Create a custom `de::Error` with `SerdeError::InvalidTimestamp`.
pub(crate) fn invalid_ts<E, T>(value: T) -> E
where
E: de::Error,
T: fmt::Display,
{
E::custom(SerdeError::InvalidTimestamp(value))
}

enum SerdeError<T: fmt::Display> {
InvalidTimestamp(T),
}

impl<T: fmt::Display> fmt::Display for SerdeError<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
SerdeError::InvalidTimestamp(ts) => {
write!(f, "value is not a legal timestamp: {}", ts)
}
}
}
}
}

/// Zero-copy serialization/deserialization with rkyv.
Expand Down
81 changes: 16 additions & 65 deletions src/naive/datetime/serde.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ use core::fmt;
use serde::{de, ser};

use super::NaiveDateTime;
use crate::offset::LocalResult;

/// Serialize a `NaiveDateTime` as an RFC 3339 string
///
Expand Down Expand Up @@ -83,7 +82,7 @@ pub mod ts_nanoseconds {
use core::fmt;
use serde::{de, ser};

use super::ne_timestamp;
use crate::serde::invalid_ts;
use crate::NaiveDateTime;

/// Serialize a datetime into an integer number of nanoseconds since the epoch
Expand Down Expand Up @@ -176,7 +175,7 @@ pub mod ts_nanoseconds {
value.div_euclid(1_000_000_000),
(value.rem_euclid(1_000_000_000)) as u32,
)
.ok_or_else(|| E::custom(ne_timestamp(value)))
.ok_or_else(|| invalid_ts(value))
}

fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
Expand All @@ -187,7 +186,7 @@ pub mod ts_nanoseconds {
(value / 1_000_000_000) as i64,
(value % 1_000_000_000) as u32,
)
.ok_or_else(|| E::custom(ne_timestamp(value)))
.ok_or_else(|| invalid_ts(value))
}
}
}
Expand Down Expand Up @@ -372,7 +371,7 @@ pub mod ts_microseconds {
use core::fmt;
use serde::{de, ser};

use super::ne_timestamp;
use crate::serde::invalid_ts;
use crate::NaiveDateTime;

/// Serialize a datetime into an integer number of microseconds since the epoch
Expand Down Expand Up @@ -451,8 +450,7 @@ pub mod ts_microseconds {
where
E: de::Error,
{
NaiveDateTime::from_timestamp_micros(value)
.ok_or_else(|| E::custom(ne_timestamp(value)))
NaiveDateTime::from_timestamp_micros(value).ok_or_else(|| invalid_ts(value))
}

fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
Expand All @@ -463,7 +461,7 @@ pub mod ts_microseconds {
(value / 1_000_000) as i64,
((value % 1_000_000) * 1_000) as u32,
)
.ok_or_else(|| E::custom(ne_timestamp(value)))
.ok_or_else(|| invalid_ts(value))
}
}
}
Expand Down Expand Up @@ -636,7 +634,7 @@ pub mod ts_milliseconds {
use core::fmt;
use serde::{de, ser};

use super::ne_timestamp;
use crate::serde::invalid_ts;
use crate::NaiveDateTime;

/// Serialize a datetime into an integer number of milliseconds since the epoch
Expand Down Expand Up @@ -715,8 +713,7 @@ pub mod ts_milliseconds {
where
E: de::Error,
{
NaiveDateTime::from_timestamp_millis(value)
.ok_or_else(|| E::custom(ne_timestamp(value)))
NaiveDateTime::from_timestamp_millis(value).ok_or_else(|| invalid_ts(value))
}

fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
Expand All @@ -727,7 +724,7 @@ pub mod ts_milliseconds {
(value / 1000) as i64,
((value % 1000) * 1_000_000) as u32,
)
.ok_or_else(|| E::custom(ne_timestamp(value)))
.ok_or_else(|| invalid_ts(value))
}
}
}
Expand Down Expand Up @@ -896,7 +893,7 @@ pub mod ts_seconds {
use core::fmt;
use serde::{de, ser};

use super::ne_timestamp;
use crate::serde::invalid_ts;
use crate::NaiveDateTime;

/// Serialize a datetime into an integer number of seconds since the epoch
Expand Down Expand Up @@ -968,16 +965,18 @@ pub mod ts_seconds {
where
E: de::Error,
{
NaiveDateTime::from_timestamp_opt(value, 0)
.ok_or_else(|| E::custom(ne_timestamp(value)))
NaiveDateTime::from_timestamp_opt(value, 0).ok_or_else(|| invalid_ts(value))
}

fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
where
E: de::Error,
{
NaiveDateTime::from_timestamp_opt(value as i64, 0)
.ok_or_else(|| E::custom(ne_timestamp(value)))
if value > i64::MAX as u64 {
Err(invalid_ts(value))
} else {
NaiveDateTime::from_timestamp_opt(value as i64, 0).ok_or_else(|| invalid_ts(value))
}
}
}
}
Expand Down Expand Up @@ -1109,54 +1108,6 @@ pub mod ts_seconds_option {
}
}

// lik? function to convert a LocalResult into a serde-ish Result
pub(crate) fn serde_from<T, E, V>(me: LocalResult<T>, ts: &V) -> Result<T, E>
where
E: de::Error,
V: fmt::Display,
T: fmt::Display,
{
match me {
LocalResult::None => Err(E::custom(ne_timestamp(ts))),
LocalResult::Ambiguous(min, max) => {
Err(E::custom(SerdeError::Ambiguous { timestamp: ts, min, max }))
}
LocalResult::Single(val) => Ok(val),
}
}

enum SerdeError<V: fmt::Display, D: fmt::Display> {
NonExistent { timestamp: V },
Ambiguous { timestamp: V, min: D, max: D },
}

/// Construct a [`SerdeError::NonExistent`]
fn ne_timestamp<T: fmt::Display>(ts: T) -> SerdeError<T, u8> {
SerdeError::NonExistent::<T, u8> { timestamp: ts }
}

impl<V: fmt::Display, D: fmt::Display> fmt::Debug for SerdeError<V, D> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "ChronoSerdeError({})", self)
}
}

// impl<V: fmt::Display, D: fmt::Debug> core::error::Error for SerdeError<V, D> {}
impl<V: fmt::Display, D: fmt::Display> fmt::Display for SerdeError<V, D> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
SerdeError::NonExistent { timestamp } => {
write!(f, "value is not a legal timestamp: {}", timestamp)
}
SerdeError::Ambiguous { timestamp, min, max } => write!(
f,
"value is an ambiguous timestamp: {}, could be either of {}, {}",
timestamp, min, max
),
}
}
}

#[cfg(test)]
mod tests {
use crate::naive::datetime::{test_decodable_json, test_encodable_json};
Expand Down