Skip to main content

gitkraft_core/utils/
time.rs

1//! Time formatting and OID helpers.
2
3/// Return a human-friendly relative-time string for the given UTC timestamp.
4///
5/// # Examples
6///
7/// - `"just now"`
8/// - `"1 minute ago"` / `"5 minutes ago"`
9/// - `"1 hour ago"` / `"3 hours ago"`
10/// - `"1 day ago"` / `"2 days ago"`
11/// - `"1 week ago"` / `"3 weeks ago"`
12/// - `"1 month ago"` / `"6 months ago"`
13/// - `"1 year ago"` / `"2 years ago"`
14pub fn relative_time(dt: chrono::DateTime<chrono::Utc>) -> String {
15    let now = chrono::Utc::now();
16    let duration = now.signed_duration_since(dt);
17
18    let seconds = duration.num_seconds();
19    if seconds < 0 {
20        return "in the future".to_string();
21    }
22
23    let minutes = duration.num_minutes();
24    let hours = duration.num_hours();
25    let days = duration.num_days();
26    let weeks = days / 7;
27    let months = days / 30;
28    let years = days / 365;
29
30    if seconds < 60 {
31        "just now".to_string()
32    } else if minutes == 1 {
33        "1 minute ago".to_string()
34    } else if minutes < 60 {
35        format!("{minutes} minutes ago")
36    } else if hours == 1 {
37        "1 hour ago".to_string()
38    } else if hours < 24 {
39        format!("{hours} hours ago")
40    } else if days == 1 {
41        "1 day ago".to_string()
42    } else if days < 7 {
43        format!("{days} days ago")
44    } else if weeks == 1 {
45        "1 week ago".to_string()
46    } else if weeks < 5 {
47        format!("{weeks} weeks ago")
48    } else if months == 1 {
49        "1 month ago".to_string()
50    } else if months < 12 {
51        format!("{months} months ago")
52    } else if years == 1 {
53        "1 year ago".to_string()
54    } else {
55        format!("{years} years ago")
56    }
57}
58
59/// Format a `git2::Oid` as a full hex string.
60pub fn fmt_oid(oid: git2::Oid) -> String {
61    oid.to_string()
62}
63
64/// Format a `git2::Oid` as an abbreviated hex string (first 7 characters).
65pub fn short_oid(oid: git2::Oid) -> String {
66    let full = oid.to_string();
67    full[..7.min(full.len())].to_string()
68}
69
70/// Abbreviate a hex OID string to the first 7 characters.
71pub fn short_oid_str(oid: &str) -> &str {
72    &oid[..7.min(oid.len())]
73}
74
75#[cfg(test)]
76mod tests {
77    use super::*;
78    use chrono::Utc;
79
80    #[test]
81    fn relative_time_just_now() {
82        let now = Utc::now();
83        assert_eq!(relative_time(now), "just now");
84    }
85
86    #[test]
87    fn relative_time_one_minute() {
88        let t = Utc::now() - chrono::Duration::minutes(1);
89        assert_eq!(relative_time(t), "1 minute ago");
90    }
91
92    #[test]
93    fn relative_time_minutes() {
94        let t = Utc::now() - chrono::Duration::minutes(5);
95        assert_eq!(relative_time(t), "5 minutes ago");
96    }
97
98    #[test]
99    fn relative_time_one_hour() {
100        let t = Utc::now() - chrono::Duration::hours(1);
101        assert_eq!(relative_time(t), "1 hour ago");
102    }
103
104    #[test]
105    fn relative_time_hours() {
106        let t = Utc::now() - chrono::Duration::hours(3);
107        assert_eq!(relative_time(t), "3 hours ago");
108    }
109
110    #[test]
111    fn relative_time_one_day() {
112        let t = Utc::now() - chrono::Duration::days(1);
113        assert_eq!(relative_time(t), "1 day ago");
114    }
115
116    #[test]
117    fn relative_time_days() {
118        let t = Utc::now() - chrono::Duration::days(4);
119        assert_eq!(relative_time(t), "4 days ago");
120    }
121
122    #[test]
123    fn relative_time_one_week() {
124        let t = Utc::now() - chrono::Duration::weeks(1);
125        assert_eq!(relative_time(t), "1 week ago");
126    }
127
128    #[test]
129    fn relative_time_weeks() {
130        let t = Utc::now() - chrono::Duration::weeks(3);
131        assert_eq!(relative_time(t), "3 weeks ago");
132    }
133
134    #[test]
135    fn relative_time_one_month() {
136        let t = Utc::now() - chrono::Duration::days(35);
137        assert_eq!(relative_time(t), "1 month ago");
138    }
139
140    #[test]
141    fn relative_time_months() {
142        let t = Utc::now() - chrono::Duration::days(180);
143        assert_eq!(relative_time(t), "6 months ago");
144    }
145
146    #[test]
147    fn relative_time_one_year() {
148        let t = Utc::now() - chrono::Duration::days(400);
149        assert_eq!(relative_time(t), "1 year ago");
150    }
151
152    #[test]
153    fn relative_time_years() {
154        let t = Utc::now() - chrono::Duration::days(800);
155        assert_eq!(relative_time(t), "2 years ago");
156    }
157
158    #[test]
159    fn relative_time_future() {
160        let t = Utc::now() + chrono::Duration::hours(1);
161        assert_eq!(relative_time(t), "in the future");
162    }
163
164    #[test]
165    fn fmt_oid_full() {
166        let oid = git2::Oid::from_str("abcdef1234567890abcdef1234567890abcdef12").unwrap();
167        assert_eq!(fmt_oid(oid), "abcdef1234567890abcdef1234567890abcdef12");
168    }
169
170    #[test]
171    fn short_oid_seven_chars() {
172        let oid = git2::Oid::from_str("abcdef1234567890abcdef1234567890abcdef12").unwrap();
173        assert_eq!(short_oid(oid), "abcdef1");
174    }
175
176    #[test]
177    fn short_oid_str_abbreviates() {
178        assert_eq!(short_oid_str("abcdef1234567890"), "abcdef1");
179    }
180
181    #[test]
182    fn short_oid_str_short_input() {
183        assert_eq!(short_oid_str("abc"), "abc");
184    }
185}