yucatio@システムエンジニア

趣味で作ったものいろいろ

date-fnsで「○分前」「約○時間前」「○日前」など現在時刻からのざっくりした時間経過を表示する

やりたいこと

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

f:id:yucatio:20181019222047p:plain

date-fnsのformatDistanceToNowでできる

date-fnsのformatDistanceToNowを使えば、現在からのざっくりした経過時間を表示することができます。

date-fns - modern JavaScript date utility library

経過時間と表示される時間の対応は以下のようになります。

範囲 表示
30秒未満 1分未満
30秒以上 1 分30 秒未満 1分
1分30秒以上 44分30秒未満 [2..44]分
44分30秒以上 89分30秒未満 約1時間
89分30秒以上 23時間59分30秒未満 約[2..24]時間
23時間59分30秒以上 41時間59分30秒未満 1日
41時間59分30秒以上 29日23時間59分30秒未満 [2..30]日
29日23時間59分30秒以上 44日23時間59分30秒未満 約1か月
44日23時間59分30秒以上 59日23時間59分30秒未満 約2か月
59日23時間59分30秒以上 1年未満 [2..12]か月
1年以上 1年3か月未満 約1年
1年3か月以上 1年9か月未満 1年以上
1年9か月以上 2年未満 2年近く
N年以上 N年3か月未満 約N年
N年3か月以上 N年9か月未満 N年以上
N年9か月以上 N+1年未満 N+1年近く

プログラム例

実際に使用してみます。Reactを使用しています。

import React from 'react';
import formatISO9075 from 'date-fns/formatISO9075'
import formatDistanceToNow from 'date-fns/formatDistanceToNow'
import { ja } from 'date-fns/locale'

function App() {
  const targetDate = new Date(2019, 11, 8, 10, 13)

  return (
    <div>
      <div>現在時刻: {formatISO9075(new Date())}</div>
      <div>対象時間: {formatISO9075(targetDate)}</div>
      <div>経過時間: {formatDistanceToNow(targetDate, {locale: ja})}</div>
    </div>
  );
}

export default App;

実行結果です。経過時間が表示されました。(実際にはスタイルを適用しています。)

f:id:yucatio:20191208230907p:plain

色々な経過時間での表示を確かめる

色々な経過時間で実際に表示時間を確かめてみました。

デフォルトの設定では、"○分"、"約○時間"のように現在からの差分がどのくらいか表示されます。今回は、optionにaddSuffix: trueを指定して、"○分前"、"約○時間後"と、"前"または"後"を後ろにつけるようにしました。

現在時刻が2019-12-08 22:23:56のとき

説明 日時 formatDistanceToNow(date, {addSuffix: true, locale: ja})
3秒前 2019-12-08 22:23:53 約1分前
40秒前 2019-12-08 22:23:16 1分前
1分20秒前 2019-12-08 22:22:36 1分前
4分50秒前 2019-12-08 22:19:06 5分前
17分25秒前 2019-12-08 22:06:31 17分前
47分4秒前 2019-12-08 21:36:52 約1時間前
1時間25分56秒前 2019-12-08 20:58:00 約1時間前
1時間37分12秒前 2019-12-08 20:46:44 約2時間前
10時間5分32秒前 2019-12-08 12:18:24 約10時間前
23時間50分15秒前 2019-12-07 22:33:41 約24時間前
1日5分52秒前 2019-12-07 22:18:04 1日前
1日17時間2分47秒前 2019-12-07 05:21:09 1日前
1日18時間11分30秒前 2019-12-07 04:12:26 2日前
3日2時間45分7秒前 2019-12-05 19:38:49 3日前
12日20時間36分3秒前 2019-11-26 01:47:53 13日前
29日10時間46分51秒前 2019-11-09 11:37:05 29日前
30日1時間29分1秒前 2019-11-08 20:54:55 約1か月前
50日6時間27分48秒前 2019-10-19 15:56:08 約2か月前
63日22時間1分55秒前 2019-10-06 00:22:01 2か月前
120日19時間4分6秒前 2019-08-10 03:19:50 4か月前
361日9時間22分29秒前 2018-12-12 13:01:27 12か月前
1年10日15時間32分15秒前 2018-11-28 06:51:41 約1年前
1年120日14時間44分20秒前 2018-08-10 07:39:36 1年以上前
1年300日12時間3分30秒前 2018-02-11 10:20:26 2年近く前
3年36日2時間22分45秒前 2016-11-02 20:01:11 約3年前
3年170日5時間38分33秒前 2016-06-21 16:45:23 3年以上前
3年290日2時間39分10秒前 2016-02-22 19:44:46 4年近く前
10年150日13時間4分2秒前 2009-07-11 09:19:54 10年以上前

30秒未満の場合、suffix("前"や"後")をつけないと、"1分未満"と表示されますが、suffixをつけると"約1分前"になります。"1分未満前"にならないところに細かい気遣いがあります。(ソースコードはこちら→ date-fns/index.js at master · date-fns/date-fns · GitHub )

ソースコード

上記の表を出力するコードです。Reactを使用しています。

# date-fnsのインストール
$ yarn add date-fns
import React from 'react';
import formatISO9075 from 'date-fns/formatISO9075'
import formatDistanceToNow from 'date-fns/formatDistanceToNow'
import subSeconds from 'date-fns/subSeconds'
import subMinutes from 'date-fns/subMinutes'
import subHours from 'date-fns/subHours'
import subDays from 'date-fns/subDays'
import subYears from 'date-fns/subYears'
import { ja } from 'date-fns/locale'
import './App.css';

const subFromNow = ({years=0, days=0, hours=0, minutes=0, seconds=0}) => {
  let timeAgo = new Date()
  let description = "";

  if (years) {
    timeAgo = subYears(timeAgo, years)
    description += `${years}年`
  }

  if (days) {
    timeAgo = subDays(timeAgo, days)
    description += `${days}日`
  }

  if (hours) {
    timeAgo = subHours(timeAgo, hours)
    description += `${hours}時間`
  }

  if (minutes) {
    timeAgo = subMinutes(timeAgo, minutes)
    description += `${minutes}分`
  }

  if (seconds) {
    timeAgo = subSeconds(timeAgo, seconds)
    description += `${seconds}秒`
  }

  description += "前"

  return {description, timeAgo}
}

function App() {
  const timeAgoArr = []

  timeAgoArr.push(subFromNow({seconds:3}))
  timeAgoArr.push(subFromNow({seconds:40}))
  timeAgoArr.push(subFromNow({minutes:1, seconds:20}))
  timeAgoArr.push(subFromNow({minutes:4, seconds:50}))
  timeAgoArr.push(subFromNow({minutes:17, seconds:25}))
  timeAgoArr.push(subFromNow({minutes:47, seconds:4}))
  timeAgoArr.push(subFromNow({hours:1, minutes:25, seconds:56}))
  timeAgoArr.push(subFromNow({hours:1, minutes:37, seconds:12}))
  timeAgoArr.push(subFromNow({hours:10, minutes:5, seconds:32}))
  timeAgoArr.push(subFromNow({hours:23, minutes:50, seconds:15}))
  timeAgoArr.push(subFromNow({days:1, hours:0, minutes:5, seconds:52}))
  timeAgoArr.push(subFromNow({days:1, hours:17, minutes:2, seconds:47}))
  timeAgoArr.push(subFromNow({days:1, hours:18, minutes:11, seconds:30}))
  timeAgoArr.push(subFromNow({days:3, hours:2, minutes:45, seconds:7}))
  timeAgoArr.push(subFromNow({days:12, hours:20 , minutes:36, seconds:3}))
  timeAgoArr.push(subFromNow({days:29, hours:10 , minutes:46, seconds:51}))
  timeAgoArr.push(subFromNow({days:30, hours:1 , minutes:29, seconds:1}))
  timeAgoArr.push(subFromNow({days:50, hours:6 , minutes:27, seconds:48}))
  timeAgoArr.push(subFromNow({days:63, hours:22 , minutes:1, seconds:55}))
  timeAgoArr.push(subFromNow({days:120, hours:19 , minutes:4, seconds:6}))
  timeAgoArr.push(subFromNow({days:361, hours:9 , minutes:22, seconds:29}))
  timeAgoArr.push(subFromNow({years:1, days:10, hours:15, minutes:32, seconds:15}))
  timeAgoArr.push(subFromNow({years:1, days:120, hours:14, minutes:44, seconds:20}))
  timeAgoArr.push(subFromNow({years:1, days:300, hours:12, minutes:3, seconds:30}))
  timeAgoArr.push(subFromNow({years:3, days:36, hours:2, minutes:22, seconds:45}))
  timeAgoArr.push(subFromNow({years:3, days:170, hours:5, minutes:38, seconds:33}))
  timeAgoArr.push(subFromNow({years:3, days:290, hours:2, minutes:39, seconds:10}))
  timeAgoArr.push(subFromNow({years:10, days:150, hours:13, minutes:4, seconds:2}))

  return (
    <div className="fromNowTable">
      <div className="currentTime">現在時刻: {formatISO9075(new Date())}</div>
      <table>
        <thead>
          <tr>
            <th>説明</th><th>日時</th><th>formatDistanceToNow({'{'}addSuffix: true, locale: ja})</th>
          </tr>
        </thead>
        <tbody>
          {timeAgoArr.map(({description, timeAgo}, index) => (
            <tr key={index}>
              <td className="description">{description}</td>
              <td>{formatISO9075(timeAgo)}</td>
              <td>{formatDistanceToNow(timeAgo, {addSuffix: true, locale: ja})}</td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

export default App;

date-fnsの他の経過時間を表示する関数

date-fnsには他にも date-fns - modern JavaScript date utility library という関数が用意されています。 こちらもざっくりとした経過時間を表示しますが、 "約"や"以上"などを表示しないのですっきりするかもしれません。 引数は2つとる必要があり、毎回現在時刻を渡す必要があります。

環境

  • Mac : Sierra 10.12.6
  • React : 16.12.0
  • date-fns : 2.8.1

関連記事

moment.jsで同様のことを行なった記事です。dete-fnsのformatDistanceToNowとは少し表示が違います。↓

yucatio.hatenablog.com