diff --git a/src/utils/__tests__/intlCache.test.ts b/src/utils/__tests__/intlCache.test.ts index f4493759..2b0005b1 100644 --- a/src/utils/__tests__/intlCache.test.ts +++ b/src/utils/__tests__/intlCache.test.ts @@ -2,9 +2,11 @@ import { describe, it, expect, beforeEach } from 'vitest'; import { getNumberFormat, getDateTimeFormat, + getRelativeTimeFormat, clearIntlCache, getNumberFormatCacheSize, getDateTimeFormatCacheSize, + getRelativeTimeFormatCacheSize, } from '../intlCache'; describe('intlCache', () => { @@ -58,6 +60,26 @@ describe('intlCache', () => { }); }); + describe('getRelativeTimeFormat', () => { + it('returns a valid Intl.RelativeTimeFormat instance', () => { + const formatter = getRelativeTimeFormat('en-US', { numeric: 'auto' }); + expect(formatter).toBeInstanceOf(Intl.RelativeTimeFormat); + expect(formatter.format(-1, 'day')).toBe('yesterday'); + }); + + it('caches formatters by locale and options', () => { + const formatter1 = getRelativeTimeFormat('en-US', { numeric: 'auto' }); + const formatter2 = getRelativeTimeFormat('en-US', { numeric: 'auto' }); + expect(formatter1).toBe(formatter2); + }); + + it('creates separate formatters for different locales', () => { + const formatterEN = getRelativeTimeFormat('en-US', { numeric: 'auto' }); + const formatterFR = getRelativeTimeFormat('fr-FR', { numeric: 'auto' }); + expect(formatterEN).not.toBe(formatterFR); + }); + }); + describe('cache sizes', () => { it('returns correct number format cache size', () => { expect(getNumberFormatCacheSize()).toBe(0); @@ -72,14 +94,23 @@ describe('intlCache', () => { expect(getDateTimeFormatCacheSize()).toBe(1); }); - it('clearIntlCache clears both caches', () => { + it('returns correct relative time format cache size', () => { + expect(getRelativeTimeFormatCacheSize()).toBe(0); + getRelativeTimeFormat('en-US'); + expect(getRelativeTimeFormatCacheSize()).toBe(1); + }); + + it('clearIntlCache clears all caches', () => { getNumberFormat('en-US'); getDateTimeFormat('en-US'); + getRelativeTimeFormat('en-US'); expect(getNumberFormatCacheSize()).toBe(1); expect(getDateTimeFormatCacheSize()).toBe(1); + expect(getRelativeTimeFormatCacheSize()).toBe(1); clearIntlCache(); expect(getNumberFormatCacheSize()).toBe(0); expect(getDateTimeFormatCacheSize()).toBe(0); + expect(getRelativeTimeFormatCacheSize()).toBe(0); }); }); }); diff --git a/src/utils/dateUtils.ts b/src/utils/dateUtils.ts index 17605612..402b9520 100644 --- a/src/utils/dateUtils.ts +++ b/src/utils/dateUtils.ts @@ -3,7 +3,7 @@ * Pass an explicit locale to override; omit it to use the browser/system locale. */ -import { getDateTimeFormat } from './intlCache'; +import { getDateTimeFormat, getRelativeTimeFormat } from './intlCache'; export function formatDate( date: Date | string | number, @@ -23,7 +23,7 @@ export function formatTime(date: Date | string | number, locale?: string): strin export function formatRelative(date: Date | string | number, locale?: string): string { const diff = Math.round((new Date(date).getTime() - Date.now()) / 1000); - const rtf = new Intl.RelativeTimeFormat(locale, { numeric: 'auto' }); + const rtf = getRelativeTimeFormat(locale, { numeric: 'auto' }); if (Math.abs(diff) < 60) return rtf.format(diff, 'second'); if (Math.abs(diff) < 3600) return rtf.format(Math.round(diff / 60), 'minute'); diff --git a/src/utils/intlCache.ts b/src/utils/intlCache.ts index 02745e87..1529f544 100644 --- a/src/utils/intlCache.ts +++ b/src/utils/intlCache.ts @@ -1,5 +1,5 @@ /** - * Intl Formatter Cache - Caches Intl.NumberFormat and Intl.DateTimeFormat instances + * Intl Formatter Cache - Caches Intl.NumberFormat, Intl.DateTimeFormat, and Intl.RelativeTimeFormat instances */ function serializeCacheKey( @@ -11,6 +11,7 @@ function serializeCacheKey( const numberFormatterCache = new Map(); const dateTimeFormatterCache = new Map(); +const relativeTimeFormatterCache = new Map(); export function getNumberFormat( locale: string | undefined, @@ -40,9 +41,24 @@ export function getDateTimeFormat( return formatter; } +export function getRelativeTimeFormat( + locale: string | undefined, + options?: Intl.RelativeTimeFormatOptions, +): Intl.RelativeTimeFormat { + const resolvedLocale = locale ?? 'en-US'; + const key = serializeCacheKey(resolvedLocale, options ?? {}); + let formatter = relativeTimeFormatterCache.get(key); + if (!formatter) { + formatter = new Intl.RelativeTimeFormat(resolvedLocale, options); + relativeTimeFormatterCache.set(key, formatter); + } + return formatter; +} + export function clearIntlCache(): void { numberFormatterCache.clear(); dateTimeFormatterCache.clear(); + relativeTimeFormatterCache.clear(); } export function getNumberFormatCacheSize(): number { @@ -52,3 +68,7 @@ export function getNumberFormatCacheSize(): number { export function getDateTimeFormatCacheSize(): number { return dateTimeFormatterCache.size; } + +export function getRelativeTimeFormatCacheSize(): number { + return relativeTimeFormatterCache.size; +}