Skip to content

Commit 96f80b3

Browse files
feat(#914): cache RelativeTimeFormat instance in intlCache, use in dateUtils.formatRelative
Add getRelativeTimeFormat to intlCache.ts following the same caching pattern as getDateTimeFormat/getNumberFormat. Update formatRelative in dateUtils.ts to use the cached instance instead of constructing new Intl.RelativeTimeFormat per call. This eliminates unnecessary formatter recreation in relative-timestamp-heavy views (feeds, notifications, activity logs).
1 parent c44c6d9 commit 96f80b3

3 files changed

Lines changed: 55 additions & 4 deletions

File tree

src/utils/__tests__/intlCache.test.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ import { describe, it, expect, beforeEach } from 'vitest';
22
import {
33
getNumberFormat,
44
getDateTimeFormat,
5+
getRelativeTimeFormat,
56
clearIntlCache,
67
getNumberFormatCacheSize,
78
getDateTimeFormatCacheSize,
9+
getRelativeTimeFormatCacheSize,
810
} from '../intlCache';
911

1012
describe('intlCache', () => {
@@ -58,6 +60,26 @@ describe('intlCache', () => {
5860
});
5961
});
6062

63+
describe('getRelativeTimeFormat', () => {
64+
it('returns a valid Intl.RelativeTimeFormat instance', () => {
65+
const formatter = getRelativeTimeFormat('en-US', { numeric: 'auto' });
66+
expect(formatter).toBeInstanceOf(Intl.RelativeTimeFormat);
67+
expect(formatter.format(-1, 'day')).toBe('yesterday');
68+
});
69+
70+
it('caches formatters by locale and options', () => {
71+
const formatter1 = getRelativeTimeFormat('en-US', { numeric: 'auto' });
72+
const formatter2 = getRelativeTimeFormat('en-US', { numeric: 'auto' });
73+
expect(formatter1).toBe(formatter2);
74+
});
75+
76+
it('creates separate formatters for different locales', () => {
77+
const formatterEN = getRelativeTimeFormat('en-US', { numeric: 'auto' });
78+
const formatterFR = getRelativeTimeFormat('fr-FR', { numeric: 'auto' });
79+
expect(formatterEN).not.toBe(formatterFR);
80+
});
81+
});
82+
6183
describe('cache sizes', () => {
6284
it('returns correct number format cache size', () => {
6385
expect(getNumberFormatCacheSize()).toBe(0);
@@ -72,14 +94,23 @@ describe('intlCache', () => {
7294
expect(getDateTimeFormatCacheSize()).toBe(1);
7395
});
7496

75-
it('clearIntlCache clears both caches', () => {
97+
it('returns correct relative time format cache size', () => {
98+
expect(getRelativeTimeFormatCacheSize()).toBe(0);
99+
getRelativeTimeFormat('en-US');
100+
expect(getRelativeTimeFormatCacheSize()).toBe(1);
101+
});
102+
103+
it('clearIntlCache clears all caches', () => {
76104
getNumberFormat('en-US');
77105
getDateTimeFormat('en-US');
106+
getRelativeTimeFormat('en-US');
78107
expect(getNumberFormatCacheSize()).toBe(1);
79108
expect(getDateTimeFormatCacheSize()).toBe(1);
109+
expect(getRelativeTimeFormatCacheSize()).toBe(1);
80110
clearIntlCache();
81111
expect(getNumberFormatCacheSize()).toBe(0);
82112
expect(getDateTimeFormatCacheSize()).toBe(0);
113+
expect(getRelativeTimeFormatCacheSize()).toBe(0);
83114
});
84115
});
85116
});

src/utils/dateUtils.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* Pass an explicit locale to override; omit it to use the browser/system locale.
44
*/
55

6-
import { getDateTimeFormat } from './intlCache';
6+
import { getDateTimeFormat, getRelativeTimeFormat } from './intlCache';
77

88
export function formatDate(
99
date: Date | string | number,
@@ -23,7 +23,7 @@ export function formatTime(date: Date | string | number, locale?: string): strin
2323

2424
export function formatRelative(date: Date | string | number, locale?: string): string {
2525
const diff = Math.round((new Date(date).getTime() - Date.now()) / 1000);
26-
const rtf = new Intl.RelativeTimeFormat(locale, { numeric: 'auto' });
26+
const rtf = getRelativeTimeFormat(locale, { numeric: 'auto' });
2727

2828
if (Math.abs(diff) < 60) return rtf.format(diff, 'second');
2929
if (Math.abs(diff) < 3600) return rtf.format(Math.round(diff / 60), 'minute');

src/utils/intlCache.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Intl Formatter Cache - Caches Intl.NumberFormat and Intl.DateTimeFormat instances
2+
* Intl Formatter Cache - Caches Intl.NumberFormat, Intl.DateTimeFormat, and Intl.RelativeTimeFormat instances
33
*/
44

55
function serializeCacheKey(
@@ -11,6 +11,7 @@ function serializeCacheKey(
1111

1212
const numberFormatterCache = new Map<string, Intl.NumberFormat>();
1313
const dateTimeFormatterCache = new Map<string, Intl.DateTimeFormat>();
14+
const relativeTimeFormatterCache = new Map<string, Intl.RelativeTimeFormat>();
1415

1516
export function getNumberFormat(
1617
locale: string | undefined,
@@ -40,9 +41,24 @@ export function getDateTimeFormat(
4041
return formatter;
4142
}
4243

44+
export function getRelativeTimeFormat(
45+
locale: string | undefined,
46+
options?: Intl.RelativeTimeFormatOptions,
47+
): Intl.RelativeTimeFormat {
48+
const resolvedLocale = locale ?? 'en-US';
49+
const key = serializeCacheKey(resolvedLocale, options ?? {});
50+
let formatter = relativeTimeFormatterCache.get(key);
51+
if (!formatter) {
52+
formatter = new Intl.RelativeTimeFormat(resolvedLocale, options);
53+
relativeTimeFormatterCache.set(key, formatter);
54+
}
55+
return formatter;
56+
}
57+
4358
export function clearIntlCache(): void {
4459
numberFormatterCache.clear();
4560
dateTimeFormatterCache.clear();
61+
relativeTimeFormatterCache.clear();
4662
}
4763

4864
export function getNumberFormatCacheSize(): number {
@@ -52,3 +68,7 @@ export function getNumberFormatCacheSize(): number {
5268
export function getDateTimeFormatCacheSize(): number {
5369
return dateTimeFormatterCache.size;
5470
}
71+
72+
export function getRelativeTimeFormatCacheSize(): number {
73+
return relativeTimeFormatterCache.size;
74+
}

0 commit comments

Comments
 (0)