Skip to content

Commit aed40df

Browse files
authored
Merge pull request #505 from wheval/feat/relative-time-label-util
feat(time-utils): add formatRelativeTimeLabel with injectable now param
2 parents 6629389 + babf668 commit aed40df

2 files changed

Lines changed: 81 additions & 0 deletions

File tree

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { describe, it, expect } from 'vitest';
2+
import { formatRelativeTimeLabel } from '../time.utils';
3+
4+
function makeDate(secondsAgo: number, from: Date): Date {
5+
return new Date(from.getTime() - secondsAgo * 1000);
6+
}
7+
8+
describe('formatRelativeTimeLabel', () => {
9+
const now = new Date('2026-06-27T12:00:00.000Z');
10+
11+
it('returns "just now" for 0 seconds ago', () => {
12+
expect(formatRelativeTimeLabel(now, now)).toBe('just now');
13+
});
14+
15+
it('returns "just now" for 59 seconds ago', () => {
16+
expect(formatRelativeTimeLabel(makeDate(59, now), now)).toBe('just now');
17+
});
18+
19+
it('returns "1 minutes ago" for exactly 60 seconds ago', () => {
20+
expect(formatRelativeTimeLabel(makeDate(60, now), now)).toBe('1 minutes ago');
21+
});
22+
23+
it('returns "45 minutes ago" for 45 minutes ago', () => {
24+
expect(formatRelativeTimeLabel(makeDate(45 * 60, now), now)).toBe('45 minutes ago');
25+
});
26+
27+
it('returns "59 minutes ago" for 59 minutes 59 seconds ago', () => {
28+
expect(formatRelativeTimeLabel(makeDate(59 * 60 + 59, now), now)).toBe('59 minutes ago');
29+
});
30+
31+
it('returns "1 hours ago" for exactly 1 hour ago', () => {
32+
expect(formatRelativeTimeLabel(makeDate(3600, now), now)).toBe('1 hours ago');
33+
});
34+
35+
it('returns "23 hours ago" for 23 hours ago', () => {
36+
expect(formatRelativeTimeLabel(makeDate(23 * 3600, now), now)).toBe('23 hours ago');
37+
});
38+
39+
it('returns "1 days ago" for exactly 24 hours ago', () => {
40+
expect(formatRelativeTimeLabel(makeDate(24 * 3600, now), now)).toBe('1 days ago');
41+
});
42+
43+
it('returns "29 days ago" for 29 days ago', () => {
44+
expect(formatRelativeTimeLabel(makeDate(29 * 24 * 3600, now), now)).toBe('29 days ago');
45+
});
46+
47+
it('returns a formatted date for exactly 30 days ago', () => {
48+
const date = makeDate(30 * 24 * 3600, now);
49+
const result = formatRelativeTimeLabel(date, now);
50+
expect(result).toMatch(/\d{1,2} \w+ \d{4}/);
51+
});
52+
53+
it('returns a formatted date for dates older than 30 days', () => {
54+
const date = new Date('2026-01-12T00:00:00.000Z');
55+
const result = formatRelativeTimeLabel(date, now);
56+
expect(result).toMatch(/12 Jan 2026/);
57+
});
58+
59+
it('defaults now to the current time when omitted', () => {
60+
const veryRecentDate = new Date(Date.now() - 5000);
61+
expect(formatRelativeTimeLabel(veryRecentDate)).toBe('just now');
62+
});
63+
});

src/utils/time.utils.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,24 @@ export function formatAbsoluteDateTime(
8181
}).format(date);
8282
}
8383

84+
export function formatRelativeTimeLabel(date: Date, now: Date = new Date()): string {
85+
const diffMs = now.getTime() - date.getTime();
86+
const diffSec = Math.floor(diffMs / 1000);
87+
88+
if (diffSec < 60) return 'just now';
89+
90+
const diffMin = Math.floor(diffSec / 60);
91+
if (diffMin < 60) return `${diffMin} minutes ago`;
92+
93+
const diffHr = Math.floor(diffMin / 60);
94+
if (diffHr < 24) return `${diffHr} hours ago`;
95+
96+
const diffDay = Math.floor(diffHr / 24);
97+
if (diffDay < 30) return `${diffDay} days ago`;
98+
99+
return date.toLocaleDateString('en-GB', { day: 'numeric', month: 'short', year: 'numeric' });
100+
}
101+
84102
export function formatRelativeTime(
85103
input: string | number | Date | null | undefined,
86104
options: RelativeTimeOptions = {}

0 commit comments

Comments
 (0)