1

In my React app, I have to show info saying create 1 hour ago or 1 day ago and also plural as 10 minutes ago or 3 days ago. To achieve that I'm trying to use this API FormatJS and in the specific intl.formatRelativeTime()

What I tried so far is something like that

const CreatedConsetee = ({ date }) => {
  // date = 2021-04-26T14:21:51.771Z
  const intl = useIntl();
  const parsedDate = new Date(date);

  const dateFormat = intl.formatRelativeTime(parsedDate, 'hour', {
    style: 'long',
  });

  return <>{dateFormat}</>;
};

The result is of the above is like this

in 1,619,446,911,771 hours

Whatever is it always that big number and I have no idea how to make it right.

The expected behavior I want is that I got a message saying created 7 days ago the same if we have 1 hour 1 minute 1 day and plural forms 2 hours 2 minutes 2 days.

Jakub
  • 2,367
  • 6
  • 31
  • 82
  • 1
    [The documentation](https://formatjs.io/docs/react-intl/api/#formatrelativetime) says it takes a value as a number, a unit, and options. So when you pass a `Date` object, it converts that to a number, which happens to be the number of milliseconds since the Unix epoch. It then formats that as a number of hours. – Heretic Monkey May 03 '21 at 18:17
  • can you show me an example to understand it better please – Jakub May 03 '21 at 19:00
  • `intl.formatRelativeTime(-4, 'hour', { style: 'long' })` would output `4 hours ago`. There's examples in that link I provided... – Heretic Monkey May 03 '21 at 19:44
  • That I understood but how to convert date = 2021-04-26T14:21:51.771Z to hours that what I meant. – Jakub May 03 '21 at 19:45
  • Well, it's a bit of a nonsensical statement. A date and time is not a number of hours. If you're talking about getting the difference between that date and now in hours, there are a number of question about that already... [JavaScript - Get minutes between two dates](https://stackoverflow.com/questions/7709803/javascript-get-minutes-between-two-dates) will get you minutes, just divide by 60 to get hours. – Heretic Monkey May 03 '21 at 19:58

1 Answers1

1

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
  • 42,981
  • 12
  • 84
  • 132
  • Unfortunately does not work `TypeError: date.getUTCTime` is not a function when I pass my parsed date which Is my starting date that happens. If I pass just the date not parsed gives the same error. So I don't know how should I pass my date to make it work – Jakub May 03 '21 at 19:41
  • That's `Intl.RelativeTimeFormat`; the OP is talking about FormatJS' `intl.formatRelativeTime`, which has a different signature. – Heretic Monkey May 03 '21 at 19:42
  • Getting a "time from now" in plain JavaScript is also a duplicate multiple times over. – Heretic Monkey May 03 '21 at 19:59
  • The problem is FormatJS doesn't work the same so would be nice to see that you used FormatJS because is not working as it is as you use react-intl. – Jakub May 03 '21 at 20:14
  • 1
    @HereticMonkey FormatJS is just a wrapper. Yes the signature is a bit diff, but this is a question about semantics, not syntax. Anyways, I added a React example. – Mr. Polywhirl May 03 '21 at 20:15