diff --git a/packages/core/useTimeAgo/index.test.ts b/packages/core/useTimeAgo/index.test.ts index dfe02de3f92..862f8a233fe 100644 --- a/packages/core/useTimeAgo/index.test.ts +++ b/packages/core/useTimeAgo/index.test.ts @@ -239,4 +239,28 @@ describe('useTimeAgo', () => { expect(useTimeAgo(changeTime).value).toBe('3 years ago') }) }) + + test('rounding', () => { + changeValue.value = getNeededTimeChange('day', 5.49) + expect(useTimeAgo(changeTime).value).toBe('in 5 days') + expect(useTimeAgo(changeTime, { rounding: 'ceil' }).value).toBe('in 6 days') + expect(useTimeAgo(changeTime, { rounding: 'floor' }).value).toBe('in 5 days') + expect(useTimeAgo(changeTime, { rounding: 1 }).value).toBe('in 5.5 days') + expect(useTimeAgo(changeTime, { rounding: 3 }).value).toBe('in 5.49 days') + }) + + test('custom units', () => { + changeValue.value = getNeededTimeChange('day', 14) + expect(useTimeAgo(changeTime).value).toBe('in 2 weeks') + expect(useTimeAgo(changeTime, { + units: [ + { max: 60000, value: 1000, name: 'second' }, + { max: 2760000, value: 60000, name: 'minute' }, + { max: 72000000, value: 3600000, name: 'hour' }, + { max: 518400000 * 30, value: 86400000, name: 'day' }, + { max: 28512000000, value: 2592000000, name: 'month' }, + { max: Infinity, value: 31536000000, name: 'year' }, + ], + }).value).toBe('in 14 days') + }) }) diff --git a/packages/core/useTimeAgo/index.ts b/packages/core/useTimeAgo/index.ts index 91165a30525..72f383043b2 100644 --- a/packages/core/useTimeAgo/index.ts +++ b/packages/core/useTimeAgo/index.ts @@ -6,20 +6,19 @@ import { useNow } from '../useNow' export type UseTimeAgoFormatter = (value: T, isPast: boolean) => string -export interface UseTimeAgoMessages { +export type UseTimeAgoUnitNamesDefault = 'second' | 'minute' | 'hour' | 'day' | 'week' | 'month' | 'year' + +export interface UseTimeAgoMessagesBuiltIn { justNow: string past: string | UseTimeAgoFormatter future: string | UseTimeAgoFormatter - year: string | UseTimeAgoFormatter - month: string | UseTimeAgoFormatter - day: string | UseTimeAgoFormatter - week: string | UseTimeAgoFormatter - hour: string | UseTimeAgoFormatter - minute: string | UseTimeAgoFormatter - second: string | UseTimeAgoFormatter } -export interface UseTimeAgoOptions { +export type UseTimeAgoMessages + = UseTimeAgoMessagesBuiltIn + & Record> + +export interface UseTimeAgoOptions { /** * Expose more controls * @@ -39,7 +38,7 @@ export interface UseTimeAgoOptions { * * @default undefined */ - max?: 'second' | 'minute' | 'hour' | 'day' | 'week' | 'month' | 'year' | number + max?: UnitNames | number /** * Formatter for full date @@ -49,7 +48,7 @@ export interface UseTimeAgoOptions { /** * Messages for formatting the string */ - messages?: UseTimeAgoMessages + messages?: UseTimeAgoMessages /** * Minimum display time unit (default is minute) @@ -63,16 +62,21 @@ export interface UseTimeAgoOptions { * * @default 'round' */ - rounding?: 'round' | 'ceil' | 'floor' + rounding?: 'round' | 'ceil' | 'floor' | number + + /** + * Custom units + */ + units?: UseTimeAgoUnit[] } -interface UseTimeAgoUnit { +interface UseTimeAgoUnit { max: number value: number - name: keyof UseTimeAgoMessages + name: Unit } -const UNITS: UseTimeAgoUnit[] = [ +const DEFAULT_UNITS: UseTimeAgoUnit[] = [ { max: 60000, value: 1000, name: 'second' }, { max: 2760000, value: 60000, name: 'minute' }, { max: 72000000, value: 3600000, name: 'hour' }, @@ -82,7 +86,7 @@ const UNITS: UseTimeAgoUnit[] = [ { max: Infinity, value: 31536000000, name: 'year' }, ] -const DEFAULT_MESSAGES: UseTimeAgoMessages = { +const DEFAULT_MESSAGES: UseTimeAgoMessages = { justNow: 'just now', past: n => n.match(/\d/) ? `${n} ago` : n, future: n => n.match(/\d/) ? `in ${n}` : n, @@ -121,21 +125,24 @@ export type UseTimeAgoReturn = Controls extend * @see https://vueuse.org/useTimeAgo * @param options */ -export function useTimeAgo(time: MaybeComputedRef, options?: UseTimeAgoOptions): UseTimeAgoReturn -export function useTimeAgo(time: MaybeComputedRef, options: UseTimeAgoOptions): UseTimeAgoReturn -export function useTimeAgo(time: MaybeComputedRef, options: UseTimeAgoOptions = {}) { +export function useTimeAgo(time: MaybeComputedRef, options?: UseTimeAgoOptions): UseTimeAgoReturn +export function useTimeAgo(time: MaybeComputedRef, options: UseTimeAgoOptions): UseTimeAgoReturn +export function useTimeAgo(time: MaybeComputedRef, options: UseTimeAgoOptions = {}) { const { controls: exposeControls = false, max, updateInterval = 30_000, - messages = DEFAULT_MESSAGES, + messages = DEFAULT_MESSAGES as UseTimeAgoMessages, fullDateFormatter = DEFAULT_FORMATTER, + units = DEFAULT_UNITS, showSecond = false, rounding = 'round', } = options const { abs } = Math - const roundFn = Math[rounding] + const roundFn = typeof rounding === 'number' + ? (n: number) => +n.toFixed(rounding) + : Math[rounding] const { now, ...controls } = useNow({ interval: updateInterval, controls: true }) function getTimeAgo(from: Date, now: Date) { @@ -150,18 +157,18 @@ export function useTimeAgo(time: MaybeComputedRef, optio return fullDateFormatter(new Date(from)) if (typeof max === 'string') { - const unitMax = UNITS.find(i => i.name === max)?.max + const unitMax = units.find(i => i.name === max)?.max if (unitMax && absDiff > unitMax) return fullDateFormatter(new Date(from)) } - for (const unit of UNITS) { + for (const unit of units) { if (absDiff < unit.max) return format(diff, unit) } } - function applyFormat(name: keyof UseTimeAgoMessages, val: number | string, isPast: boolean) { + function applyFormat(name: UnitNames | keyof UseTimeAgoMessagesBuiltIn, val: number | string, isPast: boolean) { const formatter = messages[name] if (typeof formatter === 'function') return formatter(val as never, isPast) @@ -172,7 +179,7 @@ export function useTimeAgo(time: MaybeComputedRef, optio const val = roundFn(abs(diff) / unit.value) const past = diff > 0 - const str = applyFormat(unit.name, val, past) + const str = applyFormat(unit.name as UnitNames, val, past) return applyFormat(past ? 'past' : 'future', str, past) }