JavaScriptで「○分前」「○時間前」「○日前」など現在時刻からのざっくりした時間経過を表示したい場合は、Moment.jsのfromNowを使う

やりたいこと

Twitterのように、投稿日付を表示したい。「○分前」「○時間前」「○日前」など、現在時刻からの経過時間をざっくり表示したい。

f:id:yucatio:20181019222047p:plain

moment.jsのfromNowでできる

moment.jsのfromNowを使えば実現できます。

Time from now

デフォルトの設定では以下のように表示されます。未来の日時の場合は、(“前”の部分が”後”になります)

範囲 Key(*1) 表示例
0 から 44 秒 s 数秒前
(ss から 44 秒(*2)) ss 44秒前
45 から 89 秒 m 1分前
90 秒 から 44 分 mm 2分前 ... 44分前
45 から 89 分 h 1時間前
90 分 から 21 時間 hh 2時間前 ... 21時間前
22 から 35 時間 d 1日前
36 時間 から 25 日 dd 2日前 ... 25日前
26 から 45 日 M 1ヶ月前
45 から 319 日 MM 2ヶ月前 ... 10ヶ月前
320 から 547 日 (1年半) y 1年前
548 日以上 yy 2年前 ... 20年前

(*1) Keyは、時間の丸めとなる閾値。例えば、Keyのmは45なので、45分以上は1時間と見なされます。

(*2) ssキーは、デフォルトでは44に設定されているため、”44秒前”のような表示はされません。ssの値を10にしたきには、1から10秒は”数秒前”、11から44秒は”○秒前”と表示されます。

momentjs.com/13-relative-time-threshold.md at master · moment/momentjs.com · GitHub

moment#fromNowを動かしてみる

環境の準備とmomentパッケージのインストール

yarn + ES 6 + React(create-react-app使用)を利用するので、それ以外の環境の場合は適宜読み替えてください。

# 新規プロジェクトの作成
$ yarn create react-app moment-fromnow
yarn create v1.9.4
# 中略
success Saved 10 new dependencies.
info Direct dependencies
├─ react-dom@16.5.2
├─ react-scripts@2.0.5
└─ react@16.5.2
# 中略
Success! Created moment-fromnow at /Users/yuka/react/moment-fromnow
# 後略

$ cd  moment-fromnow

# momentパッケージの追加
$ yarn add moment
# 中略
info All dependencies
├─ moment@2.22.2
├─ react-dom@16.5.2
└─ react@16.5.2

シンプルなコード

簡単なコードを動かしてみます。

src/App.js

import React from 'react';
import moment from 'moment'  // #1
import 'moment/locale/ja'  // #2

const App = () => (
  <div>
    {moment('2018-10-19 13:13:13').fromNow()}  // #3
  </div>
);

export default App;
  • momentをimportします(#1)。
  • moment/locale/jaをimportし、日本語化します(#2)。
  • moment('2018-10-19 13:13:13’)で、201年10月19日13時13分13秒を表すmomentオブジェクトを作成し、fromNow()を呼び出します(#3)。

実行結果

サーバを起動して確認します。

$ yarn start

http://localhost:3000 を開きます。

f:id:yucatio:20181019223439p:plain

無事表示されました。

様々な時刻でfromNowの実行結果を確認する

簡単な動作確認はできたので、一通り全ての範囲の表示を確認します。

src/App.js

import React from 'react';
import moment from 'moment'
import 'moment/locale/ja'
import './App.css';

const DATE_FORMAT = 'YYYY-MM-DD HH:mm:ss'

const App = () => {
  let now = moment();
  const timeAgoArr = [
    {description: '3秒前'              , timeAgo: moment(now).subtract(3, 'seconds')},
    {description: '57秒前'             , timeAgo: moment(now).subtract(57, 'seconds')},
    {description: '1分7秒前'            , timeAgo: moment(now).subtract(1, 'minutes').subtract(7, 'seconds')},
    {description: '4分55秒前'           , timeAgo: moment(now).subtract(4, 'minutes').subtract(55, 'seconds')},
    {description: '17分25秒前'          , timeAgo: moment(now).subtract(17, 'minutes').subtract(25, 'seconds')},
    {description: '56分2秒前'           , timeAgo: moment(now).subtract(56, 'minutes').subtract(2, 'seconds')},
    {description: '1時間34分22秒前'      , timeAgo: moment(now).subtract(1, 'hours').subtract(34, 'minutes').subtract(22, 'seconds')},
    {description: '10時間49分42秒前'     , timeAgo: moment(now).subtract(10, 'hours').subtract(49, 'minutes').subtract(42, 'seconds')},
    {description: '22時間50分2秒前'      , timeAgo: moment(now).subtract(22, 'hours').subtract(50, 'minutes').subtract(2, 'seconds')},
    {description: '1日13時間30分52秒前'  , timeAgo: moment(now).subtract(1, 'days').subtract(13, 'hours').subtract(30, 'minutes').subtract(52, 'seconds')},
    {description: '22日17時間6分11秒前'  , timeAgo: moment(now).subtract(22, 'days').subtract(17, 'hours').subtract(6, 'minutes').subtract(11, 'seconds')},
    {description: '31日20時間17分55秒前' , timeAgo: moment(now).subtract(31, 'days').subtract(20, 'hours').subtract(17, 'minutes').subtract(55, 'seconds')},
    {description: '106日9時間5分1秒前'   , timeAgo: moment(now).subtract(106, 'days').subtract(9, 'hours').subtract(5, 'minutes').subtract(1, 'seconds')},
    {description: '342日21時間28分14秒前', timeAgo: moment(now).subtract(342, 'days').subtract(21, 'hours').subtract(28, 'minutes').subtract(14, 'seconds')},
    {description: '1036日4時間2分53秒前' , timeAgo: moment(now).subtract(1036, 'days').subtract(4, 'hours').subtract(2, 'minutes').subtract(53, 'seconds')},
  ]
  return (
    <div className="fromNowTable">
      <div className="currentTime">現在時刻: {now.format(DATE_FORMAT)}</div>
      <table>
        <thead>
          <th>説明</th><th>日時</th><th>moment().fromNow()</th>
        </thead>
        <tbody>
          {timeAgoArr.map(({description, timeAgo}) => (
            <tr>
              <td className="description">{description}</td>
              <td>{timeAgo.format(DATE_FORMAT)}</td>
              <td>{timeAgo.fromNow()}</td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

export default App;

実行結果

$ yarn start

http://localhost:3000 を開いて確認します。

f:id:yucatio:20181019223852p:plain

それぞれ”5分前”や”2時間前”、”23日前”など表示されることが確かめられました。

あとがき

Ruby on Railsのtime_ago_in_wordsを探していたのですが、全然見つからず苦労しました。危うく自分で実装するところでした。

ちなみにTwitterfacebookは24時間以内の投稿は、○[分/時間]前、それ以上前だと投稿した日付が表示されます。SNSなどはその方が良いかもしれませんね。

環境