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

Backports #1066

Merged
merged 3 commits into from
May 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
57 changes: 32 additions & 25 deletions src/datetime/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -552,11 +552,13 @@ where
}

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`].
/// Parses an RFC 2822 date-and-time string into a `DateTime<FixedOffset>` value.
///
/// RFC 2822 is the internet message standard that specifies the
/// representation of times in HTTP and email headers.
/// This parses valid RFC 2822 datetime strings (such as `Tue, 1 Jul 2003 10:52:37 +0200`)
/// and returns a new [`DateTime`] instance with the parsed timezone as the [`FixedOffset`].
///
/// RFC 2822 is the internet message standard that specifies the representation of times in HTTP
/// and email headers.
///
/// The RFC 2822 standard allows arbitrary intermixed whitespace.
/// See [RFC 2822 Appendix A.5]
Expand All @@ -577,30 +579,35 @@ impl DateTime<FixedOffset> {
parsed.to_datetime()
}

/// 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`] with a parsed [`FixedOffset`].
/// Parses an RFC 3339 date-and-time string into a `DateTime<FixedOffset>` value.
///
/// Parses all valid RFC 3339 values (as well as the subset of valid ISO 8601 values that are
/// also valid RFC 3339 date-and-time values) and returns a new [`DateTime`] with a
/// [`FixedOffset`] corresponding to the parsed timezone. While RFC 3339 values come in a wide
/// variety of shapes and sizes, `1996-12-19T16:39:57-08:00` is an example of the most commonly
/// encountered variety of RFC 3339 formats.
///
/// 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.
/// Why isn't this named `parse_from_iso8601`? That's because ISO 8601 allows representing
/// values in a wide range of formats, only some of which represent actual date-and-time
/// instances (rather than periods, ranges, dates, or times). Some valid ISO 8601 values are
/// 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())?;
parsed.to_datetime()
}

/// Parses a string with the specified format string and returns a new
/// [`DateTime`] with a parsed [`FixedOffset`].
/// Parses a string from a user-specified format into a `DateTime<FixedOffset>` value.
///
/// See the [`crate::format::strftime`] module on the supported escape
/// sequences.
/// Note that this method *requires a timezone* in the input 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. The returned
/// [`DateTime`] value will have a [`FixedOffset`] reflecting the parsed timezone.
///
/// See also [`TimeZone::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`]
/// for a version that does not require a timezone in the to-be-parsed str.
/// See the [`format::strftime` module](./format/strftime/index.html) for supported format
/// sequences.
///
/// # Example
///
Expand Down Expand Up @@ -645,10 +652,10 @@ where
}

/// Return an RFC 3339 and ISO 8601 date and time string with subseconds
/// formatted as per a `SecondsFormat`.
/// formatted as per `SecondsFormat`.
///
/// If passed `use_z` true and the timezone is UTC (offset 0), use 'Z', as
/// per [`Fixed::TimezoneOffsetColonZ`] If passed `use_z` false, use
/// If `use_z` is true and the timezone is UTC (offset 0), uses `Z` as
/// per [`Fixed::TimezoneOffsetColonZ`]. If `use_z` is false, uses
/// [`Fixed::TimezoneOffsetColon`]
///
/// # Examples
Expand Down Expand Up @@ -727,9 +734,9 @@ where
DelayedFormat::new_with_offset(Some(local.date()), Some(local.time()), &self.offset, items)
}

/// Formats the combined date and time with the specified format string.
/// See the [`crate::format::strftime`] module
/// on the supported escape sequences.
/// Formats the combined date and time per the specified format string.
///
/// See the [`crate::format::strftime`] module for the supported escape sequences.
///
/// # Example
/// ```rust
Expand Down Expand Up @@ -771,7 +778,7 @@ where
)
}

/// Formats the combined date and time with the specified format string and
/// Formats the combined date and time per the specified format string and
/// locale.
///
/// See the [`crate::format::strftime`] module on the supported escape
Expand Down
42 changes: 24 additions & 18 deletions src/format/parsed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
//! A collection of parsed date and time items.
//! They can be constructed incrementally while being checked for consistency.

use num_traits::ToPrimitive;
use core::convert::TryFrom;

use super::{ParseResult, IMPOSSIBLE, NOT_ENOUGH, OUT_OF_RANGE};
use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime};
Expand Down Expand Up @@ -136,7 +136,7 @@ impl Parsed {
/// Tries to set the [`year`](#structfield.year) field from given value.
#[inline]
pub fn set_year(&mut self, value: i64) -> ParseResult<()> {
set_if_consistent(&mut self.year, value.to_i32().ok_or(OUT_OF_RANGE)?)
set_if_consistent(&mut self.year, i32::try_from(value).map_err(|_| OUT_OF_RANGE)?)
}

/// Tries to set the [`year_div_100`](#structfield.year_div_100) field from given value.
Expand All @@ -145,7 +145,7 @@ impl Parsed {
if value < 0 {
return Err(OUT_OF_RANGE);
}
set_if_consistent(&mut self.year_div_100, value.to_i32().ok_or(OUT_OF_RANGE)?)
set_if_consistent(&mut self.year_div_100, i32::try_from(value).map_err(|_| OUT_OF_RANGE)?)
}

/// Tries to set the [`year_mod_100`](#structfield.year_mod_100) field from given value.
Expand All @@ -154,13 +154,13 @@ impl Parsed {
if value < 0 {
return Err(OUT_OF_RANGE);
}
set_if_consistent(&mut self.year_mod_100, value.to_i32().ok_or(OUT_OF_RANGE)?)
set_if_consistent(&mut self.year_mod_100, i32::try_from(value).map_err(|_| OUT_OF_RANGE)?)
}

/// Tries to set the [`isoyear`](#structfield.isoyear) field from given value.
#[inline]
pub fn set_isoyear(&mut self, value: i64) -> ParseResult<()> {
set_if_consistent(&mut self.isoyear, value.to_i32().ok_or(OUT_OF_RANGE)?)
set_if_consistent(&mut self.isoyear, i32::try_from(value).map_err(|_| OUT_OF_RANGE)?)
}

/// Tries to set the [`isoyear_div_100`](#structfield.isoyear_div_100) field from given value.
Expand All @@ -169,7 +169,10 @@ impl Parsed {
if value < 0 {
return Err(OUT_OF_RANGE);
}
set_if_consistent(&mut self.isoyear_div_100, value.to_i32().ok_or(OUT_OF_RANGE)?)
set_if_consistent(
&mut self.isoyear_div_100,
i32::try_from(value).map_err(|_| OUT_OF_RANGE)?,
)
}

/// Tries to set the [`isoyear_mod_100`](#structfield.isoyear_mod_100) field from given value.
Expand All @@ -178,31 +181,34 @@ impl Parsed {
if value < 0 {
return Err(OUT_OF_RANGE);
}
set_if_consistent(&mut self.isoyear_mod_100, value.to_i32().ok_or(OUT_OF_RANGE)?)
set_if_consistent(
&mut self.isoyear_mod_100,
i32::try_from(value).map_err(|_| OUT_OF_RANGE)?,
)
}

/// Tries to set the [`month`](#structfield.month) field from given value.
#[inline]
pub fn set_month(&mut self, value: i64) -> ParseResult<()> {
set_if_consistent(&mut self.month, value.to_u32().ok_or(OUT_OF_RANGE)?)
set_if_consistent(&mut self.month, u32::try_from(value).map_err(|_| OUT_OF_RANGE)?)
}

/// Tries to set the [`week_from_sun`](#structfield.week_from_sun) field from given value.
#[inline]
pub fn set_week_from_sun(&mut self, value: i64) -> ParseResult<()> {
set_if_consistent(&mut self.week_from_sun, value.to_u32().ok_or(OUT_OF_RANGE)?)
set_if_consistent(&mut self.week_from_sun, u32::try_from(value).map_err(|_| OUT_OF_RANGE)?)
}

/// Tries to set the [`week_from_mon`](#structfield.week_from_mon) field from given value.
#[inline]
pub fn set_week_from_mon(&mut self, value: i64) -> ParseResult<()> {
set_if_consistent(&mut self.week_from_mon, value.to_u32().ok_or(OUT_OF_RANGE)?)
set_if_consistent(&mut self.week_from_mon, u32::try_from(value).map_err(|_| OUT_OF_RANGE)?)
}

/// Tries to set the [`isoweek`](#structfield.isoweek) field from given value.
#[inline]
pub fn set_isoweek(&mut self, value: i64) -> ParseResult<()> {
set_if_consistent(&mut self.isoweek, value.to_u32().ok_or(OUT_OF_RANGE)?)
set_if_consistent(&mut self.isoweek, u32::try_from(value).map_err(|_| OUT_OF_RANGE)?)
}

/// Tries to set the [`weekday`](#structfield.weekday) field from given value.
Expand All @@ -214,13 +220,13 @@ impl Parsed {
/// Tries to set the [`ordinal`](#structfield.ordinal) field from given value.
#[inline]
pub fn set_ordinal(&mut self, value: i64) -> ParseResult<()> {
set_if_consistent(&mut self.ordinal, value.to_u32().ok_or(OUT_OF_RANGE)?)
set_if_consistent(&mut self.ordinal, u32::try_from(value).map_err(|_| OUT_OF_RANGE)?)
}

/// Tries to set the [`day`](#structfield.day) field from given value.
#[inline]
pub fn set_day(&mut self, value: i64) -> ParseResult<()> {
set_if_consistent(&mut self.day, value.to_u32().ok_or(OUT_OF_RANGE)?)
set_if_consistent(&mut self.day, u32::try_from(value).map_err(|_| OUT_OF_RANGE)?)
}

/// Tries to set the [`hour_div_12`](#structfield.hour_div_12) field from given value.
Expand All @@ -244,7 +250,7 @@ impl Parsed {
/// [`hour_mod_12`](#structfield.hour_mod_12) fields from given value.
#[inline]
pub fn set_hour(&mut self, value: i64) -> ParseResult<()> {
let v = value.to_u32().ok_or(OUT_OF_RANGE)?;
let v = u32::try_from(value).map_err(|_| OUT_OF_RANGE)?;
set_if_consistent(&mut self.hour_div_12, v / 12)?;
set_if_consistent(&mut self.hour_mod_12, v % 12)?;
Ok(())
Expand All @@ -253,19 +259,19 @@ impl Parsed {
/// Tries to set the [`minute`](#structfield.minute) field from given value.
#[inline]
pub fn set_minute(&mut self, value: i64) -> ParseResult<()> {
set_if_consistent(&mut self.minute, value.to_u32().ok_or(OUT_OF_RANGE)?)
set_if_consistent(&mut self.minute, u32::try_from(value).map_err(|_| OUT_OF_RANGE)?)
}

/// Tries to set the [`second`](#structfield.second) field from given value.
#[inline]
pub fn set_second(&mut self, value: i64) -> ParseResult<()> {
set_if_consistent(&mut self.second, value.to_u32().ok_or(OUT_OF_RANGE)?)
set_if_consistent(&mut self.second, u32::try_from(value).map_err(|_| OUT_OF_RANGE)?)
}

/// Tries to set the [`nanosecond`](#structfield.nanosecond) field from given value.
#[inline]
pub fn set_nanosecond(&mut self, value: i64) -> ParseResult<()> {
set_if_consistent(&mut self.nanosecond, value.to_u32().ok_or(OUT_OF_RANGE)?)
set_if_consistent(&mut self.nanosecond, u32::try_from(value).map_err(|_| OUT_OF_RANGE)?)
}

/// Tries to set the [`timestamp`](#structfield.timestamp) field from given value.
Expand All @@ -277,7 +283,7 @@ impl Parsed {
/// Tries to set the [`offset`](#structfield.offset) field from given value.
#[inline]
pub fn set_offset(&mut self, value: i64) -> ParseResult<()> {
set_if_consistent(&mut self.offset, value.to_i32().ok_or(OUT_OF_RANGE)?)
set_if_consistent(&mut self.offset, i32::try_from(value).map_err(|_| OUT_OF_RANGE)?)
}

/// Returns a parsed naive date out of given fields.
Expand Down
33 changes: 31 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,8 @@ mod oldtime;
// this reexport is to aid the transition and should not be in the prelude!
pub use oldtime::{Duration, OutOfRangeError};

use core::fmt;

#[cfg(feature = "__doctest")]
#[cfg_attr(feature = "__doctest", cfg(doctest))]
use doc_comment::doctest;
Expand Down Expand Up @@ -506,14 +508,41 @@ pub use naive::__BenchYearFlags;
/// Serialization/Deserialization with serde.
///
/// This module provides default implementations for `DateTime` using the [RFC 3339][1] format and various
/// alternatives for use with serde's [`with` annotation][1].
/// alternatives for use with serde's [`with` annotation][2].
///
/// *Available on crate feature 'serde' only.*
///
/// [1]: https://tools.ietf.org/html/rfc3339
/// [2]: https://serde.rs/attributes.html#field-attributes
/// [2]: https://serde.rs/field-attrs.html#with
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
pub mod serde {
pub use super::datetime::serde::*;
}

/// Out of range error type used in various converting APIs
#[derive(Clone, Copy, Hash, PartialEq, Eq)]
pub struct OutOfRange {
_private: (),
}

impl OutOfRange {
const fn new() -> OutOfRange {
OutOfRange { _private: () }
}
}

impl fmt::Display for OutOfRange {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "out of range")
}
}

impl fmt::Debug for OutOfRange {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "out of range")
}
}

#[cfg(feature = "std")]
impl std::error::Error for OutOfRange {}
49 changes: 45 additions & 4 deletions src/month.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
use core::fmt;
use core::{convert::TryFrom, fmt};

#[cfg(feature = "rkyv")]
use rkyv::{Archive, Deserialize, Serialize};

use crate::OutOfRange;

/// The month of the year.
///
/// This enum is just a convenience implementation.
/// The month in dates created by DateLike objects does not return this enum.
///
/// It is possible to convert from a date to a month independently
/// ```
/// use num_traits::FromPrimitive;
/// # use std::convert::TryFrom;
/// use chrono::prelude::*;
/// let date = Utc.with_ymd_and_hms(2019, 10, 28, 9, 10, 11).unwrap();
/// // `2019-10-28T09:10:11Z`
/// let month = Month::from_u32(date.month());
/// let month = Month::try_from(u8::try_from(date.month()).unwrap()).ok();
/// assert_eq!(month, Some(Month::October))
/// ```
/// Or from a Month to an integer usable by dates
Expand Down Expand Up @@ -157,6 +159,28 @@ impl Month {
}
}

impl TryFrom<u8> for Month {
type Error = OutOfRange;

fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
1 => Ok(Month::January),
2 => Ok(Month::February),
3 => Ok(Month::March),
4 => Ok(Month::April),
5 => Ok(Month::May),
6 => Ok(Month::June),
7 => Ok(Month::July),
8 => Ok(Month::August),
9 => Ok(Month::September),
10 => Ok(Month::October),
11 => Ok(Month::November),
12 => Ok(Month::December),
_ => Err(OutOfRange::new()),
}
}
}

impl num_traits::FromPrimitive for Month {
/// Returns an `Option<Month>` from a i64, assuming a 1-index, January = 1.
///
Expand Down Expand Up @@ -325,8 +349,25 @@ mod month_serde {

#[cfg(test)]
mod tests {
use core::convert::TryFrom;

use super::Month;
use crate::{Datelike, TimeZone, Utc};
use crate::{Datelike, OutOfRange, TimeZone, Utc};

#[test]
fn test_month_enum_try_from() {
assert_eq!(Month::try_from(1), Ok(Month::January));
assert_eq!(Month::try_from(2), Ok(Month::February));
assert_eq!(Month::try_from(12), Ok(Month::December));
assert_eq!(Month::try_from(13), Err(OutOfRange::new()));

let date = Utc.with_ymd_and_hms(2019, 10, 28, 9, 10, 11).unwrap();
assert_eq!(Month::try_from(date.month() as u8), Ok(Month::October));

let month = Month::January;
let dt = Utc.with_ymd_and_hms(2019, month.number_from_month(), 28, 9, 10, 11).unwrap();
assert_eq!((dt.year(), dt.month(), dt.day()), (2019, 1, 28));
}

#[test]
fn test_month_enum_primitive_parse() {
Expand Down