Format long listen times in days and center the pin dot

This commit is contained in:
2026-06-09 20:35:40 +01:00
parent 8dd316f6ae
commit f6e1a6d4ea

View File

@@ -51,24 +51,44 @@ const MIN_BAR_WIDTH: usize = 8;
/// Format a duration in seconds into a human-readable string. /// Format a duration in seconds into a human-readable string.
/// ///
/// Once the duration reaches a full day it is reported in days and hours
/// (minutes are dropped, with the leftover hour rounded to the nearest hour).
///
/// Examples: /// Examples:
/// - 45 seconds → "45s" /// - 45 seconds → "45s"
/// - 120 seconds → "2m" /// - 120 seconds → "2m"
/// - 3660 seconds → "1h 01m" /// - 3660 seconds → "1h 01m"
/// - 0 seconds → "0s" /// - 0 seconds → "0s"
/// - 90000 secs → "1d 1h"
/// - 86400 secs → "1d"
pub fn format_duration(secs: i64) -> String { pub fn format_duration(secs: i64) -> String {
const HOUR: i64 = 3600;
const DAY: i64 = 86400;
if secs < 60 { if secs < 60 {
format!("{}s", secs) format!("{}s", secs)
} else if secs < 3600 { } else if secs < HOUR {
format!("{}m", secs / 60) format!("{}m", secs / 60)
} else { } else if secs < DAY {
let hours = secs / 3600; let hours = secs / HOUR;
let mins = (secs % 3600) / 60; let mins = (secs % HOUR) / 60;
if mins == 0 { if mins == 0 {
format!("{}h", hours) format!("{}h", hours)
} else { } else {
format!("{}h {:02}m", hours, mins) format!("{}h {:02}m", hours, mins)
} }
} else {
let mut days = secs / DAY;
// Round the leftover hours to the nearest hour, carrying into days.
let mut hours = (secs % DAY + HOUR / 2) / HOUR;
if hours == 24 {
days += 1;
hours = 0;
}
if hours == 0 {
format!("{}d", days)
} else {
format!("{}d {}h", days, hours)
}
} }
} }
@@ -1333,8 +1353,9 @@ tr:last-child td { border-bottom: none; }
color: #a99984; color: #a99984;
background: linear-gradient(135deg, #17130d, #2c2116); font-size: 12px; } background: linear-gradient(135deg, #17130d, #2c2116); font-size: 12px; }
.pin-dot { position: absolute; top: 5px; right: 5px; width: 22px; height: 22px; .pin-dot { position: absolute; top: 5px; right: 5px; width: 22px; height: 22px;
border-radius: 50%; background: rgba(0,0,0,0.45); border: none; border-radius: 50%; background: rgba(0,0,0,0.45); border: none; padding: 0;
cursor: pointer; font-size: 12px; line-height: 22px; text-align: center; cursor: pointer; font-size: 12px; line-height: 1;
display: flex; align-items: center; justify-content: center;
opacity: 0; transition: opacity 0.15s; z-index: 1; } opacity: 0; transition: opacity 0.15s; z-index: 1; }
.album:hover .pin-dot { opacity: 1; } .album:hover .pin-dot { opacity: 1; }
.pin-dot:hover { background: rgba(0,0,0,0.75); } .pin-dot:hover { background: rgba(0,0,0,0.75); }
@@ -1752,6 +1773,16 @@ mod tests {
assert_eq!(format_duration(0), "0s"); assert_eq!(format_duration(0), "0s");
} }
#[test]
fn test_format_duration_days() {
assert_eq!(format_duration(86400), "1d");
assert_eq!(format_duration(90000), "1d 1h");
// 1d 5h 40m rounds up to 1d 6h.
assert_eq!(format_duration(106800), "1d 6h");
// Leftover hours rounding up to 24 carries into the next day.
assert_eq!(format_duration(172799), "2d");
}
#[test] #[test]
fn test_format_play_count() { fn test_format_play_count() {
assert_eq!(format_play_count(0), "0 plays"); assert_eq!(format_play_count(0), "0 plays");