TypeScript で「○秒前」「○日前」という相対的な時刻を表示する

@Panda_Program

現在時刻との時間差を計算する

本記事は現在時刻との時間差を計算して、「○秒前」「○日前」と相対的な日付に変換する関数の紹介です。以前 PHP で同様の関数を作成したことがありました。

参考: PHPの日付処理ライブラリCarbonで現在時刻との差(diff)を取得する

今回は、TypeScript と date-fns を用いて同様のことをします。これで日付をそのまま表示せずに済み、投稿などが新しいか時間が経っているか一目瞭然になります。

実は date-fns には formatRelative という関数があり、その結果を日本語化できます。

ただ、このままだと「1日前」と表示してほしいところで「昨日の03:00」という時刻も入った表現になって使いづらいため簡単に自作してみました。このブログの投稿一覧ページで使用しています。

テストも併せて掲載するので、仕様はテストを読んでいただくとわかりやすいです。

date.ts
import differenceInDays from 'date-fns/differenceInDays'
import differenceInHours from 'date-fns/differenceInHours'
import differenceInMinutes from 'date-fns/differenceInMinutes'
import differenceInMonths from 'date-fns/differenceInMonths'
import differenceInSeconds from 'date-fns/differenceInSeconds'
import differenceInWeeks from 'date-fns/differenceInWeeks'
import differenceInYears from 'date-fns/differenceInYears'

// 「○秒前」「○分前」と表示する
export const diffTime = (base: Date, target: Date) => {
  const diffInSecs = differenceInSeconds(base, target)
  if (diffInSecs < 60) {
    return `${diffInSecs}秒前`
  }

  const diffInMins = differenceInMinutes(base, target)
  if (diffInMins < 60) {
    return `${diffInMins}分前`
  }

  const diffInHours = differenceInHours(base, target)
  if (diffInHours < 24) {
    return `${diffInHours}時間前`
  }

  const diffInDays = differenceInDays(base, target)
  if (diffInDays < 7) {
    return `${diffInDays}日前`
  }

  const diffInWeeks = differenceInWeeks(base, target)
  if (diffInWeeks < 4) {
    return `${diffInWeeks}週間前`
  }

  const diffInMonths = differenceInMonths(base, target)
  // 4週間前でも 0ヶ月前と表示されるため、条件を足して絞り込む
  if (diffInWeeks >= 4 && diffInMonths < 2) {
    return `1ヶ月前`
  } else if (diffInMonths < 12) {
    return `${diffInMonths}ヶ月前`
  }

  const diffInYears = differenceInYears(base, target)

  return `${diffInYears}年前`
}
__tests__/date.test.ts
import subDays from 'date-fns/subDays'
import subHours from 'date-fns/subHours'
import subMinutes from 'date-fns/subMinutes'
import subMonths from 'date-fns/subMonths'
import subSeconds from 'date-fns/subSeconds'
import subWeeks from 'date-fns/subWeeks'

import { diffTime } from '../date'

describe('diffTime', () => {
  const now = new Date('2025-12-31 23:59:59')

  describe('○秒前と表示する', () => {
    test('1秒前の時、1秒前を返す', () => {
      const date = subSeconds(now, 1)
      expect(diffTime(now, date)).toBe('1秒前')
    })

    test('59秒前の時、59秒前を返す', () => {
      const date = subSeconds(now, 59)
      expect(diffTime(now, date)).toBe('59秒前')
    })
  })

  describe('○分前と表示する', () => {
    test('60秒前の時、1分前を返す', () => {
      const date = subSeconds(now, 60)
      expect(diffTime(now, date)).toBe('1分前')
    })

    test('59分前の時、59分前を返す', () => {
      const date = subMinutes(now, 59)
      expect(diffTime(now, date)).toBe('59分前')
    })
  })

  describe('○時間前と表示する', () => {
    test('60分前の時、1時間前を返す', () => {
      const date = subMinutes(now, 60)
      expect(diffTime(now, date)).toBe('1時間前')
    })

    test('61分前の時、2時間前を返す', () => {
      const date = subMinutes(now, 61)
      expect(diffTime(now, date)).toBe('1時間前')
    })

    test('23時間前の時、23時間前を返す', () => {
      const date = subHours(now, 23)
      expect(diffTime(now, date)).toBe('23時間前')
    })
  })

  describe('○日前と表示する', () => {
    test('24時間前の時、1日前を返す', () => {
      const date = subHours(now, 24)
      expect(diffTime(now, date)).toBe('1日前')
    })

    test('6日前の時、6日前を返す', () => {
      const date = subDays(now, 6)
      expect(diffTime(now, date)).toBe('6日前')
    })
  })

  describe('○週間前と表示する', () => {
    test('7日前の時、1週間前を返す', () => {
      const date = subDays(now, 7)
      expect(diffTime(now, date)).toBe('1週間前')
    })

    test('3週間前の時、3週間前を返す', () => {
      const date = subWeeks(now, 3)
      expect(diffTime(now, date)).toBe('3週間前')
    })
  })

  describe('○ヶ月前と表示する', () => {
    test('4週間の時、1ヶ月前を返す', () => {
      const date = subWeeks(now, 4)
      expect(diffTime(now, date)).toBe('1ヶ月前')
    })

    test('11ヶ月前の時、11ヶ月前を返す', () => {
      const date = subMonths(now, 11)
      expect(diffTime(now, date)).toBe('11ヶ月前')
    })
  })

  describe('○年前と表示する', () => {
    test('12ヶ月前の時、1年前を返す', () => {
      const date = subMonths(now, 12)
      expect(diffTime(now, date)).toBe('1年前')
    })

    test('13ヶ月前の時、1年前を返す', () => {
      const date = subMonths(now, 13)
      expect(diffTime(now, date)).toBe('1年前')
    })

    test('23ヶ月前の時、1年前を返す', () => {
      const date = subMonths(now, 23)
      expect(diffTime(now, date)).toBe('1年前')
    })

    test('24ヶ月前の時、2年前を返す', () => {
      const date = subMonths(now, 24)
      expect(diffTime(now, date)).toBe('2年前')
    })

    test('25ヶ月前の時、2年前を返す', () => {
      const date = subMonths(now, 25)
      expect(diffTime(now, date)).toBe('2年前')
    })
  })
})

Happy Coding 🎉

パンダのイラスト
パンダ

記事が面白いと思ったらツイートやはてブをお願いします!皆さんの感想が執筆のモチベーションになります。最後まで読んでくれてありがとう。

  • Share on Hatena
  • Share on Twitter
  • Share on Line
  • Copy to clipboard