"use client"; // Times in the visitor's own time zone and language. The server doesn't know either, so it renders times in UTC and // the browser replaces them once the page is running. import { useEffect, useState, useSyncExternalStore } from "react"; const noSubscription = () => () => {}; /** False on the server and while hydrating, true after. */ function useInBrowser(): boolean { return useSyncExternalStore( noSubscription, () => true, () => false, ); } const units: [Intl.RelativeTimeFormatUnit, number][] = [ ["year", 365 * 24 * 3600], ["month", 30 * 24 * 3600], ["week", 7 * 24 * 3600], ["day", 24 * 3600], ["hour", 3600], ["minute", 60], ]; const relative = new Intl.RelativeTimeFormat("en", { numeric: "auto" }); function ago(iso: string, now: number): string { const seconds = (new Date(iso).getTime() - now) / 1000; for (const [unit, size] of units) { if (Math.abs(seconds) >= size) return relative.format(Math.round(seconds / size), unit); } return "just now"; } /** "5 minutes ago", kept up to date. */ export function TimeAgo({ iso }: { iso: string }) { const [now, setNow] = useState(() => Date.now()); useEffect(() => { const timer = setInterval(() => setNow(Date.now()), 30_000); return () => clearInterval(timer); }, []); const local = useInBrowser(); return ( ); } function format(iso: string, options: Intl.DateTimeFormatOptions, local: boolean): string { return new Date(iso).toLocaleString(local ? undefined : "en", local ? options : { ...options, timeZone: "UTC" }); } /** A date (and time). */ export function DateTime({ iso, dateOnly = false }: { iso: string; dateOnly?: boolean }) { const local = useInBrowser(); const options: Intl.DateTimeFormatOptions = dateOnly ? { dateStyle: "medium" } : { dateStyle: "medium", timeStyle: "short" }; return ; } /** A time of day ("3:04 PM"), with the date too when it isn't today. */ export function Since({ iso }: { iso: string }) { const local = useInBrowser(); const today = local && new Date(iso).toDateString() === new Date().toDateString(); const options: Intl.DateTimeFormatOptions = today ? { hour: "numeric", minute: "2-digit" } : { month: "short", day: "numeric", hour: "numeric", minute: "2-digit" }; return ( ); }