Deserialize both Serde and Serialize from timestamps
This introduces a newtype around DateTime and NaiveDateTime that deserlization is implemented for. There are two advantages to this over the previous implementation: * It is expandable to other timestamp representations (e.g. millisecond and microsecond timestamps) * It works with RustcSerialize::Decodable. AFAICT Decodable will error if you try to call more than one of the `read_*` functions in the same `decode` invocation. This is slightly annoying compared to serde which just calls the correct `visit_*` function for whatever type the deserializer encounters. On the whole I think that I prefer this to the previous implementation of deserializing timestamps (even though I don't care about RustcSerialize in the post-1.15 world) because it is much more explicit. On the other hand, this feels like it's introducing a lot of types, and possibly making downstream crates introduce a variety of different structs for ser/de and translating into different struct types.
This commit is contained in:
parent
c0c61b5bfa
commit
bc879d705e
215
src/datetime.rs
215
src/datetime.rs
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
use std::{str, fmt, hash};
|
use std::{str, fmt, hash};
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use std::ops::{Add, Sub};
|
use std::ops::{Add, Sub, Deref};
|
||||||
use oldtime::Duration as OldDuration;
|
use oldtime::Duration as OldDuration;
|
||||||
|
|
||||||
use {Weekday, Timelike, Datelike};
|
use {Weekday, Timelike, Datelike};
|
||||||
|
@ -30,6 +30,26 @@ pub struct DateTime<Tz: TimeZone> {
|
||||||
offset: Tz::Offset,
|
offset: Tz::Offset,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A DateTime that can be deserialized from a timestamp
|
||||||
|
///
|
||||||
|
/// A timestamp here is seconds since the epoch
|
||||||
|
pub struct TsSeconds<Tz: TimeZone>(DateTime<Tz>);
|
||||||
|
|
||||||
|
impl<Tz: TimeZone> From<TsSeconds<Tz>> for DateTime<Tz> {
|
||||||
|
/// Pull the inner DateTime<Tz> out
|
||||||
|
fn from(obj: TsSeconds<Tz>) -> DateTime<Tz> {
|
||||||
|
obj.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Tz: TimeZone> Deref for TsSeconds<Tz> {
|
||||||
|
type Target = DateTime<Tz>;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<Tz: TimeZone> DateTime<Tz> {
|
impl<Tz: TimeZone> DateTime<Tz> {
|
||||||
/// Makes a new `DateTime` with given *UTC* datetime and offset.
|
/// Makes a new `DateTime` with given *UTC* datetime and offset.
|
||||||
/// The local datetime should be constructed via the `TimeZone` trait.
|
/// The local datetime should be constructed via the `TimeZone` trait.
|
||||||
|
@ -432,40 +452,61 @@ fn test_decodable_json<FUTC, FFixed, FLocal, E>(utc_from_str: FUTC,
|
||||||
norm(&Some(UTC.ymd(2014, 7, 24).and_hms(12, 34, 6))));
|
norm(&Some(UTC.ymd(2014, 7, 24).and_hms(12, 34, 6))));
|
||||||
assert_eq!(norm(&utc_from_str(r#""2014-07-24T13:57:06+01:23""#).ok()),
|
assert_eq!(norm(&utc_from_str(r#""2014-07-24T13:57:06+01:23""#).ok()),
|
||||||
norm(&Some(UTC.ymd(2014, 7, 24).and_hms(12, 34, 6))));
|
norm(&Some(UTC.ymd(2014, 7, 24).and_hms(12, 34, 6))));
|
||||||
assert_eq!(norm(&utc_from_str("0").ok()),
|
|
||||||
norm(&Some(UTC.ymd(1970, 1, 1).and_hms(0, 0, 0))));
|
|
||||||
assert_eq!(norm(&utc_from_str("-1").unwrap()),
|
|
||||||
norm(&UTC.ymd(1969, 12, 31).and_hms(23, 59, 59)));
|
|
||||||
|
|
||||||
|
|
||||||
assert_eq!(norm(&fixed_from_str(r#""2014-07-24T12:34:06Z""#).ok()),
|
assert_eq!(norm(&fixed_from_str(r#""2014-07-24T12:34:06Z""#).ok()),
|
||||||
norm(&Some(FixedOffset::east(0).ymd(2014, 7, 24).and_hms(12, 34, 6))));
|
norm(&Some(FixedOffset::east(0).ymd(2014, 7, 24).and_hms(12, 34, 6))));
|
||||||
assert_eq!(norm(&fixed_from_str(r#""2014-07-24T13:57:06+01:23""#).ok()),
|
assert_eq!(norm(&fixed_from_str(r#""2014-07-24T13:57:06+01:23""#).ok()),
|
||||||
norm(&Some(FixedOffset::east(60*60 + 23*60).ymd(2014, 7, 24).and_hms(13, 57, 6))));
|
norm(&Some(FixedOffset::east(60*60 + 23*60).ymd(2014, 7, 24).and_hms(13, 57, 6))));
|
||||||
assert_eq!(norm(&fixed_from_str("0").ok()),
|
|
||||||
norm(&Some(UTC.ymd(1970, 1, 1).and_hms(0, 0, 0))));
|
|
||||||
assert_eq!(norm(&fixed_from_str("-1").unwrap()),
|
|
||||||
norm(&UTC.ymd(1969, 12, 31).and_hms(23, 59, 59)));
|
|
||||||
|
|
||||||
// we don't know the exact local offset but we can check that
|
// we don't know the exact local offset but we can check that
|
||||||
// the conversion didn't change the instant itself
|
// the conversion didn't change the instant itself
|
||||||
assert_eq!(local_from_str(r#""2014-07-24T12:34:06Z""#).unwrap(),
|
assert_eq!(local_from_str(r#""2014-07-24T12:34:06Z""#)
|
||||||
|
.expect("local shouuld parse"),
|
||||||
UTC.ymd(2014, 7, 24).and_hms(12, 34, 6));
|
UTC.ymd(2014, 7, 24).and_hms(12, 34, 6));
|
||||||
assert_eq!(local_from_str(r#""2014-07-24T13:57:06+01:23""#).unwrap(),
|
assert_eq!(local_from_str(r#""2014-07-24T13:57:06+01:23""#)
|
||||||
|
.expect("local should parse with offset"),
|
||||||
UTC.ymd(2014, 7, 24).and_hms(12, 34, 6));
|
UTC.ymd(2014, 7, 24).and_hms(12, 34, 6));
|
||||||
assert_eq!(fixed_from_str("0").unwrap(),
|
|
||||||
UTC.ymd(1970, 1, 1).and_hms(0, 0, 0));
|
|
||||||
assert_eq!(local_from_str("-1").unwrap(),
|
|
||||||
&UTC.ymd(1969, 12, 31).and_hms(23, 59, 59));
|
|
||||||
|
|
||||||
assert!(utc_from_str(r#""2014-07-32T12:34:06Z""#).is_err());
|
assert!(utc_from_str(r#""2014-07-32T12:34:06Z""#).is_err());
|
||||||
assert!(fixed_from_str(r#""2014-07-32T12:34:06Z""#).is_err());
|
assert!(fixed_from_str(r#""2014-07-32T12:34:06Z""#).is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#[cfg(all(test, any(feature = "rustc-serialize", feature = "serde")))]
|
||||||
|
fn test_decodable_json_timestamps<FUTC, FFixed, FLocal, E>(utc_from_str: FUTC,
|
||||||
|
fixed_from_str: FFixed,
|
||||||
|
local_from_str: FLocal)
|
||||||
|
where FUTC: Fn(&str) -> Result<TsSeconds<UTC>, E>,
|
||||||
|
FFixed: Fn(&str) -> Result<TsSeconds<FixedOffset>, E>,
|
||||||
|
FLocal: Fn(&str) -> Result<TsSeconds<Local>, E>,
|
||||||
|
E: ::std::fmt::Debug
|
||||||
|
{
|
||||||
|
fn norm<Tz: TimeZone>(dt: &Option<DateTime<Tz>>) -> Option<(&DateTime<Tz>, &Tz::Offset)> {
|
||||||
|
dt.as_ref().map(|dt| (dt, dt.offset()))
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(norm(&utc_from_str("0").ok().map(DateTime::from)),
|
||||||
|
norm(&Some(UTC.ymd(1970, 1, 1).and_hms(0, 0, 0))));
|
||||||
|
assert_eq!(norm(&utc_from_str("-1").ok().map(DateTime::from)),
|
||||||
|
norm(&Some(UTC.ymd(1969, 12, 31).and_hms(23, 59, 59))));
|
||||||
|
|
||||||
|
assert_eq!(norm(&fixed_from_str("0").ok().map(DateTime::from)),
|
||||||
|
norm(&Some(FixedOffset::east(0).ymd(1970, 1, 1).and_hms(0, 0, 0))));
|
||||||
|
assert_eq!(norm(&fixed_from_str("-1").ok().map(DateTime::from)),
|
||||||
|
norm(&Some(FixedOffset::east(0).ymd(1969, 12, 31).and_hms(23, 59, 59))));
|
||||||
|
|
||||||
|
assert_eq!(*fixed_from_str("0").expect("0 timestamp should parse"),
|
||||||
|
UTC.ymd(1970, 1, 1).and_hms(0, 0, 0));
|
||||||
|
assert_eq!(*local_from_str("-1").expect("-1 timestamp should parse"),
|
||||||
|
UTC.ymd(1969, 12, 31).and_hms(23, 59, 59));
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "rustc-serialize")]
|
#[cfg(feature = "rustc-serialize")]
|
||||||
mod rustc_serialize {
|
mod rustc_serialize {
|
||||||
use super::DateTime;
|
use std::fmt;
|
||||||
use offset::TimeZone;
|
use super::{DateTime, TsSeconds};
|
||||||
|
use offset::{TimeZone, LocalResult};
|
||||||
use offset::utc::UTC;
|
use offset::utc::UTC;
|
||||||
use offset::local::Local;
|
use offset::local::Local;
|
||||||
use offset::fixed::FixedOffset;
|
use offset::fixed::FixedOffset;
|
||||||
|
@ -477,22 +518,48 @@ mod rustc_serialize {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// try!-like function to convert a LocalResult into a serde-ish Result
|
||||||
|
fn from<T, D>(me: LocalResult<T>, d: &mut D) -> Result<T, D::Error>
|
||||||
|
where D: Decoder,
|
||||||
|
T: fmt::Display,
|
||||||
|
{
|
||||||
|
match me {
|
||||||
|
LocalResult::None => Err(d.error(
|
||||||
|
"value is not a legal timestamp")),
|
||||||
|
LocalResult::Ambiguous(..) => Err(d.error(
|
||||||
|
"value is an ambiguous timestamp")),
|
||||||
|
LocalResult::Single(val) => Ok(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Decodable for DateTime<FixedOffset> {
|
impl Decodable for DateTime<FixedOffset> {
|
||||||
fn decode<D: Decoder>(d: &mut D) -> Result<DateTime<FixedOffset>, D::Error> {
|
fn decode<D: Decoder>(d: &mut D) -> Result<DateTime<FixedOffset>, D::Error> {
|
||||||
match d.read_str()?.parse::<DateTime<FixedOffset>>() {
|
d.read_str()?.parse::<DateTime<FixedOffset>>()
|
||||||
Ok(dt) => Ok(dt),
|
.map_err(|_| d.error("invalid date and time"))
|
||||||
Err(_) => Err(d.error("invalid date and time")),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Decodable for TsSeconds<FixedOffset> {
|
||||||
|
fn decode<D: Decoder>(d: &mut D) -> Result<TsSeconds<FixedOffset>, D::Error> {
|
||||||
|
from(FixedOffset::east(0).timestamp_opt(d.read_i64()?, 0), d)
|
||||||
|
.map(|dt| TsSeconds(dt))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Decodable for DateTime<UTC> {
|
impl Decodable for DateTime<UTC> {
|
||||||
fn decode<D: Decoder>(d: &mut D) -> Result<DateTime<UTC>, D::Error> {
|
fn decode<D: Decoder>(d: &mut D) -> Result<DateTime<UTC>, D::Error> {
|
||||||
match d.read_str()?.parse::<DateTime<FixedOffset>>() {
|
d.read_str()?
|
||||||
Ok(dt) => Ok(dt.with_timezone(&UTC)),
|
.parse::<DateTime<FixedOffset>>()
|
||||||
Err(_) => Err(d.error("invalid date and time")),
|
.map(|dt| dt.with_timezone(&UTC))
|
||||||
|
.map_err(|_| d.error("invalid date and time"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Decodable for TsSeconds<UTC> {
|
||||||
|
fn decode<D: Decoder>(d: &mut D) -> Result<TsSeconds<UTC>, D::Error> {
|
||||||
|
from(UTC.timestamp_opt(d.read_i64()?, 0), d)
|
||||||
|
.map(|dt| TsSeconds(dt))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Decodable for DateTime<Local> {
|
impl Decodable for DateTime<Local> {
|
||||||
|
@ -504,6 +571,13 @@ mod rustc_serialize {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Decodable for TsSeconds<Local> {
|
||||||
|
fn decode<D: Decoder>(d: &mut D) -> Result<TsSeconds<Local>, D::Error> {
|
||||||
|
from(UTC.timestamp_opt(d.read_i64()?, 0), d)
|
||||||
|
.map(|dt| TsSeconds(dt.with_timezone(&Local)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)] use rustc_serialize::json;
|
#[cfg(test)] use rustc_serialize::json;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -515,12 +589,18 @@ mod rustc_serialize {
|
||||||
fn test_decodable() {
|
fn test_decodable() {
|
||||||
super::test_decodable_json(json::decode, json::decode, json::decode);
|
super::test_decodable_json(json::decode, json::decode, json::decode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_decodable_timestamps() {
|
||||||
|
super::test_decodable_json_timestamps(json::decode, json::decode, json::decode);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
mod serde {
|
mod serde {
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use super::DateTime;
|
use super::{DateTime, TsSeconds};
|
||||||
use offset::{TimeZone, LocalResult};
|
use offset::{TimeZone, LocalResult};
|
||||||
use offset::utc::UTC;
|
use offset::utc::UTC;
|
||||||
use offset::local::Local;
|
use offset::local::Local;
|
||||||
|
@ -551,8 +631,8 @@ mod serde {
|
||||||
// try!-like function to convert a LocalResult into a serde-ish Result
|
// try!-like function to convert a LocalResult into a serde-ish Result
|
||||||
fn from<T, E, V>(me: LocalResult<T>, ts: V) -> Result<T, E>
|
fn from<T, E, V>(me: LocalResult<T>, ts: V) -> Result<T, E>
|
||||||
where E: de::Error,
|
where E: de::Error,
|
||||||
V: Display,
|
V: fmt::Display,
|
||||||
T: Display,
|
T: fmt::Display,
|
||||||
{
|
{
|
||||||
match me {
|
match me {
|
||||||
LocalResult::None => Err(E::custom(
|
LocalResult::None => Err(E::custom(
|
||||||
|
@ -579,21 +659,6 @@ mod serde {
|
||||||
{
|
{
|
||||||
value.parse().map_err(|err| E::custom(format!("{}", err)))
|
value.parse().map_err(|err| E::custom(format!("{}", err)))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deserialize a timestamp in seconds since the epoch
|
|
||||||
fn visit_i64<E>(self, value: i64) -> Result<DateTime<FixedOffset>, E>
|
|
||||||
where E: de::Error
|
|
||||||
{
|
|
||||||
from(FixedOffset::east(0).timestamp_opt(value, 0), value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deserialize a timestamp in seconds since the epoch
|
|
||||||
fn visit_u64<E>(self, value: u64) -> Result<DateTime<FixedOffset>, E>
|
|
||||||
where E: de::Error
|
|
||||||
{
|
|
||||||
from(FixedOffset::east(0).timestamp_opt(value as i64, 0), value)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Deserialize a value that optionally includes a timezone offset in its
|
/// Deserialize a value that optionally includes a timezone offset in its
|
||||||
|
@ -634,6 +699,65 @@ mod serde {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct SecondsTimestampVisitor;
|
||||||
|
|
||||||
|
impl de::Visitor for SecondsTimestampVisitor {
|
||||||
|
type Value = DateTime<FixedOffset>;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result
|
||||||
|
{
|
||||||
|
write!(formatter, "a unix timestamp in seconds")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deserialize a timestamp in seconds since the epoch
|
||||||
|
fn visit_i64<E>(self, value: i64) -> Result<DateTime<FixedOffset>, E>
|
||||||
|
where E: de::Error
|
||||||
|
{
|
||||||
|
from(FixedOffset::east(0).timestamp_opt(value, 0), value)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deserialize a timestamp in seconds since the epoch
|
||||||
|
fn visit_u64<E>(self, value: u64) -> Result<DateTime<FixedOffset>, E>
|
||||||
|
where E: de::Error
|
||||||
|
{
|
||||||
|
from(FixedOffset::east(0).timestamp_opt(value as i64, 0), value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl de::Deserialize for TsSeconds<Local> {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where D: de::Deserializer
|
||||||
|
{
|
||||||
|
Ok(TsSeconds(try!(
|
||||||
|
deserializer
|
||||||
|
.deserialize_str(SecondsTimestampVisitor)
|
||||||
|
.map(|dt| dt.with_timezone(&Local)))))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Can deserialize a timestamp into a FixedOffset
|
||||||
|
///
|
||||||
|
/// The offset will always be 0, because timestamps are defined as UTC.
|
||||||
|
impl de::Deserialize for TsSeconds<FixedOffset> {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where D: de::Deserializer
|
||||||
|
{
|
||||||
|
Ok(TsSeconds(try!(
|
||||||
|
deserializer.deserialize_str(SecondsTimestampVisitor))))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deserialize into a UTC value
|
||||||
|
impl de::Deserialize for TsSeconds<UTC> {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where D: de::Deserializer
|
||||||
|
{
|
||||||
|
Ok(TsSeconds(try!(
|
||||||
|
deserializer.deserialize_str(SecondsTimestampVisitor)
|
||||||
|
.map(|dt| dt.with_timezone(&UTC)))))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)] extern crate serde_json;
|
#[cfg(test)] extern crate serde_json;
|
||||||
#[cfg(test)] extern crate bincode;
|
#[cfg(test)] extern crate bincode;
|
||||||
|
|
||||||
|
@ -648,6 +772,13 @@ mod serde {
|
||||||
|input| self::serde_json::from_str(&input));
|
|input| self::serde_json::from_str(&input));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_serde_deserialize_timestamps() {
|
||||||
|
super::test_decodable_json_timestamps(self::serde_json::from_str,
|
||||||
|
self::serde_json::from_str,
|
||||||
|
self::serde_json::from_str);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_serde_bincode() {
|
fn test_serde_bincode() {
|
||||||
// Bincode is relevant to test separately from JSON because
|
// Bincode is relevant to test separately from JSON because
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
//! ISO 8601 date and time without timezone.
|
//! ISO 8601 date and time without timezone.
|
||||||
|
|
||||||
use std::{str, fmt, hash};
|
use std::{str, fmt, hash};
|
||||||
use std::ops::{Add, Sub};
|
use std::ops::{Add, Sub, Deref};
|
||||||
use num::traits::ToPrimitive;
|
use num::traits::ToPrimitive;
|
||||||
use oldtime::Duration as OldDuration;
|
use oldtime::Duration as OldDuration;
|
||||||
|
|
||||||
|
@ -54,6 +54,24 @@ pub struct NaiveDateTime {
|
||||||
time: NaiveTime,
|
time: NaiveTime,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A DateTime that can be deserialized from a seconds-based timestamp
|
||||||
|
pub struct TsSeconds(NaiveDateTime);
|
||||||
|
|
||||||
|
impl From<TsSeconds> for NaiveDateTime {
|
||||||
|
/// Pull the internal NaiveDateTime out
|
||||||
|
fn from(obj: TsSeconds) -> NaiveDateTime {
|
||||||
|
obj.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for TsSeconds {
|
||||||
|
type Target = NaiveDateTime;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl NaiveDateTime {
|
impl NaiveDateTime {
|
||||||
/// Makes a new `NaiveDateTime` from date and time components.
|
/// Makes a new `NaiveDateTime` from date and time components.
|
||||||
/// Equivalent to [`date.and_time(time)`](../date/struct.NaiveDate.html#method.and_time)
|
/// Equivalent to [`date.and_time(time)`](../date/struct.NaiveDate.html#method.and_time)
|
||||||
|
@ -1402,16 +1420,6 @@ fn test_decodable_json<F, E>(from_str: F)
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
from_str(r#""+262143-12-31T23:59:60.9999999999997""#).ok(), // excess digits are ignored
|
from_str(r#""+262143-12-31T23:59:60.9999999999997""#).ok(), // excess digits are ignored
|
||||||
Some(date::MAX.and_hms_nano(23, 59, 59, 1_999_999_999)));
|
Some(date::MAX.and_hms_nano(23, 59, 59, 1_999_999_999)));
|
||||||
assert_eq!(
|
|
||||||
from_str("0").unwrap(),
|
|
||||||
NaiveDate::from_ymd(1970, 1, 1).and_hms(0, 0, 0),
|
|
||||||
"should parse integers as timestamps"
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
from_str("-1").unwrap(),
|
|
||||||
NaiveDate::from_ymd(1969, 12, 31).and_hms(23, 59, 59),
|
|
||||||
"should parse integers as timestamps"
|
|
||||||
);
|
|
||||||
|
|
||||||
// bad formats
|
// bad formats
|
||||||
assert!(from_str(r#""""#).is_err());
|
assert!(from_str(r#""""#).is_err());
|
||||||
|
@ -1428,7 +1436,6 @@ fn test_decodable_json<F, E>(from_str: F)
|
||||||
assert!(from_str(r#""2016-07-08 09:10:48.090""#).is_err());
|
assert!(from_str(r#""2016-07-08 09:10:48.090""#).is_err());
|
||||||
assert!(from_str(r#""2016-007-08T09:10:48.090""#).is_err());
|
assert!(from_str(r#""2016-007-08T09:10:48.090""#).is_err());
|
||||||
assert!(from_str(r#""yyyy-mm-ddThh:mm:ss.fffffffff""#).is_err());
|
assert!(from_str(r#""yyyy-mm-ddThh:mm:ss.fffffffff""#).is_err());
|
||||||
assert!(from_str(r#"0"#).is_err());
|
|
||||||
assert!(from_str(r#"20160708000000"#).is_err());
|
assert!(from_str(r#"20160708000000"#).is_err());
|
||||||
assert!(from_str(r#"{}"#).is_err());
|
assert!(from_str(r#"{}"#).is_err());
|
||||||
// pre-0.3.0 rustc-serialize format is now invalid
|
// pre-0.3.0 rustc-serialize format is now invalid
|
||||||
|
@ -1436,9 +1443,27 @@ fn test_decodable_json<F, E>(from_str: F)
|
||||||
assert!(from_str(r#"null"#).is_err());
|
assert!(from_str(r#"null"#).is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[cfg(all(test, any(feature = "rustc-serialize", feature = "serde")))]
|
||||||
|
fn test_decodable_json_timestamp<F, E>(from_str: F)
|
||||||
|
where F: Fn(&str) -> Result<TsSeconds, E>, E: ::std::fmt::Debug
|
||||||
|
{
|
||||||
|
assert_eq!(
|
||||||
|
*from_str("0").unwrap(),
|
||||||
|
NaiveDate::from_ymd(1970, 1, 1).and_hms(0, 0, 0),
|
||||||
|
"should parse integers as timestamps"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
*from_str("-1").unwrap(),
|
||||||
|
NaiveDate::from_ymd(1969, 12, 31).and_hms(23, 59, 59),
|
||||||
|
"should parse integers as timestamps"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#[cfg(feature = "rustc-serialize")]
|
#[cfg(feature = "rustc-serialize")]
|
||||||
mod rustc_serialize {
|
mod rustc_serialize {
|
||||||
use super::NaiveDateTime;
|
use super::{NaiveDateTime, TsSeconds};
|
||||||
use rustc_serialize::{Encodable, Encoder, Decodable, Decoder};
|
use rustc_serialize::{Encodable, Encoder, Decodable, Decoder};
|
||||||
|
|
||||||
impl Encodable for NaiveDateTime {
|
impl Encodable for NaiveDateTime {
|
||||||
|
@ -1449,7 +1474,15 @@ mod rustc_serialize {
|
||||||
|
|
||||||
impl Decodable for NaiveDateTime {
|
impl Decodable for NaiveDateTime {
|
||||||
fn decode<D: Decoder>(d: &mut D) -> Result<NaiveDateTime, D::Error> {
|
fn decode<D: Decoder>(d: &mut D) -> Result<NaiveDateTime, D::Error> {
|
||||||
d.read_str()?.parse().map_err(|_| d.error("invalid date and time"))
|
d.read_str()?.parse().map_err(|_| d.error("invalid date time string"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Decodable for TsSeconds {
|
||||||
|
fn decode<D: Decoder>(d: &mut D) -> Result<TsSeconds, D::Error> {
|
||||||
|
Ok(TsSeconds(
|
||||||
|
NaiveDateTime::from_timestamp_opt(d.read_i64()?, 0)
|
||||||
|
.ok_or_else(|| d.error("invalid timestamp"))?))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1464,12 +1497,17 @@ mod rustc_serialize {
|
||||||
fn test_decodable() {
|
fn test_decodable() {
|
||||||
super::test_decodable_json(json::decode);
|
super::test_decodable_json(json::decode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_decodable_timestamps() {
|
||||||
|
super::test_decodable_json_timestamp(json::decode);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
mod serde {
|
mod serde {
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use super::NaiveDateTime;
|
use super::{NaiveDateTime, TsSeconds};
|
||||||
use serde::{ser, de};
|
use serde::{ser, de};
|
||||||
|
|
||||||
// TODO not very optimized for space (binary formats would want something better)
|
// TODO not very optimized for space (binary formats would want something better)
|
||||||
|
@ -1499,7 +1537,7 @@ mod serde {
|
||||||
|
|
||||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result
|
||||||
{
|
{
|
||||||
write!(formatter, "a formatted date and time string or a unix timestamp")
|
write!(formatter, "a formatted date and time string")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn visit_str<E>(self, value: &str) -> Result<NaiveDateTime, E>
|
fn visit_str<E>(self, value: &str) -> Result<NaiveDateTime, E>
|
||||||
|
@ -1507,6 +1545,25 @@ mod serde {
|
||||||
{
|
{
|
||||||
value.parse().map_err(|err| E::custom(format!("{}", err)))
|
value.parse().map_err(|err| E::custom(format!("{}", err)))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl de::Deserialize for NaiveDateTime {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where D: de::Deserializer
|
||||||
|
{
|
||||||
|
deserializer.deserialize_str(NaiveDateTimeVisitor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct NaiveDateTimeFromSecondsVisitor;
|
||||||
|
|
||||||
|
impl de::Visitor for NaiveDateTimeFromSecondsVisitor {
|
||||||
|
type Value = NaiveDateTime;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result
|
||||||
|
{
|
||||||
|
write!(formatter, "a unix timestamp")
|
||||||
|
}
|
||||||
|
|
||||||
fn visit_i64<E>(self, value: i64) -> Result<NaiveDateTime, E>
|
fn visit_i64<E>(self, value: i64) -> Result<NaiveDateTime, E>
|
||||||
where E: de::Error
|
where E: de::Error
|
||||||
|
@ -1527,7 +1584,8 @@ mod serde {
|
||||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
where D: de::Deserializer<'de>
|
where D: de::Deserializer<'de>
|
||||||
{
|
{
|
||||||
deserializer.deserialize_str(NaiveDateTimeVisitor)
|
Ok(TsSeconds(try!(
|
||||||
|
deserializer.deserialize_str(NaiveDateTimeFromSecondsVisitor))))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1544,6 +1602,11 @@ mod serde {
|
||||||
super::test_decodable_json(|input| self::serde_json::from_str(&input));
|
super::test_decodable_json(|input| self::serde_json::from_str(&input));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_serde_deserialize_timestamp() {
|
||||||
|
super::test_decodable_json_timestamp(self::serde_json::from_str);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_serde_bincode() {
|
fn test_serde_bincode() {
|
||||||
// Bincode is relevant to test separately from JSON because
|
// Bincode is relevant to test separately from JSON because
|
||||||
|
|
Loading…
Reference in New Issue