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
|
language: rust
|
||||||
sudo: false
|
sudo: false
|
||||||
rust:
|
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
|
- 1.13.0
|
||||||
- stable
|
- stable
|
||||||
- beta
|
- beta
|
||||||
|
@ -15,16 +15,7 @@ matrix:
|
||||||
env:
|
env:
|
||||||
global:
|
global:
|
||||||
- LD_LIBRARY_PATH: /usr/local/lib
|
- LD_LIBRARY_PATH: /usr/local/lib
|
||||||
script:
|
script: ./.travis.sh
|
||||||
# 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
|
|
||||||
notifications:
|
notifications:
|
||||||
email: false
|
email: false
|
||||||
irc:
|
irc:
|
||||||
|
|
|
@ -27,4 +27,5 @@ serde = { version = "1", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
serde_json = { version = "1" }
|
serde_json = { version = "1" }
|
||||||
|
serde_derive = { version = "1" }
|
||||||
bincode = { version = "0.8.0", features = ["serde"], default-features = false }
|
bincode = { version = "0.8.0", features = ["serde"], default-features = false }
|
||||||
|
|
|
@ -14,8 +14,8 @@ install:
|
||||||
- ps: $env:PATH="$env:PATH;C:\rust\bin"
|
- ps: $env:PATH="$env:PATH;C:\rust\bin"
|
||||||
- rustc -vV
|
- rustc -vV
|
||||||
- cargo -vV
|
- cargo -vV
|
||||||
build_script:
|
|
||||||
# do not test all combinations, Travis will handle that
|
build: false
|
||||||
- cargo build -v --features "serde rustc-serialize"
|
|
||||||
test_script:
|
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::{str, fmt, hash};
|
||||||
use std::cmp::Ordering;
|
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 oldtime::Duration as OldDuration;
|
||||||
|
|
||||||
use {Weekday, Timelike, Datelike};
|
use {Weekday, Timelike, Datelike};
|
||||||
|
@ -33,8 +35,10 @@ pub struct DateTime<Tz: TimeZone> {
|
||||||
/// A DateTime that can be deserialized from a timestamp
|
/// A DateTime that can be deserialized from a timestamp
|
||||||
///
|
///
|
||||||
/// A timestamp here is seconds since the epoch
|
/// A timestamp here is seconds since the epoch
|
||||||
|
#[cfg(feature = "rustc-serialize")]
|
||||||
pub struct TsSeconds<Tz: TimeZone>(DateTime<Tz>);
|
pub struct TsSeconds<Tz: TimeZone>(DateTime<Tz>);
|
||||||
|
|
||||||
|
#[cfg(feature = "rustc-serialize")]
|
||||||
impl<Tz: TimeZone> From<TsSeconds<Tz>> for DateTime<Tz> {
|
impl<Tz: TimeZone> From<TsSeconds<Tz>> for DateTime<Tz> {
|
||||||
/// Pull the inner DateTime<Tz> out
|
/// Pull the inner DateTime<Tz> out
|
||||||
fn from(obj: TsSeconds<Tz>) -> DateTime<Tz> {
|
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> {
|
impl<Tz: TimeZone> Deref for TsSeconds<Tz> {
|
||||||
type Target = DateTime<Tz>;
|
type Target = DateTime<Tz>;
|
||||||
|
|
||||||
|
@ -50,6 +55,7 @@ impl<Tz: TimeZone> Deref for TsSeconds<Tz> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
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.
|
||||||
|
@ -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());
|
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 std::fmt;
|
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>
|
fn from<T, D>(me: LocalResult<T>, d: &mut D) -> Result<T, D::Error>
|
||||||
where D: Decoder,
|
where D: Decoder,
|
||||||
T: fmt::Display,
|
T: fmt::Display,
|
||||||
|
@ -590,23 +565,165 @@ mod rustc_serialize {
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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")]
|
#[cfg(feature = "serde")]
|
||||||
mod serde {
|
pub mod serde {
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use super::{DateTime, TsSeconds};
|
use super::DateTime;
|
||||||
use offset::{TimeZone, LocalResult};
|
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;
|
||||||
use serde::{ser, de};
|
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)
|
// TODO not very optimized for space (binary formats would want something better)
|
||||||
|
|
||||||
impl<Tz: TimeZone> ser::Serialize for DateTime<Tz> {
|
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 serde_json;
|
||||||
#[cfg(test)] extern crate bincode;
|
#[cfg(test)] extern crate bincode;
|
||||||
|
|
||||||
|
@ -772,13 +830,6 @@ 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
|
||||||
|
|
|
@ -1443,24 +1443,6 @@ 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, TsSeconds};
|
use super::{NaiveDateTime, TsSeconds};
|
||||||
|
@ -1498,20 +1480,19 @@ mod rustc_serialize {
|
||||||
super::test_decodable_json(json::decode);
|
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")]
|
#[cfg(feature = "serde")]
|
||||||
mod serde {
|
pub mod serde {
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use super::{NaiveDateTime, TsSeconds};
|
use super::{NaiveDateTime};
|
||||||
use serde::{ser, de};
|
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 {
|
impl ser::Serialize for NaiveDateTime {
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
where S: ser::Serializer
|
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>
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
where D: de::Deserializer
|
where D: de::Deserializer<'de>
|
||||||
{
|
{
|
||||||
deserializer.deserialize_str(NaiveDateTimeVisitor)
|
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 {
|
use NaiveDateTime;
|
||||||
type Value = NaiveDateTime;
|
|
||||||
|
|
||||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result
|
/// Deserialize a DateTime from a seconds timestamp
|
||||||
{
|
///
|
||||||
write!(formatter, "a unix timestamp")
|
/// Intended for use with `serde`s `deserialize_with` attribute.
|
||||||
}
|
///
|
||||||
|
/// # Example:
|
||||||
fn visit_i64<E>(self, value: i64) -> Result<NaiveDateTime, E>
|
///
|
||||||
where E: de::Error
|
/// ```rust
|
||||||
{
|
/// # // We mark this ignored so that we can test on 1.13 (which does not
|
||||||
NaiveDateTime::from_timestamp_opt(value, 0)
|
/// # // support custom derive), and run tests with --ignored on beta and
|
||||||
.ok_or_else(|| E::custom(format!("value is not a legal timestamp: {}", value)))
|
/// # // nightly to actually trigger these.
|
||||||
}
|
/// #
|
||||||
|
/// # #[macro_use] extern crate serde_derive;
|
||||||
fn visit_u64<E>(self, value: u64) -> Result<NaiveDateTime, E>
|
/// # #[macro_use] extern crate serde_json;
|
||||||
where E: de::Error
|
/// # extern crate serde;
|
||||||
{
|
/// # extern crate chrono;
|
||||||
NaiveDateTime::from_timestamp_opt(value as i64, 0)
|
/// # use chrono::{NaiveDateTime, UTC};
|
||||||
.ok_or_else(|| E::custom(format!("value is not a legal timestamp: {}", value)))
|
/// # use serde::Deserialize;
|
||||||
}
|
/// use chrono::naive::datetime::serde::ts_seconds::deserialize as from_ts;
|
||||||
}
|
/// #[derive(Deserialize)]
|
||||||
|
/// struct S {
|
||||||
impl<'de> de::Deserialize<'de> for NaiveDateTime {
|
/// #[serde(deserialize_with = "from_ts")]
|
||||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
/// 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>
|
where D: de::Deserializer<'de>
|
||||||
{
|
{
|
||||||
Ok(TsSeconds(try!(
|
Ok(try!(d.deserialize_i64(NaiveDateTimeFromSecondsVisitor)))
|
||||||
deserializer.deserialize_str(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;
|
#[cfg(test)] extern crate serde_json;
|
||||||
|
@ -1602,11 +1693,6 @@ 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