Skip to main content

mail_builder/headers/
date.rs

1use std::time::SystemTime;
2
3pub static DOW: &[&str] = &["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
4pub static MONTH: &[&str] = &[
5    "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
6];
7
8const DOW_BYTES: [[u8; 3]; 7] = [*b"Sun", *b"Mon", *b"Tue", *b"Wed", *b"Thu", *b"Fri", *b"Sat"];
9const MONTH_BYTES: [[u8; 3]; 12] = [
10    *b"Jan", *b"Feb", *b"Mar", *b"Apr", *b"May", *b"Jun", *b"Jul", *b"Aug", *b"Sep", *b"Oct", *b"Nov", *b"Dec",
11];
12
13const RFC822_LEN: usize = 40;
14const SECONDS_PER_DAY: i64 = 86400;
15
16use super::Header;
17use crate::writer::Writer;
18
19/// RFC5322 Date header
20#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
21pub struct Date {
22    pub date: i64,
23}
24
25#[inline(always)]
26fn push<const N: usize>(buffer: &mut [u8; RFC822_LEN], pos: &mut usize, bytes: [u8; N]) {
27    if let Some(slot) = buffer.get_mut(*pos..).and_then(|tail| tail.first_chunk_mut::<N>()) {
28        *slot = bytes;
29        *pos += N;
30    }
31}
32
33#[inline(always)]
34const fn two_digits(value: u64) -> [u8; 2] {
35    [b'0' + (value / 10) as u8, b'0' + (value % 10) as u8]
36}
37
38#[inline(never)]
39fn push_wide_year(buffer: &mut [u8; RFC822_LEN], pos: &mut usize, year: i64) {
40    let mut digits = [0u8; 20];
41    let mut value = year.unsigned_abs();
42    let mut start = digits.len();
43
44    loop {
45        start -= 1;
46        if let Some(slot) = digits.get_mut(start) {
47            *slot = b'0' + (value % 10) as u8;
48        }
49        value /= 10;
50        if value == 0 || start == 0 {
51            break;
52        }
53    }
54
55    let mut width = digits.len() - start;
56    if year < 0 {
57        push(buffer, pos, *b"-");
58        width += 1;
59    }
60    while width < 4 {
61        push(buffer, pos, *b"0");
62        width += 1;
63    }
64    for &digit in digits.get(start..).unwrap_or_default() {
65        push(buffer, pos, [digit]);
66    }
67}
68
69impl Date {
70    /// Create a new Date header from a timestamp.
71    pub fn new(date: i64) -> Self {
72        Self {
73            date,
74        }
75    }
76
77    /// Create a new Date header using the current time.
78    pub fn now() -> Self {
79        Self {
80            date: SystemTime::now()
81                .duration_since(SystemTime::UNIX_EPOCH)
82                .map(|d| d.as_secs())
83                .unwrap_or(0) as i64,
84        }
85    }
86
87    fn format_rfc822(&self, buffer: &mut [u8; RFC822_LEN]) -> usize {
88        let days = self.date.div_euclid(SECONDS_PER_DAY);
89        let seconds = self.date.rem_euclid(SECONDS_PER_DAY) as u64;
90
91        let z = days + 719468;
92        let era = z.div_euclid(146097);
93        let doe = (z - era * 146097) as u64;
94        let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
95        let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
96        let mp = (5 * doy + 2) / 153;
97        let day = doy - (153 * mp + 2) / 5 + 1;
98        let month = if mp < 10 { mp + 3 } else { mp - 9 };
99        let year = yoe as i64 + era * 400 + i64::from(month <= 2);
100
101        let dow = DOW_BYTES[(days + 4).rem_euclid(7) as usize];
102        let mon = MONTH_BYTES.get(month as usize - 1).copied().unwrap_or(*b"   ");
103
104        let mut pos = 0;
105        push(buffer, &mut pos, [dow[0], dow[1], dow[2], b',', b' ']);
106        if day >= 10 {
107            push(buffer, &mut pos, two_digits(day));
108        } else {
109            push(buffer, &mut pos, [b'0' + day as u8]);
110        }
111        push(buffer, &mut pos, [b' ', mon[0], mon[1], mon[2], b' ']);
112
113        if (0..10000).contains(&year) {
114            let year = year as u64;
115            push(
116                buffer,
117                &mut pos,
118                [
119                    b'0' + (year / 1000) as u8,
120                    b'0' + (year / 100 % 10) as u8,
121                    b'0' + (year / 10 % 10) as u8,
122                    b'0' + (year % 10) as u8,
123                ],
124            );
125        } else {
126            push_wide_year(buffer, &mut pos, year);
127        }
128
129        let [hour, minute, second] = [seconds / 3600, seconds / 60 % 60, seconds % 60].map(two_digits);
130        push(
131            buffer,
132            &mut pos,
133            [
134                b' ', hour[0], hour[1], b':', minute[0], minute[1], b':', second[0], second[1], b' ',
135            ],
136        );
137        push(buffer, &mut pos, *b"+0000");
138
139        pos
140    }
141
142    /// Returns an RFC822 date.
143    pub fn to_rfc822(&self) -> String {
144        let mut buffer = [0u8; RFC822_LEN];
145        let len = self.format_rfc822(&mut buffer);
146        String::from_utf8(buffer.get(..len).unwrap_or_default().to_vec()).unwrap_or_default()
147    }
148
149    /// Writes an RFC822 date without allocating.
150    pub fn write_rfc822(&self, output: &mut impl Writer) {
151        let mut buffer = [0u8; RFC822_LEN];
152        let len = self.format_rfc822(&mut buffer);
153        output.write(buffer.get(..len).unwrap_or_default());
154    }
155}
156
157impl From<i64> for Date {
158    fn from(datetime: i64) -> Self {
159        Date::new(datetime)
160    }
161}
162
163impl From<u64> for Date {
164    fn from(datetime: u64) -> Self {
165        Date::new(datetime as i64)
166    }
167}
168
169impl Header for Date {
170    fn write_header(&self, output: &mut impl Writer, _column: usize) {
171        self.write_rfc822(output);
172        output.write(b"\r\n");
173    }
174}