Bluesky app fork with some witchin' additions 馃挮
witchsky.app
bluesky
fork
client
1import {useCallback} from 'react'
2import {type I18n} from '@lingui/core'
3import {defineMessage, msg, plural} from '@lingui/core/macro'
4import {useLingui} from '@lingui/react'
5import {differenceInSeconds} from 'date-fns'
6
7export type DateDiffFormat = 'long' | 'short'
8
9type DateDiff = {
10 value: number
11 unit: 'now' | 'second' | 'minute' | 'hour' | 'day' | 'month'
12 earlier: Date
13 later: Date
14}
15
16const NOW = 5
17const MINUTE = 60
18const HOUR = MINUTE * 60
19const DAY = HOUR * 24
20const MONTH_30 = DAY * 30
21
22export function useGetTimeAgo({future = false}: {future?: boolean} = {}) {
23 const {i18n} = useLingui()
24 return useCallback(
25 (
26 earlier: number | string | Date,
27 later: number | string | Date,
28 options?: {format: DateDiffFormat},
29 ) => {
30 const diff = dateDiff(earlier, later, future ? 'up' : 'down')
31 return formatDateDiff({diff, i18n, format: options?.format})
32 },
33 [i18n, future],
34 )
35}
36
37/**
38 * Returns the difference between `earlier` and `later` dates, based on
39 * opinionated rules.
40 *
41 * - All month are considered exactly 30 days.
42 * - Dates assume `earlier` <= `later`, and will otherwise return 'now'.
43 * - All values round down
44 */
45export function dateDiff(
46 earlier: number | string | Date,
47 later: number | string | Date,
48 rounding: 'up' | 'down' = 'down',
49): DateDiff {
50 let diff = {
51 value: 0,
52 unit: 'now' as DateDiff['unit'],
53 }
54 const e = new Date(earlier)
55 const l = new Date(later)
56 const diffSeconds = differenceInSeconds(l, e)
57
58 if (diffSeconds < NOW) {
59 diff = {
60 value: 0,
61 unit: 'now' as DateDiff['unit'],
62 }
63 } else if (diffSeconds < MINUTE) {
64 diff = {
65 value: diffSeconds,
66 unit: 'second' as DateDiff['unit'],
67 }
68 } else if (diffSeconds < HOUR) {
69 const value =
70 rounding === 'up'
71 ? Math.ceil(diffSeconds / MINUTE)
72 : Math.floor(diffSeconds / MINUTE)
73 diff = {
74 value,
75 unit: 'minute' as DateDiff['unit'],
76 }
77 } else if (diffSeconds < DAY) {
78 const value =
79 rounding === 'up'
80 ? Math.ceil(diffSeconds / HOUR)
81 : Math.floor(diffSeconds / HOUR)
82 diff = {
83 value,
84 unit: 'hour' as DateDiff['unit'],
85 }
86 } else if (diffSeconds < MONTH_30) {
87 const value =
88 rounding === 'up'
89 ? Math.ceil(diffSeconds / DAY)
90 : Math.floor(diffSeconds / DAY)
91 diff = {
92 value,
93 unit: 'day' as DateDiff['unit'],
94 }
95 } else {
96 const value =
97 rounding === 'up'
98 ? Math.ceil(diffSeconds / MONTH_30)
99 : Math.floor(diffSeconds / MONTH_30)
100 diff = {
101 value,
102 unit: 'month' as DateDiff['unit'],
103 }
104 }
105
106 return {
107 ...diff,
108 earlier: e,
109 later: l,
110 }
111}
112
113/**
114 * Accepts a `DateDiff` and teturns the difference between `earlier` and
115 * `later` dates, formatted as a natural language string.
116 *
117 * - All month are considered exactly 30 days.
118 * - Dates assume `earlier` <= `later`, and will otherwise return 'now'.
119 * - Differences >= 360 days are returned as the "M/D/YYYY" string
120 * - All values round down
121 */
122export function formatDateDiff({
123 diff,
124 format = 'short',
125 i18n,
126}: {
127 diff: DateDiff
128 format?: DateDiffFormat
129 i18n: I18n
130}): string {
131 const long = format === 'long'
132
133 switch (diff.unit) {
134 case 'now': {
135 return i18n._(msg`now`)
136 }
137 case 'second': {
138 return long
139 ? i18n._(plural(diff.value, {one: '# second', other: '# seconds'}))
140 : i18n._(
141 defineMessage({
142 message: `${diff.value}s`,
143 comment: `How many seconds have passed, displayed in a narrow form`,
144 }),
145 )
146 }
147 case 'minute': {
148 return long
149 ? i18n._(plural(diff.value, {one: '# minute', other: '# minutes'}))
150 : i18n._(
151 defineMessage({
152 message: `${diff.value}m`,
153 comment: `How many minutes have passed, displayed in a narrow form`,
154 }),
155 )
156 }
157 case 'hour': {
158 return long
159 ? i18n._(plural(diff.value, {one: '# hour', other: '# hours'}))
160 : i18n._(
161 defineMessage({
162 message: `${diff.value}h`,
163 comment: `How many hours have passed, displayed in a narrow form`,
164 }),
165 )
166 }
167 case 'day': {
168 return long
169 ? i18n._(plural(diff.value, {one: '# day', other: '# days'}))
170 : i18n._(
171 defineMessage({
172 message: `${diff.value}d`,
173 comment: `How many days have passed, displayed in a narrow form`,
174 }),
175 )
176 }
177 case 'month': {
178 if (diff.value < 12) {
179 return long
180 ? i18n._(plural(diff.value, {one: '# month', other: '# months'}))
181 : i18n._(
182 defineMessage({
183 message: plural(diff.value, {one: '#mo', other: '#mo'}),
184 comment: `How many months have passed, displayed in a narrow form`,
185 }),
186 )
187 }
188 return i18n.date(new Date(diff.earlier))
189 }
190 }
191}