Javascript timestamp to relative time (eg 2 seconds ago, one week ago etc), best methods?

守給你的承諾、 提交于 2019-11-26 17:13:24

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.

Here is exact mimic of twitter time ago without plugins:

  function timeSince(timeStamp) {
    var now = new Date(),
      secondsPast = (now.getTime() - timeStamp.getTime()) / 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;
    }
  }

Gist https://gist.github.com/timuric/11386129

Fiddle http://jsfiddle.net/qE8Lu/1/

Hope it helps.

Tada! Timeago: http://timeago.yarp.com/

Oh hang on - without plugins? Why's that then? I guess you could open up the plugin file and hack the guts out of it.

Caio Tarifa

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>

Intl.RelativeTimeFormat - Native API

Currently (Dec' 18) a Stage 3 proposal, and already implemented in Chrome 71

const rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' });

const millisecondsPerDay = 24 * 60 * 60 * 1000;

[
  [3.14 , 'second' ],
  [-15  , 'minute' ],
  [8    , 'hour'   ],
  [-1   , 'day'    ],
  [3    , 'week'   ],
  [-5   , 'month'  ],
  [2    , 'quarter'],
  [-42  , 'year'   ],
  [(new Date('9/22/2018') - new Date())/millisecondsPerDay,'day']
].forEach(d => console.log(   rtf.format(d[0], d[1])  ));

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.

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';
        }
    });

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.

There is also sugar.js and relative function for this purpose.

relative - Outputs a string in units relative to the current date ("ago" or "from now").

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"

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
});

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
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!