diff --git a/src/datetime.rs b/src/datetime.rs index c6f7a87..c597e31 100644 --- a/src/datetime.rs +++ b/src/datetime.rs @@ -6,17 +6,18 @@ * ISO 8601 date and time. */ -use std::{fmt, hash}; +use std::{str, fmt, hash}; use std::cmp::Ordering; use std::ops::{Add, Sub}; use {Weekday, Timelike, Datelike}; -use offset::{Offset, FixedOffset}; +use offset::{Offset, FixedOffset, UTC}; use duration::Duration; use naive::datetime::NaiveDateTime; use time::Time; use date::Date; -use format::{parse, Item, Parsed, ParseResult, DelayedFormat, StrftimeItems}; +use format::{Item, Numeric, Pad, Fixed}; +use format::{parse, Parsed, ParseError, ParseResult, DelayedFormat, StrftimeItems}; /// ISO 8601 combined date and time with timezone. #[derive(Clone)] @@ -261,6 +262,45 @@ impl fmt::Display for DateTime { } } +impl str::FromStr for DateTime { + type Err = ParseError; + + fn from_str(s: &str) -> ParseResult> { + const ITEMS: &'static [Item<'static>] = &[ + Item::Space(""), Item::Numeric(Numeric::Year, Pad::Zero), + Item::Space(""), Item::Literal("-"), + Item::Space(""), Item::Numeric(Numeric::Month, Pad::Zero), + Item::Space(""), Item::Literal("-"), + Item::Space(""), Item::Numeric(Numeric::Day, Pad::Zero), + Item::Space(""), Item::Literal("T"), // XXX shouldn't this be case-insensitive? + Item::Space(""), Item::Numeric(Numeric::Hour, Pad::Zero), + Item::Space(""), Item::Literal(":"), + Item::Space(""), Item::Numeric(Numeric::Minute, Pad::Zero), + Item::Space(""), Item::Literal(":"), + Item::Space(""), Item::Numeric(Numeric::Second, Pad::Zero), + Item::Fixed(Fixed::Nanosecond), + Item::Space(""), Item::Fixed(Fixed::TimezoneOffsetZ), + Item::Space(""), + ]; + + let mut parsed = Parsed::new(); + try!(parse(&mut parsed, s, ITEMS.iter().cloned())); + parsed.to_datetime() + } +} + +impl str::FromStr for DateTime { + type Err = ParseError; + + fn from_str(s: &str) -> ParseResult> { + // we parse non-UTC time zones then convert them into UTC + let dt: DateTime = try!(s.parse()); + Ok(dt.with_offset(UTC)) + } +} + +// TODO: FromStr for DateTime is quite hard without a new offset design + #[cfg(test)] mod tests { use super::DateTime; @@ -294,6 +334,21 @@ mod tests { assert!(*EDT.ymd(2014, 5, 6).and_hms(7, 8, 9).offset() != EST); } + #[test] + fn test_datetime_from_str() { + assert_eq!("2015-2-18T23:16:9.15Z".parse::>(), + Ok(FixedOffset::east(0).ymd(2015, 2, 18).and_hms_milli(23, 16, 9, 150))); + assert_eq!("2015-2-18T13:16:9.15-10:00".parse::>(), + Ok(FixedOffset::west(10 * 3600).ymd(2015, 2, 18).and_hms_milli(13, 16, 9, 150))); + assert!("2015-2-18T23:16:9.15".parse::>().is_err()); + + assert_eq!("2015-2-18T23:16:9.15Z".parse::>(), + Ok(UTC.ymd(2015, 2, 18).and_hms_milli(23, 16, 9, 150))); + assert_eq!("2015-2-18T13:16:9.15-10:00".parse::>(), + Ok(UTC.ymd(2015, 2, 18).and_hms_milli(23, 16, 9, 150))); + assert!("2015-2-18T23:16:9.15".parse::>().is_err()); + } + #[test] fn test_datetime_parse_from_str() { let ymdhms = |&: y,m,d,h,n,s,off| FixedOffset::east(off).ymd(y,m,d).and_hms(h,n,s); diff --git a/src/lib.rs b/src/lib.rs index c57b0a3..553c0c0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -146,16 +146,21 @@ assert_eq!(dt.to_string(), "2014-11-28 12:00:09 UTC"); assert_eq!(format!("{:?}", dt), "2014-11-28T12:00:09Z"); ~~~~ -Parsing can be done with two methods: +Parsing can be done with three methods: -- `DateTime::parse_from_str` parses a date and time with offsets and - returns `DateTime`. - This should be used when the offset is a part of input and the caller cannot guess that. - It *cannot* be used when the offset can be missing. +1. The standard `FromStr` trait (and `parse` method on a string) can be used for + parsing `DateTime` and `DateTime` values. + This parses what the `{:?}` (`std::fmt::Debug`) format specifier prints, + and requires the offset to be present. -- `Offset::datetime_from_str` is similar but returns `DateTime` of given offset. - When the explicit offset is missing from the input, it simply uses given offset. - It issues an error when the input contains an explicit offset different from the current offset. +2. `DateTime::parse_from_str` parses a date and time with offsets and + returns `DateTime`. + This should be used when the offset is a part of input and the caller cannot guess that. + It *cannot* be used when the offset can be missing. + +3. `Offset::datetime_from_str` is similar but returns `DateTime` of given offset. + When the explicit offset is missing from the input, it simply uses given offset. + It issues an error when the input contains an explicit offset different from the current offset. More detailed control over the parsing process is available via `format` module. @@ -163,8 +168,16 @@ More detailed control over the parsing process is available via `format` module. use chrono::{UTC, Offset, DateTime}; let dt = UTC.ymd(2014, 11, 28).and_hms(12, 0, 9); + +// method 1 +assert_eq!("2014-11-28T12:00:09Z".parse::>(), Ok(dt.clone())); +assert_eq!("2014-11-28T21:00:09+09:00".parse::>(), Ok(dt.clone())); + +// method 2 assert_eq!(UTC.datetime_from_str("2014-11-28 12:00:09", "%Y-%m-%d %H:%M:%S"), Ok(dt.clone())); assert_eq!(UTC.datetime_from_str("Fri Nov 28 12:00:09 2014", "%a %b %e %T %Y"), Ok(dt.clone())); + +// method 3 assert_eq!(DateTime::parse_from_str("2014-11-28 21:00:09 +09:00", "%Y-%m-%d %H:%M:%S %z").map(|dt| dt.with_offset(UTC)), Ok(dt));