Skip to content

Instantly share code, notes, and snippets.

@LewisJEllis
Last active March 6, 2024 13:31
Show Gist options
  • Star 36 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save LewisJEllis/9ad1f35d102de8eee78f6bd081d486ad to your computer and use it in GitHub Desktop.
Save LewisJEllis/9ad1f35d102de8eee78f6bd081d486ad to your computer and use it in GitHub Desktop.
Simplified getRelativeTimeString
// from https://twitter.com/Steve8708/status/1504131981444980739
// simplified to a function body of 8 tidy lines
// no loop needed, no 2d array of 3-tuples needed
// just 2 arrays, a findIndex call, and some indexing :)
export function getRelativeTimeString(
date: Date | number,
lang = "en"
): 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]);
}
// alternate: init the arrays + RTF only once, keep in closure
// worthwhile if doing lots of calls, RTF init isn't free
// this is probably how I would actually do this for prod shipped code I guess
export function makeRelativeTimeStringGetter(lang = "en") {
const cutoffs = [60, 3600, 86400, 86400 * 7, 86400 * 30, 86400 * 365, Infinity];
const units: Intl.RelativeTimeFormatUnit[] = ["second", "minute", "hour", "day", "week", "month", "year"];
const rtf = new Intl.RelativeTimeFormat(lang, { numeric: "auto" });
return function getRelativeTimeString(date: Date | number): string {
const timeMs = typeof date === "number" ? date : date.getTime();
const deltaSeconds = Math.round((timeMs - Date.now()) / 1000);
const unitIndex = cutoffs.findIndex(cutoff => cutoff > Math.abs(deltaSeconds));
const divisor = unitIndex ? cutoffs[unitIndex - 1] : 1;
return rtf.format(Math.floor(deltaSeconds / divisor), units[unitIndex]);
}
}
console.log(getRelativeTimeString(Date.now() - 100)); // now
console.log(getRelativeTimeString(Date.now() - 10000)); // 10 seconds ago
console.log(getRelativeTimeString(Date.now() - 100000)); // 2 minutes ago
console.log(getRelativeTimeString(Date.now() - 10000000)); // 3 hours ago
console.log(getRelativeTimeString(Date.now() + 10000)); // in 10 seconds
console.log(getRelativeTimeString(Date.now() + 100000)); // in 1 minute
console.log(getRelativeTimeString(Date.now() + 10000000)); // in 2 hours
console.log(getRelativeTimeString(Date.now() + 100000000)); // tomorrow
console.log(getRelativeTimeString(Date.now() + 1000000000)); // next week
console.log(getRelativeTimeString(Date.now() + 10000000000)); // in 3 months
console.log(getRelativeTimeString(Date.now() + 100000000000)); // in 3 years
@AustinGil
Copy link

This is really nice. Great job!

@vonovak
Copy link

vonovak commented Jan 31, 2023

instead of Math.floor the function should use Math.round because it currently returns results that are off-by-one. For example:

const threeDaysAgo = new Date(Date.now() - 3 * 24 * 60 * 60 * 1000)
getRelativeTimeString(threeDaysAgo) // outputs "4 days ago"

@MrBarracuda
Copy link

@LewisJEllis
image
Hey, you have an issue, it can be unidentified

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment