100

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

vsync
  • 118,978
  • 58
  • 307
  • 400
wilsonpage
  • 17,341
  • 23
  • 103
  • 147

17 Answers17

192

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.

fearofawhackplanet
  • 52,166
  • 53
  • 160
  • 253
  • 3
    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. –  Dec 10 '19 at 18:29
  • OP is asked for timestamp to XX ago. Your script is not using timestamp but date. Please fix. – user198989 Feb 12 '20 at 20:05
  • 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) – Jacob David C. Cunningham Aug 08 '20 at 11:10
70

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.

vsync
  • 118,978
  • 58
  • 307
  • 400
  • awesome, but how to do it from a date? – DavidFM May 15 '19 at 10:30
  • 2
    @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 – vsync May 15 '19 at 12:46
  • Thanks for your answer :) – DavidFM May 15 '19 at 19:13
  • 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! – Emmanuel Sep 06 '20 at 16:01
  • @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 Sep 07 '20 at 08:07
  • @Emmanuel - updated my answer to serve as a complete-solution – vsync Sep 07 '20 at 09:01
  • https://github.com/catamphetamine/javascript-time-ago/ leverages this api in a really slick way. Maybe add an example with it? – phyatt Oct 13 '20 at 19:07
  • There's a mistake in the code. 2020 - 1984 = should be 36 years not 37. – Daryl Malibiran Oct 01 '21 at 00:15
  • @DarylMalibiran - why `2020`? where do you see `2020` in the code? please look again and then delete your comment once you spot your mistake – vsync Oct 01 '21 at 12:45
  • 1
    @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. – Daryl Malibiran Oct 02 '21 at 00:18
  • 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. – Shaun Scovil Jan 21 '22 at 19:29
  • @ShaunScovil - the order of such iterations has been predictable for years – vsync Jan 22 '22 at 16:55
  • @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: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...in – Shaun Scovil Jan 23 '22 at 14:55
  • @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 – vsync Jan 23 '22 at 16:30
  • @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. – Shaun Scovil Jan 23 '22 at 18:47
31

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.

Sunil Garg
  • 14,608
  • 25
  • 132
  • 189
ZZB
  • 790
  • 6
  • 11
  • 4
    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 Feb 12 '20 at 19:55
  • Here is an updated version which will always return "timeAgo" format, including months and years: http://jsfiddle.net/u3p9s8kn/65/ – Money_Badger Oct 05 '21 at 10:44
10

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 "";
}
Hooray Im Helping
  • 5,194
  • 4
  • 30
  • 43
User Rebo
  • 3,056
  • 25
  • 30
  • 1
    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 – Hooray Im Helping Jul 07 '21 at 23:47
7

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>
Community
  • 1
  • 1
Caio Tarifa
  • 5,973
  • 11
  • 46
  • 73
6
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...

kigiri
  • 2,952
  • 21
  • 23
  • 1
    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. – MSOACC Jan 19 '21 at 16:08
  • thx for the downvote and insightfull comment @MSOACC You can use the long version if you want maintainable. – kigiri Jan 21 '21 at 17:38
3

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
Nitin Jadhav
  • 6,495
  • 1
  • 46
  • 48
2

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.

Shaun Scovil
  • 3,905
  • 5
  • 39
  • 58
1

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';
        }
    });
Diego Castillo
  • 175
  • 1
  • 15
1

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"
abumalick
  • 2,136
  • 23
  • 27
0

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.

Art Knipe
  • 99
  • 1
  • 6
  • 1
    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. – Yetanotherjosh Jul 22 '18 at 05:18
0

(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
ZYinMD
  • 3,602
  • 1
  • 23
  • 32
0

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
}
Shri ram
  • 61
  • 5
0

if you are using PHP time(), you can try this instead;

function time_stamp(session_time)
{
    var time_difference ="<?php echo time() ?>" - session_time;
    var seconds = time_difference;
    var minutes = Math.round(time_difference / 60);
    var hours = Math.round(time_difference / 3600);
    var days = Math.round(time_difference / 86400);
    var weeks = Math.round(time_difference / 604800);
    var months = Math.round(time_difference / 2419200);
    var years = Math.round(time_difference / 29030400);

    if (seconds <= 60) {
        return seconds+" Secs Ago";
    } else if (minutes <= 60) {
        if (minutes == 1) {
            return "1 Min Ago";
        } else {
            return minutes+" Mins Ago";
        }
    } else if (hours <= 24) {
        if (hours == 1) {
            return "1 Hour Ago";
        } else {
            return hours+" Hours Ago";
        }
    } else if (days <= 7) {
        if (days == 1) {
            return "1 Day Ago";
        } else {
            return days+" Days Ago";
        }
    } else if (weeks <= 4) {
        if (weeks == 1) {
            return "1 Week Ago";
        } else {
            return weeks+" Weeks Ago";
        }
    } else if (months <= 12) {
        if (months == 1) {
            return "1 Month Ago";
        } else {
            return months+" Months Ago";
        }
    } else {
        if (years == 1) {
            return "1 Year Ago";
        } else {
            return years+" Years Ago";
        }
    }
}
Foozaweb
  • 49
  • 11
  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the [help center](https://stackoverflow.com/help/how-to-answer). – Ethan Jun 17 '22 at 13:48
0

to use this, just LITERALLY COPY ALL of this code & just import it in your component or where ever & just place in your ISOstring() date inside: showTimeAgo("2022-06-20T13:42:29-05:00") & you will get automatic time updates for every scenario.

sidenote: I made an npm package for this https://www.npmjs.com/package/showtimeago

export const showTimeAgo = () => {
    const MONTH_NAMES = [
        'January',
        'February',
        'March',
        'April',
        'May',
        'June',
        'July',
        'August',
        'September',
        'October',
        'November',
        'December',
    ];

    function getOrdinalNum() {
        return (
            n +
            (n > 0
                ? ['th', 'st', 'nd', 'rd'][
                      (n > 3 && n < 21) || n % 10 > 3 ? 0 : n % 10
                  ]
                : '')
        );
    }

    function getFormattedDate(
        date,
        preformattedDate = false,
        hideYear = false
    ) {
        const day = date.getDate();
        const month = MONTH_NAMES[date.getMonth()];
        const year = date.getFullYear();
        let hours = date.getHours();
        let minutes = date.getMinutes();

        let ampm = hours >= 12 ? 'pm' : 'am';

        switch(true){
            case (hours > 12):
                hours = hours - 12;
                break;
            case (hours === 0):
                hours = 12;
                break;
            case(minutes < 10):
                minutes = `0${minutes}`;
                break;
            case(preformattedDate):
            // Today at 10:20am
            // Yesterday at 10:20am
                return `${preformattedDate} at ${hours}:${minutes} ${ampm}`;

            case(hideYear):
                // January 10th at 10:20pm
                return `${month} ${getOrdinalNum(
                    day
                )}, at ${hours}:${minutes} ${ampm}`;
            default:
                // January 10th 2022 at 10:20pm
                return `${month} ${getOrdinalNum(
                    day
                )}, ${year} at ${hours}:${minutes} ${ampm}`;
        }
        
    }

    // --- Main function
    function timeAgo(dateParam) {
        if (!dateParam) {
            return null;
        }

        const date =
            typeof dateParam === 'object' ? dateParam : new Date(dateParam);
        const DAY_IN_MS = 86400000; // 24 * 60 * 60 * 1000
        const today = new Date();

        const yesterday = new Date(today - DAY_IN_MS);

        const seconds = Math.round((today - date) / 1000);
        const minutes = Math.round(seconds / 60);
        const hour = Math.round(seconds / 3600);
        const day = Math.round(seconds / 86400);
        const month = Math.round(seconds / 2629800);
        const year = Math.floor(seconds / 31536000);
        const isToday = today.toDateString() === date.toDateString();
        const isYesterday =
            yesterday.toDateString() === date.toDateString();
        const isThisYear = today.getFullYear() === date.getFullYear();

        switch(true){
            case (seconds < 5):
                return 'now';
            case (seconds < 60):
                return `${seconds} seconds ago`;
            case (seconds < 90):
                return 'about a minute ago';
            case (minutes < 60):
                return `${minutes} minutes ago`;
            case (hour === 1 && hour < 2):
                return `${hour} hour ago`; // 1 hour ago
            case (hour > 1 && hour <= 12):
                return `${hour} hours ago`; // 2 hours ago
            case (isToday):
                return getFormattedDate(date, 'Today'); // Today at 10:20am
            case (isYesterday): 
                return getFormattedDate(date, 'Yesterday'); // Yesterday at 10:20am
            case(day > 1 && day <= 30):
                return `${day} days ago`; // 2 days ago
            case (isThisYear):
                return getFormattedDate(date, false, true); // January 10th at 10:20pm 
            case (day > 30 && month <= 1):
                return `${hour} month ago`; // 1 month ago
            case (month > 1 && month <= 12):
                return `${month} months ago`; // 2 months ago
            case (year === 1):
                return `${year} year ago`; // 1 year ago
            case (year > 1):
                return `${year} years ago`; // 2 years ago
            default:
                return getFormattedDate(date); // January 10th 2022 at 10:20pm
        }
    }

    return timeAgo(date);
};

console.log(showTimeAgo("2022-06-20T13:42:29-05:00"));-05:00"))
Jackie Santana
  • 1,290
  • 14
  • 15
0

Here is TypeScript take. I am receiving a date from the Back-End as a string, hence the explanation in the annotation. I've built myself a helper function that goes like this:

  /**
   * A function that calculates the time difference in seconds between the input date (in string) and the current date
   * and then iterates through various time intervals (year, month, week, day, hour, and minute) to find the most
   * appropriate one for the relative time representation.
   * If the time difference is less than a minute, it returns "just now."
   * @param inputDateString - JS date as a string, for example: `2023-06-30T11:19:49.259Z`
   */
  static getRelativeTimeFromDate(inputDateString: string): string {
    const inputDate = new Date(inputDateString);
    const currentDate = new Date();
    const timeDifferenceInSeconds = Math.floor((currentDate.getTime() - inputDate.getTime()) / 1000);

    const intervals = {
      year: 31536000,
      month: 2592000,
      week: 604800,
      day: 86400,
      hour: 3600,
      minute: 60,
    };

    for (const interval in intervals) {
      const numberOfUnits = Math.floor(timeDifferenceInSeconds / intervals[interval]);
      if (numberOfUnits >= 1) {
        return `${numberOfUnits} ${interval}${numberOfUnits > 1 ? 's' : ''} ago`;
      }
    }

    return 'just now';
  }
Bullsized
  • 362
  • 4
  • 7
-1

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
});
Piyush Patel
  • 1,646
  • 1
  • 14
  • 26