use std::fmt::{self, Display}; use std::io::Write; use std::str::FromStr; use std::time::{SystemTime, UNIX_EPOCH}; use bytes::{buf::BufMutExt, BytesMut}; use http::header::{HeaderValue, InvalidHeaderValue}; use time::{PrimitiveDateTime, OffsetDateTime, offset}; use crate::error::ParseError; use crate::header::IntoHeaderValue; use crate::time_parser; /// A timestamp with HTTP formatting and parsing #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct HttpDate(OffsetDateTime); impl FromStr for HttpDate { type Err = ParseError; fn from_str(s: &str) -> Result { match time_parser::parse_http_date(s) { Some(t) => Ok(HttpDate(t.assume_utc())), None => Err(ParseError::Header) } } } impl Display for HttpDate { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(&self.0.format("%a, %d %b %Y %H:%M:%S GMT"), f) } } impl From for HttpDate { fn from(dt: OffsetDateTime) -> HttpDate { HttpDate(dt) } } impl From for HttpDate { fn from(sys: SystemTime) -> HttpDate { HttpDate(PrimitiveDateTime::from(sys).assume_utc()) } } impl IntoHeaderValue for HttpDate { type Error = InvalidHeaderValue; fn try_into(self) -> Result { let mut wrt = BytesMut::with_capacity(29).writer(); write!(wrt, "{}", self.0.to_offset(offset!(UTC)).format("%a, %d %b %Y %H:%M:%S GMT")).unwrap(); HeaderValue::from_maybe_shared(wrt.get_mut().split().freeze()) } } impl From for SystemTime { fn from(date: HttpDate) -> SystemTime { let dt = date.0; let epoch = OffsetDateTime::unix_epoch(); UNIX_EPOCH + (dt - epoch) } } #[cfg(test)] mod tests { use super::HttpDate; use time::{PrimitiveDateTime, date, time}; #[test] fn test_date() { let nov_07 = HttpDate(PrimitiveDateTime::new( date!(1994-11-07), time!(8:48:37) ).assume_utc()); assert_eq!( "Sun, 07 Nov 1994 08:48:37 GMT".parse::().unwrap(), nov_07 ); assert_eq!( "Sunday, 07-Nov-94 08:48:37 GMT" .parse::() .unwrap(), nov_07 ); assert_eq!( "Sun Nov 7 08:48:37 1994".parse::().unwrap(), nov_07 ); assert!("this-is-no-date".parse::().is_err()); } }