From ad6253f6532f1f4672066b798478f7e5a674de14 Mon Sep 17 00:00:00 2001 From: Kang Seonghoon Date: Thu, 4 Aug 2016 03:22:12 +0900 Subject: [PATCH] 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. --- src/date.rs | 53 +++++++++- src/datetime.rs | 124 ++++++++++++++++++------ src/naive/date.rs | 187 +++++++++++++++++++++++++++++------ src/naive/datetime.rs | 220 ++++++++++++++++++++++++++++++++++++------ src/naive/time.rs | 212 ++++++++++++++++++++++++++++++++++------ src/offset/fixed.rs | 85 +++++++++++++++- 6 files changed, 757 insertions(+), 124 deletions(-) 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()); + } +} +