Proper (de)serialization format handling.

For a while Chrono's serialization support was barely working,
i.e. usable but never been safe. While it wouldn't cause any memory
unsafety, attacker can fabricate an input that will make most users
confused (e.g. seemingly same Date which doesn't compare equally).
This commit will properly error for those cases.

It was also problematic that the generated rustc-serialize format is
very inefficient, especially for JSON. Due to the backward
compatibillity this commit does NOT fix them (likely to be in 0.3),
but this does try to define the exact format and define tons of
tests to detect any change to the serialization.

There are several remaining problems in the serialization format;
the serde implementation seems good, but it is unable to distinguish
some cases of leap seconds (practically won't matter, but still).
The rustc-serialize implementation would require a massive redesign.
For now, I postpone those issues to 0.3 (what a convenient excuse).

Fixes #42.
This commit is contained in:
Kang Seonghoon 2016-08-04 03:22:12 +09:00
parent d4d2ebb249
commit ad6253f653
6 changed files with 757 additions and 124 deletions

View File

@ -44,7 +44,6 @@ use format::{Item, DelayedFormat, StrftimeItems};
/// so the local date and UTC date should be equal for most cases
/// even though the raw calculation between `NaiveDate` and `Duration` may not.
#[derive(Clone)]
#[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))]
pub struct Date<Tz: TimeZone> {
date: NaiveDate,
offset: Tz::Offset,
@ -370,6 +369,58 @@ impl<Tz: TimeZone> fmt::Display for Date<Tz> where Tz::Offset: fmt::Display {
}
}
#[cfg(feature = "rustc-serialize")]
mod rustc_serialize {
use super::Date;
use offset::TimeZone;
use rustc_serialize::{Encodable, Encoder, Decodable, Decoder};
// TODO the current serialization format is NEVER intentionally defined.
// in the future it is likely to be redefined to more sane and reasonable format.
impl<Tz: TimeZone> Encodable for Date<Tz> where Tz::Offset: Encodable {
fn encode<S: Encoder>(&self, s: &mut S) -> Result<(), S::Error> {
s.emit_struct("Date", 2, |s| {
try!(s.emit_struct_field("date", 0, |s| self.date.encode(s)));
try!(s.emit_struct_field("offset", 1, |s| self.offset.encode(s)));
Ok(())
})
}
}
impl<Tz: TimeZone> Decodable for Date<Tz> where Tz::Offset: Decodable {
fn decode<D: Decoder>(d: &mut D) -> Result<Date<Tz>, D::Error> {
d.read_struct("Date", 2, |d| {
let date = try!(d.read_struct_field("date", 0, Decodable::decode));
let offset = try!(d.read_struct_field("offset", 1, Decodable::decode));
Ok(Date::from_utc(date, offset))
})
}
}
#[test]
fn test_encodable() {
use offset::utc::UTC;
use rustc_serialize::json::encode;
assert_eq!(encode(&UTC.ymd(2014, 7, 24)).ok(),
Some(r#"{"date":{"ymdf":16501977},"offset":{}}"#.into()));
}
#[test]
fn test_decodable() {
use offset::utc::UTC;
use rustc_serialize::json;
let decode = |s: &str| json::decode::<Date<UTC>>(s);
assert_eq!(decode(r#"{"date":{"ymdf":16501977},"offset":{}}"#).ok(),
Some(UTC.ymd(2014, 7, 24)));
assert!(decode(r#"{"date":{"ymdf":0},"offset":{}}"#).is_err());
}
}
#[cfg(test)]
mod tests {
use std::fmt;

View File

@ -24,7 +24,6 @@ use format::{parse, Parsed, ParseError, ParseResult, DelayedFormat, StrftimeItem
/// ISO 8601 combined date and time with time zone.
#[derive(Clone)]
#[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))]
pub struct DateTime<Tz: TimeZone> {
datetime: NaiveDateTime,
offset: Tz::Offset,
@ -396,6 +395,68 @@ impl str::FromStr for DateTime<Local> {
}
}
#[cfg(feature = "rustc-serialize")]
mod rustc_serialize {
use super::DateTime;
use offset::TimeZone;
use rustc_serialize::{Encodable, Encoder, Decodable, Decoder};
// TODO the current serialization format is NEVER intentionally defined.
// in the future it is likely to be redefined to more sane and reasonable format.
impl<Tz: TimeZone> Encodable for DateTime<Tz> where Tz::Offset: Encodable {
fn encode<S: Encoder>(&self, s: &mut S) -> Result<(), S::Error> {
s.emit_struct("DateTime", 2, |s| {
try!(s.emit_struct_field("datetime", 0, |s| self.datetime.encode(s)));
try!(s.emit_struct_field("offset", 1, |s| self.offset.encode(s)));
Ok(())
})
}
}
impl<Tz: TimeZone> Decodable for DateTime<Tz> where Tz::Offset: Decodable {
fn decode<D: Decoder>(d: &mut D) -> Result<DateTime<Tz>, D::Error> {
d.read_struct("DateTime", 2, |d| {
let datetime = try!(d.read_struct_field("datetime", 0, Decodable::decode));
let offset = try!(d.read_struct_field("offset", 1, Decodable::decode));
Ok(DateTime::from_utc(datetime, offset))
})
}
}
#[test]
fn test_encodable() {
use offset::utc::UTC;
use rustc_serialize::json::encode;
assert_eq!(
encode(&UTC.ymd(2014, 7, 24).and_hms(12, 34, 6)).ok(),
Some(concat!(r#"{"datetime":{"date":{"ymdf":16501977},"#,
r#""time":{"secs":45246,"frac":0}},"#,
r#""offset":{}}"#).into()));
}
#[test]
fn test_decodable() {
use offset::utc::UTC;
use rustc_serialize::json;
let decode = |s: &str| json::decode::<DateTime<UTC>>(s);
assert_eq!(
decode(r#"{"datetime":{"date":{"ymdf":16501977},
"time":{"secs":45246,"frac":0}},
"offset":{}}"#).ok(),
Some(UTC.ymd(2014, 7, 24).and_hms(12, 34, 6)));
assert_eq!(
decode(r#"{"datetime":{"date":{"ymdf":0},
"time":{"secs":0,"frac":0}},
"offset":{}}"#).ok(),
None);
}
}
#[cfg(feature = "serde")]
mod serde {
use super::DateTime;
@ -406,6 +467,8 @@ mod serde {
use std::fmt::Display;
use serde::{ser, de};
// TODO not very optimized for space (binary formats would want something better)
impl<Tz: TimeZone> ser::Serialize for DateTime<Tz>
where Tz::Offset: Display
{
@ -416,9 +479,9 @@ mod serde {
serializer.serialize_str(&format!("{:?}", self))
}
}
struct DateTimeVisitor;
impl de::Visitor for DateTimeVisitor {
type Value = DateTime<FixedOffset>;
@ -428,7 +491,7 @@ mod serde {
value.parse().map_err(|err| E::custom(format!("{}", err)))
}
}
impl de::Deserialize for DateTime<FixedOffset> {
fn deserialize<D>(deserializer: &mut D) -> Result<Self, D::Error>
where D: de::Deserializer
@ -436,7 +499,7 @@ mod serde {
deserializer.deserialize(DateTimeVisitor)
}
}
impl de::Deserialize for DateTime<UTC> {
fn deserialize<D>(deserializer: &mut D) -> Result<Self, D::Error>
where D: de::Deserializer
@ -444,7 +507,7 @@ mod serde {
deserializer.deserialize(DateTimeVisitor).map(|dt| dt.with_timezone(&UTC))
}
}
impl de::Deserialize for DateTime<Local> {
fn deserialize<D>(deserializer: &mut D) -> Result<Self, D::Error>
where D: de::Deserializer
@ -452,6 +515,28 @@ mod serde {
deserializer.deserialize(DateTimeVisitor).map(|dt| dt.with_timezone(&Local))
}
}
#[cfg(test)] extern crate serde_json;
#[test]
fn test_serde_serialize() {
use self::serde_json::to_string;
assert_eq!(to_string(&UTC.ymd(2014, 7, 24).and_hms(12, 34, 6)).ok(),
Some(r#""2014-07-24T12:34:06Z""#.into()));
}
#[test]
fn test_serde_deserialize() {
use self::serde_json;
let from_str = |s: &str| serde_json::from_str::<DateTime<UTC>>(s);
assert_eq!(from_str(r#""2014-07-24T12:34:06Z""#).ok(),
Some(UTC.ymd(2014, 7, 24).and_hms(12, 34, 6)));
assert!(from_str(r#""2014-07-32T12:34:06Z""#).is_err());
}
}
#[cfg(test)]
@ -626,31 +711,6 @@ mod tests {
}).join().unwrap();
}
#[cfg(feature = "serde")]
extern crate serde_json;
#[cfg(feature = "serde")]
#[test]
fn test_serde_serialize() {
use self::serde_json::to_string;
let date = UTC.ymd(2014, 7, 24).and_hms(12, 34, 6);
let serialized = to_string(&date).unwrap();
assert_eq!(serialized, "\"2014-07-24T12:34:06Z\"");
}
#[cfg(feature = "serde")]
#[test]
fn test_serde_deserialize() {
use self::serde_json::from_str;
let date = UTC.ymd(2014, 7, 24).and_hms(12, 34, 6);
let deserialized: DateTime<UTC> = from_str("\"2014-07-24T12:34:06Z\"").unwrap();
assert_eq!(deserialized, date);
}
#[test]
fn test_subsecond_part() {
let datetime = UTC.ymd(2014, 7, 8).and_hms_nano(9, 10, 11, 1234567);
@ -659,5 +719,5 @@ mod tests {
assert_eq!(1234, datetime.timestamp_subsec_micros());
assert_eq!(1234567, datetime.timestamp_subsec_nanos());
}
}

View File

@ -91,7 +91,6 @@ const MIN_DAYS_FROM_YEAR_0: i32 = (MIN_YEAR + 400_000) * 365 +
/// from Jan 1, 262145 BCE to Dec 31, 262143 CE.
/// Also supports the conversion from ISO 8601 ordinal and week date.
#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone)]
#[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))]
pub struct NaiveDate {
ymdf: DateImpl, // (year << 13) | of
}
@ -128,6 +127,24 @@ impl NaiveDate {
NaiveDate::from_of(year, mdf.to_of())
}
/// Makes a new `NaiveDate` from the serialized representation.
/// Used for serialization formats.
fn from_serialized(ymdf: i32) -> Option<NaiveDate> {
// check if the year flag is correct
if (ymdf & 0b1111) as u8 != YearFlags::from_year(ymdf >> 13).0 { return None; }
// check if the ordinal is in the range
let date = NaiveDate { ymdf: ymdf };
if !date.of().valid() { return None; }
Some(date)
}
/// Returns a serialized representation of this `NaiveDate`.
fn to_serialized(&self) -> i32 {
self.ymdf
}
/// Makes a new `NaiveDate` from the [calendar date](./index.html#calendar-date)
/// (year, month and day).
///
@ -1416,12 +1433,93 @@ impl str::FromStr for NaiveDate {
}
}
#[cfg(feature = "rustc-serialize")]
mod rustc_serialize {
use super::NaiveDate;
use rustc_serialize::{Encodable, Encoder, Decodable, Decoder};
// TODO the current serialization format is NEVER intentionally defined.
// this basically follows the automatically generated implementation for those traits,
// plus manual verification steps for avoiding security problem.
// in the future it is likely to be redefined to more sane and reasonable format.
impl Encodable for NaiveDate {
fn encode<S: Encoder>(&self, s: &mut S) -> Result<(), S::Error> {
let ymdf = self.to_serialized();
s.emit_struct("NaiveDate", 1, |s| {
try!(s.emit_struct_field("ymdf", 0, |s| ymdf.encode(s)));
Ok(())
})
}
}
impl Decodable for NaiveDate {
fn decode<D: Decoder>(d: &mut D) -> Result<NaiveDate, D::Error> {
d.read_struct("NaiveDate", 1, |d| {
let ymdf = try!(d.read_struct_field("ymdf", 0, Decodable::decode));
NaiveDate::from_serialized(ymdf).ok_or_else(|| d.error("invalid date"))
})
}
}
#[test]
fn test_encodable() {
use rustc_serialize::json::encode;
assert_eq!(encode(&NaiveDate::from_ymd(2016, 7, 8)).ok(),
Some(r#"{"ymdf":16518115}"#.into()));
assert_eq!(encode(&NaiveDate::from_ymd(0, 1, 1)).ok(),
Some(r#"{"ymdf":20}"#.into()));
assert_eq!(encode(&NaiveDate::from_ymd(-1, 12, 31)).ok(),
Some(r#"{"ymdf":-2341}"#.into()));
assert_eq!(encode(&super::MIN).ok(),
Some(r#"{"ymdf":-2147483625}"#.into()));
assert_eq!(encode(&super::MAX).ok(),
Some(r#"{"ymdf":2147481311}"#.into()));
}
#[test]
fn test_decodable() {
use rustc_serialize::json;
use std::{i32, i64};
let decode = |s: &str| json::decode::<NaiveDate>(s);
assert_eq!(decode(r#"{"ymdf":16518115}"#).ok(), Some(NaiveDate::from_ymd(2016, 7, 8)));
assert_eq!(decode(r#"{"ymdf":20}"#).ok(), Some(NaiveDate::from_ymd(0, 1, 1)));
assert_eq!(decode(r#"{"ymdf":-2341}"#).ok(), Some(NaiveDate::from_ymd(-1, 12, 31)));
assert_eq!(decode(r#"{"ymdf":-2147483625}"#).ok(), Some(super::MIN));
assert_eq!(decode(r#"{"ymdf":2147481311}"#).ok(), Some(super::MAX));
// some extreme values and zero are always invalid
assert!(decode(r#"{"ymdf":0}"#).is_err());
assert!(decode(r#"{"ymdf":1}"#).is_err());
assert!(decode(r#"{"ymdf":-1}"#).is_err());
assert!(decode(&format!(r#"{{"ymdf":{}}}"#, i32::MIN)).is_err());
assert!(decode(&format!(r#"{{"ymdf":{}}}"#, i32::MAX)).is_err());
assert!(decode(&format!(r#"{{"ymdf":{}}}"#, i64::MIN)).is_err());
assert!(decode(&format!(r#"{{"ymdf":{}}}"#, i64::MAX)).is_err());
// bad formats
assert!(decode(r#"{"ymdf":20.01}"#).is_err());
assert!(decode(r#"{"ymdf":"string"}"#).is_err());
assert!(decode(r#"{"ymdf":null}"#).is_err());
assert!(decode(r#"{}"#).is_err());
assert!(decode(r#"{"date":20}"#).is_err());
assert!(decode(r#"20"#).is_err());
assert!(decode(r#""string""#).is_err());
assert!(decode(r#""2016-07-08""#).is_err()); // :(
assert!(decode(r#"null"#).is_err());
}
}
#[cfg(feature = "serde")]
mod serde {
use super::NaiveDate;
use serde::{ser, de};
// TODO not very optimized for space (binary formats would want something better)
impl ser::Serialize for NaiveDate {
fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error>
where S: ser::Serializer
@ -1429,9 +1527,9 @@ mod serde {
serializer.serialize_str(&format!("{:?}", self))
}
}
struct NaiveDateVisitor;
impl de::Visitor for NaiveDateVisitor {
type Value = NaiveDate;
@ -1441,7 +1539,7 @@ mod serde {
value.parse().map_err(|err| E::custom(format!("{}", err)))
}
}
impl de::Deserialize for NaiveDate {
fn deserialize<D>(deserializer: &mut D) -> Result<Self, D::Error>
where D: de::Deserializer
@ -1449,6 +1547,59 @@ mod serde {
deserializer.deserialize(NaiveDateVisitor)
}
}
#[cfg(test)] extern crate serde_json;
#[test]
fn test_serde_serialize() {
use self::serde_json::to_string;
assert_eq!(to_string(&NaiveDate::from_ymd(2014, 7, 24)).ok(),
Some(r#""2014-07-24""#.into()));
assert_eq!(to_string(&NaiveDate::from_ymd(0, 1, 1)).ok(),
Some(r#""0000-01-01""#.into()));
assert_eq!(to_string(&NaiveDate::from_ymd(-1, 12, 31)).ok(),
Some(r#""-0001-12-31""#.into()));
assert_eq!(to_string(&super::MIN).ok(),
Some(r#""-262144-01-01""#.into()));
assert_eq!(to_string(&super::MAX).ok(),
Some(r#""+262143-12-31""#.into()));
}
#[test]
fn test_serde_deserialize() {
use self::serde_json;
use std::{i32, i64};
let from_str = |s: &str| serde_json::from_str::<NaiveDate>(s);
assert_eq!(from_str(r#""2016-07-08""#).ok(), Some(NaiveDate::from_ymd(2016, 7, 8)));
assert_eq!(from_str(r#""2016-7-8""#).ok(), Some(NaiveDate::from_ymd(2016, 7, 8)));
assert_eq!(from_str(r#""+002016-07-08""#).ok(), Some(NaiveDate::from_ymd(2016, 7, 8)));
assert_eq!(from_str(r#""0000-01-01""#).ok(), Some(NaiveDate::from_ymd(0, 1, 1)));
assert_eq!(from_str(r#""0-1-1""#).ok(), Some(NaiveDate::from_ymd(0, 1, 1)));
assert_eq!(from_str(r#""-0001-12-31""#).ok(), Some(NaiveDate::from_ymd(-1, 12, 31)));
assert_eq!(from_str(r#""-262144-01-01""#).ok(), Some(super::MIN));
assert_eq!(from_str(r#""+262143-12-31""#).ok(), Some(super::MAX));
// bad formats
assert!(from_str(r#""""#).is_err());
assert!(from_str(r#""20001231""#).is_err());
assert!(from_str(r#""2000-00-00""#).is_err());
assert!(from_str(r#""2000-02-30""#).is_err());
assert!(from_str(r#""2001-02-29""#).is_err());
assert!(from_str(r#""2002-002-28""#).is_err());
assert!(from_str(r#""yyyy-mm-dd""#).is_err());
assert!(from_str(r#"0"#).is_err());
assert!(from_str(r#"20.01"#).is_err());
assert!(from_str(&i32::MIN.to_string()).is_err());
assert!(from_str(&i32::MAX.to_string()).is_err());
assert!(from_str(&i64::MIN.to_string()).is_err());
assert!(from_str(&i64::MAX.to_string()).is_err());
assert!(from_str(r#"{}"#).is_err());
assert!(from_str(r#"{"ymdf":20}"#).is_err()); // :(
assert!(from_str(r#"null"#).is_err());
}
}
#[cfg(test)]
@ -1874,31 +2025,6 @@ mod tests {
assert_eq!(NaiveDate::from_ymd(2010, 1, 3).format("%G,%g,%U,%W,%V").to_string(),
"2009,09,01,00,53");
}
#[cfg(feature = "serde")]
extern crate serde_json;
#[cfg(feature = "serde")]
#[test]
fn test_serde_serialize() {
use self::serde_json::to_string;
let date = NaiveDate::from_ymd(2014, 7, 24);
let serialized = to_string(&date).unwrap();
assert_eq!(serialized, "\"2014-07-24\"");
}
#[cfg(feature = "serde")]
#[test]
fn test_serde_deserialize() {
use self::serde_json::from_str;
let date = NaiveDate::from_ymd(2014, 7, 24);
let deserialized: NaiveDate = from_str("\"2014-07-24\"").unwrap();
assert_eq!(deserialized, date);
}
}
/// The internal implementation of the calendar and ordinal date.
@ -1934,7 +2060,6 @@ mod internals {
/// and `bbb` is a non-zero `Weekday` (mapping `Mon` to 7) of the last day in the past year
/// (simplifies the day of week calculation from the 1-based ordinal).
#[derive(PartialEq, Eq, Copy, Clone)]
#[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))]
pub struct YearFlags(pub u8);
pub const A: YearFlags = YearFlags(0o15); pub const AG: YearFlags = YearFlags(0o05);
@ -2175,7 +2300,6 @@ mod internals {
/// The whole bits except for the least 3 bits are referred as `Ol` (ordinal and leap flag),
/// which is an index to the `OL_TO_MDL` lookup table.
#[derive(PartialEq, PartialOrd, Copy, Clone)]
#[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))]
pub struct Of(pub u32);
impl Of {
@ -2277,7 +2401,6 @@ mod internals {
/// (month, day of month and leap flag),
/// which is an index to the `MDL_TO_OL` lookup table.
#[derive(PartialEq, PartialOrd, Copy, Clone)]
#[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))]
pub struct Mdf(pub u32);
impl Mdf {

View File

@ -18,7 +18,6 @@ use format::{parse, Parsed, ParseError, ParseResult, DelayedFormat, StrftimeItem
/// ISO 8601 combined date and time without timezone.
#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone)]
#[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))]
pub struct NaiveDateTime {
date: NaiveDate,
time: NaiveTime,
@ -757,11 +756,115 @@ impl str::FromStr for NaiveDateTime {
}
}
#[cfg(feature = "rustc-serialize")]
mod rustc_serialize {
use super::NaiveDateTime;
use rustc_serialize::{Encodable, Encoder, Decodable, Decoder};
// TODO the current serialization format is NEVER intentionally defined.
// in the future it is likely to be redefined to more sane and reasonable format.
impl Encodable for NaiveDateTime {
fn encode<S: Encoder>(&self, s: &mut S) -> Result<(), S::Error> {
s.emit_struct("NaiveDateTime", 2, |s| {
try!(s.emit_struct_field("date", 0, |s| self.date.encode(s)));
try!(s.emit_struct_field("time", 1, |s| self.time.encode(s)));
Ok(())
})
}
}
impl Decodable for NaiveDateTime {
fn decode<D: Decoder>(d: &mut D) -> Result<NaiveDateTime, D::Error> {
d.read_struct("NaiveDateTime", 2, |d| {
let date = try!(d.read_struct_field("date", 0, Decodable::decode));
let time = try!(d.read_struct_field("time", 1, Decodable::decode));
Ok(NaiveDateTime::new(date, time))
})
}
}
#[test]
fn test_encodable() {
use naive::date::{self, NaiveDate};
use rustc_serialize::json::encode;
assert_eq!(
encode(&NaiveDate::from_ymd(2016, 7, 8).and_hms_milli(9, 10, 48, 90)).ok(),
Some(r#"{"date":{"ymdf":16518115},"time":{"secs":33048,"frac":90000000}}"#.into()));
assert_eq!(
encode(&NaiveDate::from_ymd(2014, 7, 24).and_hms(12, 34, 6)).ok(),
Some(r#"{"date":{"ymdf":16501977},"time":{"secs":45246,"frac":0}}"#.into()));
assert_eq!(
encode(&NaiveDate::from_ymd(0, 1, 1).and_hms_milli(0, 0, 59, 1_000)).ok(),
Some(r#"{"date":{"ymdf":20},"time":{"secs":59,"frac":1000000000}}"#.into()));
assert_eq!(
encode(&NaiveDate::from_ymd(-1, 12, 31).and_hms_nano(23, 59, 59, 7)).ok(),
Some(r#"{"date":{"ymdf":-2341},"time":{"secs":86399,"frac":7}}"#.into()));
assert_eq!(
encode(&date::MIN.and_hms(0, 0, 0)).ok(),
Some(r#"{"date":{"ymdf":-2147483625},"time":{"secs":0,"frac":0}}"#.into()));
assert_eq!(
encode(&date::MAX.and_hms_nano(23, 59, 59, 1_999_999_999)).ok(),
Some(r#"{"date":{"ymdf":2147481311},"time":{"secs":86399,"frac":1999999999}}"#.into()));
}
#[test]
fn test_decodable() {
use naive::date::{self, NaiveDate};
use rustc_serialize::json;
let decode = |s: &str| json::decode::<NaiveDateTime>(s);
assert_eq!(
decode(r#"{"date":{"ymdf":16518115},"time":{"secs":33048,"frac":90000000}}"#).ok(),
Some(NaiveDate::from_ymd(2016, 7, 8).and_hms_milli(9, 10, 48, 90)));
assert_eq!(
decode(r#"{"time":{"frac":0,"secs":45246},"date":{"ymdf":16501977}}"#).ok(),
Some(NaiveDate::from_ymd(2014, 7, 24).and_hms(12, 34, 6)));
assert_eq!(
decode(r#"{"date": {"ymdf": 20},
"time": {"secs": 59,
"frac": 1000000000}}"#).ok(),
Some(NaiveDate::from_ymd(0, 1, 1).and_hms_milli(0, 0, 59, 1_000)));
assert_eq!(
decode(r#"{"date":{"ymdf":-2341},"time":{"secs":86399,"frac":7}}"#).ok(),
Some(NaiveDate::from_ymd(-1, 12, 31).and_hms_nano(23, 59, 59, 7)));
assert_eq!(
decode(r#"{"date":{"ymdf":-2147483625},"time":{"secs":0,"frac":0}}"#).ok(),
Some(date::MIN.and_hms(0, 0, 0)));
assert_eq!(
decode(r#"{"date":{"ymdf":2147481311},"time":{"secs":86399,"frac":1999999999}}"#).ok(),
Some(date::MAX.and_hms_nano(23, 59, 59, 1_999_999_999)));
// bad formats
assert!(decode(r#"{"date":{},"time":{}}"#).is_err());
assert!(decode(r#"{"date":{"ymdf":0},"time":{"secs":0,"frac":0}}"#).is_err());
assert!(decode(r#"{"date":{"ymdf":20},"time":{"secs":86400,"frac":0}}"#).is_err());
assert!(decode(r#"{"date":{"ymdf":20},"time":{"secs":0,"frac":-1}}"#).is_err());
assert!(decode(r#"{"date":20,"time":{"secs":0,"frac":0}}"#).is_err());
assert!(decode(r#"{"date":"2016-08-04","time":"01:02:03.456"}"#).is_err());
assert!(decode(r#"{"date":{"ymdf":20}}"#).is_err());
assert!(decode(r#"{"time":{"secs":0,"frac":0}}"#).is_err());
assert!(decode(r#"{"ymdf":20}"#).is_err());
assert!(decode(r#"{"secs":0,"frac":0}"#).is_err());
assert!(decode(r#"{}"#).is_err());
assert!(decode(r#"0"#).is_err());
assert!(decode(r#"-1"#).is_err());
assert!(decode(r#""string""#).is_err());
assert!(decode(r#""2016-08-04T12:34:56""#).is_err()); // :(
assert!(decode(r#""2016-08-04T12:34:56.789""#).is_err()); // :(
assert!(decode(r#"null"#).is_err());
}
}
#[cfg(feature = "serde")]
mod serde {
use super::NaiveDateTime;
use serde::{ser, de};
// TODO not very optimized for space (binary formats would want something better)
impl ser::Serialize for NaiveDateTime {
fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error>
where S: ser::Serializer
@ -769,9 +872,9 @@ mod serde {
serializer.serialize_str(&format!("{:?}", self))
}
}
struct NaiveDateTimeVisitor;
impl de::Visitor for NaiveDateTimeVisitor {
type Value = NaiveDateTime;
@ -781,7 +884,7 @@ mod serde {
value.parse().map_err(|err| E::custom(format!("{}", err)))
}
}
impl de::Deserialize for NaiveDateTime {
fn deserialize<D>(deserializer: &mut D) -> Result<Self, D::Error>
where D: de::Deserializer
@ -789,6 +892,90 @@ mod serde {
deserializer.deserialize(NaiveDateTimeVisitor)
}
}
#[cfg(test)] extern crate serde_json;
#[test]
fn test_serde_serialize() {
use naive::date::{self, NaiveDate};
use self::serde_json::to_string;
assert_eq!(
to_string(&NaiveDate::from_ymd(2016, 7, 8).and_hms_milli(9, 10, 48, 90)).ok(),
Some(r#""2016-07-08T09:10:48.090""#.into()));
assert_eq!(
to_string(&NaiveDate::from_ymd(2014, 7, 24).and_hms(12, 34, 6)).ok(),
Some(r#""2014-07-24T12:34:06""#.into()));
assert_eq!(
to_string(&NaiveDate::from_ymd(0, 1, 1).and_hms_milli(0, 0, 59, 1_000)).ok(),
Some(r#""0000-01-01T00:00:60""#.into()));
assert_eq!(
to_string(&NaiveDate::from_ymd(-1, 12, 31).and_hms_nano(23, 59, 59, 7)).ok(),
Some(r#""-0001-12-31T23:59:59.000000007""#.into()));
assert_eq!(
to_string(&date::MIN.and_hms(0, 0, 0)).ok(),
Some(r#""-262144-01-01T00:00:00""#.into()));
assert_eq!(
to_string(&date::MAX.and_hms_nano(23, 59, 59, 1_999_999_999)).ok(),
Some(r#""+262143-12-31T23:59:60.999999999""#.into()));
}
#[test]
fn test_serde_deserialize() {
use naive::date::{self, NaiveDate};
use self::serde_json::from_str;
let from_str = |s: &str| serde_json::from_str::<NaiveDateTime>(s);
assert_eq!(
from_str(r#""2016-07-08T09:10:48.090""#).ok(),
Some(NaiveDate::from_ymd(2016, 7, 8).and_hms_milli(9, 10, 48, 90)));
assert_eq!(
from_str(r#""2016-7-8T9:10:48.09""#).ok(),
Some(NaiveDate::from_ymd(2016, 7, 8).and_hms_milli(9, 10, 48, 90)));
assert_eq!(
from_str(r#""2014-07-24T12:34:06""#).ok(),
Some(NaiveDate::from_ymd(2014, 7, 24).and_hms(12, 34, 6)));
assert_eq!(
from_str(r#""0000-01-01T00:00:60""#).ok(),
Some(NaiveDate::from_ymd(0, 1, 1).and_hms_milli(0, 0, 59, 1_000)));
assert_eq!(
from_str(r#""0-1-1T0:0:60""#).ok(),
Some(NaiveDate::from_ymd(0, 1, 1).and_hms_milli(0, 0, 59, 1_000)));
assert_eq!(
from_str(r#""-0001-12-31T23:59:59.000000007""#).ok(),
Some(NaiveDate::from_ymd(-1, 12, 31).and_hms_nano(23, 59, 59, 7)));
assert_eq!(
from_str(r#""-262144-01-01T00:00:00""#).ok(),
Some(date::MIN.and_hms(0, 0, 0)));
assert_eq!(
from_str(r#""+262143-12-31T23:59:60.999999999""#).ok(),
Some(date::MAX.and_hms_nano(23, 59, 59, 1_999_999_999)));
assert_eq!(
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)));
// bad formats
assert!(from_str(r#""""#).is_err());
assert!(from_str(r#""2016-07-08""#).is_err());
assert!(from_str(r#""09:10:48.090""#).is_err());
assert!(from_str(r#""20160708T091048.090""#).is_err());
assert!(from_str(r#""2000-00-00T00:00:00""#).is_err());
assert!(from_str(r#""2000-02-30T00:00:00""#).is_err());
assert!(from_str(r#""2001-02-29T00:00:00""#).is_err());
assert!(from_str(r#""2002-02-28T24:00:00""#).is_err());
assert!(from_str(r#""2002-02-28T23:60:00""#).is_err());
assert!(from_str(r#""2002-02-28T23:59:61""#).is_err());
assert!(from_str(r#""2016-07-08T09: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#""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#"{}"#).is_err());
assert!(from_str(r#"{"date":{"ymdf":20},"time":{"secs":0,"frac":0}}"#).is_err()); // :(
assert!(from_str(r#"null"#).is_err());
}
}
#[cfg(test)]
@ -944,29 +1131,4 @@ mod tests {
let time = base + Duration::microseconds(t);
assert_eq!(t, (time - base).num_microseconds().unwrap());
}
#[cfg(feature = "serde")]
extern crate serde_json;
#[cfg(feature = "serde")]
#[test]
fn test_serde_serialize() {
use self::serde_json::to_string;
let date = NaiveDate::from_ymd(2014, 7, 24).and_hms(12, 34, 6);
let serialized = to_string(&date).unwrap();
assert_eq!(serialized, "\"2014-07-24T12:34:06\"");
}
#[cfg(feature = "serde")]
#[test]
fn test_serde_deserialize() {
use self::serde_json::from_str;
let date = NaiveDate::from_ymd(2014, 7, 24).and_hms(12, 34, 6);
let deserialized: NaiveDateTime = from_str("\"2014-07-24T12:34:06\"").unwrap();
assert_eq!(deserialized, date);
}
}

View File

@ -58,13 +58,28 @@ use format::{parse, Parsed, ParseError, ParseResult, DelayedFormat, StrftimeItem
/// Chrono has a notable policy on the [leap second handling](./index.html#leap-second-handling),
/// designed to be maximally useful for typical users.
#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone)]
#[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))]
pub struct NaiveTime {
secs: u32,
frac: u32,
}
impl NaiveTime {
/// Makes a new `NaiveTime` from the serialized representation.
/// Used for serialization formats.
fn from_serialized(secs: u32, frac: u32) -> Option<NaiveTime> {
// check if the values are in the range
if secs >= 86400 { return None; }
if frac >= 2_000_000_000 { return None; }
let time = NaiveTime { secs: secs, frac: frac };
Some(time)
}
/// Returns a serialized representation of this `NaiveDate`.
fn to_serialized(&self) -> (u32, u32) {
(self.secs, self.frac)
}
/// Makes a new `NaiveTime` from hour, minute and second.
///
/// No [leap second](./index.html#leap-second-handling) is allowed here;
@ -742,11 +757,106 @@ impl str::FromStr for NaiveTime {
}
}
#[cfg(feature = "rustc-serialize")]
mod rustc_serialize {
use super::NaiveTime;
use rustc_serialize::{Encodable, Encoder, Decodable, Decoder};
// TODO the current serialization format is NEVER intentionally defined.
// this basically follows the automatically generated implementation for those traits,
// plus manual verification steps for avoiding security problem.
// in the future it is likely to be redefined to more sane and reasonable format.
impl Encodable for NaiveTime {
fn encode<S: Encoder>(&self, s: &mut S) -> Result<(), S::Error> {
let (secs, frac) = self.to_serialized();
s.emit_struct("NaiveTime", 2, |s| {
try!(s.emit_struct_field("secs", 0, |s| secs.encode(s)));
try!(s.emit_struct_field("frac", 1, |s| frac.encode(s)));
Ok(())
})
}
}
impl Decodable for NaiveTime {
fn decode<D: Decoder>(d: &mut D) -> Result<NaiveTime, D::Error> {
d.read_struct("NaiveTime", 2, |d| {
let secs = try!(d.read_struct_field("secs", 0, Decodable::decode));
let frac = try!(d.read_struct_field("frac", 1, Decodable::decode));
NaiveTime::from_serialized(secs, frac).ok_or_else(|| d.error("invalid time"))
})
}
}
#[test]
fn test_encodable() {
use rustc_serialize::json::encode;
assert_eq!(encode(&NaiveTime::from_hms(0, 0, 0)).ok(),
Some(r#"{"secs":0,"frac":0}"#.into()));
assert_eq!(encode(&NaiveTime::from_hms_milli(0, 0, 0, 950)).ok(),
Some(r#"{"secs":0,"frac":950000000}"#.into()));
assert_eq!(encode(&NaiveTime::from_hms_milli(0, 0, 59, 1_000)).ok(),
Some(r#"{"secs":59,"frac":1000000000}"#.into()));
assert_eq!(encode(&NaiveTime::from_hms(0, 1, 2)).ok(),
Some(r#"{"secs":62,"frac":0}"#.into()));
assert_eq!(encode(&NaiveTime::from_hms(7, 8, 9)).ok(),
Some(r#"{"secs":25689,"frac":0}"#.into()));
assert_eq!(encode(&NaiveTime::from_hms_micro(12, 34, 56, 789)).ok(),
Some(r#"{"secs":45296,"frac":789000}"#.into()));
assert_eq!(encode(&NaiveTime::from_hms_nano(23, 59, 59, 1_999_999_999)).ok(),
Some(r#"{"secs":86399,"frac":1999999999}"#.into()));
}
#[test]
fn test_decodable() {
use rustc_serialize::json;
let decode = |s: &str| json::decode::<NaiveTime>(s);
assert_eq!(decode(r#"{"secs":0,"frac":0}"#).ok(),
Some(NaiveTime::from_hms(0, 0, 0)));
assert_eq!(decode(r#"{"frac":950000000,"secs":0}"#).ok(),
Some(NaiveTime::from_hms_milli(0, 0, 0, 950)));
assert_eq!(decode(r#"{"secs":59,"frac":1000000000}"#).ok(),
Some(NaiveTime::from_hms_milli(0, 0, 59, 1_000)));
assert_eq!(decode(r#"{"frac": 0,
"secs": 62}"#).ok(),
Some(NaiveTime::from_hms(0, 1, 2)));
assert_eq!(decode(r#"{"secs":25689,"frac":0}"#).ok(),
Some(NaiveTime::from_hms(7, 8, 9)));
assert_eq!(decode(r#"{"secs":45296,"frac":789000}"#).ok(),
Some(NaiveTime::from_hms_micro(12, 34, 56, 789)));
assert_eq!(decode(r#"{"secs":86399,"frac":1999999999}"#).ok(),
Some(NaiveTime::from_hms_nano(23, 59, 59, 1_999_999_999)));
// bad formats
assert!(decode(r#"{"secs":0,"frac":-1}"#).is_err());
assert!(decode(r#"{"secs":-1,"frac":0}"#).is_err());
assert!(decode(r#"{"secs":86400,"frac":0}"#).is_err());
assert!(decode(r#"{"secs":0,"frac":2000000000}"#).is_err());
assert!(decode(r#"{"secs":0}"#).is_err());
assert!(decode(r#"{"frac":0}"#).is_err());
assert!(decode(r#"{"secs":0.3,"frac":0}"#).is_err());
assert!(decode(r#"{"secs":0,"frac":0.4}"#).is_err());
assert!(decode(r#"{}"#).is_err());
assert!(decode(r#"0"#).is_err());
assert!(decode(r#"86399"#).is_err());
assert!(decode(r#""string""#).is_err());
assert!(decode(r#""12:34:56""#).is_err()); // :(
assert!(decode(r#""12:34:56.789""#).is_err()); // :(
assert!(decode(r#"null"#).is_err());
}
}
#[cfg(feature = "serde")]
mod serde {
use super::NaiveTime;
use serde::{ser, de};
// TODO not very optimized for space (binary formats would want something better)
// TODO round-trip for general leap seconds (not just those with second = 60)
impl ser::Serialize for NaiveTime {
fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error>
where S: ser::Serializer
@ -754,9 +864,9 @@ mod serde {
serializer.serialize_str(&format!("{:?}", self))
}
}
struct NaiveTimeVisitor;
impl de::Visitor for NaiveTimeVisitor {
type Value = NaiveTime;
@ -766,7 +876,7 @@ mod serde {
value.parse().map_err(|err| E::custom(format!("{}", err)))
}
}
impl de::Deserialize for NaiveTime {
fn deserialize<D>(deserializer: &mut D) -> Result<Self, D::Error>
where D: de::Deserializer
@ -774,6 +884,75 @@ mod serde {
deserializer.deserialize(NaiveTimeVisitor)
}
}
#[cfg(test)] extern crate serde_json;
#[test]
fn test_serde_serialize() {
use self::serde_json::to_string;
assert_eq!(to_string(&NaiveTime::from_hms(0, 0, 0)).ok(),
Some(r#""00:00:00""#.into()));
assert_eq!(to_string(&NaiveTime::from_hms_milli(0, 0, 0, 950)).ok(),
Some(r#""00:00:00.950""#.into()));
assert_eq!(to_string(&NaiveTime::from_hms_milli(0, 0, 59, 1_000)).ok(),
Some(r#""00:00:60""#.into()));
assert_eq!(to_string(&NaiveTime::from_hms(0, 1, 2)).ok(),
Some(r#""00:01:02""#.into()));
assert_eq!(to_string(&NaiveTime::from_hms_nano(3, 5, 7, 98765432)).ok(),
Some(r#""03:05:07.098765432""#.into()));
assert_eq!(to_string(&NaiveTime::from_hms(7, 8, 9)).ok(),
Some(r#""07:08:09""#.into()));
assert_eq!(to_string(&NaiveTime::from_hms_micro(12, 34, 56, 789)).ok(),
Some(r#""12:34:56.000789""#.into()));
assert_eq!(to_string(&NaiveTime::from_hms_nano(23, 59, 59, 1_999_999_999)).ok(),
Some(r#""23:59:60.999999999""#.into()));
}
#[test]
fn test_serde_deserialize() {
use self::serde_json::from_str;
let from_str = |s: &str| serde_json::from_str::<NaiveTime>(s);
assert_eq!(from_str(r#""00:00:00""#).ok(),
Some(NaiveTime::from_hms(0, 0, 0)));
assert_eq!(from_str(r#""0:0:0""#).ok(),
Some(NaiveTime::from_hms(0, 0, 0)));
assert_eq!(from_str(r#""00:00:00.950""#).ok(),
Some(NaiveTime::from_hms_milli(0, 0, 0, 950)));
assert_eq!(from_str(r#""0:0:0.95""#).ok(),
Some(NaiveTime::from_hms_milli(0, 0, 0, 950)));
assert_eq!(from_str(r#""00:00:60""#).ok(),
Some(NaiveTime::from_hms_milli(0, 0, 59, 1_000)));
assert_eq!(from_str(r#""00:01:02""#).ok(),
Some(NaiveTime::from_hms(0, 1, 2)));
assert_eq!(from_str(r#""03:05:07.098765432""#).ok(),
Some(NaiveTime::from_hms_nano(3, 5, 7, 98765432)));
assert_eq!(from_str(r#""07:08:09""#).ok(),
Some(NaiveTime::from_hms(7, 8, 9)));
assert_eq!(from_str(r#""12:34:56.000789""#).ok(),
Some(NaiveTime::from_hms_micro(12, 34, 56, 789)));
assert_eq!(from_str(r#""23:59:60.999999999""#).ok(),
Some(NaiveTime::from_hms_nano(23, 59, 59, 1_999_999_999)));
assert_eq!(from_str(r#""23:59:60.9999999999997""#).ok(), // excess digits are ignored
Some(NaiveTime::from_hms_nano(23, 59, 59, 1_999_999_999)));
// bad formats
assert!(from_str(r#""""#).is_err());
assert!(from_str(r#""000000""#).is_err());
assert!(from_str(r#""00:00:61""#).is_err());
assert!(from_str(r#""00:60:00""#).is_err());
assert!(from_str(r#""24:00:00""#).is_err());
assert!(from_str(r#""23:59:59,1""#).is_err());
assert!(from_str(r#""012:34:56""#).is_err());
assert!(from_str(r#""hh:mm:ss""#).is_err());
assert!(from_str(r#"0"#).is_err());
assert!(from_str(r#"86399"#).is_err());
assert!(from_str(r#"{}"#).is_err());
assert!(from_str(r#"{"secs":0,"frac":0}"#).is_err()); // :(
assert!(from_str(r#"null"#).is_err());
}
}
#[cfg(test)]
@ -979,30 +1158,5 @@ mod tests {
assert_eq!(NaiveTime::from_hms_milli(23, 59, 59, 1_000).format("%X").to_string(),
"23:59:60");
}
#[cfg(feature = "serde")]
extern crate serde_json;
#[cfg(feature = "serde")]
#[test]
fn test_serde_serialize() {
use self::serde_json::to_string;
let time = NaiveTime::from_hms_nano(3, 5, 7, 98765432);
let serialized = to_string(&time).unwrap();
assert_eq!(serialized, "\"03:05:07.098765432\"");
}
#[cfg(feature = "serde")]
#[test]
fn test_serde_deserialize() {
use self::serde_json::from_str;
let time = NaiveTime::from_hms_nano(3, 5, 7, 98765432);
let deserialized: NaiveTime = from_str("\"03:05:07.098765432\"").unwrap();
assert_eq!(deserialized, time);
}
}

View File

@ -16,12 +16,26 @@ use super::{TimeZone, Offset, LocalResult};
/// The time zone with fixed offset, from UTC-23:59:59 to UTC+23:59:59.
#[derive(Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))]
pub struct FixedOffset {
local_minus_utc: i32,
}
impl FixedOffset {
/// Makes a new `FixedOffset` from the serialized representation.
/// Used for serialization formats.
fn from_serialized(secs: i32) -> Option<FixedOffset> {
// check if the values are in the range
if secs <= -86400 || 86400 <= secs { return None; }
let offset = FixedOffset { local_minus_utc: secs };
Some(offset)
}
/// Returns a serialized representation of this `FixedOffset`.
fn to_serialized(&self) -> i32 {
self.local_minus_utc
}
/// Makes a new `FixedOffset` for the Eastern Hemisphere with given timezone difference.
/// The negative `secs` means the Western Hemisphere.
///
@ -101,3 +115,72 @@ impl fmt::Display for FixedOffset {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Debug::fmt(self, f) }
}
#[cfg(feature = "rustc-serialize")]
mod rustc_serialize {
use super::FixedOffset;
use rustc_serialize::{Encodable, Encoder, Decodable, Decoder};
// TODO the current serialization format is NEVER intentionally defined.
// this basically follows the automatically generated implementation for those traits,
// plus manual verification steps for avoiding security problem.
// in the future it is likely to be redefined to more sane and reasonable format.
impl Encodable for FixedOffset {
fn encode<S: Encoder>(&self, s: &mut S) -> Result<(), S::Error> {
let secs = self.to_serialized();
s.emit_struct("FixedOffset", 1, |s| {
try!(s.emit_struct_field("local_minus_utc", 0, |s| secs.encode(s)));
Ok(())
})
}
}
impl Decodable for FixedOffset {
fn decode<D: Decoder>(d: &mut D) -> Result<FixedOffset, D::Error> {
d.read_struct("FixedOffset", 1, |d| {
let secs = try!(d.read_struct_field("local_minus_utc", 0, Decodable::decode));
FixedOffset::from_serialized(secs).ok_or_else(|| d.error("invalid offset"))
})
}
}
#[test]
fn test_encodable() {
use rustc_serialize::json::encode;
assert_eq!(encode(&FixedOffset::east(0)).ok(),
Some(r#"{"local_minus_utc":0}"#.into()));
assert_eq!(encode(&FixedOffset::east(1234)).ok(),
Some(r#"{"local_minus_utc":1234}"#.into()));
assert_eq!(encode(&FixedOffset::east(86399)).ok(),
Some(r#"{"local_minus_utc":86399}"#.into()));
assert_eq!(encode(&FixedOffset::west(1234)).ok(),
Some(r#"{"local_minus_utc":-1234}"#.into()));
assert_eq!(encode(&FixedOffset::west(86399)).ok(),
Some(r#"{"local_minus_utc":-86399}"#.into()));
}
#[test]
fn test_decodable() {
use rustc_serialize::json;
let decode = |s: &str| json::decode::<FixedOffset>(s);
assert_eq!(decode(r#"{"local_minus_utc":0}"#).ok(), Some(FixedOffset::east(0)));
assert_eq!(decode(r#"{"local_minus_utc": 1234}"#).ok(), Some(FixedOffset::east(1234)));
assert_eq!(decode(r#"{"local_minus_utc":86399}"#).ok(), Some(FixedOffset::east(86399)));
assert_eq!(decode(r#"{"local_minus_utc":-1234}"#).ok(), Some(FixedOffset::west(1234)));
assert_eq!(decode(r#"{"local_minus_utc":-86399}"#).ok(), Some(FixedOffset::west(86399)));
assert!(decode(r#"{"local_minus_utc":86400}"#).is_err());
assert!(decode(r#"{"local_minus_utc":-86400}"#).is_err());
assert!(decode(r#"{"local_minus_utc":0.1}"#).is_err());
assert!(decode(r#"{"local_minus_utc":null}"#).is_err());
assert!(decode(r#"{}"#).is_err());
assert!(decode(r#"0"#).is_err());
assert!(decode(r#"1234"#).is_err());
assert!(decode(r#""string""#).is_err());
assert!(decode(r#"null"#).is_err());
}
}