mirror of
https://github.com/kilyabin/psysonic.git
synced 2026-07-21 22:15:40 +00:00
fix(format): round human duration totals to the nearest minute (#710)
* fix(format): round human duration totals to the nearest minute The Phase L dedup refactor folded the old `formatAlbumDuration` helper into `formatHumanHoursMinutes`, but the shared version truncated seconds to whole minutes instead of rounding. Aggregate duration labels (album, playlist, total playtime) could read up to ~59 s short and flip the hour boundary the wrong way — a 59:30 total showed "59 m" instead of "1 h 0 m". Restore the round-to-nearest-minute behaviour (and the negative-input clamp) the helper had before the consolidation. Adds a test pinning the rounding and the hour-boundary roll-up so it can't regress again. * docs(changelog): add human-duration rounding fix under Fixed (#710)
This commit is contained in:
committed by
GitHub
parent
778c9e00fd
commit
a7a3148949
@@ -284,6 +284,12 @@ Foundational work: faster reviews, narrower diffs, and a safety net under the pa
|
||||
|
||||
## Fixed
|
||||
|
||||
### Statistics / playlists — duration totals rounded to the nearest minute again
|
||||
|
||||
**By [@Psychotoxical](https://github.com/Psychotoxical), PR [#710](https://github.com/Psychotoxical/psysonic/pull/710)**
|
||||
|
||||
* Aggregate duration labels (album and playlist totals, total playtime in **Statistics**) could read up to ~59 s short and round the wrong way at the hour boundary — a 59:30 total showed **"59 m"** instead of **"1 h 0 m"**. A `format` helper consolidation had switched the shared formatter from rounding to truncating; the round-to-nearest-minute behaviour is restored.
|
||||
|
||||
### Artists — infinite scroll after first page
|
||||
|
||||
**By [@cucadmuh](https://github.com/cucadmuh), PR [#709](https://github.com/Psychotoxical/psysonic/pull/709)**
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
|
||||
// Echo the i18n key + params so the rounding/branching can be asserted
|
||||
// without depending on a locale's exact "N hours M minutes" template.
|
||||
vi.mock('../../i18n', () => ({
|
||||
default: {
|
||||
t: (key: string, params: Record<string, unknown>) => `${key}|${JSON.stringify(params)}`,
|
||||
},
|
||||
}));
|
||||
|
||||
import { formatHumanHoursMinutes } from './formatHumanDuration';
|
||||
|
||||
describe('formatHumanHoursMinutes', () => {
|
||||
it('rounds to the nearest minute instead of truncating', () => {
|
||||
// 3 min 30 s → rounds up to 4 min
|
||||
expect(formatHumanHoursMinutes(210)).toBe('common.durationMinutesOnly|{"minutes":4}');
|
||||
// 3 min 29 s → rounds down to 3 min
|
||||
expect(formatHumanHoursMinutes(209)).toBe('common.durationMinutesOnly|{"minutes":3}');
|
||||
});
|
||||
|
||||
it('rolls a near-hour total up into the hours branch', () => {
|
||||
// 59 min 30 s → rounds to 60 min → "1 h 0 m", not "59 m"
|
||||
expect(formatHumanHoursMinutes(3570)).toBe('common.durationHoursMinutes|{"hours":"1","minutes":0}');
|
||||
});
|
||||
|
||||
it('formats hours plus minutes', () => {
|
||||
expect(formatHumanHoursMinutes(3 * 3600 + 25 * 60)).toBe(
|
||||
'common.durationHoursMinutes|{"hours":"3","minutes":25}',
|
||||
);
|
||||
});
|
||||
|
||||
it('clamps negative input to zero', () => {
|
||||
expect(formatHumanHoursMinutes(-5)).toBe('common.durationMinutesOnly|{"minutes":0}');
|
||||
});
|
||||
});
|
||||
@@ -1,11 +1,18 @@
|
||||
import i18n from '../../i18n';
|
||||
|
||||
/** Totals / statistics: localized "N hours M minutes" (not track mm:ss). */
|
||||
/**
|
||||
* Totals / statistics: localized "N hours M minutes" (not track mm:ss).
|
||||
*
|
||||
* Rounds to the nearest minute before splitting into hours/minutes — a 3:21:40
|
||||
* total reads as "3 h 22 m", and a 59:30 total rolls up to "1 h 0 m" rather
|
||||
* than truncating to "59 m". Negative input is clamped to zero.
|
||||
*/
|
||||
export function formatHumanHoursMinutes(seconds: number): string {
|
||||
const h = Math.floor(seconds / 3600);
|
||||
const m = Math.floor((seconds % 3600) / 60);
|
||||
const totalMin = Math.max(0, Math.round(seconds / 60));
|
||||
const h = Math.floor(totalMin / 60);
|
||||
const m = totalMin % 60;
|
||||
if (h > 0) {
|
||||
return i18n.t('common.durationHoursMinutes', { hours: h.toLocaleString(), minutes: m });
|
||||
}
|
||||
return i18n.t('common.durationMinutesOnly', { minutes: m });
|
||||
return i18n.t('common.durationMinutesOnly', { minutes: totalMin });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user