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

Add parse_from_* to DateTime #278

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,15 @@ This documents all notable changes to [Chrono](https://github.com/chronotope/chr
Chrono obeys the principle of [Semantic Versioning](http://semver.org/).

There were/are numerous minor versions before 1.0 due to the language changes.
Versions with only mechnical changes will be omitted from the following list.
Versions with only mechanical changes will be omitted from the following list.

## 0.4.6

## Features

* Add `std::convert::From` conversions between the different timezone formats (@mqudsi #271)
* Add `parse_from_rfc2822()`, `parse_from_rfc3339()`, and `parse_from_str()` methods to `DateTime`
objects (@mqudsi #278)

## 0.4.5

Expand Down
139 changes: 131 additions & 8 deletions src/datetime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -235,12 +235,127 @@ impl<Tz: TimeZone> DateTime<Tz> {
}
}

/// Convert a `DateTime<Utc>` instance into a `DateTime<FixedOffset>` instance.
impl From<DateTime<Utc>> for DateTime<FixedOffset> {
/// Convert this `DateTime<Utc>` instance into a `DateTime<FixedOffset>` instance.
///
/// Conversion is done via [`DateTime::with_timezone`]. Note that the converted value returned by
/// this will be created with a fixed timezone offset of 0.
fn from(src: DateTime<Utc>) -> Self {
src.with_timezone(&FixedOffset::east(0))
}
}

/// Convert a `DateTime<Utc>` instance into a `DateTime<Local>` instance.
#[cfg(feature="clock")]
impl From<DateTime<Utc>> for DateTime<Local> {
/// Convert this `DateTime<Utc>` instance into a `DateTime<Local>` instance.
///
/// Conversion is performed via [`DateTime::with_timezone`], accounting for the difference in timezones.
fn from(src: DateTime<Utc>) -> Self {
src.with_timezone(&Local)
}
}

/// Convert a `DateTime<FixedOffset>` instance into a `DateTime<Utc>` instance.
impl From<DateTime<FixedOffset>> for DateTime<Utc> {
/// Convert this `DateTime<FixedOffset>` instance into a `DateTime<Utc>` instance.
///
/// Conversion is performed via [`DateTime::with_timezone`], accounting for the timezone
/// difference.
fn from(src: DateTime<FixedOffset>) -> Self {
src.with_timezone(&Utc)
}
}

/// Convert a `DateTime<FixedOffset>` instance into a `DateTime<Local>` instance.
#[cfg(feature="clock")]
impl From<DateTime<FixedOffset>> for DateTime<Local> {
/// Convert this `DateTime<FixedOffset>` instance into a `DateTime<Local>` instance.
///
/// Conversion is performed via [`DateTime::with_timezone`]. Returns the equivalent value in local
/// time.
fn from(src: DateTime<FixedOffset>) -> Self {
src.with_timezone(&Local)
}
}

/// Convert a `DateTime<Local>` instance into a `DateTime<Utc>` instance.
#[cfg(feature="clock")]
impl From<DateTime<Local>> for DateTime<Utc> {
/// Convert this `DateTime<Local>` instance into a `DateTime<Utc>` instance.
///
/// Conversion is performed via [`DateTime::with_timezone`], accounting for the difference in
/// timezones.
fn from(src: DateTime<Local>) -> Self {
src.with_timezone(&Utc)
}
}

/// Convert a `DateTime<Local>` instance into a `DateTime<FixedOffset>` instance.
#[cfg(feature="clock")]
impl From<DateTime<Local>> for DateTime<FixedOffset> {
/// Convert this `DateTime<Local>` instance into a `DateTime<FixedOffset>` instance.
///
/// Conversion is performed via [`DateTime::with_timezone`]. Note that the converted value returned
/// by this will be created with a fixed timezone offset of 0.
fn from(src: DateTime<Local>) -> Self {
src.with_timezone(&FixedOffset::east(0))
}
}

/// Maps the local datetime to other datetime with given conversion function.
fn map_local<Tz: TimeZone, F>(dt: &DateTime<Tz>, mut f: F) -> Option<DateTime<Tz>>
where F: FnMut(NaiveDateTime) -> Option<NaiveDateTime> {
f(dt.naive_local()).and_then(|datetime| dt.timezone().from_local_datetime(&datetime).single())
}

impl DateTime<Utc> {
/// Parses an RFC 2822 date and time string such as `Tue, 1 Jul 2003 10:52:37 +0200`,
/// then returns a new `DateTime<Utc>` instance corresponding to the UTC date/time accounting
/// for the difference between UTC and the parsed timezone.
pub fn parse_from_rfc2822(s: &str) -> ParseResult<DateTime<Utc>> {
DateTime::<FixedOffset>::parse_from_rfc2822(s)
.map(|result| result.into())
}

/// Parses an RFC 3339 and ISO 8601 date and time string such as `1996-12-19T16:39:57-08:00`,
/// then returns a new `DateTime<Utc>` instance corresponding to the UTC date/time accounting
/// for the difference between UTC and the parsed timezone.
///
/// Why isn't this named `parse_from_iso8601`? That's because ISO 8601 allows some freedom
/// over the syntax and RFC 3339 exercises that freedom to rigidly define a fixed format.
pub fn parse_from_rfc3339(s: &str) -> ParseResult<DateTime<Utc>> {
DateTime::<FixedOffset>::parse_from_rfc2822(s)
.map(|result| result.into())
}

/// Parses a string with the specified format string and
/// returns a new `DateTime` with a parsed `FixedOffset`.
/// See the [`format::strftime` module](./format/strftime/index.html)
/// on the supported escape sequences.
///
/// See also `Offset::datetime_from_str` which gives a local `DateTime` on specific time zone.
///
/// Note that this method *requires a timezone* in the string. See
/// [`NaiveDateTime::parse_from_str`](./naive/struct.NaiveDateTime.html#method.parse_from_str)
/// for a version that does not require a timezone in the to-be-parsed str.
///
/// # Example
///
/// ```rust
/// use chrono::{DateTime, TimeZone, Utc};
///
/// let dt = DateTime::<Utc>::parse_from_str(
/// "1983 Apr 13 12:09:14.274 +0100", "%Y %b %d %H:%M:%S%.3f %z");
/// assert_eq!(dt, Ok(Utc.ymd(1983, 4, 13).and_hms_milli(11, 9, 14, 274)));
/// ```
pub fn parse_from_str(s: &str, fmt: &str) -> ParseResult<DateTime<Utc>> {
DateTime::<FixedOffset>::parse_from_str(s, fmt)
.map(|result| result.into())
}
}

impl DateTime<FixedOffset> {
/// Parses an RFC 2822 date and time string such as `Tue, 1 Jul 2003 10:52:37 +0200`,
/// then returns a new `DateTime` with a parsed `FixedOffset`.
Expand Down Expand Up @@ -279,7 +394,7 @@ impl DateTime<FixedOffset> {
/// ```rust
/// use chrono::{DateTime, FixedOffset, TimeZone};
///
/// let dt = DateTime::parse_from_str(
/// let dt = DateTime::<FixedOffset>::parse_from_str(
/// "1983 Apr 13 12:09:14.274 +0000", "%Y %b %d %H:%M:%S%.3f %z");
/// assert_eq!(dt, Ok(FixedOffset::east(0).ymd(1983, 4, 13).and_hms_milli(12, 9, 14, 274)));
/// ```
Expand Down Expand Up @@ -616,6 +731,14 @@ impl<Tz: TimeZone> From<DateTime<Tz>> for SystemTime {
}
}

#[test]
fn test_auto_conversion() {
let utc_dt = Utc.ymd(2018, 9, 5).and_hms(23, 58, 0);
let cdt_dt = FixedOffset::west(5 * 60 * 60).ymd(2018, 9, 5).and_hms(18, 58, 0);
let utc_dt2: DateTime<Utc> = cdt_dt.into();
assert_eq!(utc_dt, utc_dt2);
}

#[cfg(all(test, any(feature = "rustc-serialize", feature = "serde")))]
fn test_encodable_json<FUtc, FFixed, E>(to_string_utc: FUtc, to_string_fixed: FFixed)
where FUtc: Fn(&DateTime<Utc>) -> Result<String, E>,
Expand Down Expand Up @@ -1509,13 +1632,13 @@ mod tests {
assert_eq!(EDT.ymd(2015, 2, 18).and_hms_micro(23, 59, 59, 1_234_567).to_rfc3339(),
"2015-02-18T23:59:60.234567+05:00");

assert_eq!(DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:16:09 +0000"),
assert_eq!(DateTime::<FixedOffset>::parse_from_rfc2822("Wed, 18 Feb 2015 23:16:09 +0000"),
Ok(FixedOffset::east(0).ymd(2015, 2, 18).and_hms(23, 16, 9)));
assert_eq!(DateTime::parse_from_rfc3339("2015-02-18T23:16:09Z"),
assert_eq!(DateTime::<FixedOffset>::parse_from_rfc3339("2015-02-18T23:16:09Z"),
Ok(FixedOffset::east(0).ymd(2015, 2, 18).and_hms(23, 16, 9)));
assert_eq!(DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:59:60 +0500"),
assert_eq!(DateTime::<FixedOffset>::parse_from_rfc2822("Wed, 18 Feb 2015 23:59:60 +0500"),
Ok(EDT.ymd(2015, 2, 18).and_hms_milli(23, 59, 59, 1_000)));
assert_eq!(DateTime::parse_from_rfc3339("2015-02-18T23:59:60.234567+05:00"),
assert_eq!(DateTime::<FixedOffset>::parse_from_rfc3339("2015-02-18T23:59:60.234567+05:00"),
Ok(EDT.ymd(2015, 2, 18).and_hms_micro(23, 59, 59, 1_234_567)));
}

Expand Down Expand Up @@ -1569,10 +1692,10 @@ mod tests {
#[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);
assert_eq!(DateTime::parse_from_str("2014-5-7T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"),
assert_eq!(DateTime::<FixedOffset>::parse_from_str("2014-5-7T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"),
Ok(ymdhms(2014, 5, 7, 12, 34, 56, 570*60))); // ignore offset
assert!(DateTime::parse_from_str("20140507000000", "%Y%m%d%H%M%S").is_err()); // no offset
assert!(DateTime::parse_from_str("Fri, 09 Aug 2013 23:54:35 GMT",
assert!(DateTime::<FixedOffset>::parse_from_str("20140507000000", "%Y%m%d%H%M%S").is_err()); // no offset
assert!(DateTime::<FixedOffset>::parse_from_str("Fri, 09 Aug 2013 23:54:35 GMT",
"%a, %d %b %Y %H:%M:%S GMT").is_err());
assert_eq!(Utc.datetime_from_str("Fri, 09 Aug 2013 23:54:35 GMT",
"%a, %d %b %Y %H:%M:%S GMT"),
Expand Down
10 changes: 5 additions & 5 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -273,11 +273,11 @@
//! assert_eq!("2014-11-28T21:00:09+09:00".parse::<DateTime<FixedOffset>>(), Ok(fixed_dt.clone()));
//!
//! // method 2
//! assert_eq!(DateTime::parse_from_str("2014-11-28 21:00:09 +09:00", "%Y-%m-%d %H:%M:%S %z"),
//! assert_eq!(DateTime::<FixedOffset>::parse_from_str("2014-11-28 21:00:09 +09:00", "%Y-%m-%d %H:%M:%S %z"),
//! Ok(fixed_dt.clone()));
//! assert_eq!(DateTime::parse_from_rfc2822("Fri, 28 Nov 2014 21:00:09 +0900"),
//! assert_eq!(DateTime::<FixedOffset>::parse_from_rfc2822("Fri, 28 Nov 2014 21:00:09 +0900"),
//! Ok(fixed_dt.clone()));
//! assert_eq!(DateTime::parse_from_rfc3339("2014-11-28T21:00:09+09:00"), Ok(fixed_dt.clone()));
//! assert_eq!(DateTime::<FixedOffset>::parse_from_rfc3339("2014-11-28T21:00:09+09:00"), Ok(fixed_dt.clone()));
//!
//! // method 3
//! assert_eq!(Utc.datetime_from_str("2014-11-28 12:00:09", "%Y-%m-%d %H:%M:%S"), Ok(dt.clone()));
Expand Down Expand Up @@ -307,7 +307,7 @@
//!
//! ```rust
//! # use chrono::DateTime;
//! # use chrono::Utc;
//! # use chrono::{FixedOffset, Utc};
//! // We need the trait in scope to use Utc::timestamp().
//! use chrono::TimeZone;
//!
Expand All @@ -316,7 +316,7 @@
//! assert_eq!(dt.to_rfc2822(), "Fri, 14 Jul 2017 02:40:00 +0000");
//!
//! // Get epoch value from a datetime:
//! let dt = DateTime::parse_from_rfc2822("Fri, 14 Jul 2017 02:40:00 +0000").unwrap();
//! let dt = DateTime::<FixedOffset>::parse_from_rfc2822("Fri, 14 Jul 2017 02:40:00 +0000").unwrap();
//! assert_eq!(dt.timestamp(), 1_500_000_000);
//! ```
//!
Expand Down
6 changes: 3 additions & 3 deletions src/naive/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,14 +159,14 @@ use format::{parse, Parsed, ParseError, ParseResult, DelayedFormat, StrftimeItem
/// and would be read back to the next non-leap second.
///
/// ~~~~
/// use chrono::{DateTime, Utc, TimeZone};
/// use chrono::{DateTime, Utc, FixedOffset, TimeZone};
///
/// let dt = Utc.ymd(2015, 6, 30).and_hms_milli(23, 56, 4, 1_000);
/// assert_eq!(format!("{:?}", dt), "2015-06-30T23:56:05Z");
///
/// let dt = Utc.ymd(2015, 6, 30).and_hms(23, 56, 5);
/// assert_eq!(format!("{:?}", dt), "2015-06-30T23:56:05Z");
/// assert_eq!(DateTime::parse_from_rfc3339("2015-06-30T23:56:05Z").unwrap(), dt);
/// assert_eq!(DateTime::<FixedOffset>::parse_from_rfc3339("2015-06-30T23:56:05Z").unwrap(), dt);
/// ~~~~
///
/// Since Chrono alone cannot determine any existence of leap seconds,
Expand Down Expand Up @@ -1431,7 +1431,7 @@ mod serde {
impl<'de> de::Visitor<'de> for NaiveTimeVisitor {
type Value = NaiveTime;

fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result
{
write!(formatter, "a formatted time string")
}
Expand Down