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 /// so the local date and UTC date should be equal for most cases
/// even though the raw calculation between `NaiveDate` and `Duration` may not. /// even though the raw calculation between `NaiveDate` and `Duration` may not.
#[derive(Clone)] #[derive(Clone)]
#[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))]
pub struct Date<Tz: TimeZone> { pub struct Date<Tz: TimeZone> {
date: NaiveDate, date: NaiveDate,
offset: Tz::Offset, 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)] #[cfg(test)]
mod tests { mod tests {
use std::fmt; 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. /// ISO 8601 combined date and time with time zone.
#[derive(Clone)] #[derive(Clone)]
#[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))]
pub struct DateTime<Tz: TimeZone> { pub struct DateTime<Tz: TimeZone> {
datetime: NaiveDateTime, datetime: NaiveDateTime,
offset: Tz::Offset, 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")] #[cfg(feature = "serde")]
mod serde { mod serde {
use super::DateTime; use super::DateTime;
@ -406,6 +467,8 @@ mod serde {
use std::fmt::Display; use std::fmt::Display;
use serde::{ser, de}; use serde::{ser, de};
// 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>
where Tz::Offset: Display where Tz::Offset: Display
{ {
@ -416,9 +479,9 @@ mod serde {
serializer.serialize_str(&format!("{:?}", self)) serializer.serialize_str(&format!("{:?}", self))
} }
} }
struct DateTimeVisitor; struct DateTimeVisitor;
impl de::Visitor for DateTimeVisitor { impl de::Visitor for DateTimeVisitor {
type Value = DateTime<FixedOffset>; type Value = DateTime<FixedOffset>;
@ -428,7 +491,7 @@ mod serde {
value.parse().map_err(|err| E::custom(format!("{}", err))) value.parse().map_err(|err| E::custom(format!("{}", err)))
} }
} }
impl de::Deserialize for DateTime<FixedOffset> { impl de::Deserialize for DateTime<FixedOffset> {
fn deserialize<D>(deserializer: &mut D) -> Result<Self, D::Error> fn deserialize<D>(deserializer: &mut D) -> Result<Self, D::Error>
where D: de::Deserializer where D: de::Deserializer
@ -436,7 +499,7 @@ mod serde {
deserializer.deserialize(DateTimeVisitor) deserializer.deserialize(DateTimeVisitor)
} }
} }
impl de::Deserialize for DateTime<UTC> { impl de::Deserialize for DateTime<UTC> {
fn deserialize<D>(deserializer: &mut D) -> Result<Self, D::Error> fn deserialize<D>(deserializer: &mut D) -> Result<Self, D::Error>
where D: de::Deserializer where D: de::Deserializer
@ -444,7 +507,7 @@ mod serde {
deserializer.deserialize(DateTimeVisitor).map(|dt| dt.with_timezone(&UTC)) deserializer.deserialize(DateTimeVisitor).map(|dt| dt.with_timezone(&UTC))
} }
} }
impl de::Deserialize for DateTime<Local> { impl de::Deserialize for DateTime<Local> {
fn deserialize<D>(deserializer: &mut D) -> Result<Self, D::Error> fn deserialize<D>(deserializer: &mut D) -> Result<Self, D::Error>
where D: de::Deserializer where D: de::Deserializer
@ -452,6 +515,28 @@ mod serde {
deserializer.deserialize(DateTimeVisitor).map(|dt| dt.with_timezone(&Local)) 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)] #[cfg(test)]
@ -626,31 +711,6 @@ mod tests {
}).join().unwrap(); }).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] #[test]
fn test_subsecond_part() { fn test_subsecond_part() {
let datetime = UTC.ymd(2014, 7, 8).and_hms_nano(9, 10, 11, 1234567); 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!(1234, datetime.timestamp_subsec_micros());
assert_eq!(1234567, datetime.timestamp_subsec_nanos()); 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. /// from Jan 1, 262145 BCE to Dec 31, 262143 CE.
/// Also supports the conversion from ISO 8601 ordinal and week date. /// Also supports the conversion from ISO 8601 ordinal and week date.
#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone)] #[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone)]
#[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))]
pub struct NaiveDate { pub struct NaiveDate {
ymdf: DateImpl, // (year << 13) | of ymdf: DateImpl, // (year << 13) | of
} }
@ -128,6 +127,24 @@ impl NaiveDate {
NaiveDate::from_of(year, mdf.to_of()) 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) /// Makes a new `NaiveDate` from the [calendar date](./index.html#calendar-date)
/// (year, month and day). /// (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")] #[cfg(feature = "serde")]
mod serde { mod serde {
use super::NaiveDate; use super::NaiveDate;
use serde::{ser, de}; use serde::{ser, de};
// TODO not very optimized for space (binary formats would want something better)
impl ser::Serialize for NaiveDate { impl ser::Serialize for NaiveDate {
fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error> fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error>
where S: ser::Serializer where S: ser::Serializer
@ -1429,9 +1527,9 @@ mod serde {
serializer.serialize_str(&format!("{:?}", self)) serializer.serialize_str(&format!("{:?}", self))
} }
} }
struct NaiveDateVisitor; struct NaiveDateVisitor;
impl de::Visitor for NaiveDateVisitor { impl de::Visitor for NaiveDateVisitor {
type Value = NaiveDate; type Value = NaiveDate;
@ -1441,7 +1539,7 @@ mod serde {
value.parse().map_err(|err| E::custom(format!("{}", err))) value.parse().map_err(|err| E::custom(format!("{}", err)))
} }
} }
impl de::Deserialize for NaiveDate { impl de::Deserialize for NaiveDate {
fn deserialize<D>(deserializer: &mut D) -> Result<Self, D::Error> fn deserialize<D>(deserializer: &mut D) -> Result<Self, D::Error>
where D: de::Deserializer where D: de::Deserializer
@ -1449,6 +1547,59 @@ mod serde {
deserializer.deserialize(NaiveDateVisitor) 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)] #[cfg(test)]
@ -1874,31 +2025,6 @@ mod tests {
assert_eq!(NaiveDate::from_ymd(2010, 1, 3).format("%G,%g,%U,%W,%V").to_string(), assert_eq!(NaiveDate::from_ymd(2010, 1, 3).format("%G,%g,%U,%W,%V").to_string(),
"2009,09,01,00,53"); "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. /// 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 /// 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). /// (simplifies the day of week calculation from the 1-based ordinal).
#[derive(PartialEq, Eq, Copy, Clone)] #[derive(PartialEq, Eq, Copy, Clone)]
#[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))]
pub struct YearFlags(pub u8); pub struct YearFlags(pub u8);
pub const A: YearFlags = YearFlags(0o15); pub const AG: YearFlags = YearFlags(0o05); 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), /// 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. /// which is an index to the `OL_TO_MDL` lookup table.
#[derive(PartialEq, PartialOrd, Copy, Clone)] #[derive(PartialEq, PartialOrd, Copy, Clone)]
#[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))]
pub struct Of(pub u32); pub struct Of(pub u32);
impl Of { impl Of {
@ -2277,7 +2401,6 @@ mod internals {
/// (month, day of month and leap flag), /// (month, day of month and leap flag),
/// which is an index to the `MDL_TO_OL` lookup table. /// which is an index to the `MDL_TO_OL` lookup table.
#[derive(PartialEq, PartialOrd, Copy, Clone)] #[derive(PartialEq, PartialOrd, Copy, Clone)]
#[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))]
pub struct Mdf(pub u32); pub struct Mdf(pub u32);
impl Mdf { impl Mdf {

View File

@ -18,7 +18,6 @@ use format::{parse, Parsed, ParseError, ParseResult, DelayedFormat, StrftimeItem
/// ISO 8601 combined date and time without timezone. /// ISO 8601 combined date and time without timezone.
#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone)] #[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone)]
#[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))]
pub struct NaiveDateTime { pub struct NaiveDateTime {
date: NaiveDate, date: NaiveDate,
time: NaiveTime, 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")] #[cfg(feature = "serde")]
mod serde { mod serde {
use super::NaiveDateTime; use super::NaiveDateTime;
use serde::{ser, de}; use serde::{ser, de};
// TODO not very optimized for space (binary formats would want something better)
impl ser::Serialize for NaiveDateTime { impl ser::Serialize for NaiveDateTime {
fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error> fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error>
where S: ser::Serializer where S: ser::Serializer
@ -769,9 +872,9 @@ mod serde {
serializer.serialize_str(&format!("{:?}", self)) serializer.serialize_str(&format!("{:?}", self))
} }
} }
struct NaiveDateTimeVisitor; struct NaiveDateTimeVisitor;
impl de::Visitor for NaiveDateTimeVisitor { impl de::Visitor for NaiveDateTimeVisitor {
type Value = NaiveDateTime; type Value = NaiveDateTime;
@ -781,7 +884,7 @@ mod serde {
value.parse().map_err(|err| E::custom(format!("{}", err))) value.parse().map_err(|err| E::custom(format!("{}", err)))
} }
} }
impl de::Deserialize for NaiveDateTime { impl de::Deserialize for NaiveDateTime {
fn deserialize<D>(deserializer: &mut D) -> Result<Self, D::Error> fn deserialize<D>(deserializer: &mut D) -> Result<Self, D::Error>
where D: de::Deserializer where D: de::Deserializer
@ -789,6 +892,90 @@ mod serde {
deserializer.deserialize(NaiveDateTimeVisitor) 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)] #[cfg(test)]
@ -944,29 +1131,4 @@ mod tests {
let time = base + Duration::microseconds(t); let time = base + Duration::microseconds(t);
assert_eq!(t, (time - base).num_microseconds().unwrap()); 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), /// Chrono has a notable policy on the [leap second handling](./index.html#leap-second-handling),
/// designed to be maximally useful for typical users. /// designed to be maximally useful for typical users.
#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone)] #[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone)]
#[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))]
pub struct NaiveTime { pub struct NaiveTime {
secs: u32, secs: u32,
frac: u32, frac: u32,
} }
impl NaiveTime { 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. /// Makes a new `NaiveTime` from hour, minute and second.
/// ///
/// No [leap second](./index.html#leap-second-handling) is allowed here; /// 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")] #[cfg(feature = "serde")]
mod serde { mod serde {
use super::NaiveTime; use super::NaiveTime;
use serde::{ser, de}; 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 { impl ser::Serialize for NaiveTime {
fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error> fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error>
where S: ser::Serializer where S: ser::Serializer
@ -754,9 +864,9 @@ mod serde {
serializer.serialize_str(&format!("{:?}", self)) serializer.serialize_str(&format!("{:?}", self))
} }
} }
struct NaiveTimeVisitor; struct NaiveTimeVisitor;
impl de::Visitor for NaiveTimeVisitor { impl de::Visitor for NaiveTimeVisitor {
type Value = NaiveTime; type Value = NaiveTime;
@ -766,7 +876,7 @@ mod serde {
value.parse().map_err(|err| E::custom(format!("{}", err))) value.parse().map_err(|err| E::custom(format!("{}", err)))
} }
} }
impl de::Deserialize for NaiveTime { impl de::Deserialize for NaiveTime {
fn deserialize<D>(deserializer: &mut D) -> Result<Self, D::Error> fn deserialize<D>(deserializer: &mut D) -> Result<Self, D::Error>
where D: de::Deserializer where D: de::Deserializer
@ -774,6 +884,75 @@ mod serde {
deserializer.deserialize(NaiveTimeVisitor) 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)] #[cfg(test)]
@ -979,30 +1158,5 @@ mod tests {
assert_eq!(NaiveTime::from_hms_milli(23, 59, 59, 1_000).format("%X").to_string(), assert_eq!(NaiveTime::from_hms_milli(23, 59, 59, 1_000).format("%X").to_string(),
"23:59:60"); "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. /// The time zone with fixed offset, from UTC-23:59:59 to UTC+23:59:59.
#[derive(Copy, Clone, PartialEq, Eq)] #[derive(Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))]
pub struct FixedOffset { pub struct FixedOffset {
local_minus_utc: i32, local_minus_utc: i32,
} }
impl FixedOffset { 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. /// Makes a new `FixedOffset` for the Eastern Hemisphere with given timezone difference.
/// The negative `secs` means the Western Hemisphere. /// 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) } 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());
}
}