Skip to content

Commit 1d3cf54

Browse files
authored
Fix album duration, simplify ScrobbleItem duration (#249)
* fix album duration format on Scrobble Album page * use same logic to format durations of an album and a scrobble * add utils.datetime.formatDuration unit tests
1 parent 9765de1 commit 1d3cf54

File tree

4 files changed

+46
-36
lines changed

4 files changed

+46
-36
lines changed

src/components/ScrobbleItem.tsx

+3-29
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,6 @@ import { get } from 'lodash-es';
55

66
import { enqueueScrobble } from 'store/actions/scrobbleActions';
77

8-
import format from 'date-fns/format';
9-
import isToday from 'date-fns/isToday';
10-
import getYear from 'date-fns/getYear';
11-
128
import { Button, Input, Dropdown, DropdownToggle, DropdownMenu, DropdownItem, FormGroup, Label } from 'reactstrap';
139
import { useState } from 'react';
1410
import { LazyLoadImage } from 'react-lazy-load-image-component';
@@ -31,6 +27,7 @@ import type { Scrobble } from 'utils/types/scrobble';
3127

3228
import './ScrobbleItem.css';
3329
import { useSettings } from 'hooks/useSettings';
30+
import { formatDuration, formatScrobbleTimestamp } from 'utils/datetime';
3431

3532
interface ScrobbleItemProps {
3633
scrobble: Scrobble;
@@ -110,8 +107,6 @@ export default function ScrobbleItem({
110107
let songInfo;
111108
let songFullTitle;
112109
let statusIcon;
113-
let theTimestamp;
114-
let timestampFormat = '';
115110

116111
if (noCover || compact) {
117112
albumArt = null;
@@ -165,36 +160,15 @@ export default function ScrobbleItem({
165160
}
166161
}
167162

168-
if (scrobble.timestamp) {
169-
const scrobbleDate = new Date(scrobble.timestamp);
170-
if (!isToday(scrobbleDate)) {
171-
timestampFormat = settings?.use12Hours ? 'M/d' : 'd/MM';
172-
if (getYear(scrobbleDate) < getYear(new Date())) {
173-
timestampFormat += '/yyyy';
174-
}
175-
timestampFormat += ' ';
176-
}
177-
timestampFormat += settings?.use12Hours ? 'hh:mm a' : 'HH:mm';
178-
theTimestamp = format(scrobbleDate, timestampFormat);
179-
} else {
180-
if (scrobble.duration > 0) {
181-
// Yes, there are songs over one hour. Is it worth making this more complex for those? (no, it isn't)
182-
const minutes = Math.floor(scrobble.duration / 60);
183-
const seconds = `0${scrobble.duration % 60}`.slice(-2);
184-
theTimestamp = `${minutes}:${seconds}`;
185-
} else {
186-
theTimestamp = '';
187-
}
188-
}
189-
190163
const timeOrDuration = (
191164
<small
192165
className={`text-end timestamp d-flex align-items-center ${compact ? 'flex-row' : 'flex-row-reverse'} ${
193166
!scrobble.timestamp && 'duration text-muted'
194167
}`}
195168
>
196169
{scrobble.timestamp && <FontAwesomeIcon className={`${compact ? 'me-2' : 'ms-2'}`} icon={faClock} />}
197-
{theTimestamp}
170+
{scrobble.timestamp && formatScrobbleTimestamp(scrobble.timestamp, settings?.use12Hours)}
171+
{!scrobble.timestamp && scrobble.duration > 0 && formatDuration(scrobble.duration)}
198172
</small>
199173
);
200174

src/domains/scrobbleAlbum/partials/Tracklist.tsx

+3-7
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import { Trans, useTranslation } from 'react-i18next';
55
import ReactGA from 'react-ga-neo';
66
import addSeconds from 'date-fns/addSeconds';
77
import subSeconds from 'date-fns/subSeconds';
8-
import format from 'date-fns/format';
98

109
import { Alert, Badge, Button, FormGroup, Label, Input } from 'reactstrap';
1110
import { faShoppingCart, faStopwatch, faQuestionCircle } from '@fortawesome/free-solid-svg-icons';
@@ -24,6 +23,8 @@ import type { Album, DiscogsAlbum } from 'utils/types/album';
2423
import type { Scrobble } from 'utils/types/scrobble';
2524
import type { Track } from 'utils/types/track';
2625

26+
import { formatDuration } from 'utils/datetime';
27+
2728
const DateTimePicker = lazyWithPreload(() => import('components/DateTimePicker'));
2829

2930
// ToDo: refactor this component completely.
@@ -42,7 +43,6 @@ export default function Tracklist({ albumInfo, tracks }: { albumInfo: Album | nu
4243
const [totalDuration, setTotalDuration] = useState(0);
4344
const albumHasTracks = tracks && tracks.length > 0;
4445
const hasAlbumInfo = !!albumInfo && Object.keys(albumInfo).length > 0;
45-
const durationFormat = totalDuration > 3600 ? 'H:mm:ss' : 'mm:ss';
4646

4747
useEffect(() => {
4848
let newDuration = 0;
@@ -155,11 +155,7 @@ export default function Tracklist({ albumInfo, tracks }: { albumInfo: Album | nu
155155
{tracks.length > 0 && (
156156
<div className="album-heading-duration">
157157
<FontAwesomeIcon icon={faStopwatch} className="me-2" color="var(--bs-gray)" />
158-
{totalDuration ? (
159-
format(addSeconds(new Date(0), totalDuration), durationFormat)
160-
) : (
161-
<Trans i18nKey="unknown">Unknown</Trans>
162-
)}
158+
{totalDuration ? formatDuration(totalDuration) : <Trans i18nKey="unknown">Unknown</Trans>}
163159
</div>
164160
)}
165161
</div>

src/utils/datetime.test.ts

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { formatDuration } from './datetime';
2+
3+
describe('`formatDuration` helper', () => {
4+
it('is correct', () => {
5+
expect(formatDuration(45)).toEqual('0:45');
6+
expect(formatDuration(150)).toEqual('2:30');
7+
expect(formatDuration(187)).toEqual('3:07');
8+
expect(formatDuration(777)).toEqual('12:57');
9+
expect(formatDuration(3801)).toEqual('1:03:21');
10+
expect(formatDuration(4202)).toEqual('1:10:02');
11+
});
12+
});

src/utils/datetime.ts

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import format from 'date-fns/format';
2+
import isToday from 'date-fns/isToday';
3+
import getYear from 'date-fns/getYear';
4+
5+
function zeroPad(secondsOrMinutes: number): string {
6+
return secondsOrMinutes < 10 ? '0' + secondsOrMinutes : secondsOrMinutes.toString();
7+
}
8+
9+
export function formatDuration(totalSeconds: number): string {
10+
const datetime = new Date(totalSeconds * 1000);
11+
const h = datetime.getUTCHours();
12+
const m = datetime.getUTCMinutes();
13+
const s = datetime.getUTCSeconds();
14+
return h > 0 ? `${h}:${zeroPad(m)}:${zeroPad(s)}` : `${m}:${zeroPad(s)}`;
15+
}
16+
17+
export function formatScrobbleTimestamp(timestamp: Date, use12Hours: boolean): string {
18+
let timestampFormat = '';
19+
if (!isToday(timestamp)) {
20+
timestampFormat = use12Hours ? 'M/d' : 'd/MM';
21+
if (getYear(timestamp) < getYear(new Date())) {
22+
timestampFormat += '/yyyy';
23+
}
24+
timestampFormat += ' ';
25+
}
26+
timestampFormat += use12Hours ? 'hh:mm a' : 'HH:mm';
27+
return format(timestamp, timestampFormat);
28+
}

0 commit comments

Comments
 (0)