about summary refs log tree commit diff
path: root/src/lib/hooks/useTimeAgo.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/hooks/useTimeAgo.ts')
-rw-r--r--src/lib/hooks/useTimeAgo.ts95
1 files changed, 95 insertions, 0 deletions
diff --git a/src/lib/hooks/useTimeAgo.ts b/src/lib/hooks/useTimeAgo.ts
new file mode 100644
index 000000000..5f0782f96
--- /dev/null
+++ b/src/lib/hooks/useTimeAgo.ts
@@ -0,0 +1,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
+    }
+  }
+}