diff options
Diffstat (limited to 'src/lib/hooks/useTimeAgo.ts')
-rw-r--r-- | src/lib/hooks/useTimeAgo.ts | 95 |
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 + } + } +} |