//! Human time strings from ISO 8601 UTC timestamps. //! //! Used by the pretty renderer for "resets in 3h12m" and "in 47m". Parses are //! lenient. A parse failure returns the raw string unchanged. use chrono::{DateTime, Utc}; /// Relative phrasing of a future or past timestamp. Examples: "in 30d7h", /// "in 3h12m", "in 47m", "12m ago". Falls back to the input on parse failure. pub fn relative(iso: &str) -> String { let Some(t) = parse(iso) else { return iso.to_string(); }; let now = Utc::now(); let d = t.signed_duration_since(now); let secs = d.num_seconds(); let sign = if secs >= 0 { "in " } else { " ago" }; let body = dur_words(secs.abs()); format!("{sign}{body}") } /// Absolute UTC string like "2025-08-31 17:00 UTC". pub fn absolute(iso: &str) -> String { let Some(t) = parse(iso) else { return iso.to_string(); }; t.format("%Y-%m-%d %H:%M UTC").to_string() } /// Compact duration. Starts with days when the span is at least one day. fn dur_words(total: i64) -> String { let s = total.max(0); if s == 0 { return "<1m".to_string(); } let days = s / 86_400; let rem = s % 86_400; if days > 0 { let hours = rem / 3_600; if hours > 0 { return format!("{days}d{hours}h"); } return format!("{days}d"); } let hours = s / 3_600; let mins = (s % 3_600) / 60; if hours > 0 { return format!("{hours}h{m}m", m = if mins > 0 { mins.to_string() } else { String::new() }); } let mins = (s / 60).max(1); format!("{mins}m") } fn parse(iso: &str) -> Option> { DateTime::parse_from_rfc3339(iso) .ok() .map(|dt| dt.with_timezone(&Utc)) }