support round tripping display <-> datetime
This extends `FromStr` to allow either a `T` or a ` ` (space) as the delimiter between the date and the time, and, because of the fact that the `Z` parser-specifier is shared with the Fixed notation, extends the fixed notation to support `UTC` in addition to `Z` as the zero-offset. IMO this Fixes #147
This commit is contained in:
parent
b9cd0ce803
commit
b9c967b2ac
|
@ -12,6 +12,9 @@ Versions with only mechanical changes will be omitted from the following list.
|
||||||
|
|
||||||
### Improvements
|
### Improvements
|
||||||
|
|
||||||
|
* Support a space or `T` in `FromStr` for `DateTime<Tz>`, meaning that e.g.
|
||||||
|
`dt.to_string().parse::<DateTime<Utc>>()` now correctly works on round-trip.
|
||||||
|
(@quodlibetor in #378)
|
||||||
* Support "negative UTC" in `parse_from_rfc2822` (@quodlibetor #368 reported in
|
* Support "negative UTC" in `parse_from_rfc2822` (@quodlibetor #368 reported in
|
||||||
#102)
|
#102)
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ use offset::Local;
|
||||||
use offset::{TimeZone, Offset, Utc, FixedOffset};
|
use offset::{TimeZone, Offset, Utc, FixedOffset};
|
||||||
use naive::{NaiveTime, NaiveDateTime, IsoWeek};
|
use naive::{NaiveTime, NaiveDateTime, IsoWeek};
|
||||||
use Date;
|
use Date;
|
||||||
use format::{Item, Numeric, Pad, Fixed};
|
use format::{Item, Fixed};
|
||||||
use format::{parse, Parsed, ParseError, ParseResult, StrftimeItems};
|
use format::{parse, Parsed, ParseError, ParseResult, StrftimeItems};
|
||||||
#[cfg(any(feature = "alloc", feature = "std", test))]
|
#[cfg(any(feature = "alloc", feature = "std", test))]
|
||||||
use format::DelayedFormat;
|
use format::DelayedFormat;
|
||||||
|
@ -628,33 +628,6 @@ impl<Tz: TimeZone> fmt::Display for DateTime<Tz> where Tz::Offset: fmt::Display
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl str::FromStr for DateTime<FixedOffset> {
|
|
||||||
type Err = ParseError;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> ParseResult<DateTime<FixedOffset>> {
|
|
||||||
const ITEMS: &'static [Item<'static>] = &[
|
|
||||||
Item::Numeric(Numeric::Year, Pad::Zero),
|
|
||||||
Item::Space(""), Item::Literal("-"),
|
|
||||||
Item::Numeric(Numeric::Month, Pad::Zero),
|
|
||||||
Item::Space(""), Item::Literal("-"),
|
|
||||||
Item::Numeric(Numeric::Day, Pad::Zero),
|
|
||||||
Item::Space(""), Item::Literal("T"), // XXX shouldn't this be case-insensitive?
|
|
||||||
Item::Numeric(Numeric::Hour, Pad::Zero),
|
|
||||||
Item::Space(""), Item::Literal(":"),
|
|
||||||
Item::Numeric(Numeric::Minute, Pad::Zero),
|
|
||||||
Item::Space(""), Item::Literal(":"),
|
|
||||||
Item::Numeric(Numeric::Second, Pad::Zero),
|
|
||||||
Item::Fixed(Fixed::Nanosecond),
|
|
||||||
Item::Space(""), Item::Fixed(Fixed::TimezoneOffsetZ),
|
|
||||||
Item::Space(""),
|
|
||||||
];
|
|
||||||
|
|
||||||
let mut parsed = Parsed::new();
|
|
||||||
parse(&mut parsed, s, ITEMS.iter())?;
|
|
||||||
parsed.to_datetime()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl str::FromStr for DateTime<Utc> {
|
impl str::FromStr for DateTime<Utc> {
|
||||||
type Err = ParseError;
|
type Err = ParseError;
|
||||||
|
|
||||||
|
@ -2104,6 +2077,15 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_datetime_from_str() {
|
fn test_datetime_from_str() {
|
||||||
|
assert_eq!("2015-02-18T23:16:9.15Z".parse::<DateTime<FixedOffset>>(),
|
||||||
|
Ok(FixedOffset::east(0).ymd(2015, 2, 18).and_hms_milli(23, 16, 9, 150)));
|
||||||
|
assert_eq!("2015-02-18T23:16:9.15Z".parse::<DateTime<Utc>>(),
|
||||||
|
Ok(Utc.ymd(2015, 2, 18).and_hms_milli(23, 16, 9, 150)));
|
||||||
|
assert_eq!("2015-02-18T23:16:9.15 UTC".parse::<DateTime<Utc>>(),
|
||||||
|
Ok(Utc.ymd(2015, 2, 18).and_hms_milli(23, 16, 9, 150)));
|
||||||
|
assert_eq!("2015-02-18T23:16:9.15UTC".parse::<DateTime<Utc>>(),
|
||||||
|
Ok(Utc.ymd(2015, 2, 18).and_hms_milli(23, 16, 9, 150)));
|
||||||
|
|
||||||
assert_eq!("2015-2-18T23:16:9.15Z".parse::<DateTime<FixedOffset>>(),
|
assert_eq!("2015-2-18T23:16:9.15Z".parse::<DateTime<FixedOffset>>(),
|
||||||
Ok(FixedOffset::east(0).ymd(2015, 2, 18).and_hms_milli(23, 16, 9, 150)));
|
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::<DateTime<FixedOffset>>(),
|
assert_eq!("2015-2-18T13:16:9.15-10:00".parse::<DateTime<FixedOffset>>(),
|
||||||
|
@ -2132,6 +2114,25 @@ mod tests {
|
||||||
Ok(Utc.ymd(2013, 8, 9).and_hms(23, 54, 35)));
|
Ok(Utc.ymd(2013, 8, 9).and_hms(23, 54, 35)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_to_string_round_trip() {
|
||||||
|
let dt = Utc.ymd(2000, 1, 1).and_hms(0, 0, 0);
|
||||||
|
let _dt: DateTime<Utc> = dt.to_string().parse().unwrap();
|
||||||
|
|
||||||
|
let ndt_fixed = dt.with_timezone(&FixedOffset::east(3600));
|
||||||
|
let _dt: DateTime<FixedOffset> = ndt_fixed.to_string().parse().unwrap();
|
||||||
|
|
||||||
|
let ndt_fixed = dt.with_timezone(&FixedOffset::east(0));
|
||||||
|
let _dt: DateTime<FixedOffset> = ndt_fixed.to_string().parse().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature="clock")]
|
||||||
|
fn test_to_string_round_trip_with_local() {
|
||||||
|
let ndt = Local::now();
|
||||||
|
let _dt: DateTime<FixedOffset> = ndt.to_string().parse().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(feature="clock")]
|
#[cfg(feature="clock")]
|
||||||
fn test_datetime_format_with_local() {
|
fn test_datetime_format_with_local() {
|
||||||
|
|
|
@ -284,6 +284,7 @@ macro_rules! internal_fix { ($x:ident) => (Item::Fixed(Fixed::Internal(InternalF
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Copy)]
|
#[derive(Debug, Clone, PartialEq, Eq, Copy)]
|
||||||
pub struct ParseError(ParseErrorKind);
|
pub struct ParseError(ParseErrorKind);
|
||||||
|
|
||||||
|
/// The category of parse error
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Copy)]
|
#[derive(Debug, Clone, PartialEq, Eq, Copy)]
|
||||||
enum ParseErrorKind {
|
enum ParseErrorKind {
|
||||||
/// Given field is out of permitted range.
|
/// Given field is out of permitted range.
|
||||||
|
|
|
@ -8,12 +8,13 @@
|
||||||
|
|
||||||
use core::borrow::Borrow;
|
use core::borrow::Borrow;
|
||||||
use core::usize;
|
use core::usize;
|
||||||
|
use core::str;
|
||||||
|
|
||||||
use Weekday;
|
use {DateTime, FixedOffset, Weekday};
|
||||||
|
|
||||||
use super::scan;
|
use super::scan;
|
||||||
use super::{Parsed, ParseResult, Item, InternalFixed, InternalInternal};
|
use super::{Parsed, Numeric, Pad, Fixed, Item, InternalFixed, InternalInternal};
|
||||||
use super::{OUT_OF_RANGE, INVALID, TOO_SHORT, TOO_LONG, BAD_FORMAT};
|
use super::{ParseResult, ParseError, ParseErrorKind};
|
||||||
|
use super::{OUT_OF_RANGE, INVALID, TOO_SHORT, TOO_LONG, BAD_FORMAT, NOT_ENOUGH};
|
||||||
|
|
||||||
fn set_weekday_with_num_days_from_sunday(p: &mut Parsed, v: i64) -> ParseResult<()> {
|
fn set_weekday_with_num_days_from_sunday(p: &mut Parsed, v: i64) -> ParseResult<()> {
|
||||||
p.set_weekday(match v {
|
p.set_weekday(match v {
|
||||||
|
@ -201,24 +202,39 @@ fn parse_rfc3339<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a st
|
||||||
/// so one can prepend any number of whitespace then any number of zeroes before numbers.
|
/// so one can prepend any number of whitespace then any number of zeroes before numbers.
|
||||||
///
|
///
|
||||||
/// - (Still) obeying the intrinsic parsing width. This allows, for example, parsing `HHMMSS`.
|
/// - (Still) obeying the intrinsic parsing width. This allows, for example, parsing `HHMMSS`.
|
||||||
pub fn parse<'a, I, B>(parsed: &mut Parsed, mut s: &str, items: I) -> ParseResult<()>
|
pub fn parse<'a, I, B>(parsed: &mut Parsed, s: &str, items: I) -> ParseResult<()>
|
||||||
|
where I: Iterator<Item=B>, B: Borrow<Item<'a>> {
|
||||||
|
parse_internal(parsed, s, items).map(|_| ()).map_err(|(_s, e)| e)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_internal<'a, 'b, I, B>(
|
||||||
|
parsed: &mut Parsed, mut s: &'b str, items: I
|
||||||
|
) -> Result<&'b str, (&'b str, ParseError)>
|
||||||
where I: Iterator<Item=B>, B: Borrow<Item<'a>> {
|
where I: Iterator<Item=B>, B: Borrow<Item<'a>> {
|
||||||
macro_rules! try_consume {
|
macro_rules! try_consume {
|
||||||
($e:expr) => ({ let (s_, v) = $e?; s = s_; v })
|
($e:expr) => ({
|
||||||
|
match $e {
|
||||||
|
Ok((s_, v)) => {
|
||||||
|
s = s_;
|
||||||
|
v
|
||||||
|
}
|
||||||
|
Err(e) => return Err((s, e))
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
for item in items {
|
for item in items {
|
||||||
match item.borrow() {
|
match item.borrow() {
|
||||||
&Item::Literal(prefix) => {
|
&Item::Literal(prefix) => {
|
||||||
if s.len() < prefix.len() { return Err(TOO_SHORT); }
|
if s.len() < prefix.len() { return Err((s, TOO_SHORT)); }
|
||||||
if !s.starts_with(prefix) { return Err(INVALID); }
|
if !s.starts_with(prefix) { return Err((s, INVALID)); }
|
||||||
s = &s[prefix.len()..];
|
s = &s[prefix.len()..];
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(feature = "alloc", feature = "std", test))]
|
#[cfg(any(feature = "alloc", feature = "std", test))]
|
||||||
&Item::OwnedLiteral(ref prefix) => {
|
&Item::OwnedLiteral(ref prefix) => {
|
||||||
if s.len() < prefix.len() { return Err(TOO_SHORT); }
|
if s.len() < prefix.len() { return Err((s, TOO_SHORT)); }
|
||||||
if !s.starts_with(&prefix[..]) { return Err(INVALID); }
|
if !s.starts_with(&prefix[..]) { return Err((s, INVALID)); }
|
||||||
s = &s[prefix.len()..];
|
s = &s[prefix.len()..];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -265,7 +281,7 @@ pub fn parse<'a, I, B>(parsed: &mut Parsed, mut s: &str, items: I) -> ParseResul
|
||||||
let v = if signed {
|
let v = if signed {
|
||||||
if s.starts_with('-') {
|
if s.starts_with('-') {
|
||||||
let v = try_consume!(scan::number(&s[1..], 1, usize::MAX));
|
let v = try_consume!(scan::number(&s[1..], 1, usize::MAX));
|
||||||
0i64.checked_sub(v).ok_or(OUT_OF_RANGE)?
|
0i64.checked_sub(v).ok_or((s, OUT_OF_RANGE))?
|
||||||
} else if s.starts_with('+') {
|
} else if s.starts_with('+') {
|
||||||
try_consume!(scan::number(&s[1..], 1, usize::MAX))
|
try_consume!(scan::number(&s[1..], 1, usize::MAX))
|
||||||
} else {
|
} else {
|
||||||
|
@ -275,7 +291,7 @@ pub fn parse<'a, I, B>(parsed: &mut Parsed, mut s: &str, items: I) -> ParseResul
|
||||||
} else {
|
} else {
|
||||||
try_consume!(scan::number(s, 1, width))
|
try_consume!(scan::number(s, 1, width))
|
||||||
};
|
};
|
||||||
set(parsed, v)?;
|
set(parsed, v).map_err(|e| (s, e))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
&Item::Fixed(ref spec) => {
|
&Item::Fixed(ref spec) => {
|
||||||
|
@ -284,77 +300,77 @@ pub fn parse<'a, I, B>(parsed: &mut Parsed, mut s: &str, items: I) -> ParseResul
|
||||||
match spec {
|
match spec {
|
||||||
&ShortMonthName => {
|
&ShortMonthName => {
|
||||||
let month0 = try_consume!(scan::short_month0(s));
|
let month0 = try_consume!(scan::short_month0(s));
|
||||||
parsed.set_month(i64::from(month0) + 1)?;
|
parsed.set_month(i64::from(month0) + 1).map_err(|e| (s, e))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
&LongMonthName => {
|
&LongMonthName => {
|
||||||
let month0 = try_consume!(scan::short_or_long_month0(s));
|
let month0 = try_consume!(scan::short_or_long_month0(s));
|
||||||
parsed.set_month(i64::from(month0) + 1)?;
|
parsed.set_month(i64::from(month0) + 1).map_err(|e| (s, e))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
&ShortWeekdayName => {
|
&ShortWeekdayName => {
|
||||||
let weekday = try_consume!(scan::short_weekday(s));
|
let weekday = try_consume!(scan::short_weekday(s));
|
||||||
parsed.set_weekday(weekday)?;
|
parsed.set_weekday(weekday).map_err(|e| (s, e))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
&LongWeekdayName => {
|
&LongWeekdayName => {
|
||||||
let weekday = try_consume!(scan::short_or_long_weekday(s));
|
let weekday = try_consume!(scan::short_or_long_weekday(s));
|
||||||
parsed.set_weekday(weekday)?;
|
parsed.set_weekday(weekday).map_err(|e| (s, e))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
&LowerAmPm | &UpperAmPm => {
|
&LowerAmPm | &UpperAmPm => {
|
||||||
if s.len() < 2 { return Err(TOO_SHORT); }
|
if s.len() < 2 { return Err((s, TOO_SHORT)); }
|
||||||
let ampm = match (s.as_bytes()[0] | 32, s.as_bytes()[1] | 32) {
|
let ampm = match (s.as_bytes()[0] | 32, s.as_bytes()[1] | 32) {
|
||||||
(b'a',b'm') => false,
|
(b'a',b'm') => false,
|
||||||
(b'p',b'm') => true,
|
(b'p',b'm') => true,
|
||||||
_ => return Err(INVALID)
|
_ => return Err((s, INVALID))
|
||||||
};
|
};
|
||||||
parsed.set_ampm(ampm)?;
|
parsed.set_ampm(ampm).map_err(|e| (s, e))?;
|
||||||
s = &s[2..];
|
s = &s[2..];
|
||||||
}
|
}
|
||||||
|
|
||||||
&Nanosecond | &Nanosecond3 | &Nanosecond6 | &Nanosecond9 => {
|
&Nanosecond | &Nanosecond3 | &Nanosecond6 | &Nanosecond9 => {
|
||||||
if s.starts_with('.') {
|
if s.starts_with('.') {
|
||||||
let nano = try_consume!(scan::nanosecond(&s[1..]));
|
let nano = try_consume!(scan::nanosecond(&s[1..]));
|
||||||
parsed.set_nanosecond(nano)?;
|
parsed.set_nanosecond(nano).map_err(|e| (s, e))?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&Internal(InternalFixed { val: InternalInternal::Nanosecond3NoDot }) => {
|
&Internal(InternalFixed { val: InternalInternal::Nanosecond3NoDot }) => {
|
||||||
if s.len() < 3 { return Err(TOO_SHORT); }
|
if s.len() < 3 { return Err((s, TOO_SHORT)); }
|
||||||
let nano = try_consume!(scan::nanosecond_fixed(s, 3));
|
let nano = try_consume!(scan::nanosecond_fixed(s, 3));
|
||||||
parsed.set_nanosecond(nano)?;
|
parsed.set_nanosecond(nano).map_err(|e| (s, e))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
&Internal(InternalFixed { val: InternalInternal::Nanosecond6NoDot }) => {
|
&Internal(InternalFixed { val: InternalInternal::Nanosecond6NoDot }) => {
|
||||||
if s.len() < 6 { return Err(TOO_SHORT); }
|
if s.len() < 6 { return Err((s, TOO_SHORT)); }
|
||||||
let nano = try_consume!(scan::nanosecond_fixed(s, 6));
|
let nano = try_consume!(scan::nanosecond_fixed(s, 6));
|
||||||
parsed.set_nanosecond(nano)?;
|
parsed.set_nanosecond(nano).map_err(|e| (s, e))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
&Internal(InternalFixed { val: InternalInternal::Nanosecond9NoDot }) => {
|
&Internal(InternalFixed { val: InternalInternal::Nanosecond9NoDot }) => {
|
||||||
if s.len() < 9 { return Err(TOO_SHORT); }
|
if s.len() < 9 { return Err((s, TOO_SHORT)); }
|
||||||
let nano = try_consume!(scan::nanosecond_fixed(s, 9));
|
let nano = try_consume!(scan::nanosecond_fixed(s, 9));
|
||||||
parsed.set_nanosecond(nano)?;
|
parsed.set_nanosecond(nano).map_err(|e| (s, e))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
&TimezoneName => return Err(BAD_FORMAT),
|
&TimezoneName => return Err((s, BAD_FORMAT)),
|
||||||
|
|
||||||
&TimezoneOffsetColon | &TimezoneOffset => {
|
&TimezoneOffsetColon | &TimezoneOffset => {
|
||||||
let offset = try_consume!(scan::timezone_offset(s.trim_left(),
|
let offset = try_consume!(scan::timezone_offset(s.trim_left(),
|
||||||
scan::colon_or_space));
|
scan::colon_or_space));
|
||||||
parsed.set_offset(i64::from(offset))?;
|
parsed.set_offset(i64::from(offset)).map_err(|e| (s, e))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
&TimezoneOffsetColonZ | &TimezoneOffsetZ => {
|
&TimezoneOffsetColonZ | &TimezoneOffsetZ => {
|
||||||
let offset = try_consume!(scan::timezone_offset_zulu(s.trim_left(),
|
let offset = try_consume!(scan::timezone_offset_zulu(s.trim_left(),
|
||||||
scan::colon_or_space));
|
scan::colon_or_space));
|
||||||
parsed.set_offset(i64::from(offset))?;
|
parsed.set_offset(i64::from(offset)).map_err(|e| (s, e))?;
|
||||||
}
|
}
|
||||||
&Internal(InternalFixed { val: InternalInternal::TimezoneOffsetPermissive }) => {
|
&Internal(InternalFixed { val: InternalInternal::TimezoneOffsetPermissive }) => {
|
||||||
let offset = try_consume!(scan::timezone_offset_permissive(
|
let offset = try_consume!(scan::timezone_offset_permissive(
|
||||||
s.trim_left(), scan::colon_or_space));
|
s.trim_left(), scan::colon_or_space));
|
||||||
parsed.set_offset(i64::from(offset))?;
|
parsed.set_offset(i64::from(offset)).map_err(|e| (s, e))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
&RFC2822 => try_consume!(parse_rfc2822(parsed, s)),
|
&RFC2822 => try_consume!(parse_rfc2822(parsed, s)),
|
||||||
|
@ -363,16 +379,54 @@ pub fn parse<'a, I, B>(parsed: &mut Parsed, mut s: &str, items: I) -> ParseResul
|
||||||
}
|
}
|
||||||
|
|
||||||
&Item::Error => {
|
&Item::Error => {
|
||||||
return Err(BAD_FORMAT);
|
return Err((s, BAD_FORMAT));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if there are trailling chars, it is an error
|
// if there are trailling chars, it is an error
|
||||||
if !s.is_empty() {
|
if !s.is_empty() {
|
||||||
Err(TOO_LONG)
|
Err((s, TOO_LONG))
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
Ok(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl str::FromStr for DateTime<FixedOffset> {
|
||||||
|
type Err = ParseError;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> ParseResult<DateTime<FixedOffset>> {
|
||||||
|
const DATE_ITEMS: &'static [Item<'static>] = &[
|
||||||
|
Item::Numeric(Numeric::Year, Pad::Zero),
|
||||||
|
Item::Space(""), Item::Literal("-"),
|
||||||
|
Item::Numeric(Numeric::Month, Pad::Zero),
|
||||||
|
Item::Space(""), Item::Literal("-"),
|
||||||
|
Item::Numeric(Numeric::Day, Pad::Zero),
|
||||||
|
];
|
||||||
|
const TIME_ITEMS: &'static [Item<'static>] = &[
|
||||||
|
Item::Numeric(Numeric::Hour, Pad::Zero),
|
||||||
|
Item::Space(""), Item::Literal(":"),
|
||||||
|
Item::Numeric(Numeric::Minute, Pad::Zero),
|
||||||
|
Item::Space(""), Item::Literal(":"),
|
||||||
|
Item::Numeric(Numeric::Second, Pad::Zero),
|
||||||
|
Item::Fixed(Fixed::Nanosecond),
|
||||||
|
Item::Space(""), Item::Fixed(Fixed::TimezoneOffsetZ),
|
||||||
|
Item::Space(""),
|
||||||
|
];
|
||||||
|
|
||||||
|
let mut parsed = Parsed::new();
|
||||||
|
match parse_internal(&mut parsed, s, DATE_ITEMS.iter()) {
|
||||||
|
Err((remainder, e)) if e.0 == ParseErrorKind::TooLong =>{
|
||||||
|
if remainder.starts_with('T') || remainder.starts_with(' ') {
|
||||||
|
parse(&mut parsed, &remainder[1..], TIME_ITEMS.iter())?;
|
||||||
|
} else {
|
||||||
|
Err(INVALID)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err((_s, e)) => Err(e)?,
|
||||||
|
Ok(_) => Err(NOT_ENOUGH)?,
|
||||||
|
};
|
||||||
|
parsed.to_datetime()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -262,8 +262,20 @@ pub fn timezone_offset_zulu<F>(s: &str, colon: F)
|
||||||
-> ParseResult<(&str, i32)>
|
-> ParseResult<(&str, i32)>
|
||||||
where F: FnMut(&str) -> ParseResult<&str>
|
where F: FnMut(&str) -> ParseResult<&str>
|
||||||
{
|
{
|
||||||
match s.as_bytes().first() {
|
let bytes = s.as_bytes();
|
||||||
|
match bytes.first() {
|
||||||
Some(&b'z') | Some(&b'Z') => Ok((&s[1..], 0)),
|
Some(&b'z') | Some(&b'Z') => Ok((&s[1..], 0)),
|
||||||
|
Some(&b'u') | Some(&b'U') => {
|
||||||
|
if bytes.len() >= 3 {
|
||||||
|
let (b, c) = (bytes[1], bytes[2]);
|
||||||
|
match (b | 32, c | 32) {
|
||||||
|
(b't', b'c') => Ok((&s[3..], 0)),
|
||||||
|
_ => Err(INVALID),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(INVALID)
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => timezone_offset(s, colon),
|
_ => timezone_offset(s, colon),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue