fix: use integer truncation in FormatAge to prevent rounding past display boundaries

Math.Round with C# default banker's rounding (MidpointRounding.ToEven) can
produce counterintuitive results at display-threshold boundaries:

- 59.5 minutes -> Math.Round(59.5) = 60 -> displayed as '60m ago'
  instead of '59m ago' (or the correct transition to '1h ago')
- 47.5 hours -> Math.Round(47.5) = 48 -> displayed as '48h ago'
  instead of '47h ago' (near the 48h/days boundary)

Using integer truncation ((int)delta.TotalX) matches the idiomatic
convention for age display: show the floor of the elapsed time, which
is consistent, predictable, and never exceeds the guard condition.

Adds three regression tests covering:
- 59.5-minute boundary (was '60m ago', now '59m ago')
- 47.5-hour boundary (was '48h ago', now '47h ago')
- Exactly 60 seconds (correctly '1m ago')

Test status: Shared.Tests 589 passed, 20 skipped; Tray.Tests 122 passed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
github-actions[bot] 2026-04-19 12:40:31 +00:00 committed by GitHub
parent e46dfe7830
commit c076085eee
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 28 additions and 3 deletions

View File

@ -441,9 +441,9 @@ internal static class ModelFormatting
{
var delta = DateTime.UtcNow - timestampUtc;
if (delta.TotalSeconds < 60) return "just now";
if (delta.TotalMinutes < 60) return $"{(int)Math.Round(delta.TotalMinutes)}m ago";
if (delta.TotalHours < 48) return $"{(int)Math.Round(delta.TotalHours)}h ago";
return $"{(int)Math.Round(delta.TotalDays)}d ago";
if (delta.TotalMinutes < 60) return $"{(int)delta.TotalMinutes}m ago";
if (delta.TotalHours < 48) return $"{(int)delta.TotalHours}h ago";
return $"{(int)delta.TotalDays}d ago";
}
/// <summary>

View File

@ -762,6 +762,31 @@ public class SessionInfoAgeTextTests
};
Assert.Equal("10m ago", session.AgeText);
}
[Fact]
public void AgeText_NearMinuteBoundary_DoesNotRoundUpTo60m()
{
// 59.5 minutes: Math.Round would produce 60 with banker's rounding;
// truncation correctly yields 59m ago.
var session = new SessionInfo { UpdatedAt = DateTime.UtcNow.AddSeconds(-3570) }; // 59.5 min
Assert.Equal("59m ago", session.AgeText);
}
[Fact]
public void AgeText_NearHourBoundary_DoesNotRoundUpTo48h()
{
// 47.5 hours: Math.Round would produce 48 with banker's rounding;
// truncation correctly yields 47h ago.
var session = new SessionInfo { UpdatedAt = DateTime.UtcNow.AddSeconds(-(int)(47.5 * 3600)) };
Assert.Equal("47h ago", session.AgeText);
}
[Fact]
public void AgeText_ExactlyOneMinute_ShowsMinutesAgo()
{
var session = new SessionInfo { UpdatedAt = DateTime.UtcNow.AddSeconds(-60) };
Assert.Equal("1m ago", session.AgeText);
}
}
public class SessionInfoRichDisplayTextTests