bbc_news_cli/
date_utils.rs

1use chrono::{DateTime, Utc};
2
3/// Humanizes a date string from "YYYY-MM-DD HH:MM:SS" format to relative time
4/// Returns strings like "just now", "5 minutes ago", "2 hours ago", "yesterday", etc.
5pub fn humanize_time(date_str: &str) -> String {
6    // Try to parse the date string (format: "YYYY-MM-DD HH:MM:SS")
7    let parsed = DateTime::parse_from_str(&format!("{} +0000", date_str), "%Y-%m-%d %H:%M:%S %z");
8
9    let date_time = match parsed {
10        Ok(dt) => dt.with_timezone(&Utc),
11        Err(_) => {
12            // If parsing fails, return the original string
13            return date_str.to_string();
14        }
15    };
16
17    let now = Utc::now();
18    let duration = now.signed_duration_since(date_time);
19
20    let seconds = duration.num_seconds();
21    let minutes = duration.num_minutes();
22    let hours = duration.num_hours();
23    let days = duration.num_days();
24
25    if seconds < 0 {
26        // Future date
27        return "just now".to_string();
28    } else if seconds < 60 {
29        return "just now".to_string();
30    } else if minutes < 60 {
31        if minutes == 1 {
32            return "1 minute ago".to_string();
33        } else {
34            return format!("{} minutes ago", minutes);
35        }
36    } else if hours < 24 {
37        if hours == 1 {
38            return "1 hour ago".to_string();
39        } else {
40            return format!("{} hours ago", hours);
41        }
42    } else if days == 1 {
43        return "yesterday".to_string();
44    } else if days < 7 {
45        return format!("{} days ago", days);
46    } else if days < 30 {
47        let weeks = days / 7;
48        if weeks == 1 {
49            return "1 week ago".to_string();
50        } else {
51            return format!("{} weeks ago", weeks);
52        }
53    } else if days < 365 {
54        let months = days / 30;
55        if months == 1 {
56            return "1 month ago".to_string();
57        } else {
58            return format!("{} months ago", months);
59        }
60    } else {
61        let years = days / 365;
62        if years == 1 {
63            return "1 year ago".to_string();
64        } else {
65            return format!("{} years ago", years);
66        }
67    }
68}
69
70#[cfg(test)]
71mod tests {
72    use super::*;
73    use chrono::Duration;
74
75    #[test]
76    fn test_humanize_recent() {
77        let now = Utc::now();
78
79        // Just now
80        let recent = (now - Duration::seconds(30)).format("%Y-%m-%d %H:%M:%S").to_string();
81        assert_eq!(humanize_time(&recent), "just now");
82
83        // 5 minutes ago
84        let mins = (now - Duration::minutes(5)).format("%Y-%m-%d %H:%M:%S").to_string();
85        assert_eq!(humanize_time(&mins), "5 minutes ago");
86    }
87
88    #[test]
89    fn test_humanize_hours() {
90        let now = Utc::now();
91        let hours = (now - Duration::hours(3)).format("%Y-%m-%d %H:%M:%S").to_string();
92        assert_eq!(humanize_time(&hours), "3 hours ago");
93    }
94
95    #[test]
96    fn test_humanize_days() {
97        let now = Utc::now();
98        let yesterday = (now - Duration::days(1)).format("%Y-%m-%d %H:%M:%S").to_string();
99        assert_eq!(humanize_time(&yesterday), "yesterday");
100
101        let days = (now - Duration::days(5)).format("%Y-%m-%d %H:%M:%S").to_string();
102        assert_eq!(humanize_time(&days), "5 days ago");
103    }
104
105    #[test]
106    fn test_invalid_date() {
107        let invalid = "invalid date";
108        assert_eq!(humanize_time(invalid), "invalid date");
109    }
110}