requiem_http/header/shared/
httpdate.rs1use std::fmt::{self, Display};
2use std::io::Write;
3use std::str::FromStr;
4use std::time::{Duration, SystemTime, UNIX_EPOCH};
5
6use bytes::{buf::BufMutExt, BytesMut};
7use http::header::{HeaderValue, InvalidHeaderValue};
8
9use crate::error::ParseError;
10use crate::header::IntoHeaderValue;
11
12#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
14pub struct HttpDate(time::Tm);
15
16impl FromStr for HttpDate {
17 type Err = ParseError;
18
19 fn from_str(s: &str) -> Result<HttpDate, ParseError> {
20 match time::strptime(s, "%a, %d %b %Y %T %Z")
21 .or_else(|_| time::strptime(s, "%A, %d-%b-%y %T %Z"))
22 .or_else(|_| time::strptime(s, "%c"))
23 {
24 Ok(t) => Ok(HttpDate(t)),
25 Err(_) => Err(ParseError::Header),
26 }
27 }
28}
29
30impl Display for HttpDate {
31 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32 fmt::Display::fmt(&self.0.to_utc().rfc822(), f)
33 }
34}
35
36impl From<time::Tm> for HttpDate {
37 fn from(tm: time::Tm) -> HttpDate {
38 HttpDate(tm)
39 }
40}
41
42impl From<SystemTime> for HttpDate {
43 fn from(sys: SystemTime) -> HttpDate {
44 let tmspec = match sys.duration_since(UNIX_EPOCH) {
45 Ok(dur) => {
46 time::Timespec::new(dur.as_secs() as i64, dur.subsec_nanos() as i32)
47 }
48 Err(err) => {
49 let neg = err.duration();
50 time::Timespec::new(
51 -(neg.as_secs() as i64),
52 -(neg.subsec_nanos() as i32),
53 )
54 }
55 };
56 HttpDate(time::at_utc(tmspec))
57 }
58}
59
60impl IntoHeaderValue for HttpDate {
61 type Error = InvalidHeaderValue;
62
63 fn try_into(self) -> Result<HeaderValue, Self::Error> {
64 let mut wrt = BytesMut::with_capacity(29).writer();
65 write!(wrt, "{}", self.0.rfc822()).unwrap();
66 HeaderValue::from_maybe_shared(wrt.get_mut().split().freeze())
67 }
68}
69
70impl From<HttpDate> for SystemTime {
71 fn from(date: HttpDate) -> SystemTime {
72 let spec = date.0.to_timespec();
73 if spec.sec >= 0 {
74 UNIX_EPOCH + Duration::new(spec.sec as u64, spec.nsec as u32)
75 } else {
76 UNIX_EPOCH - Duration::new(spec.sec as u64, spec.nsec as u32)
77 }
78 }
79}
80
81#[cfg(test)]
82mod tests {
83 use super::HttpDate;
84 use time::Tm;
85
86 const NOV_07: HttpDate = HttpDate(Tm {
87 tm_nsec: 0,
88 tm_sec: 37,
89 tm_min: 48,
90 tm_hour: 8,
91 tm_mday: 7,
92 tm_mon: 10,
93 tm_year: 94,
94 tm_wday: 0,
95 tm_isdst: 0,
96 tm_yday: 0,
97 tm_utcoff: 0,
98 });
99
100 #[test]
101 fn test_date() {
102 assert_eq!(
103 "Sun, 07 Nov 1994 08:48:37 GMT".parse::<HttpDate>().unwrap(),
104 NOV_07
105 );
106 assert_eq!(
107 "Sunday, 07-Nov-94 08:48:37 GMT"
108 .parse::<HttpDate>()
109 .unwrap(),
110 NOV_07
111 );
112 assert_eq!(
113 "Sun Nov 7 08:48:37 1994".parse::<HttpDate>().unwrap(),
114 NOV_07
115 );
116 assert!("this-is-no-date".parse::<HttpDate>().is_err());
117 }
118}