Add the ability for serde to deserialize timestamps

Timestamps are defined in terms of UTC, so what this does is, if we encounter
an integer instead of a str, create a FixedOffset timestamp with an offset of
zero and create the timestamp from that.
This commit is contained in:
Brandon W Maister 2017-02-04 16:51:19 -05:00 committed by Kang Seonghoon
parent 388c04002b
commit c0c61b5bfa
2 changed files with 87 additions and 6 deletions

View File

@ -432,11 +432,20 @@ 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
@ -444,6 +453,10 @@ fn test_decodable_json<FUTC, FFixed, FLocal, E>(utc_from_str: FUTC,
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""#).unwrap(),
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());
@ -508,7 +521,7 @@ mod rustc_serialize {
mod serde { mod serde {
use std::fmt; use std::fmt;
use super::DateTime; use super::DateTime;
use offset::TimeZone; 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;
@ -535,6 +548,22 @@ mod serde {
} }
} }
// 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>
where E: de::Error,
V: Display,
T: Display,
{
match me {
LocalResult::None => Err(E::custom(
format!("value is not a legal timestamp: {}", ts))),
LocalResult::Ambiguous(min, max) => Err(E::custom(
format!("value is an ambiguous timestamp: {}, could be either of {}, {}",
ts, min, max))),
LocalResult::Single(val) => Ok(val)
}
}
struct DateTimeVisitor; struct DateTimeVisitor;
impl<'de> de::Visitor<'de> for DateTimeVisitor { impl<'de> de::Visitor<'de> for DateTimeVisitor {
@ -542,7 +571,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") write!(formatter, "a formatted date and time string or a unix timestamp")
} }
fn visit_str<E>(self, value: &str) -> Result<DateTime<FixedOffset>, E> fn visit_str<E>(self, value: &str) -> Result<DateTime<FixedOffset>, E>
@ -550,8 +579,28 @@ 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
/// string representation
///
/// The serialized value can be either a string representation or a unix
/// timestamp
impl<'de> de::Deserialize<'de> for DateTime<FixedOffset> { impl<'de> de::Deserialize<'de> for DateTime<FixedOffset> {
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>
@ -560,6 +609,10 @@ mod serde {
} }
} }
/// Deserialize into a UTC value
///
/// The serialized value can be either a string representation or a unix
/// timestamp
impl<'de> de::Deserialize<'de> for DateTime<UTC> { impl<'de> de::Deserialize<'de> for DateTime<UTC> {
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>
@ -568,6 +621,11 @@ mod serde {
} }
} }
/// Deserialize a value that includes no timezone in its string
/// representation
///
/// The serialized value can be either a string representation or a unix
/// timestamp
impl<'de> de::Deserialize<'de> for DateTime<Local> { impl<'de> de::Deserialize<'de> for DateTime<Local> {
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>
@ -785,4 +843,3 @@ mod tests {
assert_eq!(1234567, datetime.timestamp_subsec_nanos()); assert_eq!(1234567, datetime.timestamp_subsec_nanos());
} }
} }

View File

@ -1402,6 +1402,16 @@ 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());
@ -1489,7 +1499,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") write!(formatter, "a formatted date and time string or a unix timestamp")
} }
fn visit_str<E>(self, value: &str) -> Result<NaiveDateTime, E> fn visit_str<E>(self, value: &str) -> Result<NaiveDateTime, E>
@ -1497,6 +1507,20 @@ mod serde {
{ {
value.parse().map_err(|err| E::custom(format!("{}", err))) value.parse().map_err(|err| E::custom(format!("{}", err)))
} }
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 { impl<'de> de::Deserialize<'de> for NaiveDateTime {