From c4ecaf9a64980151b0daa55356627e25160f103f Mon Sep 17 00:00:00 2001 From: trevore69 Date: Sat, 29 Aug 2026 14:19:46 +0000 Subject: [PATCH] fix(timer): carry rounded minutes into the hour in humanDuration humanDuration derived hours and minutes independently from the raw seconds, so a sub-hour remainder that rounds up to 60 minutes printed as "60m", "1h 60m" or "23h 60m" instead of carrying into the hour it belongs in. Any tracked stretch in the last 30 seconds of an hour (e.g. 1h59m59s) rendered wrong in /timer off, /timer status and /timer log totals. Round to whole minutes first, then split into hours and minutes so the carry happens once. Add regression cases for the boundary. --- src/timer.mjs | 8 ++++++-- test/timer.test.mjs | 7 +++++++ 2 files changed, 13 insertions(+), 2 deletions(-) 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) => {