192 lines
4.8 KiB
TypeScript
192 lines
4.8 KiB
TypeScript
import {useCallback} from 'react'
|
|
import {type I18n} from '@lingui/core'
|
|
import {defineMessage, msg, plural} from '@lingui/core/macro'
|
|
import {useLingui} from '@lingui/react'
|
|
import {differenceInSeconds} from 'date-fns'
|
|
|
|
export type DateDiffFormat = 'long' | 'short'
|
|
|
|
type DateDiff = {
|
|
value: number
|
|
unit: 'now' | 'second' | 'minute' | 'hour' | 'day' | 'month'
|
|
earlier: Date
|
|
later: Date
|
|
}
|
|
|
|
const NOW = 5
|
|
const MINUTE = 60
|
|
const HOUR = MINUTE * 60
|
|
const DAY = HOUR * 24
|
|
const MONTH_30 = DAY * 30
|
|
|
|
export function useGetTimeAgo({future = false}: {future?: boolean} = {}) {
|
|
const {i18n} = useLingui()
|
|
return useCallback(
|
|
(
|
|
earlier: number | string | Date,
|
|
later: number | string | Date,
|
|
options?: {format: DateDiffFormat},
|
|
) => {
|
|
const diff = dateDiff(earlier, later, future ? 'up' : 'down')
|
|
return formatDateDiff({diff, i18n, format: options?.format})
|
|
},
|
|
[i18n, future],
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Returns the difference between `earlier` and `later` dates, based on
|
|
* opinionated rules.
|
|
*
|
|
* - All month are considered exactly 30 days.
|
|
* - Dates assume `earlier` <= `later`, and will otherwise return 'now'.
|
|
* - All values round down
|
|
*/
|
|
export function dateDiff(
|
|
earlier: number | string | Date,
|
|
later: number | string | Date,
|
|
rounding: 'up' | 'down' = 'down',
|
|
): DateDiff {
|
|
let diff = {
|
|
value: 0,
|
|
unit: 'now' as DateDiff['unit'],
|
|
}
|
|
const e = new Date(earlier)
|
|
const l = new Date(later)
|
|
const diffSeconds = differenceInSeconds(l, e)
|
|
|
|
if (diffSeconds < NOW) {
|
|
diff = {
|
|
value: 0,
|
|
unit: 'now',
|
|
}
|
|
} else if (diffSeconds < MINUTE) {
|
|
diff = {
|
|
value: diffSeconds,
|
|
unit: 'second',
|
|
}
|
|
} else if (diffSeconds < HOUR) {
|
|
const value =
|
|
rounding === 'up'
|
|
? Math.ceil(diffSeconds / MINUTE)
|
|
: Math.floor(diffSeconds / MINUTE)
|
|
diff = {
|
|
value,
|
|
unit: 'minute',
|
|
}
|
|
} else if (diffSeconds < DAY) {
|
|
const value =
|
|
rounding === 'up'
|
|
? Math.ceil(diffSeconds / HOUR)
|
|
: Math.floor(diffSeconds / HOUR)
|
|
diff = {
|
|
value,
|
|
unit: 'hour',
|
|
}
|
|
} else if (diffSeconds < MONTH_30) {
|
|
const value =
|
|
rounding === 'up'
|
|
? Math.ceil(diffSeconds / DAY)
|
|
: Math.floor(diffSeconds / DAY)
|
|
diff = {
|
|
value,
|
|
unit: 'day',
|
|
}
|
|
} else {
|
|
const value =
|
|
rounding === 'up'
|
|
? Math.ceil(diffSeconds / MONTH_30)
|
|
: Math.floor(diffSeconds / MONTH_30)
|
|
diff = {
|
|
value,
|
|
unit: 'month',
|
|
}
|
|
}
|
|
|
|
return {
|
|
...diff,
|
|
earlier: e,
|
|
later: l,
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Accepts a `DateDiff` and teturns 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 formatDateDiff({
|
|
diff,
|
|
format = 'short',
|
|
i18n,
|
|
}: {
|
|
diff: DateDiff
|
|
format?: DateDiffFormat
|
|
i18n: I18n
|
|
}): string {
|
|
const long = format === 'long'
|
|
|
|
switch (diff.unit) {
|
|
case 'now': {
|
|
return i18n._(msg`now`)
|
|
}
|
|
case 'second': {
|
|
return long
|
|
? i18n._(plural(diff.value, {one: '# second', other: '# seconds'}))
|
|
: i18n._(
|
|
defineMessage({
|
|
message: `${diff.value}s`,
|
|
comment: `How many seconds have passed, displayed in a narrow form`,
|
|
}),
|
|
)
|
|
}
|
|
case 'minute': {
|
|
return long
|
|
? i18n._(plural(diff.value, {one: '# minute', other: '# minutes'}))
|
|
: i18n._(
|
|
defineMessage({
|
|
message: `${diff.value}m`,
|
|
comment: `How many minutes have passed, displayed in a narrow form`,
|
|
}),
|
|
)
|
|
}
|
|
case 'hour': {
|
|
return long
|
|
? i18n._(plural(diff.value, {one: '# hour', other: '# hours'}))
|
|
: i18n._(
|
|
defineMessage({
|
|
message: `${diff.value}h`,
|
|
comment: `How many hours have passed, displayed in a narrow form`,
|
|
}),
|
|
)
|
|
}
|
|
case 'day': {
|
|
return long
|
|
? i18n._(plural(diff.value, {one: '# day', other: '# days'}))
|
|
: i18n._(
|
|
defineMessage({
|
|
message: `${diff.value}d`,
|
|
comment: `How many days have passed, displayed in a narrow form`,
|
|
}),
|
|
)
|
|
}
|
|
case 'month': {
|
|
if (diff.value < 12) {
|
|
return long
|
|
? i18n._(plural(diff.value, {one: '# month', other: '# months'}))
|
|
: i18n._(
|
|
defineMessage({
|
|
message: plural(diff.value, {one: '#mo', other: '#mo'}),
|
|
comment: `How many months have passed, displayed in a narrow form`,
|
|
}),
|
|
)
|
|
}
|
|
return i18n.date(new Date(diff.earlier))
|
|
}
|
|
}
|
|
}
|