OXYGENHUB
◆ v0.9.2 oxygenhub / crates / hub / src / render / time.rs
2.5 KiB · Rust · last changed An issue's references come in a box per day (closes #292) 11 days ago source blame history raw
1
//! Times as people read them: relative ("3 hours ago") with the absolute
2
//! form for a tooltip.
3
4
use time::format_description::well_known::Rfc3339;
5
use time::OffsetDateTime;
6
7
/// `just now`, `5 minutes ago`, `3 days ago`, `2 years ago`.
8
pub fn ago(when_ms: u64, now_ms: u64) -> String {
9
    if when_ms == 0 {
10
        return "unknown".into();
11
    }
12
    if when_ms > now_ms {
13
        return "in the future".into();
14
    }
15
    let s = (now_ms - when_ms) / 1000;
16
    let (n, unit) = if s < 45 {
17
        return "just now".into();
18
    } else if s < 90 {
19
        (1, "minute")
20
    } else if s < 3600 {
21
        (s / 60, "minute")
22
    } else if s < 86_400 {
23
        (s / 3600, "hour")
24
    } else if s < 86_400 * 30 {
25
        (s / 86_400, "day")
26
    } else if s < 86_400 * 365 {
27
        (s / (86_400 * 30), "month")
28
    } else {
29
        (s / (86_400 * 365), "year")
30
    };
31
    format!("{n} {unit}{} ago", if n == 1 { "" } else { "s" })
32
}
33
34
/// `2026-09-27 17:02 UTC`.
35
pub fn absolute(when_ms: u64) -> String {
36
    match OffsetDateTime::from_unix_timestamp((when_ms / 1000) as i64) {
37
        Ok(t) => {
38
            let f = time::macros::format_description!("[year]-[month]-[day] [hour]:[minute] UTC");
39
            t.format(&f).unwrap_or_default()
40
        }
41
        Err(_) => String::new(),
42
    }
43
}
44
45
/// `21 Oct 2026`, the UTC day.
46
pub fn day(when_ms: u64) -> String {
47
    match OffsetDateTime::from_unix_timestamp((when_ms / 1000) as i64) {
48
        Ok(t) => {
49
            let f = time::macros::format_description!("[day padding:none] [month repr:short] [year]");
50
            t.format(&f).unwrap_or_default()
51
        }
52
        Err(_) => String::new(),
53
    }
54
}
55
56
pub fn rfc3339(when_ms: u64) -> String {
57
    OffsetDateTime::from_unix_timestamp((when_ms / 1000) as i64).ok().and_then(|t| t.format(&Rfc3339).ok()).unwrap_or_default()
58
}
59
60
#[cfg(test)]
61
mod tests {
62
    use super::*;
63
64
    #[test]
65
    fn days() {
66
        assert_eq!(day(1_792_540_800_000), "21 Oct 2026");
67
    }
68
69
    #[test]
70
    fn relative() {
71
        let now = 1_800_000_000_000;
72
        assert_eq!(ago(now - 10_000, now), "just now");
73
        assert_eq!(ago(now - 60_000, now), "1 minute ago");
74
        assert_eq!(ago(now - 5 * 60_000, now), "5 minutes ago");
75
        assert_eq!(ago(now - 3 * 3_600_000, now), "3 hours ago");
76
        assert_eq!(ago(now - 2 * 86_400_000, now), "2 days ago");
77
        assert_eq!(ago(now - 400 * 86_400_000, now), "1 year ago");
78
        assert_eq!(ago(0, now), "unknown");
79
        assert_eq!(absolute(1_758_990_120_000), "2025-09-27 16:22 UTC");
80
    }
81
}