Fork of atp.tools as a universal profile for people on the ATmosphere
at main 100 lines 2.5 kB view raw
1import { clsx, type ClassValue } from "clsx"; 2import { twMerge } from "tailwind-merge"; 3 4export function cn(...inputs: ClassValue[]) { 5 return twMerge(clsx(inputs)); 6} 7type TimeUnit = 8 | "year" 9 | "month" 10 | "week" 11 | "day" 12 | "hour" 13 | "minute" 14 | "second"; 15 16interface TimeInterval { 17 seconds: number; 18 label: TimeUnit; 19} 20 21interface TimeagoOptions { 22 maxUnit?: TimeUnit; 23 minUnit?: TimeUnit; 24 future?: boolean; 25 useShortLabels?: boolean; 26} 27 28export function timeAgo( 29 date: Date | string | number, 30 options: TimeagoOptions = {}, 31): string { 32 const { 33 maxUnit = "year", 34 minUnit = "second", 35 future = true, 36 useShortLabels = false, 37 } = options; 38 39 const currentDate = new Date(); 40 const targetDate = new Date(date); 41 42 const seconds = Math.floor( 43 (currentDate.getTime() - targetDate.getTime()) / 1000, 44 ); 45 const isFuture = seconds < 0; 46 const absoluteSeconds = Math.abs(seconds); 47 48 const intervals: TimeInterval[] = [ 49 { seconds: 31536000, label: "year" }, 50 { seconds: 2592000, label: "month" }, 51 { seconds: 604800, label: "week" }, 52 { seconds: 86400, label: "day" }, 53 { seconds: 3600, label: "hour" }, 54 { seconds: 60, label: "minute" }, 55 { seconds: 1, label: "second" }, 56 ]; 57 58 // Short labels mapping 59 const shortLabels: Record<TimeUnit, string> = { 60 year: "y", 61 month: "mo", 62 week: "w", 63 day: "d", 64 hour: "h", 65 minute: "m", 66 second: "s", 67 }; 68 69 // Handle future dates if not allowed 70 if (isFuture && !future) { 71 return "in the future"; 72 } 73 74 // Handle just now 75 if (absoluteSeconds < 30 && minUnit === "second") { 76 return "just now"; 77 } 78 79 // Filter intervals based on max and min units 80 const filteredIntervals = intervals.filter((interval) => { 81 const unitIndex = intervals.findIndex((i) => i.label === interval.label); 82 const maxUnitIndex = intervals.findIndex((i) => i.label === maxUnit); 83 const minUnitIndex = intervals.findIndex((i) => i.label === minUnit); 84 return unitIndex >= maxUnitIndex && unitIndex <= minUnitIndex; 85 }); 86 87 for (const { seconds: secondsInUnit, label } of filteredIntervals) { 88 const interval = Math.floor(absoluteSeconds / secondsInUnit); 89 90 if (interval >= 1) { 91 const unitLabel = useShortLabels ? shortLabels[label] : label; 92 const plural = interval === 1 ? "" : "s"; 93 const timeLabel = `${interval}${useShortLabels ? "" : " "}${unitLabel}${useShortLabels ? "" : plural}`; 94 95 return isFuture ? `in ${timeLabel}` : `${timeLabel} ago`; 96 } 97 } 98 99 return "just now"; 100}