1use jiff::Timestamp;
5use jiff::tz::TimeZone;
6
7pub fn now() -> i64 {
8 Timestamp::now().as_second()
9}
10
11pub fn now_millis() -> i64 {
13 Timestamp::now().as_millisecond()
14}
15
16pub fn parse(text: &str) -> Option<i64> {
19 text.parse::<Timestamp>().ok().map(Timestamp::as_second)
20}
21
22pub fn local(epoch: i64, pattern: &str) -> String {
24 Timestamp::from_second(epoch)
25 .map(|t| t.to_zoned(TimeZone::system()).strftime(pattern).to_string())
26 .unwrap_or_default()
27}
28
29pub fn moment(epoch: i64, now: i64) -> String {
31 let pattern = if local(epoch, "%F") == local(now, "%F") {
32 "%H:%M"
33 } else {
34 "%b %-d %H:%M"
35 };
36 local(epoch, pattern)
37}
38
39pub fn span(seconds: i64) -> String {
41 let s = seconds.max(0);
42 let (days, hours, minutes) = (s / 86_400, s % 86_400 / 3_600, s % 3_600 / 60);
43 match (days, hours) {
44 (0, 0) if minutes == 0 => "<1m".into(),
45 (0, 0) => format!("{minutes}m"),
46 (0, h) => format!("{h}h {minutes:02}m"),
47 (d, h) => format!("{d}d {h}h"),
48 }
49}
50
51#[cfg(test)]
52mod tests {
53 use super::*;
54
55 #[test]
56 fn parses_the_shapes_the_usage_api_actually_emits() {
57 assert_eq!(parse("2026-09-20T22:20:00.095287+00:00"), Some(1789942800));
59 assert_eq!(parse("2026-09-27T02:00:00.095306+00:00"), Some(1790474400));
60 assert_eq!(parse("2026-09-20T22:20:00Z"), Some(1789942800));
61 assert_eq!(parse("2026-09-21T05:20:00+07:00"), Some(1789942800));
62 }
63
64 #[test]
65 fn refuses_rather_than_guesses() {
66 for bad in [
67 "",
68 "not a date",
69 "2026-09-20",
70 "2026-09-20T22:20:00",
71 "2026/09/20T22:20:00Z",
72 "2026-13-01T00:00:00Z",
73 "2026-01-32T00:00:00Z",
74 "2026-01-01T24:00:00Z",
75 ] {
76 assert_eq!(parse(bad), None, "should refuse {bad:?}");
77 }
78 }
79
80 #[test]
81 fn spans_read_at_a_glance() {
82 assert_eq!(span(-5), "<1m");
83 assert_eq!(span(59), "<1m");
84 assert_eq!(span(60 * 47), "47m");
85 assert_eq!(span(3600 * 2 + 60 * 5), "2h 05m");
86 assert_eq!(span(86_400 * 6 + 3600 * 4 + 59), "6d 4h");
87 }
88
89 #[test]
90 fn a_moment_today_is_just_its_time() {
91 let now = now();
92 assert_eq!(moment(now, now).len(), 5);
93 assert!(moment(now - 3 * 86_400, now).len() > 5);
94 }
95
96 #[test]
97 fn formats_in_the_local_zone() {
98 assert_eq!(local(0, "%Y").len(), 4);
99 assert_eq!(
100 local(i64::MAX, "%Y"),
101 "",
102 "out of range is empty, not a panic"
103 );
104 }
105}