diff --git a/src/date.rs b/src/date.rs index c8a32de..364ee95 100644 --- a/src/date.rs +++ b/src/date.rs @@ -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 { date: NaiveDate, offset: Tz::Offset, @@ -370,6 +369,58 @@ impl fmt::Display for Date 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 Encodable for Date where Tz::Offset: Encodable { + fn encode(&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 Decodable for Date where Tz::Offset: Decodable { + fn decode(d: &mut D) -> Result, 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::>(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; diff --git a/src/datetime.rs b/src/datetime.rs index 919dd83..d1d6b60 100644 --- a/src/datetime.rs +++ b/src/datetime.rs @@ -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 { datetime: NaiveDateTime, offset: Tz::Offset, @@ -396,6 +395,68 @@ impl str::FromStr for DateTime { } } +#[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 Encodable for DateTime where Tz::Offset: Encodable { + fn encode(&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 Decodable for DateTime where Tz::Offset: Decodable { + fn decode(d: &mut D) -> Result, 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::>(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 ser::Serialize for DateTime 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; @@ -428,7 +491,7 @@ mod serde { value.parse().map_err(|err| E::custom(format!("{}", err))) } } - + impl de::Deserialize for DateTime { fn deserialize(deserializer: &mut D) -> Result where D: de::Deserializer @@ -436,7 +499,7 @@ mod serde { deserializer.deserialize(DateTimeVisitor) } } - + impl de::Deserialize for DateTime { fn deserialize(deserializer: &mut D) -> Result where D: de::Deserializer @@ -444,7 +507,7 @@ mod serde { deserializer.deserialize(DateTimeVisitor).map(|dt| dt.with_timezone(&UTC)) } } - + impl de::Deserialize for DateTime { fn deserialize(deserializer: &mut D) -> Result 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::>(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 = 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()); } - } + diff --git a/src/naive/date.rs b/src/naive/date.rs index 95eeb9c..b308321 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -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 { + // 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(&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: &mut D) -> Result { + 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::(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(&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(deserializer: &mut D) -> Result 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::(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 { diff --git a/src/naive/datetime.rs b/src/naive/datetime.rs index 05f52c3..254703e 100644 --- a/src/naive/datetime.rs +++ b/src/naive/datetime.rs @@ -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(&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: &mut D) -> Result { + 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::(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(&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(deserializer: &mut D) -> Result 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::(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); - } } diff --git a/src/naive/time.rs b/src/naive/time.rs index f198daf..fc85db2 100644 --- a/src/naive/time.rs +++ b/src/naive/time.rs @@ -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 { + // 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(&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: &mut D) -> Result { + 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::(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(&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(deserializer: &mut D) -> Result 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::(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); - } } diff --git a/src/offset/fixed.rs b/src/offset/fixed.rs index 60ac2ee..eba3bea 100644 --- a/src/offset/fixed.rs +++ b/src/offset/fixed.rs @@ -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 { + // 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(&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: &mut D) -> Result { + 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::(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()); + } +} +