Javascript timestamp to relative time
JavascriptDatetimeTimeRelative DateJavascript Problem Overview
I'm looking for a nice JS snippet to convert a timestamp (e.g. from Twitter API) to a nice user friendly relative time (e.g. 2 seconds ago, one week ago etc).
Anyone care to share some of their favourite methods (preferably not using plugins)?
Javascript Solutions
Solution 1 - Javascript
Well it's pretty easy if you aren't overly concerned with accuracy. What wrong with the trivial method?
function timeDifference(current, previous) {
var msPerMinute = 60 * 1000;
var msPerHour = msPerMinute * 60;
var msPerDay = msPerHour * 24;
var msPerMonth = msPerDay * 30;
var msPerYear = msPerDay * 365;
var elapsed = current - previous;
if (elapsed < msPerMinute) {
return Math.round(elapsed/1000) + ' seconds ago';
}
else if (elapsed < msPerHour) {
return Math.round(elapsed/msPerMinute) + ' minutes ago';
}
else if (elapsed < msPerDay ) {
return Math.round(elapsed/msPerHour ) + ' hours ago';
}
else if (elapsed < msPerMonth) {
return 'approximately ' + Math.round(elapsed/msPerDay) + ' days ago';
}
else if (elapsed < msPerYear) {
return 'approximately ' + Math.round(elapsed/msPerMonth) + ' months ago';
}
else {
return 'approximately ' + Math.round(elapsed/msPerYear ) + ' years ago';
}
}
Working example here.
You might want to tweak it to handle the singular values better (e.g. 1 day
instead of 1 days
) if that bothers you.
Solution 2 - Javascript
Update April 4, 2021:
I've converted the below code to a node package. Here's the repository.
Intl.RelativeTimeFormat - Native API
[✔] (Dec' 18) a Stage 3 proposal, and already implemented in Chrome 71
[✔] (Oct' 20) at Stage 4 (finished), and ready for inclusion in the formal ECMAScript standard
// in miliseconds var units = { year : 24 * 60 * 60 * 1000 * 365, month : 24 * 60 * 60 * 1000 * 365/12, day : 24 * 60 * 60 * 1000, hour : 60 * 60 * 1000, minute: 60 * 1000, second: 1000 }
var rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' })
var getRelativeTime = (d1, d2 = new Date()) => {
var elapsed = d1 - d2
// "Math.abs" accounts for both "past" & "future" scenarios
for (var u in units)
if (Math.abs(elapsed) > units[u] || u == 'second')
return rtf.format(Math.round(elapsed/units[u]), u)
}
// test-list of dates to compare with current date
[
'10/20/1984',
'10/20/2015',
+new Date() - units.year,
+new Date() - units.month,
+new Date() - units.day,
+new Date() - units.hour,
+new Date() - units.minute,
+new Date() + units.minute*2,
+new Date() + units.day*7,
]
.forEach(d => console.log(
new Date(d).toLocaleDateString(),
new Date(d).toLocaleTimeString(),
'(Relative to now) →',
getRelativeTime(+new Date(d))
))
> Intl.RelativeTimeFormat is available by default in V8 v7.1.179 and > Chrome 71. As this API becomes more widely available, you’ll find > libraries such as Moment.js, Globalize, and date-fns dropping their > dependency on hardcoded CLDR databases in favor of the native relative > time formatting functionality, thereby improving load-time > performance, parse- and compile-time performance, run-time > performance, and memory usage.
Solution 3 - Javascript
Here is the exact mimic of twitter time ago without plugins:
function timeSince(timeStamp) {
var now = new Date(),
secondsPast = (now.getTime() - timeStamp) / 1000;
if (secondsPast < 60) {
return parseInt(secondsPast) + 's';
}
if (secondsPast < 3600) {
return parseInt(secondsPast / 60) + 'm';
}
if (secondsPast <= 86400) {
return parseInt(secondsPast / 3600) + 'h';
}
if (secondsPast > 86400) {
day = timeStamp.getDate();
month = timeStamp.toDateString().match(/ [a-zA-Z]*/)[0].replace(" ", "");
year = timeStamp.getFullYear() == now.getFullYear() ? "" : " " + timeStamp.getFullYear();
return day + " " + month + year;
}
}
const currentTimeStamp = new Date().getTime();
console.log(timeSince(currentTimeStamp));
Gist https://gist.github.com/timuric/11386129
Fiddle http://jsfiddle.net/qE8Lu/1/
Hope it helps.
Solution 4 - Javascript
Inspirated on Diego Castillo awnser's and in the timeago.js plugin, I wrote my own vanilla plugin for this.
var timeElement = document.querySelector('time'),
time = new Date(timeElement.getAttribute('datetime'));
timeElement.innerText = TimeAgo.inWords(time.getTime());
var TimeAgo = (function() {
var self = {};
// Public Methods
self.locales = {
prefix: '',
sufix: 'ago',
seconds: 'less than a minute',
minute: 'about a minute',
minutes: '%d minutes',
hour: 'about an hour',
hours: 'about %d hours',
day: 'a day',
days: '%d days',
month: 'about a month',
months: '%d months',
year: 'about a year',
years: '%d years'
};
self.inWords = function(timeAgo) {
var seconds = Math.floor((new Date() - parseInt(timeAgo)) / 1000),
separator = this.locales.separator || ' ',
words = this.locales.prefix + separator,
interval = 0,
intervals = {
year: seconds / 31536000,
month: seconds / 2592000,
day: seconds / 86400,
hour: seconds / 3600,
minute: seconds / 60
};
var distance = this.locales.seconds;
for (var key in intervals) {
interval = Math.floor(intervals[key]);
if (interval > 1) {
distance = this.locales[key + 's'];
break;
} else if (interval === 1) {
distance = this.locales[key];
break;
}
}
distance = distance.replace(/%d/i, interval);
words += distance + separator + this.locales.sufix;
return words.trim();
};
return self;
}());
// USAGE
var timeElement = document.querySelector('time'),
time = new Date(timeElement.getAttribute('datetime'));
timeElement.innerText = TimeAgo.inWords(time.getTime());
<time datetime="2016-06-13"></time>
Solution 5 - Javascript
const units = [
['year', 31536000000],
['month', 2628000000],
['day', 86400000],
['hour', 3600000],
['minute', 60000],
['second', 1000],
]
const rtf = new Intl.RelativeTimeFormat('en', { style:'narrow'})
const relatime = elapsed => {
for (const [unit, amount] of units) {
if (Math.abs(elapsed) > amount || unit === 'second') {
return rtf.format(Math.round(elapsed/amount), unit)
}
}
}
had some fun golfing it 192b
hehe
const relatime = e=>{for(let[u,a]of Object.entries({year:31536e6,month:2628e6,day:864e5,hour:36e5,minute:6e4,second:1e3})){if(Math.abs(e)>a||a===1e3){return new Intl.RelativeTimeFormat('en',{style:'narrow'}).format(~~(e/a),u)}}}
I also tested a functionnal version while golfing:
const rtf = new Intl.RelativeTimeFormat('en', { style:'narrow'})
const relatime = Object.entries({year:31536e6,month:2628e6,day:864e5,hour:36e5,minute:6e4,second:1e3})
.reduce((f, [unit, amount]) => amount === 1e3
? f(elapsed => rtf.format(Math.round(elapsed/amount), unit))
: next => f(e => Math.abs(e) < amount
? next(elapsed)
: rtf.format(Math.round(elapsed/amount), unit)), _=>_)
All right i really have to get back to work now...
Solution 6 - Javascript
Typescript and Intl.RelativeTimeFormat (2020)
Combined Typescript implementation of @vsync and @kigiri approach using Web API RelativeTimeFormat.
const units: {unit: Intl.RelativeTimeFormatUnit; ms: number}[] = [
{unit: "year", ms: 31536000000},
{unit: "month", ms: 2628000000},
{unit: "day", ms: 86400000},
{unit: "hour", ms: 3600000},
{unit: "minute", ms: 60000},
{unit: "second", ms: 1000},
];
const rtf = new Intl.RelativeTimeFormat("en", {numeric: "auto"});
/**
* Get language-sensitive relative time message from Dates.
* @param relative - the relative dateTime, generally is in the past or future
* @param pivot - the dateTime of reference, generally is the current time
*/
export function relativeTimeFromDates(relative: Date | null, pivot: Date = new Date()): string {
if (!relative) return "";
const elapsed = relative.getTime() - pivot.getTime();
return relativeTimeFromElapsed(elapsed);
}
/**
* Get language-sensitive relative time message from elapsed time.
* @param elapsed - the elapsed time in milliseconds
*/
export function relativeTimeFromElapsed(elapsed: number): string {
for (const {unit, ms} of units) {
if (Math.abs(elapsed) >= ms || unit === "second") {
return rtf.format(Math.round(elapsed / ms), unit);
}
}
return "";
}
Solution 7 - Javascript
MomentJS Answer
For Moment.js users, it has fromNow() function that returns "x days" or "x hours ago" from current date/time.
moment([2007, 0, 29]).fromNow(); // 4 years ago
moment([2007, 0, 29]).fromNow(true); // 4 years
Solution 8 - Javascript
Like the OP, I tend to avoid plugins and packages for code that seems trivial to write myself. Of course, then I end up writing my own packages.
I created this NPM package so I could easily convert any date to a relative time string (e.g., "yesterday", "last week", "2 years ago"), with translations for internationalization (i18n) and localization (l10n).
Feel free to look through the source code; it is a pretty small, single file. Most of what's in the repository is for unit tests, version control, and publishing to NPM.
export default class RTF {
formatters;
options;
/**
* @param options {{localeMatcher: string?, numeric: string?, style: string?}} Intl.RelativeTimeFormat() options
*/
constructor(options = RTF.defaultOptions) {
this.options = options;
this.formatters = { auto: new Intl.RelativeTimeFormat(undefined, this.options) };
}
/**
* Add a formatter for a given locale.
*
* @param locale {string} A string with a BCP 47 language tag, or an array of such strings
* @returns {boolean} True if locale is supported; otherwise false
*/
addLocale(locale) {
if (!Intl.RelativeTimeFormat.supportedLocalesOf(locale).includes(locale)) {
return false;
}
if (!this.formatters.hasOwnProperty(locale)) {
this.formatters[locale] = new Intl.RelativeTimeFormat(locale, this.options);
}
return true;
}
/**
* Format a given date as a relative time string, with support for i18n.
*
* @param date {Date|number|string} Date object (or timestamp, or valid string representation of a date) to format
* @param locale {string?} i18n code to use (e.g. 'en', 'fr', 'zh'); if omitted, default locale of runtime is used
* @returns {string} Localized relative time string (e.g. '1 minute ago', '12 hours ago', '3 days ago')
*/
format(date, locale = "auto") {
if (!(date instanceof Date)) {
date = new Date(Number.isNaN(date) ? Date.parse(date) : date);
}
if (!this.formatters.hasOwnProperty(locale) && !this.addLocale(locale)) {
locale = "auto";
}
const elapsed = date - Date.now();
for (let i = 0; i < RTF.units.length; i++) {
const { unit, value } = RTF.units[i];
if (unit === 'second' || Math.abs(elapsed) >= value) {
return this.formatters[locale].format(Math.round(elapsed/value), unit);
}
}
}
/**
* Generate HTTP middleware that works with popular frameworks and i18n tools like Express and i18next.
*
* @param rtf {RTF?} Instance of RTF to use; defaults to a new instance with default options
* @param reqProp {string?} Property name to add to the HTTP request context; defaults to `rtf`
* @param langProp {string?} Property of HTTP request context where language is stored; defaults to `language`
* @returns {function(*, *, *): *} HTTP middleware function
*/
static httpMiddleware(rtf = new RTF(), reqProp = "rtf", langProp = "language") {
return (req, res, next) => {
req[reqProp] = (date) => rtf.format(date, req[langProp]);
next();
};
}
/**
* Default options object used by Intl.RelativeTimeFormat() constructor.
*
* @type {{localeMatcher: string, numeric: string, style: string}}
*/
static defaultOptions = {
localeMatcher: "best fit",
numeric: "auto", // this intentionally differs from Intl.RelativeTimeFormat(), because "always" is dumb
style: "long",
};
/**
* Used to determine the arguments to pass to Intl.RelativeTimeFormat.prototype.format().
*/
static units = [
{ unit: "year", value: 365 * 24 * 60 * 60 * 1000 },
{ unit: "month", value: 365 / 12 * 24 * 60 * 60 * 1000 },
{ unit: "week", value: 7 * 24 * 60 * 60 * 1000 },
{ unit: "day", value: 24 * 60 * 60 * 1000 },
{ unit: "hour", value: 60 * 60 * 1000 },
{ unit: "minute", value: 60 * 1000 },
{ unit: "second", value: 1000 },
];
/**
* Enumerated values for options object used by Intl.RelativeTimeFormat() constructor.
*
* @type {{localeMatcher: {lookup: string, default: string, bestFit: string}, numeric: {always: string, default: string, auto: string}, style: {default: string, short: string, narrow: string, long: string}}}
*/
static opt = {
localeMatcher: {
bestFit: "best fit",
lookup: "lookup",
},
numeric: {
always: "always",
auto: "auto",
},
style: {
long: "long",
narrow: "narrow",
short: "short",
},
};
}
Key Features
- Uses native JavaScript Intl.RelativeTimeFormat under the hood, with no dependencies.
- Formats any Date object, timestamp, or valid string representation of a date that can be parsed by Date.parse().
- Provides HTTP middleware compatible with popular REST frameworks like Express and i18n tools like i18next.
Why use this instead of Intl.RelativeTimeFormat.prototype.format()?
Intl.RelativeTimeFormat.prototype.format() takes two arguments: value and units.
const rtf = new Intl.RelativeTimeFormat("en", { style: "narrow" });
expect(rtf.format(-1, "day")).toBe("1 day ago");
expect(rtf.format(10, "seconds")).toBe("in 10 sec.");
In order to convert a Date object, timestamp, or date string, you need to write a bunch of boilerplate. This library saves you that headache, and can also be used to generate a middleware function for your REST API that works with your i18n library.
Solution 9 - Javascript
For anyone interested, I ended up creating a Handlebars helper to do this. Usage:
{{#beautify_date}}
{{timestamp_ms}}
{{/beautify_date}}
Helper:
Handlebars.registerHelper('beautify_date', function(options) {
var timeAgo = new Date(parseInt(options.fn(this)));
if (Object.prototype.toString.call(timeAgo) === "[object Date]") {
if (isNaN(timeAgo.getTime())) {
return 'Not Valid';
} else {
var seconds = Math.floor((new Date() - timeAgo) / 1000),
intervals = [
Math.floor(seconds / 31536000),
Math.floor(seconds / 2592000),
Math.floor(seconds / 86400),
Math.floor(seconds / 3600),
Math.floor(seconds / 60)
],
times = [
'year',
'month',
'day',
'hour',
'minute'
];
var key;
for(key in intervals) {
if (intervals[key] > 1)
return intervals[key] + ' ' + times[key] + 's ago';
else if (intervals[key] === 1)
return intervals[key] + ' ' + times[key] + ' ago';
}
return Math.floor(seconds) + ' seconds ago';
}
} else {
return 'Not Valid';
}
});
Solution 10 - Javascript
If you need multilingual and don't want to add a big library like moment. intl-relativeformat from yahoo it a nice solution.
var rf = new IntlRelativeFormat('en-US');
var posts = [
{
id : 1,
title: 'Some Blog Post',
date : new Date(1426271670524)
},
{
id : 2,
title: 'Another Blog Post',
date : new Date(1426278870524)
}
];
posts.forEach(function (post) {
console.log(rf.format(post.date));
});
// => "3 hours ago"
// => "1 hour ago"
Solution 11 - Javascript
Datetime plugins exist because it's very hard to get it right. This video explaining date-time inconsistencies will shed some light on the issue.
All above solutions without plugins are incorrect.
For working with Dates and times using a plugin is preferable. Out of the hundreds of plugins that deal with it, we use Moment.js and it's doing the job.
From the twitter API dcumentation we can see their timestamp format:
"created_at":"Wed Aug 27 13:08:45 +0000 2008"
We can parse with it with Moment.js
const postDatetime = moment(
"Wed Aug 27 13:08:45 +0000 2008",
"dddd, MMMM Do, h:mm:ss a, YYYY"
);
const now = moment();
const timeAgo = now.diff(postDatetime, 'seconds');
To specify the preferred time unit for the diff
, we can use the isSame
method. eg:
if (now.isSame(postDatetime, 'day')) {
const timeUnit = 'days';
}
Overall, constructing something like:
`Posted ${timeAgo} ${timeUnit} ago`;
Refer to your plugin's documentation for handling relative time (ie: "How long ago?") calculations.
Solution 12 - Javascript
(2021) If you only need days, like 243 days ago or in 127 days, then it can be super simple:
function relativeDays(timestamp) {
const rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' });
const aDay = 1000 * 60 * 60 * 24;
const diffInDays = Math.round((timestamp - Date.now()) / aDay);
return rtf.format(diffInDays, 'day');
}
Try running the snippet below:
function relativeDays(timestamp) {
const rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' });
const aDay = 1000 * 60 * 60 * 24;
const diffInDays = Math.round((timestamp - Date.now()) / aDay);
return rtf.format(diffInDays, 'day');
}
console.log(relativeDays(Date.now() - 86400000)); // "yesterday"
console.log(relativeDays(Date.now())); // "today"
console.log(relativeDays(Date.now() + 86400000)); // "tomorrow"
console.log(relativeDays(Date.now() - 8640000000)); // "100 days ago"
console.log(relativeDays(Date.now() + 8640000000)); // "in 100 days"
// Note my timestamp argument is a number in ms, if you want to pass in a Date object, modify the function accordingly
Solution 13 - Javascript
Adding my code for time labels based on the current design from twitter
function timeElapsed(targetTimestamp:string) {
let currentDate=new Date();
let currentTimeInms = currentDate.getTime();
let targetDate=new Date(targetTimestamp);
let targetTimeInms = targetDate.getTime();
let elapsed = Math.floor((currentTimeInms-targetTimeInms)/1000);
if(elapsed<1) {
return '0s';
}
if(elapsed<60) { //< 60 sec
return `${elapsed}s`;
}
if (elapsed < 3600) { //< 60 minutes
return `${Math.floor(elapsed/(60))}m`;
}
if (elapsed < 86400) { //< 24 hours
return `${Math.floor(elapsed/(3600))}h`;
}
if (elapsed < 604800) { //<7 days
return `${Math.floor(elapsed/(86400))}d`;
}
if (elapsed < 2628000) { //<1 month
return `${targetDate.getDate()} ${MonthNames[targetDate.getMonth()]}`;
}
return `${targetDate.getDate()} ${MonthNames[targetDate.getMonth()]} ${targetDate.getFullYear()}`; //more than a monh
}
Solution 14 - Javascript
You can use machinepack-datetime for this purpose. It is easy and clear with its defined API.
tutorialSchema.virtual('createdOn').get(function () {
const DateTime = require('machinepack-datetime');
let timeAgoString = "";
try {
timeAgoString = DateTime.timeFrom({
toWhen: DateTime.parse({
datetime: this.createdAt
}).execSync(),
fromWhen: new Date().getTime()
}).execSync();
} catch(err) {
console.log('error getting createdon', err);
}
return timeAgoString; // a second ago
});