diff --git a/src/format/mod.rs b/src/format/mod.rs index 7c9c3b7dd0..9591fa4d16 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -420,7 +420,7 @@ impl Error for ParseError { } // to be used in this module and submodules -const OUT_OF_RANGE: ParseError = ParseError(ParseErrorKind::OutOfRange); +pub(crate) const OUT_OF_RANGE: ParseError = ParseError(ParseErrorKind::OutOfRange); const IMPOSSIBLE: ParseError = ParseError(ParseErrorKind::Impossible); const NOT_ENOUGH: ParseError = ParseError(ParseErrorKind::NotEnough); const INVALID: ParseError = ParseError(ParseErrorKind::Invalid); @@ -838,7 +838,7 @@ mod parsed; // due to the size of parsing routines, they are in separate modules. mod parse; -mod scan; +pub(crate) mod scan; pub mod strftime; diff --git a/src/format/scan.rs b/src/format/scan.rs index 2273dec7d6..00e06c7c13 100644 --- a/src/format/scan.rs +++ b/src/format/scan.rs @@ -197,7 +197,7 @@ pub(super) fn space(s: &str) -> ParseResult<&str> { } /// Consumes any number (including zero) of colon or spaces. -pub(super) fn colon_or_space(s: &str) -> ParseResult<&str> { +pub(crate) fn colon_or_space(s: &str) -> ParseResult<&str> { Ok(s.trim_start_matches(|c: char| c == ':' || c.is_whitespace())) } @@ -205,7 +205,7 @@ pub(super) fn colon_or_space(s: &str) -> ParseResult<&str> { /// /// The additional `colon` may be used to parse a mandatory or optional `:` /// between hours and minutes, and should return either a new suffix or `Err` when parsing fails. -pub(super) fn timezone_offset(s: &str, consume_colon: F) -> ParseResult<(&str, i32)> +pub(crate) fn timezone_offset(s: &str, consume_colon: F) -> ParseResult<(&str, i32)> where F: FnMut(&str) -> ParseResult<&str>, { diff --git a/src/offset/fixed.rs b/src/offset/fixed.rs index 246d6666ea..fcf445fd97 100644 --- a/src/offset/fixed.rs +++ b/src/offset/fixed.rs @@ -5,14 +5,18 @@ use core::fmt; use core::ops::{Add, Sub}; +use core::str::FromStr; #[cfg(feature = "rkyv")] use rkyv::{Archive, Deserialize, Serialize}; use super::{LocalResult, Offset, TimeZone}; +use crate::format::scan; +use crate::format::OUT_OF_RANGE; use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime}; use crate::oldtime::Duration as OldDuration; use crate::DateTime; +use crate::ParseError; use crate::Timelike; /// The time zone with fixed offset, from UTC-23:59:59 to UTC+23:59:59. @@ -113,6 +117,15 @@ impl FixedOffset { } } +/// Parsing a `str` into a `FixedOffset` uses the format [`%z`](crate::format::strftime). +impl FromStr for FixedOffset { + type Err = ParseError; + fn from_str(s: &str) -> Result { + let (_, offset) = scan::timezone_offset(s, scan::colon_or_space)?; + Self::east_opt(offset).ok_or(OUT_OF_RANGE) + } +} + impl TimeZone for FixedOffset { type Offset = FixedOffset; @@ -246,6 +259,7 @@ impl Sub for DateTime { mod tests { use super::FixedOffset; use crate::offset::TimeZone; + use std::str::FromStr; #[test] fn test_date_extreme_offset() { @@ -292,4 +306,14 @@ mod tests { "2012-03-04T05:06:07-23:59:59".to_string() ); } + + #[test] + fn test_parse_offset() { + let offset = FixedOffset::from_str("-0500").unwrap(); + assert_eq!(offset.local_minus_utc, -5 * 3600); + let offset = FixedOffset::from_str("-08:00").unwrap(); + assert_eq!(offset.local_minus_utc, -8 * 3600); + let offset = FixedOffset::from_str("+06:30").unwrap(); + assert_eq!(offset.local_minus_utc, (6 * 3600) + 1800); + } }