broadcast_common/
http_date.rs1use thiserror::Error;
6
7const HTTP_DATE_RFC850: &str = "%A, %d-%b-%y %H:%M:%S GMT";
8const HTTP_DATE_ASCTIME: &str = "%a %b %e %H:%M:%S %Y";
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
12#[non_exhaustive]
13pub enum HttpDateError {
14 #[error("not an HTTP Date value")]
16 HttpDate,
17}
18
19pub fn parse_http_date(text: &str) -> Result<i64, HttpDateError> {
28 let text = text.trim();
29 if let Ok(t) = jiff::fmt::rfc2822::DateTimeParser::new().parse_timestamp(text) {
30 return Ok(t.as_microsecond());
31 }
32 [HTTP_DATE_RFC850, HTTP_DATE_ASCTIME]
33 .iter()
34 .find_map(|format| {
35 jiff::fmt::strtime::parse(format, text)
36 .and_then(|broken| broken.to_datetime())
37 .and_then(|datetime| datetime.to_zoned(jiff::tz::TimeZone::UTC))
38 .ok()
39 })
40 .map(|zoned| zoned.timestamp().as_microsecond())
41 .ok_or(HttpDateError::HttpDate)
42}
43
44#[cfg(test)]
45mod tests {
46 use super::*;
47
48 #[test]
49 fn the_three_forms_agree() {
50 let expected = 784_111_777 * 1_000_000;
51 for text in [
52 "Sun, 06 Nov 1994 08:49:37 GMT",
53 "Sunday, 06-Nov-94 08:49:37 GMT",
54 "Sun Nov 6 08:49:37 1994",
55 ] {
56 assert_eq!(parse_http_date(text), Ok(expected), "{text}");
57 }
58 }
59
60 #[test]
61 fn garbage_is_rejected() {
62 assert_eq!(parse_http_date("yesterday"), Err(HttpDateError::HttpDate));
63 }
64}