diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 146a86df..e5e48c43 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -32,7 +32,7 @@ jobs: rust: - stable - nightly - - 1.48 + - 1.51 features: - default - ssl diff --git a/Cargo.toml b/Cargo.toml index 900a37fe..6e77eb18 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ chunked_transfer = "1" openssl = { version = "0.10", optional = true } url = "2" log = "0.4" -time = { version = "0.3", features = [ "std", "formatting" ] } +time = { version = "0.3", features = [ "std", "formatting", "macros", "parsing" ] } [dev-dependencies] rustc-serialize = "0.3" diff --git a/src/common.rs b/src/common.rs index 12d06be8..5ab07374 100644 --- a/src/common.rs +++ b/src/common.rs @@ -2,7 +2,7 @@ use ascii::{AsciiStr, AsciiString, FromAsciiError}; use std::cmp::Ordering; use std::fmt::{self, Display, Formatter}; use std::str::FromStr; -use time::{format_description, OffsetDateTime}; +use time::OffsetDateTime; /// Status code of a request or response. #[derive(Eq, PartialEq, Copy, Clone, Debug, Ord, PartialOrd)] @@ -394,7 +394,7 @@ impl From<(u8, u8)> for HTTPVersion { } } -/// Represents the current date, expressed in RFC 1123 format, e.g. Sun, 06 Nov 1994 08:49:37 GMT +/// Represents the current date, expressed in RFC 7231 (IMF-fixdate) format, e.g. Sun, 06 Nov 1994 08:49:37 GMT #[allow(clippy::upper_case_acronyms)] pub struct HTTPDate { d: OffsetDateTime, @@ -408,13 +408,18 @@ impl HTTPDate { } } +/// Format description for emitting a [`time::PrimitiveDateTime`] or [`time::OffsetDateTime`] in the format required by RFC7231 +/// +/// `format_description!` is a procedural macro, but since `time-rs` doesn't provide any other way +/// to construct the format description slice in a `const` context we haven't much choice. +const IMF_FIXDATE_FORMAT: &[time::format_description::FormatItem<'static>] = time::macros::format_description!( + "[weekday repr:short], [day padding:zero] [month repr:short] [year repr:full] [hour repr:24 padding:zero]:[minute padding:zero]:[second padding:zero] GMT" +); + impl ToString for HTTPDate { fn to_string(&self) -> String { - // Note: This can probably be made Self::format however parse is not a const function, making this difficult. - let format = format_description::parse("%a, %e %b %Y %H:%M:%S GMT") - .expect("Cannot fail. The format string is correct."); self.d - .format(&format) + .format(&IMF_FIXDATE_FORMAT) .expect("Cannot fail with this format under any reasonable conditions.") } } @@ -422,6 +427,8 @@ impl ToString for HTTPDate { #[cfg(test)] mod test { use super::Header; + use crate::common::HTTPDate; + use time::OffsetDateTime; #[test] fn test_parse_header() { @@ -433,6 +440,15 @@ mod test { assert!("hello world".parse::
().is_err()); } + #[test] + fn formats_date_correctly() { + let http_date = HTTPDate { + d: OffsetDateTime::from_unix_timestamp(420895020).unwrap(), + }; + + assert_eq!(http_date.to_string(), "Wed, 04 May 1983 11:17:00 GMT") + } + #[test] fn test_parse_header_with_doublecolon() { let header: Header = "Time: 20: 34".parse().unwrap();