Skip to content

Commit 14e3aa4

Browse files
authored
feat: Support numberingSystem and style for relative time formatting (#1057)
Fixes #1056
1 parent 3eefe6f commit 14e3aa4

File tree

5 files changed

+58
-36
lines changed

5 files changed

+58
-36
lines changed

packages/next-intl/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@
114114
"size-limit": [
115115
{
116116
"path": "dist/production/index.react-client.js",
117-
"limit": "15.735 KB"
117+
"limit": "15.765 KB"
118118
},
119119
{
120120
"path": "dist/production/index.react-server.js",
@@ -134,7 +134,7 @@
134134
},
135135
{
136136
"path": "dist/production/server.react-server.js",
137-
"limit": "15.645 KB"
137+
"limit": "15.675 KB"
138138
},
139139
{
140140
"path": "dist/production/middleware.js",

packages/use-intl/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@
9090
"size-limit": [
9191
{
9292
"path": "dist/production/index.js",
93-
"limit": "15.235 kB"
93+
"limit": "15.26 kB"
9494
}
9595
]
9696
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
type RelativeTimeFormatOptions = {
22
now?: number | Date;
33
unit?: Intl.RelativeTimeFormatUnit;
4+
numberingSystem?: string;
5+
style?: Intl.RelativeTimeFormatStyle;
6+
// We don't support the `numeric` property by design (see https://github.com/amannn/next-intl/pull/765)
47
};
58

69
export default RelativeTimeFormatOptions;

packages/use-intl/src/core/createFormatter.tsx

Lines changed: 35 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -212,49 +212,53 @@ export default function createFormatter({
212212
}
213213
}
214214

215-
function extractNowDate(
216-
nowOrOptions?: RelativeTimeFormatOptions['now'] | RelativeTimeFormatOptions
217-
) {
218-
if (nowOrOptions instanceof Date || typeof nowOrOptions === 'number') {
219-
return new Date(nowOrOptions);
220-
}
221-
if (nowOrOptions?.now !== undefined) {
222-
return new Date(nowOrOptions.now);
223-
}
224-
return getGlobalNow();
225-
}
226-
227215
function relativeTime(
228216
/** The date time that needs to be formatted. */
229217
date: number | Date,
230218
/** The reference point in time to which `date` will be formatted in relation to. */
231219
nowOrOptions?: RelativeTimeFormatOptions['now'] | RelativeTimeFormatOptions
232220
) {
233221
try {
222+
let nowDate: Date | undefined,
223+
unit: Intl.RelativeTimeFormatUnit | undefined;
224+
const opts: Intl.RelativeTimeFormatOptions = {};
225+
if (nowOrOptions instanceof Date || typeof nowOrOptions === 'number') {
226+
nowDate = new Date(nowOrOptions);
227+
} else if (nowOrOptions) {
228+
if (nowOrOptions.now != null) {
229+
nowDate = new Date(nowOrOptions.now);
230+
} else {
231+
nowDate = getGlobalNow();
232+
}
233+
unit = nowOrOptions.unit;
234+
opts.style = nowOrOptions.style;
235+
// @ts-expect-error -- Types are slightly outdated
236+
opts.numberingSystem = nowOrOptions.numberingSystem;
237+
}
238+
239+
if (!nowDate) {
240+
nowDate = getGlobalNow();
241+
}
242+
234243
const dateDate = new Date(date);
235-
const nowDate = extractNowDate(nowOrOptions);
236244
const seconds = (dateDate.getTime() - nowDate.getTime()) / 1000;
237245

238-
const unit =
239-
typeof nowOrOptions === 'number' ||
240-
nowOrOptions instanceof Date ||
241-
nowOrOptions?.unit === undefined
242-
? resolveRelativeTimeUnit(seconds)
243-
: nowOrOptions.unit;
246+
if (!unit) {
247+
unit = resolveRelativeTimeUnit(seconds);
248+
}
244249

245-
const value = calculateRelativeTimeValue(seconds, unit);
250+
// `numeric: 'auto'` can theoretically produce output like "yesterday",
251+
// but it only works with integers. E.g. -1 day will produce "yesterday",
252+
// but -1.1 days will produce "-1.1 days". Rounding before formatting is
253+
// not desired, as the given dates might cross a threshold were the
254+
// output isn't correct anymore. Example: 2024-01-08T23:00:00.000Z and
255+
// 2024-01-08T01:00:00.000Z would produce "yesterday", which is not the
256+
// case. By using `always` we can ensure correct output. The only exception
257+
// is the formatting of times <1 second as "now".
258+
opts.numeric = unit === 'second' ? 'auto' : 'always';
246259

247-
return new Intl.RelativeTimeFormat(locale, {
248-
// `numeric: 'auto'` can theoretically produce output like "yesterday",
249-
// but it only works with integers. E.g. -1 day will produce "yesterday",
250-
// but -1.1 days will produce "-1.1 days". Rounding before formatting is
251-
// not desired, as the given dates might cross a threshold were the
252-
// output isn't correct anymore. Example: 2024-01-08T23:00:00.000Z and
253-
// 2024-01-08T01:00:00.000Z would produce "yesterday", which is not the
254-
// case. By using `always` we can ensure correct output. The only exception
255-
// is the formatting of times <1 second as "now".
256-
numeric: unit === 'second' ? 'auto' : 'always'
257-
}).format(value, unit);
260+
const value = calculateRelativeTimeValue(seconds, unit);
261+
return new Intl.RelativeTimeFormat(locale, opts).format(value, unit);
258262
} catch (error) {
259263
onError(
260264
new IntlError(IntlErrorCode.FORMATTING_ERROR, (error as Error).message)

packages/use-intl/test/core/createFormatter.test.tsx

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,19 @@ describe('relativeTime', () => {
227227
).toBe('in 2 years');
228228
});
229229

230+
it('formats a relative time with a different style', () => {
231+
const formatter = createFormatter({
232+
locale: 'en',
233+
timeZone: 'Europe/Berlin'
234+
});
235+
expect(
236+
formatter.relativeTime(parseISO('2020-03-20T08:30:00.000Z'), {
237+
now: parseISO('2020-11-22T10:36:00.000Z'),
238+
style: 'narrow'
239+
})
240+
).toBe('8mo ago');
241+
});
242+
230243
it('formats a relative time with options', () => {
231244
const formatter = createFormatter({
232245
locale: 'en',
@@ -235,9 +248,11 @@ describe('relativeTime', () => {
235248
expect(
236249
formatter.relativeTime(parseISO('2020-03-20T08:30:00.000Z'), {
237250
now: parseISO('2020-11-22T10:36:00.000Z'),
238-
unit: 'day'
251+
unit: 'day',
252+
numberingSystem: 'arab',
253+
style: 'narrow'
239254
})
240-
).toBe('247 days ago');
255+
).toBe('٢٤٧d ago');
241256
});
242257

243258
it('supports the quarter unit', () => {

0 commit comments

Comments
 (0)