diff --git a/src/ui/widgets.rs b/src/ui/widgets.rs index f583f13..e0d14aa 100644 --- a/src/ui/widgets.rs +++ b/src/ui/widgets.rs @@ -338,6 +338,51 @@ fn crash_explanation(stderr: &str) -> String { may help identify the cause.".to_string() } +/// Format a timestamp string as a human-readable relative time. +/// Accepts RFC3339, "YYYY-MM-DD HH:MM:SS", or ISO8601 formats. +pub fn relative_time(timestamp: &str) -> String { + let parsed = chrono::DateTime::parse_from_rfc3339(timestamp) + .map(|dt| dt.with_timezone(&chrono::Utc)) + .or_else(|_| { + chrono::NaiveDateTime::parse_from_str(timestamp, "%Y-%m-%d %H:%M:%S") + .map(|ndt| ndt.and_utc()) + }); + + let dt = match parsed { + Ok(dt) => dt, + Err(_) => return timestamp.to_string(), + }; + + let now = chrono::Utc::now(); + let duration = now.signed_duration_since(dt); + + if duration.num_seconds() < 0 { + return "Just now".to_string(); + } + + if duration.num_seconds() < 60 { + "Just now".to_string() + } else if duration.num_minutes() < 60 { + let m = duration.num_minutes(); + format!("{} min ago", m) + } else if duration.num_hours() < 24 { + let h = duration.num_hours(); + format!("{} hr ago", h) + } else if duration.num_days() == 1 { + "Yesterday".to_string() + } else if duration.num_days() < 7 { + format!("{} days ago", duration.num_days()) + } else if duration.num_weeks() == 1 { + "Last week".to_string() + } else if duration.num_days() < 30 { + format!("{} weeks ago", duration.num_weeks()) + } else if duration.num_days() < 60 { + "Last month".to_string() + } else { + format!("{} months ago", duration.num_days() / 30) + } +} + /// Create a screen-reader live region announcement. /// Inserts a hidden label with AccessibleRole::Alert into the given container, /// which causes AT-SPI to announce the text to screen readers.