Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Small fixes to the RFC 3339 parsers #1145

Merged
merged 9 commits into from
Aug 29, 2023
12 changes: 8 additions & 4 deletions src/datetime/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@ use std::time::{SystemTime, UNIX_EPOCH};

#[cfg(feature = "unstable-locales")]
use crate::format::Locale;
use crate::format::{parse, parse_and_remainder, ParseError, ParseResult, Parsed, StrftimeItems};
use crate::format::{
parse, parse_and_remainder, parse_rfc3339, Fixed, Item, ParseError, ParseResult, Parsed,
StrftimeItems, TOO_LONG,
};
#[cfg(any(feature = "alloc", feature = "std"))]
use crate::format::{write_rfc3339, DelayedFormat};
use crate::format::{Fixed, Item};
use crate::naive::{Days, IsoWeek, NaiveDate, NaiveDateTime, NaiveTime};
#[cfg(feature = "clock")]
use crate::offset::Local;
Expand Down Expand Up @@ -699,9 +701,11 @@ impl DateTime<FixedOffset> {
/// also simultaneously valid RFC 3339 values, but not all RFC 3339 values are valid ISO 8601
/// values (or the other way around).
pub fn parse_from_rfc3339(s: &str) -> ParseResult<DateTime<FixedOffset>> {
const ITEMS: &[Item<'static>] = &[Item::Fixed(Fixed::RFC3339)];
let mut parsed = Parsed::new();
parse(&mut parsed, s, ITEMS.iter())?;
let (s, _) = parse_rfc3339(&mut parsed, s)?;
if !s.is_empty() {
return Err(TOO_LONG);
}
parsed.to_datetime()
}

Expand Down
88 changes: 51 additions & 37 deletions src/datetime/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crate::offset::{FixedOffset, TimeZone, Utc};
#[cfg(feature = "clock")]
use crate::offset::{Local, Offset};
use crate::oldtime::Duration as OldDuration;
use crate::{Datelike, Days, LocalResult, Months, NaiveDateTime};
use crate::{Datelike, Days, LocalResult, Months, NaiveDateTime, Timelike};

#[derive(Clone)]
struct DstTester;
Expand Down Expand Up @@ -262,12 +262,12 @@ fn ymdhms_milli(
hour: u32,
min: u32,
sec: u32,
milli: i64,
milli: u32,
) -> DateTime<FixedOffset> {
fixedoffset
.with_ymd_and_hms(year, month, day, hour, min, sec)
.unwrap()
.checked_add_signed(OldDuration::milliseconds(milli))
.with_nanosecond(milli * 1_000_000)
.unwrap()
}

Expand All @@ -282,12 +282,12 @@ fn ymdhms_micro(
hour: u32,
min: u32,
sec: u32,
micro: i64,
micro: u32,
) -> DateTime<FixedOffset> {
fixedoffset
.with_ymd_and_hms(year, month, day, hour, min, sec)
.unwrap()
.checked_add_signed(OldDuration::microseconds(micro))
.with_nanosecond(micro * 1000)
.unwrap()
}

Expand All @@ -302,12 +302,12 @@ fn ymdhms_nano(
hour: u32,
min: u32,
sec: u32,
nano: i64,
nano: u32,
) -> DateTime<FixedOffset> {
fixedoffset
.with_ymd_and_hms(year, month, day, hour, min, sec)
.unwrap()
.checked_add_signed(OldDuration::nanoseconds(nano))
.with_nanosecond(nano)
.unwrap()
}

Expand All @@ -324,11 +324,11 @@ fn ymdhms_milli_utc(
hour: u32,
min: u32,
sec: u32,
milli: i64,
milli: u32,
) -> DateTime<Utc> {
Utc.with_ymd_and_hms(year, month, day, hour, min, sec)
.unwrap()
.checked_add_signed(OldDuration::milliseconds(milli))
.with_nanosecond(milli * 1_000_000)
.unwrap()
}

Expand Down Expand Up @@ -471,6 +471,29 @@ fn test_datetime_rfc2822() {
.to_rfc2822(),
"Wed, 18 Feb 2015 23:16:09 +0500"
);
assert_eq!(
DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:59:60 +0500"),
Ok(edt
.from_local_datetime(
&NaiveDate::from_ymd_opt(2015, 2, 18)
.unwrap()
.and_hms_milli_opt(23, 59, 59, 1_000)
.unwrap()
)
.unwrap())
);
assert!(DateTime::parse_from_rfc2822("31 DEC 262143 23:59 -2359").is_err());
assert_eq!(
DateTime::parse_from_rfc3339("2015-02-18T23:59:60.234567+05:00"),
Ok(edt
.from_local_datetime(
&NaiveDate::from_ymd_opt(2015, 2, 18)
.unwrap()
.and_hms_micro_opt(23, 59, 59, 1_234_567)
.unwrap()
)
.unwrap())
);
// seconds 60
assert_eq!(
edt.from_local_datetime(
Expand All @@ -493,8 +516,8 @@ fn test_datetime_rfc2822() {
Ok(FixedOffset::east_opt(0).unwrap().with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap())
);
assert_eq!(
ymdhms_milli(&edt, 2015, 2, 18, 23, 59, 58, 1_234_567).to_rfc2822(),
"Thu, 19 Feb 2015 00:20:32 +0500"
ymdhms_micro(&edt, 2015, 2, 18, 23, 59, 59, 1_234_567).to_rfc2822(),
"Wed, 18 Feb 2015 23:59:60 +0500"
);
assert_eq!(
DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:59:58 +0500"),
Expand Down Expand Up @@ -579,7 +602,7 @@ fn test_datetime_rfc3339() {
);
assert_eq!(
ymdhms_micro(&edt5, 2015, 2, 18, 23, 59, 59, 1_234_567).to_rfc3339(),
"2015-02-19T00:00:00.234567+05:00"
"2015-02-18T23:59:60.234567+05:00"
);
assert_eq!(
DateTime::parse_from_rfc3339("2015-02-18T23:59:59.123+05:00"),
Expand All @@ -600,7 +623,7 @@ fn test_datetime_rfc3339() {

assert_eq!(
ymdhms_micro(&edt5, 2015, 2, 18, 23, 59, 59, 1_234_567).to_rfc3339(),
"2015-02-19T00:00:00.234567+05:00"
"2015-02-18T23:59:60.234567+05:00"
);
assert_eq!(
ymdhms_milli(&edt5, 2015, 2, 18, 23, 16, 9, 150).to_rfc3339(),
Expand All @@ -615,27 +638,8 @@ fn test_datetime_rfc3339() {
Ok(ymdhms(&edt0, 2015, 2, 18, 23, 16, 9))
);
assert_eq!(
DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:59:60 +0500"),
Ok(edt5
.from_local_datetime(
&NaiveDate::from_ymd_opt(2015, 2, 18)
.unwrap()
.and_hms_milli_opt(23, 59, 59, 1_000)
.unwrap()
)
.unwrap())
);
assert!(DateTime::parse_from_rfc2822("31 DEC 262143 23:59 -2359").is_err());
assert_eq!(
DateTime::parse_from_rfc3339("2015-02-18T23:59:60.234567+05:00"),
Ok(edt5
.from_local_datetime(
&NaiveDate::from_ymd_opt(2015, 2, 18)
.unwrap()
.and_hms_micro_opt(23, 59, 59, 1_234_567)
.unwrap()
)
.unwrap())
DateTime::parse_from_rfc3339("2015-02-18 23:59:60.234567+05:00"),
Ok(ymdhms_micro(&edt5, 2015, 2, 18, 23, 59, 59, 1_234_567))
);
assert_eq!(ymdhms_utc(2015, 2, 18, 23, 16, 9).to_rfc3339(), "2015-02-18T23:16:09+00:00");

Expand All @@ -646,7 +650,6 @@ fn test_datetime_rfc3339() {
assert!(DateTime::parse_from_rfc3339("2015-02-18T23:59:60.234567PST").is_err());
assert!(DateTime::parse_from_rfc3339("2015-02-18T23:59:60.234567+0500").is_err());
assert!(DateTime::parse_from_rfc3339("2015-02-18T23:59:60.234567+05:00:00").is_err());
assert!(DateTime::parse_from_rfc3339("2015-02-18 23:59:60.234567+05:00").is_err());
assert!(DateTime::parse_from_rfc3339("2015-02-18T23:59:60.234567:+05:00").is_err());
assert!(DateTime::parse_from_rfc3339("2015-02-18T23:59:60.234567+05:00 ").is_err());
assert!(DateTime::parse_from_rfc3339(" 2015-02-18T23:59:60.234567+05:00").is_err());
Expand Down Expand Up @@ -740,6 +743,17 @@ fn test_datetime_from_str() {
)
.unwrap())
);
assert_eq!(
"2015-02-18T23:16:9.15Utc".parse::<DateTime<Utc>>(),
Ok(Utc
.from_local_datetime(
&NaiveDate::from_ymd_opt(2015, 2, 18)
.unwrap()
.and_hms_milli_opt(23, 16, 9, 150)
.unwrap()
)
.unwrap())
);

assert_eq!(
"2015-2-18T23:16:9.15Z".parse::<DateTime<FixedOffset>>(),
Expand Down Expand Up @@ -802,6 +816,8 @@ fn test_parse_datetime_utc() {
"2001-02-03T04:05:06+0000",
"2001-02-03T04:05:06-00:00",
"2001-02-03T04:05:06-01:00",
"2012-12-12 12:12:12Z",
"2012-12-12t12:12:12Z",
"2012-12-12T12:12:12Z",
"2012 -12-12T12:12:12Z",
"2012 -12-12T12:12:12Z",
Expand Down Expand Up @@ -868,7 +884,6 @@ fn test_parse_datetime_utc() {
"2012-12-12T12:61:12Z", // invalid minute
"2012-12-12T12:12:62Z", // invalid second
"2012-12-12 T12:12:12Z", // space after date
"2012-12-12t12:12:12Z", // wrong divider 't'
"2012-12-12T12:12:12ZZ", // trailing literal 'Z'
"+802701-12-12T12:12:12Z", // invalid year (out of bounds)
"+ 2012-12-12T12:12:12Z", // invalid space before year
Expand Down Expand Up @@ -1489,7 +1504,6 @@ fn test_test_deprecated_from_offset() {
#[cfg(all(feature = "unstable-locales", any(feature = "alloc", feature = "std")))]
fn locale_decimal_point() {
use crate::Locale::{ar_SY, nl_NL};
use crate::Timelike;
let dt =
Utc.with_ymd_and_hms(2018, 9, 5, 18, 58, 0).unwrap().with_nanosecond(123456780).unwrap();

Expand Down
3 changes: 2 additions & 1 deletion src/format/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ pub use formatting::{format_item_localized, format_localized};
pub use locales::Locale;
#[cfg(all(not(feature = "unstable-locales"), any(feature = "alloc", feature = "std")))]
pub(crate) use locales::Locale;
pub(crate) use parse::parse_rfc3339;
pub use parse::{parse, parse_and_remainder};
pub use parsed::Parsed;
pub use strftime::StrftimeItems;
Expand Down Expand Up @@ -450,7 +451,7 @@ const IMPOSSIBLE: ParseError = ParseError(ParseErrorKind::Impossible);
const NOT_ENOUGH: ParseError = ParseError(ParseErrorKind::NotEnough);
const INVALID: ParseError = ParseError(ParseErrorKind::Invalid);
const TOO_SHORT: ParseError = ParseError(ParseErrorKind::TooShort);
const TOO_LONG: ParseError = ParseError(ParseErrorKind::TooLong);
pub(crate) const TOO_LONG: ParseError = ParseError(ParseErrorKind::TooLong);
const BAD_FORMAT: ParseError = ParseError(ParseErrorKind::BadFormat);

// this implementation is here only because we need some private code from `scan`
Expand Down