about summary refs log tree commit diff
path: root/src/lib/hooks/useTimeAgo.ts
blob: 5f0782f96f47e9dfd8076d76d15bdd1db3fd9dba (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
import {useCallback, useMemo} from 'react'
import {msg, plural} from '@lingui/macro'
import {I18nContext, useLingui} from '@lingui/react'
import {differenceInSeconds} from 'date-fns'

export type TimeAgoOptions = {
  lingui: I18nContext['_']
  format?: 'long' | 'short'
}

export function useGetTimeAgo() {
  const {_} = useLingui()
  return useCallback(
    (
      date: number | string | Date,
      options?: Omit<TimeAgoOptions, 'lingui'>,
    ) => {
      return dateDiff(date, Date.now(), {lingui: _, format: options?.format})
    },
    [_],
  )
}

export function useTimeAgo(
  date: number | string | Date,
  options?: Omit<TimeAgoOptions, 'lingui'>,
): string {
  const timeAgo = useGetTimeAgo()
  return useMemo(() => {
    return timeAgo(date, {...options})
  }, [date, options, timeAgo])
}

const NOW = 5
const MINUTE = 60
const HOUR = MINUTE * 60
const DAY = HOUR * 24
const MONTH_30 = DAY * 30

/**
 * Returns the difference between `earlier` and `later` dates, formatted as a
 * natural language string.
 *
 * - All month are considered exactly 30 days.
 * - Dates assume `earlier` <= `later`, and will otherwise return 'now'.
 * - Differences >= 360 days are returned as the "M/D/YYYY" string
 * - All values round down
 */
export function dateDiff(
  earlier: number | string | Date,
  later: number | string | Date,
  options: TimeAgoOptions,
): string {
  const _ = options.lingui
  const format = options?.format || 'short'
  const long = format === 'long'
  const diffSeconds = differenceInSeconds(new Date(later), new Date(earlier))

  if (diffSeconds < NOW) {
    return _(msg`now`)
  } else if (diffSeconds < MINUTE) {
    return `${diffSeconds}${
      long ? ` ${plural(diffSeconds, {one: 'second', other: 'seconds'})}` : 's'
    }`
  } else if (diffSeconds < HOUR) {
    const diff = Math.floor(diffSeconds / MINUTE)
    return `${diff}${
      long ? ` ${plural(diff, {one: 'minute', other: 'minutes'})}` : 'm'
    }`
  } else if (diffSeconds < DAY) {
    const diff = Math.floor(diffSeconds / HOUR)
    return `${diff}${
      long ? ` ${plural(diff, {one: 'hour', other: 'hours'})}` : 'h'
    }`
  } else if (diffSeconds < MONTH_30) {
    const diff = Math.floor(diffSeconds / DAY)
    return `${diff}${
      long ? ` ${plural(diff, {one: 'day', other: 'days'})}` : 'd'
    }`
  } else {
    const diff = Math.floor(diffSeconds / MONTH_30)
    if (diff < 12) {
      return `${diff}${
        long ? ` ${plural(diff, {one: 'month', other: 'months'})}` : 'mo'
      }`
    } else {
      const str = new Date(earlier).toLocaleDateString()

      if (long) {
        return _(msg`on ${str}`)
      }
      return str
    }
  }
}