diff --git a/src/timer.mjs b/src/timer.mjs index 7f5f895..f3b9204 100644 --- a/src/timer.mjs +++ b/src/timer.mjs @@ -45,8 +45,12 @@ export function parseDuration(text) { export function humanDuration(seconds) { const total = Math.max(0, Math.round(Number(seconds) || 0)); if (total < 60) return `${total}s`; - const h = Math.floor(total / 3600); - const m = Math.round((total % 3600) / 60); + // Round to whole minutes first, then split — computing hours and minutes off + // the raw seconds lets a remainder that rounds up to 60 (59m30s..59m59s) print + // as "60m"/"1h 60m" instead of carrying into the hour it belongs in. + const minutes = Math.round(total / 60); + const h = Math.floor(minutes / 60); + const m = minutes % 60; if (!h) return `${m}m`; return m ? `${h}h ${m}m` : `${h}h`; } diff --git a/test/timer.test.mjs b/test/timer.test.mjs index 6d82500..f5f2ce4 100644 --- a/test/timer.test.mjs +++ b/test/timer.test.mjs @@ -42,6 +42,13 @@ test("a duration is written short, and stays exact enough", () => { assert.equal(humanDuration(5400), "1h 30m"); assert.equal(humanDuration(7200), "2h"); assert.equal(humanDuration(0), "0s"); + // A sub-hour remainder that rounds up to 60 minutes carries into the hour, + // rather than printing "60m" or "1h 60m". + assert.equal(humanDuration(3599), "1h"); + assert.equal(humanDuration(3570), "1h"); + assert.equal(humanDuration(3569), "59m"); + assert.equal(humanDuration(7199), "2h"); + assert.equal(humanDuration(86399), "24h"); }); test("on writes an active timer and off turns it into an entry", async (t) => {