Javascript timestamp to relative time

88,649

Solution 1

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

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

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

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

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...

Share:
88,649
wilsonpage
Author by

wilsonpage

Follow Me on Twitter: @wilsonpage

Updated on January 22, 2022

Comments

  • wilsonpage
    wilsonpage over 2 years

    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)?

  • Yetanotherjosh
    Yetanotherjosh almost 6 years
    You don't need a plugin library to deal with relative differences between Javascript Date objects. They already handle conversion to UTC and when converted to integers give absolute milliseconds since the UTC epoch. So, date1 - date2 will give you a proper time delta in milliseconds regardless of the original timezones. Likewise, new Date().getTime() in two browsers in different time zones will report the same number if executed simultaneously on correct system clocks.
  • DavidFM
    DavidFM about 5 years
    awesome, but how to do it from a date?
  • vsync
    vsync about 5 years
    @Dayd - updated answer with an example for days, but if you want to compare dates in a robust way then you must first create a comparison method between two dates and understand if the margin is in hours, days, years, etc.. Then it's as simple as passing that result to the Intl.RelativeTimeFormat method. What you are asking is a whole different topic
  • DavidFM
    DavidFM about 5 years
    Thanks for your answer :)
  • Admin
    Admin over 4 years
    This has a tiny quirk I noticed due to rounding where it'll say, for example, "24 hours ago" instead of "1 days ago" if it's been less than 24 hours, but closer to 24 than 23. Using Math.floor instead of Math.round should do the trick.
  • user198989
    user198989 over 4 years
    This should not be an answer. Your script outputs "9 Sep 2001", what he is asking is getting like "XX minutes/seconds/hours/days" ago
  • user198989
    user198989 over 4 years
    OP is asked for timestamp to XX ago. Your script is not using timestamp but date. Please fix.
  • Jacob David C. Cunningham
    Jacob David C. Cunningham almost 4 years
    Looks like you just need to take out the * 1000 if you want to use timestamp(seconds) and update variables eg. msPerMinute to sPerMinute. Also if using Date.now() need to only use first 10 digits(match lengths of timestamps)
  • Emmanuel
    Emmanuel over 3 years
    This, as an answer, is incomplete without going from a timestamp to relative time. The Intl.RelativeTimeFormat API seems more of a locale conscious string formatter (for time units) than something for formatting raw date/time objects. You still have to process the timestamp to decide the appropriate time frame (minute, hour, days etc) to use. You don't want to be saying 36,020 seconds ago!
  • vsync
    vsync over 3 years
    @Emmanuel - @kigiri posted a more complete answer, but i will update mine to have the ability automatically detect the "best" unit parameter for the RelativeTimeFormat instance
  • vsync
    vsync over 3 years
    @Emmanuel - updated my answer to serve as a complete-solution
  • phyatt
    phyatt over 3 years
    github.com/catamphetamine/javascript-time-ago leverages this api in a really slick way. Maybe add an example with it?
  • sunpietro
    sunpietro over 3 years
    Moment.js is no longer supported library. You should consider using other libraries.
  • MSOACC
    MSOACC over 3 years
    This might be a fun exercise in writing the shortest code possible but anyone reading this should avoid copy-pasting this answer; this is not maintainable code.
  • kigiri
    kigiri over 3 years
    thx for the downvote and insightfull comment @MSOACC You can use the long version if you want maintainable.
  • Hooray Im Helping
    Hooray Im Helping almost 3 years
    I edited this answer to change the comparison from Math.abs(elasped) > ms to Math.abs(elapsed) >= ms. The former would say things like 24 hours ago / 365 days ago. The latter (what is reflected in the edit) says a day ago, / a year ago
  • Daryl Malibiran
    Daryl Malibiran over 2 years
    There's a mistake in the code. 2020 - 1984 = should be 36 years not 37.
  • vsync
    vsync over 2 years
    @DarylMalibiran - why 2020? where do you see 2020 in the code? please look again and then delete your comment once you spot your mistake
  • Daryl Malibiran
    Daryl Malibiran over 2 years
    @vsync I was about to check in to modify my comment yesterday, but Stack Overflow was down for maintenance. '10/20/1984' is one of your example dates. Today is '10-02-2021' and not equal or over '10-20-2021' so I subtracted 2020 to 1984. I realized the Intl.RelativeTimeFormat probably rounds-off the date / generating approximate date (e.g. 36 and 11 months is rounded-off to 37 years). Now I say I'm sorry, there's no mistake in your code.
  • Money_Badger
    Money_Badger over 2 years
    Here is an updated version which will always return "timeAgo" format, including months and years: jsfiddle.net/u3p9s8kn/65
  • Shaun Scovil
    Shaun Scovil over 2 years
    Minor, but using for...in to iterate through the keys of an object can be unpredictable. Since your logic depends on starting at the largest unit ("year") and iterating in a specific order, it would be better to store units in an array.
  • vsync
    vsync over 2 years
    @ShaunScovil - the order of such iterations has been predictable for years
  • Shaun Scovil
    Shaun Scovil over 2 years
    @vsync "A for...in loop iterates over the properties of an object in an arbitrary order (see the delete operator for more on why one cannot depend on the seeming orderliness of iteration, at least in a cross-browser setting)." Source: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
  • vsync
    vsync over 2 years
    @ShaunScovil - that case is irrelevant to my code. I disagree with your underlying statement that using the for...in iterator is "bad practice". A good developers knows which tool is good for which task under certain circumstances. The for...in iterator has been intensively used for many years, successfully by many. One should know better not to mess with an iterable object/array items count/order within the iterator
  • Shaun Scovil
    Shaun Scovil over 2 years
    @vsync Like I said, it was minor and not meant to be a critique. People learn from the code examples they see in SO, so it's good to illustrate best practices and not make assumptions about what others should know.