hyper_sync/header/shared/
httpdate.rs

1use std::fmt::{self, Display};
2use std::str::FromStr;
3use std::time::{Duration, SystemTime, UNIX_EPOCH};
4
5use time;
6
7/// A timestamp with HTTP formatting and parsing
8//   Prior to 1995, there were three different formats commonly used by
9//   servers to communicate timestamps.  For compatibility with old
10//   implementations, all three are defined here.  The preferred format is
11//   a fixed-length and single-zone subset of the date and time
12//   specification used by the Internet Message Format [RFC5322].
13//
14//     HTTP-date    = IMF-fixdate / obs-date
15//
16//   An example of the preferred format is
17//
18//     Sun, 06 Nov 1994 08:49:37 GMT    ; IMF-fixdate
19//
20//   Examples of the two obsolete formats are
21//
22//     Sunday, 06-Nov-94 08:49:37 GMT   ; obsolete RFC 850 format
23//     Sun Nov  6 08:49:37 1994         ; ANSI C's asctime() format
24//
25//   A recipient that parses a timestamp value in an HTTP header field
26//   MUST accept all three HTTP-date formats.  When a sender generates a
27//   header field that contains one or more timestamps defined as
28//   HTTP-date, the sender MUST generate those timestamps in the
29//   IMF-fixdate format.
30#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
31pub struct HttpDate(time::Tm);
32
33impl FromStr for HttpDate {
34    type Err = ::Error;
35    fn from_str(s: &str) -> ::Result<HttpDate> {
36        match time::strptime(s, "%a, %d %b %Y %T %Z").or_else(|_| {
37            time::strptime(s, "%A, %d-%b-%y %T %Z")
38            }).or_else(|_| {
39                time::strptime(s, "%c")
40                }) {
41                    Ok(t) => Ok(HttpDate(t)),
42                    Err(_) => Err(::Error::Header),
43                    }
44    }
45}
46
47impl Display for HttpDate {
48    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
49        fmt::Display::fmt(&self.0.to_utc().rfc822(), f)
50    }
51}
52
53impl From<SystemTime> for HttpDate {
54    fn from(sys: SystemTime) -> HttpDate {
55        let tmspec = match sys.duration_since(UNIX_EPOCH) {
56            Ok(dur) => {
57                time::Timespec::new(dur.as_secs() as i64, dur.subsec_nanos() as i32)
58            },
59            Err(err) => {
60                let neg = err.duration();
61                time::Timespec::new(-(neg.as_secs() as i64), -(neg.subsec_nanos() as i32))
62            },
63        };
64        HttpDate(time::at_utc(tmspec))
65    }
66}
67
68impl From<HttpDate> for SystemTime {
69    fn from(date: HttpDate) -> SystemTime {
70        let spec = date.0.to_timespec();
71        if spec.sec >= 0 {
72            UNIX_EPOCH + Duration::new(spec.sec as u64, spec.nsec as u32)
73        } else {
74            UNIX_EPOCH - Duration::new(spec.sec as u64, spec.nsec as u32)
75        }
76    }
77}
78
79#[cfg(test)]
80mod tests {
81    use time::Tm;
82    use super::HttpDate;
83
84    const NOV_07: HttpDate = HttpDate(Tm {
85        tm_nsec: 0,
86        tm_sec: 37,
87        tm_min: 48,
88        tm_hour: 8,
89        tm_mday: 7,
90        tm_mon: 10,
91        tm_year: 94,
92        tm_wday: 0,
93        tm_isdst: 0,
94        tm_yday: 0,
95        tm_utcoff: 0,
96    });
97
98    #[test]
99    fn test_imf_fixdate() {
100        assert_eq!("Sun, 07 Nov 1994 08:48:37 GMT".parse::<HttpDate>().unwrap(), NOV_07);
101    }
102
103    #[test]
104    fn test_rfc_850() {
105        assert_eq!("Sunday, 07-Nov-94 08:48:37 GMT".parse::<HttpDate>().unwrap(), NOV_07);
106    }
107
108    #[test]
109    fn test_asctime() {
110        assert_eq!("Sun Nov  7 08:48:37 1994".parse::<HttpDate>().unwrap(), NOV_07);
111    }
112
113    #[test]
114    fn test_no_date() {
115        assert!("this-is-no-date".parse::<HttpDate>().is_err());
116    }
117}