Use serde's serialize_with instead of a newtype
This is a significantly less horrible API than in the previous commit.
This commit is contained in:
parent
bc879d705e
commit
44fc13d7df
|
@ -0,0 +1,62 @@
|
|||
#!/bin/bash
|
||||
|
||||
# This is the script that's executed by travis, you can run it yourself to run
|
||||
# the exact same suite
|
||||
|
||||
set -e
|
||||
|
||||
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
channel() {
|
||||
if [ -n "${TRAVIS}" ]; then
|
||||
if [ "${TRAVIS_RUST_VERSION}" = "${CHANNEL}" ]; then
|
||||
pwd
|
||||
(set -x; cargo "$@")
|
||||
fi
|
||||
elif [ -n "${APPVEYOR}" ]; then
|
||||
if [ "${APPVEYOR_RUST_CHANNEL}" = "${CHANNEL}" ]; then
|
||||
pwd
|
||||
(set -x; cargo "$@")
|
||||
fi
|
||||
else
|
||||
pwd
|
||||
(set -x; cargo "+${CHANNEL}" "$@")
|
||||
fi
|
||||
}
|
||||
|
||||
build_and_test() {
|
||||
# interleave building and testing in hope that it saves time
|
||||
# also vary the local time zone to (hopefully) catch tz-dependent bugs
|
||||
# also avoid doc-testing multiple times---it takes a lot and rarely helps
|
||||
cargo clean
|
||||
channel build -v
|
||||
TZ=ACST-9:30 channel test -v --lib
|
||||
channel build -v --features rustc-serialize
|
||||
TZ=EST4 channel test -v --features rustc-serialize --lib
|
||||
channel build -v --features 'serde bincode'
|
||||
TZ=UTC0 channel test -v --features 'serde bincode'
|
||||
}
|
||||
|
||||
build_only() {
|
||||
# Rust 1.13 doesn't support custom derive, so, to avoid doctests which
|
||||
# validate that, we just build there.
|
||||
cargo clean
|
||||
channel build -v
|
||||
channel build -v --features rustc-serialize
|
||||
channel build -v --features 'serde bincode'
|
||||
}
|
||||
|
||||
rustc --version
|
||||
cargo --version
|
||||
|
||||
CHANNEL=nightly
|
||||
build_and_test
|
||||
|
||||
CHANNEL=beta
|
||||
build_and_test
|
||||
|
||||
CHANNEL=stable
|
||||
build_and_test
|
||||
|
||||
CHANNEL=1.13.0
|
||||
build_only
|
13
.travis.yml
13
.travis.yml
|
@ -1,7 +1,7 @@
|
|||
language: rust
|
||||
sudo: false
|
||||
rust:
|
||||
# 1.13.0 is the earliest version that Serde 0.9 tests, so we follow suit
|
||||
# 1.13.0 is the earliest version that Serde 1.0 tests, so we follow suit
|
||||
- 1.13.0
|
||||
- stable
|
||||
- beta
|
||||
|
@ -15,16 +15,7 @@ matrix:
|
|||
env:
|
||||
global:
|
||||
- LD_LIBRARY_PATH: /usr/local/lib
|
||||
script:
|
||||
# interleave building and testing in hope that it saves time
|
||||
# also vary the local time zone to (hopefully) catch tz-dependent bugs
|
||||
# also avoid doc-testing multiple times---it takes a lot and rarely helps
|
||||
- cargo build -v
|
||||
- TZ=ACST-9:30 cargo test -v
|
||||
- cargo build -v --features rustc-serialize
|
||||
- TZ=EST4 cargo test -v --features rustc-serialize --lib
|
||||
- cargo build -v --features 'serde bincode'
|
||||
- TZ=UTC0 cargo test -v --features 'serde bincode' --lib
|
||||
script: ./.travis.sh
|
||||
notifications:
|
||||
email: false
|
||||
irc:
|
||||
|
|
|
@ -27,4 +27,5 @@ serde = { version = "1", optional = true }
|
|||
|
||||
[dev-dependencies]
|
||||
serde_json = { version = "1" }
|
||||
serde_derive = { version = "1" }
|
||||
bincode = { version = "0.8.0", features = ["serde"], default-features = false }
|
||||
|
|
|
@ -14,8 +14,8 @@ install:
|
|||
- ps: $env:PATH="$env:PATH;C:\rust\bin"
|
||||
- rustc -vV
|
||||
- cargo -vV
|
||||
build_script:
|
||||
# do not test all combinations, Travis will handle that
|
||||
- cargo build -v --features "serde rustc-serialize"
|
||||
|
||||
build: false
|
||||
|
||||
test_script:
|
||||
- cargo test -v --features "serde rustc-serialize"
|
||||
- sh -c 'PATH=`rustc --print sysroot`/bin:$PATH ./.travis.sh'
|
||||
|
|
263
src/datetime.rs
263
src/datetime.rs
|
@ -5,7 +5,9 @@
|
|||
|
||||
use std::{str, fmt, hash};
|
||||
use std::cmp::Ordering;
|
||||
use std::ops::{Add, Sub, Deref};
|
||||
use std::ops::{Add, Sub};
|
||||
#[cfg(feature = "rustc-serialize")]
|
||||
use std::ops::Deref;
|
||||
use oldtime::Duration as OldDuration;
|
||||
|
||||
use {Weekday, Timelike, Datelike};
|
||||
|
@ -33,8 +35,10 @@ pub struct DateTime<Tz: TimeZone> {
|
|||
/// A DateTime that can be deserialized from a timestamp
|
||||
///
|
||||
/// A timestamp here is seconds since the epoch
|
||||
#[cfg(feature = "rustc-serialize")]
|
||||
pub struct TsSeconds<Tz: TimeZone>(DateTime<Tz>);
|
||||
|
||||
#[cfg(feature = "rustc-serialize")]
|
||||
impl<Tz: TimeZone> From<TsSeconds<Tz>> for DateTime<Tz> {
|
||||
/// Pull the inner DateTime<Tz> out
|
||||
fn from(obj: TsSeconds<Tz>) -> DateTime<Tz> {
|
||||
|
@ -42,6 +46,7 @@ impl<Tz: TimeZone> From<TsSeconds<Tz>> for DateTime<Tz> {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustc-serialize")]
|
||||
impl<Tz: TimeZone> Deref for TsSeconds<Tz> {
|
||||
type Target = DateTime<Tz>;
|
||||
|
||||
|
@ -50,6 +55,7 @@ impl<Tz: TimeZone> Deref for TsSeconds<Tz> {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
impl<Tz: TimeZone> DateTime<Tz> {
|
||||
/// Makes a new `DateTime` with given *UTC* datetime and offset.
|
||||
/// The local datetime should be constructed via the `TimeZone` trait.
|
||||
|
@ -471,37 +477,6 @@ fn test_decodable_json<FUTC, FFixed, FLocal, E>(utc_from_str: FUTC,
|
|||
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")]
|
||||
mod rustc_serialize {
|
||||
use std::fmt;
|
||||
|
@ -518,7 +493,7 @@ mod rustc_serialize {
|
|||
}
|
||||
}
|
||||
|
||||
// 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, D>(me: LocalResult<T>, d: &mut D) -> Result<T, D::Error>
|
||||
where D: Decoder,
|
||||
T: fmt::Display,
|
||||
|
@ -590,23 +565,165 @@ mod rustc_serialize {
|
|||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// Ser/de helpers
|
||||
///
|
||||
/// The various modules in here are intended to be used with serde's [`with`
|
||||
/// annotation](https://serde.rs/attributes.html#field-attributes).
|
||||
#[cfg(feature = "serde")]
|
||||
mod serde {
|
||||
pub mod serde {
|
||||
use std::fmt;
|
||||
use super::{DateTime, TsSeconds};
|
||||
use super::DateTime;
|
||||
use offset::{TimeZone, LocalResult};
|
||||
use offset::utc::UTC;
|
||||
use offset::local::Local;
|
||||
use offset::fixed::FixedOffset;
|
||||
use serde::{ser, de};
|
||||
|
||||
/// Ser/de to/from timestamps in seconds
|
||||
///
|
||||
/// Intended for use with `serde`'s `with` attribute.
|
||||
///
|
||||
/// # Example:
|
||||
///
|
||||
/// ```rust
|
||||
/// # // We mark this ignored so that we can test on 1.13 (which does not
|
||||
/// # // support custom derive), and run tests with --ignored on beta and
|
||||
/// # // nightly to actually trigger these.
|
||||
/// #
|
||||
/// # #[macro_use] extern crate serde_derive;
|
||||
/// # #[macro_use] extern crate serde_json;
|
||||
/// # extern crate chrono;
|
||||
/// # use chrono::{TimeZone, DateTime, UTC};
|
||||
/// use chrono::datetime::serde::ts_seconds;
|
||||
/// #[derive(Deserialize, Serialize)]
|
||||
/// struct S {
|
||||
/// #[serde(with = "ts_seconds")]
|
||||
/// time: DateTime<UTC>
|
||||
/// }
|
||||
///
|
||||
/// # fn example() -> Result<S, serde_json::Error> {
|
||||
/// let time = UTC.ymd(2015, 5, 15).and_hms(10, 0, 0);
|
||||
/// let my_s = S {
|
||||
/// time: time.clone(),
|
||||
/// };
|
||||
///
|
||||
/// let as_string = serde_json::to_string(&my_s)?;
|
||||
/// assert_eq!(as_string, r#"{"time":1431684000}"#);
|
||||
/// let my_s: S = serde_json::from_str(&as_string)?;
|
||||
/// assert_eq!(my_s.time, time);
|
||||
/// # Ok(my_s)
|
||||
/// # }
|
||||
/// # fn main() { example().unwrap(); }
|
||||
/// ```
|
||||
pub mod ts_seconds {
|
||||
use std::fmt;
|
||||
use serde::{ser, de};
|
||||
|
||||
use {DateTime, UTC, FixedOffset};
|
||||
use offset::TimeZone;
|
||||
use super::from;
|
||||
|
||||
/// Deserialize a DateTime from a seconds timestamp
|
||||
///
|
||||
/// Intended for use with `serde`s `deserialize_with` attribute.
|
||||
///
|
||||
/// # Example:
|
||||
///
|
||||
/// ```rust
|
||||
/// # // We mark this ignored so that we can test on 1.13 (which does not
|
||||
/// # // support custom derive), and run tests with --ignored on beta and
|
||||
/// # // nightly to actually trigger these.
|
||||
/// #
|
||||
/// # #[macro_use] extern crate serde_derive;
|
||||
/// # #[macro_use] extern crate serde_json;
|
||||
/// # extern crate chrono;
|
||||
/// # use chrono::{DateTime, UTC};
|
||||
/// use chrono::datetime::serde::ts_seconds::deserialize as from_ts;
|
||||
/// #[derive(Deserialize)]
|
||||
/// struct S {
|
||||
/// #[serde(deserialize_with = "from_ts")]
|
||||
/// time: DateTime<UTC>
|
||||
/// }
|
||||
///
|
||||
/// # fn example() -> Result<S, serde_json::Error> {
|
||||
/// let my_s: S = serde_json::from_str(r#"{ "time": 1431684000 }"#)?;
|
||||
/// # Ok(my_s)
|
||||
/// # }
|
||||
/// # fn main() { example().unwrap(); }
|
||||
/// ```
|
||||
pub fn deserialize<'de, D>(d: D) -> Result<DateTime<UTC>, D::Error>
|
||||
where D: de::Deserializer<'de>
|
||||
{
|
||||
Ok(try!(d.deserialize_i64(SecondsTimestampVisitor).map(|dt| dt.with_timezone(&UTC))))
|
||||
}
|
||||
|
||||
/// Serialize a UTC datetime into an integer number of seconds since the epoch
|
||||
///
|
||||
/// Intended for use with `serde`s `serialize_with` attribute.
|
||||
///
|
||||
/// # Example:
|
||||
///
|
||||
/// ```rust
|
||||
/// # // We mark this ignored so that we can test on 1.13 (which does not
|
||||
/// # // support custom derive), and run tests with --ignored on beta and
|
||||
/// # // nightly to actually trigger these.
|
||||
/// #
|
||||
/// # #[macro_use] extern crate serde_derive;
|
||||
/// # #[macro_use] extern crate serde_json;
|
||||
/// # extern crate chrono;
|
||||
/// # use chrono::{TimeZone, DateTime, UTC};
|
||||
/// use chrono::datetime::serde::ts_seconds::serialize as to_ts;
|
||||
/// #[derive(Serialize)]
|
||||
/// struct S {
|
||||
/// #[serde(serialize_with = "to_ts")]
|
||||
/// time: DateTime<UTC>
|
||||
/// }
|
||||
///
|
||||
/// # fn example() -> Result<String, serde_json::Error> {
|
||||
/// let my_s = S {
|
||||
/// time: UTC.ymd(2015, 5, 15).and_hms(10, 0, 0),
|
||||
/// };
|
||||
/// let as_string = serde_json::to_string(&my_s)?;
|
||||
/// assert_eq!(as_string, r#"{"time":1431684000}"#);
|
||||
/// # Ok(as_string)
|
||||
/// # }
|
||||
/// # fn main() { example().unwrap(); }
|
||||
/// ```
|
||||
pub fn serialize<S>(dt: &DateTime<UTC>, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where S: ser::Serializer
|
||||
{
|
||||
serializer.serialize_i64(dt.timestamp())
|
||||
}
|
||||
|
||||
struct SecondsTimestampVisitor;
|
||||
|
||||
impl<'de> de::Visitor<'de> 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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// TODO not very optimized for space (binary formats would want something better)
|
||||
|
||||
impl<Tz: TimeZone> ser::Serialize for DateTime<Tz> {
|
||||
|
@ -699,65 +816,6 @@ 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 bincode;
|
||||
|
||||
|
@ -772,13 +830,6 @@ mod serde {
|
|||
|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]
|
||||
fn test_serde_bincode() {
|
||||
// Bincode is relevant to test separately from JSON because
|
||||
|
|
|
@ -1443,24 +1443,6 @@ fn test_decodable_json<F, E>(from_str: F)
|
|||
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")]
|
||||
mod rustc_serialize {
|
||||
use super::{NaiveDateTime, TsSeconds};
|
||||
|
@ -1498,20 +1480,19 @@ mod rustc_serialize {
|
|||
super::test_decodable_json(json::decode);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decodable_timestamps() {
|
||||
super::test_decodable_json_timestamp(json::decode);
|
||||
}
|
||||
}
|
||||
|
||||
/// Tools to help serializing/deserializing NaiveDateTimes
|
||||
#[cfg(feature = "serde")]
|
||||
mod serde {
|
||||
pub mod serde {
|
||||
use std::fmt;
|
||||
use super::{NaiveDateTime, TsSeconds};
|
||||
use super::{NaiveDateTime};
|
||||
use serde::{ser, de};
|
||||
|
||||
// TODO not very optimized for space (binary formats would want something better)
|
||||
|
||||
/// Serialize a NaiveDateTime as a string
|
||||
///
|
||||
/// See the [`ts_seconds`](./ts_seconds/index.html) module to serialize as
|
||||
/// a timestamp.
|
||||
impl ser::Serialize for NaiveDateTime {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where S: ser::Serializer
|
||||
|
@ -1547,46 +1528,156 @@ mod serde {
|
|||
}
|
||||
}
|
||||
|
||||
impl de::Deserialize for NaiveDateTime {
|
||||
impl<'de> de::Deserialize<'de> for NaiveDateTime {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where D: de::Deserializer
|
||||
where D: de::Deserializer<'de>
|
||||
{
|
||||
deserializer.deserialize_str(NaiveDateTimeVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
struct NaiveDateTimeFromSecondsVisitor;
|
||||
/// Used to serialize/deserialize from second-precision timestamps
|
||||
///
|
||||
/// # Example:
|
||||
///
|
||||
/// ```rust
|
||||
/// # // We mark this ignored so that we can test on 1.13 (which does not
|
||||
/// # // support custom derive), and run tests with --ignored on beta and
|
||||
/// # // nightly to actually trigger these.
|
||||
/// #
|
||||
/// # #[macro_use] extern crate serde_derive;
|
||||
/// # extern crate serde_json;
|
||||
/// # extern crate serde;
|
||||
/// # extern crate chrono;
|
||||
/// # use chrono::{TimeZone, NaiveDate, NaiveDateTime, UTC};
|
||||
/// use chrono::naive::datetime::serde::ts_seconds;
|
||||
/// #[derive(Deserialize, Serialize)]
|
||||
/// struct S {
|
||||
/// #[serde(with = "ts_seconds")]
|
||||
/// time: NaiveDateTime
|
||||
/// }
|
||||
///
|
||||
/// # fn example() -> Result<S, serde_json::Error> {
|
||||
/// let time = NaiveDate::from_ymd(2015, 5, 15).and_hms(10, 0, 0);
|
||||
/// let my_s = S {
|
||||
/// time: time.clone(),
|
||||
/// };
|
||||
///
|
||||
/// let as_string = serde_json::to_string(&my_s)?;
|
||||
/// assert_eq!(as_string, r#"{"time":1431684000}"#);
|
||||
/// let my_s: S = serde_json::from_str(&as_string)?;
|
||||
/// assert_eq!(my_s.time, time);
|
||||
/// # Ok(my_s)
|
||||
/// # }
|
||||
/// # fn main() { example().unwrap(); }
|
||||
/// ```
|
||||
pub mod ts_seconds {
|
||||
use std::fmt;
|
||||
use serde::{ser, de};
|
||||
|
||||
impl de::Visitor for NaiveDateTimeFromSecondsVisitor {
|
||||
type Value = NaiveDateTime;
|
||||
use 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>
|
||||
where E: de::Error
|
||||
{
|
||||
NaiveDateTime::from_timestamp_opt(value, 0)
|
||||
.ok_or_else(|| E::custom(format!("value is not a legal timestamp: {}", value)))
|
||||
}
|
||||
|
||||
fn visit_u64<E>(self, value: u64) -> Result<NaiveDateTime, E>
|
||||
where E: de::Error
|
||||
{
|
||||
NaiveDateTime::from_timestamp_opt(value as i64, 0)
|
||||
.ok_or_else(|| E::custom(format!("value is not a legal timestamp: {}", value)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> de::Deserialize<'de> for NaiveDateTime {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
/// Deserialize a DateTime from a seconds timestamp
|
||||
///
|
||||
/// Intended for use with `serde`s `deserialize_with` attribute.
|
||||
///
|
||||
/// # Example:
|
||||
///
|
||||
/// ```rust
|
||||
/// # // We mark this ignored so that we can test on 1.13 (which does not
|
||||
/// # // support custom derive), and run tests with --ignored on beta and
|
||||
/// # // nightly to actually trigger these.
|
||||
/// #
|
||||
/// # #[macro_use] extern crate serde_derive;
|
||||
/// # #[macro_use] extern crate serde_json;
|
||||
/// # extern crate serde;
|
||||
/// # extern crate chrono;
|
||||
/// # use chrono::{NaiveDateTime, UTC};
|
||||
/// # use serde::Deserialize;
|
||||
/// use chrono::naive::datetime::serde::ts_seconds::deserialize as from_ts;
|
||||
/// #[derive(Deserialize)]
|
||||
/// struct S {
|
||||
/// #[serde(deserialize_with = "from_ts")]
|
||||
/// time: NaiveDateTime
|
||||
/// }
|
||||
///
|
||||
/// # fn example() -> Result<S, serde_json::Error> {
|
||||
/// let my_s: S = serde_json::from_str(r#"{ "time": 1431684000 }"#)?;
|
||||
/// # Ok(my_s)
|
||||
/// # }
|
||||
/// # fn main() { example().unwrap(); }
|
||||
/// ```
|
||||
pub fn deserialize<'de, D>(d: D) -> Result<NaiveDateTime, D::Error>
|
||||
where D: de::Deserializer<'de>
|
||||
{
|
||||
Ok(TsSeconds(try!(
|
||||
deserializer.deserialize_str(NaiveDateTimeFromSecondsVisitor))))
|
||||
Ok(try!(d.deserialize_i64(NaiveDateTimeFromSecondsVisitor)))
|
||||
}
|
||||
|
||||
/// Serialize a UTC datetime into an integer number of seconds since the epoch
|
||||
///
|
||||
/// Intended for use with `serde`s `serialize_with` attribute.
|
||||
///
|
||||
/// # Example:
|
||||
///
|
||||
/// ```rust
|
||||
/// # // We mark this ignored so that we can test on 1.13 (which does not
|
||||
/// # // support custom derive), and run tests with --ignored on beta and
|
||||
/// # // nightly to actually trigger these.
|
||||
/// #
|
||||
/// # #[macro_use] extern crate serde_derive;
|
||||
/// # #[macro_use] extern crate serde_json;
|
||||
/// # #[macro_use] extern crate serde;
|
||||
/// # extern crate chrono;
|
||||
/// # use chrono::{TimeZone, NaiveDate, NaiveDateTime, UTC};
|
||||
/// # use serde::Serialize;
|
||||
/// use chrono::naive::datetime::serde::ts_seconds::serialize as to_ts;
|
||||
/// #[derive(Serialize)]
|
||||
/// struct S {
|
||||
/// #[serde(serialize_with = "to_ts")]
|
||||
/// time: NaiveDateTime
|
||||
/// }
|
||||
///
|
||||
/// # fn example() -> Result<String, serde_json::Error> {
|
||||
/// let my_s = S {
|
||||
/// time: NaiveDate::from_ymd(2015, 5, 15).and_hms(10, 0, 0),
|
||||
/// };
|
||||
/// let as_string = serde_json::to_string(&my_s)?;
|
||||
/// assert_eq!(as_string, r#"{"time":1431684000}"#);
|
||||
/// # Ok(as_string)
|
||||
/// # }
|
||||
/// # fn main() { example().unwrap(); }
|
||||
/// ```
|
||||
pub fn serialize<S>(dt: &NaiveDateTime, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where S: ser::Serializer
|
||||
{
|
||||
serializer.serialize_i64(dt.timestamp())
|
||||
}
|
||||
|
||||
struct NaiveDateTimeFromSecondsVisitor;
|
||||
|
||||
impl<'de> de::Visitor<'de> 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>
|
||||
where E: de::Error
|
||||
{
|
||||
NaiveDateTime::from_timestamp_opt(value, 0)
|
||||
.ok_or_else(|| E::custom(format!("value is not a legal timestamp: {}", value)))
|
||||
}
|
||||
|
||||
fn visit_u64<E>(self, value: u64) -> Result<NaiveDateTime, E>
|
||||
where E: de::Error
|
||||
{
|
||||
NaiveDateTime::from_timestamp_opt(value as i64, 0)
|
||||
.ok_or_else(|| E::custom(format!("value is not a legal timestamp: {}", value)))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#[cfg(test)] extern crate serde_json;
|
||||
|
@ -1602,11 +1693,6 @@ mod serde {
|
|||
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]
|
||||
fn test_serde_bincode() {
|
||||
// Bincode is relevant to test separately from JSON because
|
||||
|
|
Loading…
Reference in New Issue