use chrono::{DateTime, Utc};
pub fn format(reset: Option<DateTime<Utc>>, now: DateTime<Utc>) -> String {
let Some(reset) = reset else {
return "—".to_string();
};
let diff = reset.signed_duration_since(now);
let secs = diff.num_seconds();
if secs <= 0 {
return "now".to_string();
}
let days = secs / 86_400;
let hours = (secs % 86_400) / 3_600;
let mins = (secs % 3_600) / 60;
if days > 0 {
format!("{days}d {hours}h")
} else {
format!("{hours}h {mins:02}m")
}
}
#[cfg(test)]
mod tests {
use super::*;
use chrono::TimeZone;
fn at(year: i32, month: u32, day: u32, h: u32, m: u32) -> DateTime<Utc> {
Utc.with_ymd_and_hms(year, month, day, h, m, 0).unwrap()
}
#[test]
fn missing_reset_renders_em_dash() {
let now = at(2026, 5, 23, 12, 0);
assert_eq!(format(None, now), "—");
}
#[test]
fn past_reset_renders_now() {
let now = at(2026, 5, 23, 12, 0);
let reset = at(2026, 5, 23, 11, 0);
assert_eq!(format(Some(reset), now), "now");
}
#[test]
fn exact_zero_renders_now() {
let t = at(2026, 5, 23, 12, 0);
assert_eq!(format(Some(t), t), "now");
}
#[test]
fn hours_minutes_zero_padded() {
let now = at(2026, 5, 23, 12, 0);
let reset = at(2026, 5, 23, 13, 5); assert_eq!(format(Some(reset), now), "1h 05m");
}
#[test]
fn hours_minutes_no_days_under_one_day() {
let now = at(2026, 5, 23, 12, 0);
let reset = at(2026, 5, 24, 11, 59); assert_eq!(format(Some(reset), now), "23h 59m");
}
#[test]
fn one_day_one_hour() {
let now = at(2026, 5, 23, 12, 0);
let reset = at(2026, 5, 24, 13, 30); assert_eq!(format(Some(reset), now), "1d 1h");
}
#[test]
fn multiple_days_drops_minutes() {
let now = at(2026, 5, 23, 12, 0);
let reset = at(2026, 5, 27, 13, 45); assert_eq!(format(Some(reset), now), "4d 1h");
}
#[test]
fn one_second_remaining_renders_zero_hours() {
let now = at(2026, 5, 23, 12, 0);
let reset = now + chrono::Duration::seconds(1);
assert_eq!(format(Some(reset), now), "0h 00m");
}
}