use core::{
cell::RefCell,
fmt::{self, Write},
ops::Deref,
time::Duration,
};
use std::{rc::Rc, time::SystemTime};
use tokio::{
task::JoinHandle,
time::{Instant, interval},
};
use crate::http::header::HeaderValue;
const DATE_VALUE_LENGTH: usize = 29;
const DAY: [&[u8; 3]; 7] = [b"Sun", b"Mon", b"Tue", b"Wed", b"Thu", b"Fri", b"Sat"];
const MONTH: [&[u8; 3]; 12] = [
b"Jan", b"Feb", b"Mar", b"Apr", b"May", b"Jun", b"Jul", b"Aug", b"Sep", b"Oct", b"Nov", b"Dec",
];
const TEMPLATE: [u8; DATE_VALUE_LENGTH] = *b"Xxx, 00 Xxx 0000 00:00:00 GMT";
const fn two_digits<const OFF: usize>(buf: &mut [u8; DATE_VALUE_LENGTH], n: u32) {
const {
assert!(
OFF + 1 < DATE_VALUE_LENGTH,
"two_digits would write past the date buffer"
)
}
buf[OFF] = b'0' + (n / 10) as u8;
buf[OFF + 1] = b'0' + (n % 10) as u8;
}
struct HttpDate {
secs: i64,
}
impl From<SystemTime> for HttpDate {
fn from(time: SystemTime) -> Self {
let secs = match time.duration_since(SystemTime::UNIX_EPOCH) {
Ok(dur) => dur.as_secs() as i64,
Err(e) => -(e.duration().as_secs() as i64),
};
Self { secs }
}
}
impl fmt::Display for HttpDate {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let days = self.secs.div_euclid(86400);
let rem = self.secs.rem_euclid(86400);
let z = days + 719468;
let era = z.div_euclid(146097);
let doe = z - era * 146097;
let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
let mp = (5 * doy + 2) / 153;
let day = (doy - (153 * mp + 2) / 5 + 1) as u32;
let month = (if mp < 10 { mp + 3 } else { mp - 9 }) as u32;
let year = yoe + era * 400 + if month <= 2 { 1 } else { 0 };
let week_day = DAY[(days + 4).rem_euclid(7) as usize];
let mut buf = TEMPLATE;
buf[..3].copy_from_slice(week_day);
two_digits::<5>(&mut buf, day);
buf[8..11].copy_from_slice(MONTH[month as usize - 1]);
let year = year.clamp(0, 9999) as u32;
two_digits::<12>(&mut buf, year / 100);
two_digits::<14>(&mut buf, year % 100);
two_digits::<17>(&mut buf, (rem / 3600) as u32);
two_digits::<20>(&mut buf, (rem % 3600 / 60) as u32);
two_digits::<23>(&mut buf, (rem % 60) as u32);
f.write_str(core::str::from_utf8(&buf).expect("date buffer is always ASCII"))
}
}
pub trait DateTime {
const DATE_SIZE_HINT: usize = DATE_VALUE_LENGTH;
fn with_date<F, O>(&self, f: F) -> O
where
F: FnOnce(&[u8]) -> O;
fn with_date_header<F, O>(&self, f: F) -> O
where
F: FnOnce(&HeaderValue) -> O;
fn now(&self) -> Instant;
}
pub struct DateTimeService {
state: Rc<RefCell<DateTimeState>>,
handle: JoinHandle<()>,
}
impl Drop for DateTimeService {
fn drop(&mut self) {
self.handle.abort();
}
}
impl Default for DateTimeService {
fn default() -> Self {
Self::new()
}
}
impl DateTimeService {
pub fn new() -> Self {
let state = Rc::new(RefCell::new(DateTimeState::default()));
let state_clone = Rc::clone(&state);
let handle = tokio::task::spawn_local(async move {
let mut interval = interval(Duration::from_millis(500));
loop {
let _ = interval.tick().await;
*state_clone.borrow_mut() = DateTimeState::default();
}
});
Self { state, handle }
}
#[inline]
pub fn get(&self) -> &DateTimeHandle {
self.state.deref()
}
}
pub(crate) type DateTimeHandle = RefCell<DateTimeState>;
#[derive(Clone)]
pub struct DateTimeState {
pub date: [u8; DATE_VALUE_LENGTH],
pub date_header: HeaderValue,
pub now: Instant,
}
impl Default for DateTimeState {
fn default() -> Self {
let mut date = Self {
date: [0; DATE_VALUE_LENGTH],
date_header: HeaderValue::from_static(""),
now: Instant::now(),
};
let _ = write!(date, "{}", HttpDate::from(SystemTime::now()));
date.date_header = HeaderValue::from_bytes(&date.date).unwrap();
date
}
}
impl Write for DateTimeState {
fn write_str(&mut self, s: &str) -> fmt::Result {
self.date[..].copy_from_slice(s.as_bytes());
Ok(())
}
}
impl DateTime for DateTimeHandle {
#[inline]
fn with_date<F, O>(&self, f: F) -> O
where
F: FnOnce(&[u8]) -> O,
{
let date = self.borrow();
f(&date.date[..])
}
#[inline]
fn with_date_header<F, O>(&self, f: F) -> O
where
F: FnOnce(&HeaderValue) -> O,
{
let date = self.borrow();
f(&date.date_header)
}
#[inline(always)]
fn now(&self) -> Instant {
self.borrow().now
}
}
pub struct SystemTimeDateTimeHandler;
impl DateTime for SystemTimeDateTimeHandler {
#[allow(dead_code)]
fn with_date<F, O>(&self, f: F) -> O
where
F: FnOnce(&[u8]) -> O,
{
let date = HttpDate::from(SystemTime::now()).to_string();
f(date.as_bytes())
}
#[allow(dead_code)]
fn with_date_header<F, O>(&self, f: F) -> O
where
F: FnOnce(&HeaderValue) -> O,
{
self.with_date(|date| {
let val = HeaderValue::from_bytes(date).unwrap();
f(&val)
})
}
fn now(&self) -> Instant {
Instant::now()
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn imf_fixdate_vectors() {
let format = |secs: i64| HttpDate { secs }.to_string();
assert_eq!(format(784_111_777), "Sun, 06 Nov 1994 08:49:37 GMT");
assert_eq!(format(0), "Thu, 01 Jan 1970 00:00:00 GMT");
assert_eq!(format(2_147_483_647), "Tue, 19 Jan 2038 03:14:07 GMT");
assert_eq!(format(4_102_444_800), "Fri, 01 Jan 2100 00:00:00 GMT");
assert_eq!(format(951_782_400), "Tue, 29 Feb 2000 00:00:00 GMT");
assert_eq!(format(-1), "Wed, 31 Dec 1969 23:59:59 GMT");
}
#[test]
fn formatted_length_is_always_the_date_value_length() {
let steps = (0..100_000).map(|i| i * 40_000).chain(0..86_400);
for secs in steps {
let date = HttpDate { secs }.to_string();
assert_eq!(date.len(), DATE_VALUE_LENGTH, "{date}");
}
}
#[test]
fn date_time_state_default_is_a_valid_header() {
let state = DateTimeState::default();
assert_eq!(state.date.len(), DATE_VALUE_LENGTH);
assert_eq!(state.date_header.as_bytes(), &state.date[..]);
assert!(state.date.ends_with(b" GMT"));
}
}