Skip to main content

borg/
clock.rs

1//! Wall-clock units and formats, each defined once: a day, the civil calendar, how long ago.
2//!
3//! The calendar is borg-trace's (it parses transcript stamps with it), re-exported here so that
4//! every crate on top of borg reaches the one routine. The web page (borg-web ui, its own wasm
5//! workspace) has its own copy in ui/src/util.rs.
6
7pub use borg_trace::{civil_from_days, days_from_civil};
8
9/// A day in milliseconds. Derived: 24 h of 60 min of 60 s of 1000 ms, a unit, not a choice. Every
10/// "a day" or "a week" in borg is a multiple of it.
11pub const DAY_MS: i64 = 24 * 60 * 60 * 1000;
12
13/// How long ago something was, from its age in ms, in at most 6 characters for an age under 100
14/// days: `45s`, `12m`, `3h05m` under a day, `2d` from a day on. A negative age (a sender's clock
15/// ahead of this one) is `0s`.
16pub fn ago(ms: i64) -> String {
17    let s = (ms / 1000).max(0);
18    let day = DAY_MS / 1000;
19    match s {
20        0..60 => format!("{s}s"),
21        60..3600 => format!("{}m", s / 60),
22        _ if s < day => format!("{}h{:02}m", s / 3600, (s % 3600) / 60),
23        _ => format!("{}d", s / day),
24    }
25}
26
27#[cfg(test)]
28mod tests {
29    use super::*;
30
31    #[test]
32    fn a_day_is_a_day() {
33        assert_eq!(DAY_MS, 86_400_000);
34    }
35
36    #[test]
37    fn an_age_reads_in_its_largest_unit() {
38        assert_eq!(ago(-5_000), "0s");
39        assert_eq!(ago(59_999), "59s");
40        assert_eq!(ago(60_000), "1m");
41        assert_eq!(ago(3_599_999), "59m");
42        assert_eq!(ago(3_600_000), "1h00m");
43        assert_eq!(ago(3 * 3_600_000 + 5 * 60_000 + 59_000), "3h05m");
44        assert_eq!(ago(DAY_MS - 1), "23h59m");
45        assert_eq!(ago(DAY_MS), "1d");
46        assert_eq!(ago(7 * DAY_MS - 1), "6d");
47        assert!(ago(99 * DAY_MS).len() <= 6 && ago(DAY_MS - 1).len() <= 6, "fits a 6-wide column");
48    }
49
50    #[test]
51    fn the_calendar_is_reachable_here() {
52        assert_eq!(civil_from_days(days_from_civil(2026, 10, 28)), (2026, 10, 28));
53    }
54}