From 7d80fca4a79d88a1793684b73349943cc8e73b46 Mon Sep 17 00:00:00 2001 From: Zoltan Papp Date: Tue, 21 Jul 2026 16:25:29 +0200 Subject: [PATCH] [client] Round session remaining-time label up to nearest unit Ceiling each unit so the tray label never shows less time than actually remains, and shift the unit thresholds (<=59m, <=23h) so ceiling never overflows into 60 minutes or 24 hours. --- client/ui/tray_session.go | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/client/ui/tray_session.go b/client/ui/tray_session.go index c3f32f846..a2566feb7 100644 --- a/client/ui/tray_session.go +++ b/client/ui/tray_session.go @@ -99,25 +99,27 @@ func (t *Tray) sessionRowLabel(deadline time.Time) string { } // formatSessionRemaining renders d as a localised long-form string picking the largest non-zero unit. +// Each unit is rounded up so the label never claims less time than actually remains, matching the +// upper-bound sense of the sub-minute "less than a minute" fragment. // Singular/plural keys are split per language for proper translation. func (t *Tray) formatSessionRemaining(d time.Duration) string { switch { case d < time.Minute: return t.loc.T("tray.session.unit.lessThanMinute") - case d < time.Hour: - m := int(d / time.Minute) + case d <= 59*time.Minute: + m := ceilDiv(d, time.Minute) if m == 1 { return t.loc.T("tray.session.unit.minute") } return t.loc.T("tray.session.unit.minutes", "count", strconv.Itoa(m)) - case d < 24*time.Hour: - h := int((d + 30*time.Minute) / time.Hour) + case d <= 23*time.Hour: + h := ceilDiv(d, time.Hour) if h == 1 { return t.loc.T("tray.session.unit.hour") } return t.loc.T("tray.session.unit.hours", "count", strconv.Itoa(h)) default: - days := int((d + 12*time.Hour) / (24 * time.Hour)) + days := ceilDiv(d, 24*time.Hour) if days == 1 { return t.loc.T("tray.session.unit.day") } @@ -125,6 +127,11 @@ func (t *Tray) formatSessionRemaining(d time.Duration) string { } } +// ceilDiv divides d by unit rounding up, assuming d > 0. +func ceilDiv(d, unit time.Duration) int { + return int((d + unit - time.Nanosecond) / unit) +} + // registerSessionWarningCategory wires the OS notification category and response handler for the expiry warning. // Errors are swallowed since the worst case is a plain notification without buttons. func (t *Tray) registerSessionWarningCategory() {