aboutsummaryrefslogtreecommitdiffstats
path: root/src/date.rs
blob: 149dc79085b53cd60facdfce7fed4f14ceee7a9a (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
use core::fmt;
use std::{
    process::Command,
    time::{Duration, SystemTime},
};

use crate::BString;

#[derive(Clone)]
pub struct DateTime {
    sys: SystemTime,
}

impl DateTime {
    pub fn from_unix(unix: u64) -> Self {
        Self {
            sys: SystemTime::UNIX_EPOCH + Duration::from_secs(unix),
        }
    }
    pub fn now() -> Self {
        Self {
            sys: SystemTime::now(),
        }
    }
    fn format(&self) -> std::io::Result<BString> {
        let mut out = Command::new("date")
            .arg("-d")
            .arg(format!(
                "@{}",
                self.sys
                    .duration_since(SystemTime::UNIX_EPOCH)
                    .unwrap()
                    .as_secs()
            ))
            .output()?
            .stdout;
        while let Some(x) = out.last()
            && x.is_ascii_whitespace()
        {
            out.pop();
        }
        Ok(out)
    }

    fn unix(&self) -> u64 {
        self.sys
            .duration_since(SystemTime::UNIX_EPOCH)
            .unwrap()
            .as_secs()
    }

    pub fn relative_to(&self, other: &Self) -> String {
        let a = self.unix();
        let b = other.unix();

        let (pre, post, mut diff) = if a < b {
            ("in ", "", b - a)
        } else {
            ("", " ago", a - b)
        };

        let unit = if diff < 60 {
            "s"
        } else if {
            diff /= 60;
            diff < 60
        } {
            "m"
        } else if {
            diff /= 60;
            diff < 24
        } {
            "h"
        } else if {
            diff /= 24;
            diff < 365
        } {
            "d"
        } else {
            diff /= 365;
            "y"
        };

        format!("{pre}{diff}{unit}{post}")
    }

    pub const fn longest_reasonable_delta() -> usize {
        7
    }
}

#[test]
fn long_delta() {
    assert_eq!(
        DateTime::now()
            .relative_to(&DateTime {
                // 30 years ago
                sys: SystemTime::now() - Duration::from_secs(60 * 60 * 24 * 365 * 30)
            })
            .len(),
        DateTime::longest_reasonable_delta()
    );
}

impl fmt::Display for DateTime {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "{}",
            String::from_utf8_lossy(&self.format().unwrap_or_else(|_| Vec::new()))
        )
    }
}