Skip to content

Commit

Permalink
feat: Support numberingSystem and style for relative time formatt…
Browse files Browse the repository at this point in the history
…ing (#1057)

Fixes #1056
  • Loading branch information
amannn committed May 8, 2024
1 parent 3eefe6f commit 14e3aa4
Show file tree
Hide file tree
Showing 5 changed files with 58 additions and 36 deletions.
4 changes: 2 additions & 2 deletions packages/next-intl/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@
"size-limit": [
{
"path": "dist/production/index.react-client.js",
"limit": "15.735 KB"
"limit": "15.765 KB"
},
{
"path": "dist/production/index.react-server.js",
Expand All @@ -134,7 +134,7 @@
},
{
"path": "dist/production/server.react-server.js",
"limit": "15.645 KB"
"limit": "15.675 KB"
},
{
"path": "dist/production/middleware.js",
Expand Down
2 changes: 1 addition & 1 deletion packages/use-intl/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@
"size-limit": [
{
"path": "dist/production/index.js",
"limit": "15.235 kB"
"limit": "15.26 kB"
}
]
}
3 changes: 3 additions & 0 deletions packages/use-intl/src/core/RelativeTimeFormatOptions.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
type RelativeTimeFormatOptions = {
now?: number | Date;
unit?: Intl.RelativeTimeFormatUnit;
numberingSystem?: string;
style?: Intl.RelativeTimeFormatStyle;
// We don't support the `numeric` property by design (see https://github.com/amannn/next-intl/pull/765)
};

export default RelativeTimeFormatOptions;
66 changes: 35 additions & 31 deletions packages/use-intl/src/core/createFormatter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -212,49 +212,53 @@ export default function createFormatter({
}
}

function extractNowDate(
nowOrOptions?: RelativeTimeFormatOptions['now'] | RelativeTimeFormatOptions
) {
if (nowOrOptions instanceof Date || typeof nowOrOptions === 'number') {
return new Date(nowOrOptions);
}
if (nowOrOptions?.now !== undefined) {
return new Date(nowOrOptions.now);
}
return getGlobalNow();
}

function relativeTime(
/** The date time that needs to be formatted. */
date: number | Date,
/** The reference point in time to which `date` will be formatted in relation to. */
nowOrOptions?: RelativeTimeFormatOptions['now'] | RelativeTimeFormatOptions
) {
try {
let nowDate: Date | undefined,
unit: Intl.RelativeTimeFormatUnit | undefined;
const opts: Intl.RelativeTimeFormatOptions = {};
if (nowOrOptions instanceof Date || typeof nowOrOptions === 'number') {
nowDate = new Date(nowOrOptions);
} else if (nowOrOptions) {
if (nowOrOptions.now != null) {
nowDate = new Date(nowOrOptions.now);
} else {
nowDate = getGlobalNow();
}
unit = nowOrOptions.unit;
opts.style = nowOrOptions.style;
// @ts-expect-error -- Types are slightly outdated
opts.numberingSystem = nowOrOptions.numberingSystem;
}

if (!nowDate) {
nowDate = getGlobalNow();
}

const dateDate = new Date(date);
const nowDate = extractNowDate(nowOrOptions);
const seconds = (dateDate.getTime() - nowDate.getTime()) / 1000;

const unit =
typeof nowOrOptions === 'number' ||
nowOrOptions instanceof Date ||
nowOrOptions?.unit === undefined
? resolveRelativeTimeUnit(seconds)
: nowOrOptions.unit;
if (!unit) {
unit = resolveRelativeTimeUnit(seconds);
}

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

return new Intl.RelativeTimeFormat(locale, {
// `numeric: 'auto'` can theoretically produce output like "yesterday",
// but it only works with integers. E.g. -1 day will produce "yesterday",
// but -1.1 days will produce "-1.1 days". Rounding before formatting is
// not desired, as the given dates might cross a threshold were the
// output isn't correct anymore. Example: 2024-01-08T23:00:00.000Z and
// 2024-01-08T01:00:00.000Z would produce "yesterday", which is not the
// case. By using `always` we can ensure correct output. The only exception
// is the formatting of times <1 second as "now".
numeric: unit === 'second' ? 'auto' : 'always'
}).format(value, unit);
const value = calculateRelativeTimeValue(seconds, unit);
return new Intl.RelativeTimeFormat(locale, opts).format(value, unit);
} catch (error) {
onError(
new IntlError(IntlErrorCode.FORMATTING_ERROR, (error as Error).message)
Expand Down
19 changes: 17 additions & 2 deletions packages/use-intl/test/core/createFormatter.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,19 @@ describe('relativeTime', () => {
).toBe('in 2 years');
});

it('formats a relative time with a different style', () => {
const formatter = createFormatter({
locale: 'en',
timeZone: 'Europe/Berlin'
});
expect(
formatter.relativeTime(parseISO('2020-03-20T08:30:00.000Z'), {
now: parseISO('2020-11-22T10:36:00.000Z'),
style: 'narrow'
})
).toBe('8mo ago');
});

it('formats a relative time with options', () => {
const formatter = createFormatter({
locale: 'en',
Expand All @@ -235,9 +248,11 @@ describe('relativeTime', () => {
expect(
formatter.relativeTime(parseISO('2020-03-20T08:30:00.000Z'), {
now: parseISO('2020-11-22T10:36:00.000Z'),
unit: 'day'
unit: 'day',
numberingSystem: 'arab',
style: 'narrow'
})
).toBe('247 days ago');
).toBe('٢٤٧d ago');
});

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

0 comments on commit 14e3aa4

Please sign in to comment.