mail_builder/headers/
date.rs

1/*
2 * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
3 *
4 * SPDX-License-Identifier: Apache-2.0 OR MIT
5 */
6
7use std::{
8    io::{self, Write},
9    time::SystemTime,
10};
11
12pub static DOW: &[&str] = &["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
13pub static MONTH: &[&str] = &[
14    "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
15];
16
17use super::Header;
18
19/// RFC5322 Date header
20#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
21pub struct Date {
22    pub date: i64,
23}
24
25impl Date {
26    /// Create a new Date header from a timestamp.
27    pub fn new(date: i64) -> Self {
28        Self { date }
29    }
30
31    /// Create a new Date header using the current time.
32    pub fn now() -> Self {
33        Self {
34            date: SystemTime::now()
35                .duration_since(SystemTime::UNIX_EPOCH)
36                .map(|d| d.as_secs())
37                .unwrap_or(0) as i64,
38        }
39    }
40
41    /// Returns an RFC822 date.
42    pub fn to_rfc822(&self) -> String {
43        // Ported from http://howardhinnant.github.io/date_algorithms.html#civil_from_days
44        let (z, seconds) = ((self.date / 86400) + 719468, self.date % 86400);
45        let era: i64 = (if z >= 0 { z } else { z - 146096 }) / 146097;
46        let doe: u64 = (z - era * 146097) as u64; // [0, 146096]
47        let yoe: u64 = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365; // [0, 399]
48        let y: i64 = (yoe as i64) + era * 400;
49        let doy: u64 = doe - (365 * yoe + yoe / 4 - yoe / 100); // [0, 365]
50        let mp = (5 * doy + 2) / 153; // [0, 11]
51        let d: u64 = doy - (153 * mp + 2) / 5 + 1; // [1, 31]
52        let m: u64 = if mp < 10 { mp + 3 } else { mp - 9 }; // [1, 12]
53        let (h, mn, s) = (seconds / 3600, (seconds / 60) % 60, seconds % 60);
54
55        format!(
56            "{}, {} {} {:04} {:02}:{:02}:{:02} +0000", //{}{:02}{:02}",
57            DOW[(((self.date as f64 / 86400.0).floor() as i64 + 4).rem_euclid(7)) as usize],
58            d,
59            MONTH.get(m.saturating_sub(1) as usize).unwrap_or(&""),
60            (y + i64::from(m <= 2)),
61            h,
62            mn,
63            s,
64            /*if self.tz_before_gmt && (self.tz_hour > 0 || self.tz_minute > 0) {
65                "-"
66            } else {
67                "+"
68            },
69            self.tz_hour,
70            self.tz_minute*/
71        )
72    }
73}
74
75impl From<i64> for Date {
76    fn from(datetime: i64) -> Self {
77        Date::new(datetime)
78    }
79}
80
81impl From<u64> for Date {
82    fn from(datetime: u64) -> Self {
83        Date::new(datetime as i64)
84    }
85}
86
87impl Header for Date {
88    fn write_header(&self, mut output: impl Write, _bytes_written: usize) -> io::Result<usize> {
89        output.write_all(self.to_rfc822().as_bytes())?;
90        output.write_all(b"\r\n")?;
91        Ok(0)
92    }
93}