You would have to take the current time and diff the tiem you are formatting to get the milliseconds.
Now, you need to figure out the closest unit to round down to and format using that unit.
Just swap Intl.RelativeTimeFormat for intl.formatRelativeTime where applicable, but the algorithm should remain the same.
if (Date.prototype.getUTCTime === undefined) {
Date.prototype.getUTCTime = function() {
return this.getTime() - (this.getTimezoneOffset() * 60000);
};
}
const
WEEK_IN_MILLIS = 6.048e8,
DAY_IN_MILLIS = 8.64e7,
HOUR_IN_MILLIS = 3.6e6,
MIN_IN_MILLIS = 6e4,
SEC_IN_MILLIS = 1e3;
// For testing only, remove the constructor argument in production.
const getCurrentUTCTime = () => new Date('2021-04-26T14:21:51.771Z').getUTCTime();
const timeFromNow = (date, formatter) => {
const
millis = typeof date === 'string' ? new Date(date).getUTCTime() : date.getUTCTime(),
diff = millis - getCurrentUTCTime();
if (Math.abs(diff) > WEEK_IN_MILLIS)
return formatter.format(Math.trunc(diff / WEEK_IN_MILLIS), 'week');
else if (Math.abs(diff) > DAY_IN_MILLIS)
return formatter.format(Math.trunc(diff / DAY_IN_MILLIS), 'day');
else if (Math.abs(diff) > HOUR_IN_MILLIS)
return formatter.format(Math.trunc((diff % DAY_IN_MILLIS) / HOUR_IN_MILLIS), 'hour');
else if (Math.abs(diff) > MIN_IN_MILLIS)
return formatter.format(Math.trunc((diff % HOUR_IN_MILLIS) / MIN_IN_MILLIS), 'minute');
else
return formatter.format(Math.trunc((diff % MIN_IN_MILLIS) / SEC_IN_MILLIS), 'second');
};
const dateFormat = new Intl.RelativeTimeFormat('en', { style: 'long' });
console.log(timeFromNow('2021-04-24T14:21:51.771Z', dateFormat));
console.log(timeFromNow('2021-04-25T14:21:51.771Z', dateFormat));
console.log(timeFromNow('2021-04-26T14:21:51.771Z', dateFormat));
console.log(timeFromNow('2021-04-27T14:21:51.771Z', dateFormat));
console.log(timeFromNow('2021-04-28T14:21:51.771Z', dateFormat));
console.log(timeFromNow('2021-04-29T14:21:51.771Z', dateFormat));
console.log(timeFromNow('2021-04-30T14:21:51.771Z', dateFormat));
console.log(timeFromNow('2021-05-01T14:21:51.771Z', dateFormat));
console.log(timeFromNow('2021-05-02T14:21:51.771Z', dateFormat));
console.log(timeFromNow('2021-05-03T14:21:51.771Z', dateFormat));
console.log(timeFromNow('2021-05-04T14:21:51.771Z', dateFormat));
.as-console-wrapper { top: 0; max-height: 100% !important; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/intl-messageformat/9.0.2/intl-messageformat.min.js"></script>
Here is a version of the code above written in React:
View on CodeSandbox
import { StrictMode } from "react";
import ReactDOM from "react-dom";
import { IntlProvider, useIntl } from "react-intl";
const WEEK_IN_MILLIS = 6.048e8,
DAY_IN_MILLIS = 8.64e7,
HOUR_IN_MILLIS = 3.6e6,
MIN_IN_MILLIS = 6e4,
SEC_IN_MILLIS = 1e3;
const getUTCTime = (date) => date.getTime() - date.getTimezoneOffset() * 60000;
// For testing only, remove the constructor argument in production.
const getCurrentUTCTime = () => getUTCTime(new Date());
const defaultFormatOptions = {
style: "long"
};
const timeFromNow = (date, intl, options = defaultFormatOptions) => {
const millis =
typeof date === "string" ? getUTCTime(new Date(date)) : getUTCTime(date),
diff = millis - getCurrentUTCTime();
if (Math.abs(diff) > WEEK_IN_MILLIS)
return intl.formatRelativeTime(
Math.trunc(diff / WEEK_IN_MILLIS),
"week",
options
);
else if (Math.abs(diff) > DAY_IN_MILLIS)
return intl.formatRelativeTime(
Math.trunc(diff / DAY_IN_MILLIS),
"day",
options
);
else if (Math.abs(diff) > HOUR_IN_MILLIS)
return intl.formatRelativeTime(
Math.trunc((diff % DAY_IN_MILLIS) / HOUR_IN_MILLIS),
"hour",
options
);
else if (Math.abs(diff) > MIN_IN_MILLIS)
return intl.formatRelativeTime(
Math.trunc((diff % HOUR_IN_MILLIS) / MIN_IN_MILLIS),
"minute",
options
);
else
return intl.formatRelativeTime(
Math.trunc((diff % MIN_IN_MILLIS) / SEC_IN_MILLIS),
"second",
options
);
};
const CreatedConsetee = ({ date }) => {
return <>{timeFromNow(date, useIntl())}</>;
};
ReactDOM.render(
<StrictMode>
<IntlProvider locale={navigator.language}>
<div className="App">
<h1>
<CreatedConsetee date={new Date("2021-04-26T14:21:51.771Z")} />
</h1>
</div>
</IntlProvider>
</StrictMode>,
document.getElementById("root")
);
Answer from Mr. Polywhirl on Stack OverflowVideos
You would have to take the current time and diff the tiem you are formatting to get the milliseconds.
Now, you need to figure out the closest unit to round down to and format using that unit.
Just swap Intl.RelativeTimeFormat for intl.formatRelativeTime where applicable, but the algorithm should remain the same.
if (Date.prototype.getUTCTime === undefined) {
Date.prototype.getUTCTime = function() {
return this.getTime() - (this.getTimezoneOffset() * 60000);
};
}
const
WEEK_IN_MILLIS = 6.048e8,
DAY_IN_MILLIS = 8.64e7,
HOUR_IN_MILLIS = 3.6e6,
MIN_IN_MILLIS = 6e4,
SEC_IN_MILLIS = 1e3;
// For testing only, remove the constructor argument in production.
const getCurrentUTCTime = () => new Date('2021-04-26T14:21:51.771Z').getUTCTime();
const timeFromNow = (date, formatter) => {
const
millis = typeof date === 'string' ? new Date(date).getUTCTime() : date.getUTCTime(),
diff = millis - getCurrentUTCTime();
if (Math.abs(diff) > WEEK_IN_MILLIS)
return formatter.format(Math.trunc(diff / WEEK_IN_MILLIS), 'week');
else if (Math.abs(diff) > DAY_IN_MILLIS)
return formatter.format(Math.trunc(diff / DAY_IN_MILLIS), 'day');
else if (Math.abs(diff) > HOUR_IN_MILLIS)
return formatter.format(Math.trunc((diff % DAY_IN_MILLIS) / HOUR_IN_MILLIS), 'hour');
else if (Math.abs(diff) > MIN_IN_MILLIS)
return formatter.format(Math.trunc((diff % HOUR_IN_MILLIS) / MIN_IN_MILLIS), 'minute');
else
return formatter.format(Math.trunc((diff % MIN_IN_MILLIS) / SEC_IN_MILLIS), 'second');
};
const dateFormat = new Intl.RelativeTimeFormat('en', { style: 'long' });
console.log(timeFromNow('2021-04-24T14:21:51.771Z', dateFormat));
console.log(timeFromNow('2021-04-25T14:21:51.771Z', dateFormat));
console.log(timeFromNow('2021-04-26T14:21:51.771Z', dateFormat));
console.log(timeFromNow('2021-04-27T14:21:51.771Z', dateFormat));
console.log(timeFromNow('2021-04-28T14:21:51.771Z', dateFormat));
console.log(timeFromNow('2021-04-29T14:21:51.771Z', dateFormat));
console.log(timeFromNow('2021-04-30T14:21:51.771Z', dateFormat));
console.log(timeFromNow('2021-05-01T14:21:51.771Z', dateFormat));
console.log(timeFromNow('2021-05-02T14:21:51.771Z', dateFormat));
console.log(timeFromNow('2021-05-03T14:21:51.771Z', dateFormat));
console.log(timeFromNow('2021-05-04T14:21:51.771Z', dateFormat));
.as-console-wrapper { top: 0; max-height: 100% !important; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/intl-messageformat/9.0.2/intl-messageformat.min.js"></script>
Here is a version of the code above written in React:
View on CodeSandbox
import { StrictMode } from "react";
import ReactDOM from "react-dom";
import { IntlProvider, useIntl } from "react-intl";
const WEEK_IN_MILLIS = 6.048e8,
DAY_IN_MILLIS = 8.64e7,
HOUR_IN_MILLIS = 3.6e6,
MIN_IN_MILLIS = 6e4,
SEC_IN_MILLIS = 1e3;
const getUTCTime = (date) => date.getTime() - date.getTimezoneOffset() * 60000;
// For testing only, remove the constructor argument in production.
const getCurrentUTCTime = () => getUTCTime(new Date());
const defaultFormatOptions = {
style: "long"
};
const timeFromNow = (date, intl, options = defaultFormatOptions) => {
const millis =
typeof date === "string" ? getUTCTime(new Date(date)) : getUTCTime(date),
diff = millis - getCurrentUTCTime();
if (Math.abs(diff) > WEEK_IN_MILLIS)
return intl.formatRelativeTime(
Math.trunc(diff / WEEK_IN_MILLIS),
"week",
options
);
else if (Math.abs(diff) > DAY_IN_MILLIS)
return intl.formatRelativeTime(
Math.trunc(diff / DAY_IN_MILLIS),
"day",
options
);
else if (Math.abs(diff) > HOUR_IN_MILLIS)
return intl.formatRelativeTime(
Math.trunc((diff % DAY_IN_MILLIS) / HOUR_IN_MILLIS),
"hour",
options
);
else if (Math.abs(diff) > MIN_IN_MILLIS)
return intl.formatRelativeTime(
Math.trunc((diff % HOUR_IN_MILLIS) / MIN_IN_MILLIS),
"minute",
options
);
else
return intl.formatRelativeTime(
Math.trunc((diff % MIN_IN_MILLIS) / SEC_IN_MILLIS),
"second",
options
);
};
const CreatedConsetee = ({ date }) => {
return <>{timeFromNow(date, useIntl())}</>;
};
ReactDOM.render(
<StrictMode>
<IntlProvider locale={navigator.language}>
<div className="App">
<h1>
<CreatedConsetee date={new Date("2021-04-26T14:21:51.771Z")} />
</h1>
</div>
</IntlProvider>
</StrictMode>,
document.getElementById("root")
);
@mr-polywhirl I really liked your answer, so I adapted it to something a little shorter that better fit my needs:
/**
* Adapted from https://stackoverflow.com/a/67374710/
*/
const millisecondsPerSecond = 1000;
const secondsPerMinute = 60;
const minutesPerHour = 60;
const hoursPerDay = 24;
const daysPerWeek = 7;
const intervals = {
'week': millisecondsPerSecond * secondsPerMinute * minutesPerHour * hoursPerDay * daysPerWeek,
'day': millisecondsPerSecond * secondsPerMinute * minutesPerHour * hoursPerDay,
'hour': millisecondsPerSecond * secondsPerMinute * minutesPerHour,
'minute': millisecondsPerSecond * secondsPerMinute,
'second': millisecondsPerSecond,
}
const relativeDateFormat = new Intl.RelativeTimeFormat('en', { style: 'long' });
function formatRelativeTime(/** @type Date */ createTime) {
const diff = createTime - new Date();
for (const interval in intervals) {
if (intervals[interval] <= Math.abs(diff)) {
return relativeDateFormat.format(Math.trunc(diff / intervals[interval]), interval);
}
}
return relativeDateFormat.format(diff / 1000, 'second');
}
» npm install @formatjs/intl-relativetimeformat
Documentation for the RelativeFormat function can be found here - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/RelativeTimeFormat.
The idea is that you create an instance of relative time format function, with some pre-defined settings that you want the output to follow. For example, you can set your relative time format function to return English strings in a shortened format.
const rtf = new Intl.RelativeTimeFormat('en', { style: 'narrow' });
console.log(rtf.format(3, 'quarters'));
//expected output: "in 3 qtrs."
You also need to pass negative values in order to get labels intended for the past.
const rtf = new Intl.RelativeTimeFormat('en', { style: 'narrow' });
console.log(rtf.format(-3, 'quarters'));
//expected output: "3 qtrs. ago"
The next part leverages an answer given by @fearofawhackplanet here on StackOverflow
//The 'timestamp' function parameter is your timestamp passed in milliseconds.
function timeDifference(timestamp, locale) {
const msPerMinute = 60 * 1000;
const msPerHour = msPerMinute * 60;
const msPerDay = msPerHour * 24;
const msPerMonth = msPerDay * 30;
const msPerYear = msPerDay * 365;
const current = Date.now();
const elapsed = current - timestamp;
const rtf = new Intl.RelativeTimeFormat(locale, { numeric: "auto" });
if (elapsed < msPerMinute) {
return rtf.format(-Math.floor(elapsed/1000), 'seconds');
}
else if (elapsed < msPerHour) {
return rtf.format(-Math.floor(elapsed/msPerMinute), 'minutes');
}
else if (elapsed < msPerDay) {
return rtf.format(-Math.floor(elapsed/msPerHour), 'hours');
}
else {
return new Date(timestamp).toLocaleDateString(locale);
}
}
//
// code to test the above function
//
const fifteenSecondsAgo = new Date();
const tenMinutesAgo = new Date();
const twoHoursAgo = new Date();
fifteenSecondsAgo.setSeconds(fifteenSecondsAgo.getSeconds() - 15);
tenMinutesAgo.setMinutes(tenMinutesAgo.getMinutes() - 10);
twoHoursAgo.setHours(twoHoursAgo.getHours() - 2);
console.log(timeDifference(fifteenSecondsAgo.getTime(), 'en'));
console.log(timeDifference(fifteenSecondsAgo.getTime(), 'es'));
console.log(timeDifference(tenMinutesAgo.getTime(), 'en'));
console.log(timeDifference(tenMinutesAgo.getTime(), 'es'));
console.log(timeDifference(twoHoursAgo.getTime(), 'en'));
console.log(timeDifference(twoHoursAgo.getTime(), 'es'));
Here is a JSFiddle link to see the code running - https://jsfiddle.net/mhzya237/1/
Here is a similar idea, it also deals with future/present/past times.
function getRelativeTime(time) {
const now = new Date();
const diff = Math.abs(time - now);
const mark = (time - now) >> -1 || 1;
if (diff === 0) return new Intl.RelativeTimeFormat('en').format(0,"second");
const times = [
{ type: 'second', seconds: 1000 },
{ type: 'minute', seconds: 60 * 1000 },
{ type: 'hour', seconds: 60 * 60 * 1000 },
{ type: 'day', seconds: 24 * 60 * 60 * 1000 },
{ type: 'week', seconds: 7 * 24 * 60 * 60 * 1000 },
{ type: 'month', seconds: 30 * 24 * 60 * 60 * 1000 },
{ type: 'year', seconds: 12 * 30 * 24 * 60 * 60 * 1000 },
];
let params = [];
for (let t of times) {
const segment = Math.round(diff / t.seconds);
if (segment >= 0 && segment < 10) {
params = [(segment * mark) | 0, t.type];
break;
}
}
return new Intl.RelativeTimeFormat('en').format(...params);
}
const time = getRelativeTime(new Date(new Date().getTime() - 2 * 1000));
console.info('relative time is', time);
The function takes a time param, finds the seconds difference relative to now, uses the array map to calculate which type yields the closest match and uses it as a param for Intl.RelativeTimeFormat
You can improve getRelativeTime(time) function by either returning the params array and call Intl.RelativeTimeFormat from outside the function or also pass the locale (and options) to the function.
I'm sure there are smarter ways to get rid of the times array, perhaps by creating a wrapping closure but it will force you to "initialize" this utility function first
» npm install intl-relative-time-format