I think, the following piece of code, do you often use it in development to calculate how much time has passed since now:
import moment from 'moment' // 61k (gzipped:19.k)
function Relative(props) {
const timeString = moment(props.date).fromNow()
return <>
{
timeString}
</>
}
However, you actually use a package with a size of 20k (or compressed, not compressed 61k) only for date conversion? really? What you want is just a date conversion.
Now, after all this scaring from me, you might be wondering if you could do this:
function nativeGetRelativeTime(unit = 'days', amount = 2) {
return `in ${
amount} ${
unit}s`
}
No, please don't do this. Although relative time may seem like a simple problem for the time being, you should be aware that relative time has many complex problems that need to be solved, such as:
- Abbreviations: "1 day ago" is usually not displayed? Usually words such as "yesterday", "tomorrow" or "next year" are displayed
- Future and Past: For example, instead of displaying "within -2 days", we display "2 days ago"
There may also be other issues, such as time zone issues . Once these complex problems come, developers often use libraries like momentJs
and to solve the problem. dayjs
Although, the following piece of code can also try to solve your problem:
function nativeGetRelativeTime(locale, unit, amount) {
if (locale === 'zh') {
const isPlural = amount !== 1 && amount !== -1
const isPast = amount < 0
if (amount === 1 && unit === 'day') return '明天'
if (amount === -1 && unit === 'day') return '昨天'
if (amount === 1 && unit === 'year') return '明年'
if (isPast) {
return `${
amount} ${
unit}${
isPlural ? 's' : ''} 前`
}
return `in ${
amount} ${
day}${
isPlural ? 's' : ''}`
}
}
However, please don't do it. Because it seems like it's getting complicated. And a built-in object that I recommend to you can help you solve the problem of relative time.
Intl.RelativeTimeFormat
Again, when you encounter these situations, remember that there are already many built-in solutions to common problems in modern front-ends, which can be easily used.
In the face of the relative time problem mentioned in this article, what I want to talk about is Intl.RelativeTimeFormat
this object.
I first use the following piece of code to style its convenience:
const rtf = new Intl.RelativeTimeFormat('en', {
numeric: 'auto'
})
rtf.format(1, 'day') // 'tomorrow'
rtf.format(-2, 'year') // '2 years ago'
rtf.format(10, 'minute') // 'in 10 minutes'
Also, it handles different timezones nicely:
const rtf = new Intl.RelativeTimeFormat('es-ES', {
numeric: 'auto'
})
rtf.format(1, 'day') // 'mañana'
rtf.format(-2, 'year') // 'hace 2 años'
rtf.format(10, 'minute') // 'dentro de 10 minutos'
You can even just pass in navigator.language
as the first argument:
const rtf = new Intl.RelativeTimeFormat(
navigator.language // ✅
)
Among other things, Intl.RelativeTimeFormat
supported units include:"year", "quarter", "month", "week", "day", "hour", "minute", 和 "second"
Now, going back to our original example, let's Intl.RelativeTimeFormat
rewrite it as:
First, let's write a simple wrapper function to handle relative time conversions:
function Relative(props) {
const timeString = getRelativeTimeString(props.date)
return <>
{
timeString}
</>
}
Next, we use Intl.RelativeTimeFormat
the object to implement getRelativeTimeString
the function:
export function getRelativeTimeString(
date: Date | number,
lang = navigator.language
): string {
const timeMs = typeof date === "number" ? date : date.getTime();
const deltaSeconds = Math.round((timeMs - Date.now()) / 1000);
const cutoffs = [60, 3600, 86400, 86400 * 7, 86400 * 30, 86400 * 365, Infinity];
const units: Intl.RelativeTimeFormatUnit[] = ["second", "minute", "hour", "day", "week", "month", "year"];
const unitIndex = cutoffs.findIndex(cutoff => cutoff > Math.abs(deltaSeconds));
const divisor = unitIndex ? cutoffs[unitIndex - 1] : 1;
const rtf = new Intl.RelativeTimeFormat(lang, {
numeric: "auto" });
return rtf.format(Math.floor(deltaSeconds / divisor), units[unitIndex]);
}
Using date-fns
But if you don't want your own solution, there is also an open source solution. date-fns
Is a great JavaScript date tool library, each date supports 树摇
a separate export in the way.
Among them, date-fns
there is a built-in intlFormatDistance
function, which is Intl.RelativeTimeFormat
a small wrapper of , which does exactly what we need. And, its size is under 2kb.
Look at the following code, is the code much simpler:
Intl.DateTimeFormat
In addition to this, Intl.DateTimeformat
formatting dates and times are also provided:
new Intl.DateTimeFormat('en-US').format(new Date())
// -> 1/23/2023
new Intl.DateTimeFormat('en-US', {
dateStyle: 'full'
}).format(new Date())
// -> Monday, January 23, 2023
new Intl.DateTimeFormat('en-US', {
timeStyle: 'medium'
}).format(new Date())
// -> 4:17:23 PM
new Intl.DateTimeFormat('en-US', {
dayPeriod: 'short', hour: 'numeric'
}).format(new Date())
// -> 4 in the afternoon
Intl.NumberFormat
Also, Intl.NumberFormat
this object can format numbers for you:
new Intl.NumberFormat('en', {
style: 'currency', currency: 'USD'
}).format(123456.789)
// -> $123,456.79
new Intl.NumberFormat('de-DE', {
style: 'currency', currency: 'EUR'
}).format(123456.789)
// -> 123.456,79 €
new Intl.NumberFormat('pt-PT', {
style: 'unit', unit: 'kilometer-per-hour'
}).format(50));
// -> 50 km/h
(16).toLocaleString('en-GB', {
style: 'unit', unit: 'liter', unitDisplay: 'long',
}));
// -> 16 litres
Currently, all major browsers are supported, as well as Node.js and Deno Intl.RelativeTimeFormat
.
If you are still using momentJs
a large data processing library like this, consider whether Intl.RelativeTimeFormat, Intl.DateTimeFormat
these objects can help you solve the problems you face.
Here is the programming track, see you in the next article~