use thiserror::Error;
const HTTP_DATE_RFC850: &str = "%A, %d-%b-%y %H:%M:%S GMT";
const HTTP_DATE_ASCTIME: &str = "%a %b %e %H:%M:%S %Y";
#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
#[non_exhaustive]
pub enum HttpDateError {
#[error("not an HTTP Date value")]
HttpDate,
}
pub fn parse_http_date(text: &str) -> Result<i64, HttpDateError> {
let text = text.trim();
if let Ok(t) = jiff::fmt::rfc2822::DateTimeParser::new().parse_timestamp(text) {
return Ok(t.as_microsecond());
}
[HTTP_DATE_RFC850, HTTP_DATE_ASCTIME]
.iter()
.find_map(|format| {
jiff::fmt::strtime::parse(format, text)
.and_then(|broken| broken.to_datetime())
.and_then(|datetime| datetime.to_zoned(jiff::tz::TimeZone::UTC))
.ok()
})
.map(|zoned| zoned.timestamp().as_microsecond())
.ok_or(HttpDateError::HttpDate)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_three_forms_agree() {
let expected = 784_111_777 * 1_000_000;
for text in [
"Sun, 06 Nov 1994 08:49:37 GMT",
"Sunday, 06-Nov-94 08:49:37 GMT",
"Sun Nov 6 08:49:37 1994",
] {
assert_eq!(parse_http_date(text), Ok(expected), "{text}");
}
}
#[test]
fn garbage_is_rejected() {
assert_eq!(parse_http_date("yesterday"), Err(HttpDateError::HttpDate));
}
}