Summary
-
By reducing the usage of external packages/dependencies, we can reduce the amount of bundle size when shipping
-
We can rely on the built-in class
Intl.RelativeTimeFormat
-
We first define the locale(s) we want to use, such as
en-US
,en-GB
,id-ID
, and many more, followed by the options like the style, the information density, etc, like this:const relativeTimeFormat = new Intl.RelativeTimeFormat("en-US", { numeric: "auto", style: "short", });
-
To generate the relative timestamp, use the
format
method, like this:// "in 10 days" const result1 = relativeTimeFormat.format(10, "day");
-
There is some drawback, though. Unfortunately, the logic of automatically deciding the unit to be used (like "months", "weeks", "days", etc) are not handled by the method, so we have to implement the logic ourselves.
Handling dates can be cumbersome in general. Especially in JavaScript, the
built-in Date
class isn't the most convinient at handling dates. So, what I've
seen is that a lot of JavaScript developers tends to use external
libraries/package.
But developers aren't exactly the wisest at choosing the package. One of the most popular libraries for date handling in JavaScript, moment, has been recommended by the developers to use something else, due to various reasons. This package isn't exactly small either. According to bundlephobia, it's bundle size is 290.4kb as of May 2023. You can take a look at it here. All these reasons to not use this particular package, yet I've seen some developers start a new project using this package.
Fortunately, though, there have been an alternative of moment package. My most favorite one is dayjs. Not only it's similar to moment syntax-wise, it's also the lightest one among other alternatives.
But I think we can do better, how about we avoid installing any dependencies and instead create our own implementation of relative timestamp by relying on JavaScript's built-in class--therefore we don't have to think about dependencies and increased bundle size.
Create a relative timestamps (like "in 4 min." etc) using built-in Intl.RelativeTimeFormat
class
The documentation for this class can be read here.
First, we need instantiate the class with locales and some options, like this:
const relativeTimeFormat = new Intl.RelativeTimeFormat("en-US", {
// "always", or "auto"
numeric: "auto",
// "long", "short", or "narrow"
style: "long",
});
We can then generate a relative timestamps using the method format
, like this:
relativeTimeFormat.format(-10, "month"); // "10 months ago"
relativeTimeFormat.format(20, "day"); // "in 20 days"
At this point, we already have a reliable and nicely relative timestamp
generator, that will also generate the timestamp based on the desired locale
too. But there's some drawbacks. What if, hypothetically you use the unit day
for the formatter, but the input to the formatter is 15 years, or in day
unit,
5478 days?
relativeTimeFormat.format(-5478, "day"); // "5,478 days ago"
Clearly, this is a bit hard to read. Personally, I prefer reading "15 years" instead of reading "5,478 days ago" then calculating it using my potato-brain how many years is that.
Automatically deciding the unit based on the input
Unfortunately, the format
method doesn't have that responsibility, so we'd
have to create our own logic.
We'll need these to get our logic up and running:
- a function that should return the correct unit based on the time difference
- a function that should return the correct value based on the unit
Before we start, let's define some constants to make our life easier:
const ONE_MILLISECOND = 1;
const ONE_SECOND = 1000 * ONE_MILLISECOND;
const ONE_MINUTE = 60 * ONE_SECOND;
const ONE_HOUR = 60 * ONE_MINUTE;
const ONE_DAY = 24 * ONE_HOUR;
const ONE_WEEK = 7 * ONE_DAY;
const ONE_MONTH = 30 * ONE_DAY;
const ONE_YEAR = 12 * ONE_MONTH;
Let's create the first function:
export function getUnit(inputTime: Date, currentTime: Date) {
const timeDiff = Math.abs(currentTime.getTime() - inputTime.getTime());
if (timeDiff < ONE_MINUTE) return "minute";
if (timeDiff < ONE_HOUR) return "minute";
if (timeDiff < ONE_DAY) return "hour";
if (timeDiff < ONE_WEEK) return "day";
if (timeDiff < ONE_MONTH) return "week";
if (timeDiff < ONE_YEAR) return "month";
return "year";
}
Then let's create the function to return the correct value:
export function getCorrectTimestampValue(inputTime: Date, currentTime: Date) {
const timeDiff = currentTime.getTime() - inputTime.getTime();
if (timeDiff < ONE_MINUTE) return Math.floor(timeDiff / ONE_MINUTE);
if (timeDiff < ONE_HOUR) return Math.floor(timeDiff / ONE_MINUTE);
if (timeDiff < ONE_DAY) return Math.floor(timeDiff / ONE_HOUR);
if (timeDiff < ONE_WEEK) return Math.floor(timeDiff / ONE_DAY);
if (timeDiff < ONE_MONTH) return Math.floor(timeDiff / ONE_WEEK);
if (timeDiff < ONE_YEAR) return Math.floor(timeDiff / ONE_MONTH);
return Math.round(timeDiff / ONE_YEAR);
}
Finally, we can call both function to create a better relative timestamps:
export function relativeTimeFormatter(
inputTime: Date,
currentTime: Date = new Date()
) {
return relativeTimeFormat.format(
getCorrectTimestampValue(inputTime, currentTime),
getUnit(inputTime, currentTime)
);
}
Hope this helps,
Rizqi