/* Copyright 2026 Marimo. All rights reserved. */ import React from "react"; import { useDateFormatter, useLocale } from "react-aria"; import { Tooltip } from "@/components/ui/tooltip"; interface DatePopoverProps { date: Date | string | number | null | undefined; type: "date" | "datetime"; children: React.ReactNode; } export const DatePopover: React.FC = ({ date, type, children, }) => { // Return just the text if date is invalid if (!date || Number.isNaN(new Date(date).getTime())) { return children; } const dateObj = new Date(date); const content = (
{type === "datetime" ? ( ) : ( )}
); return ( {children} ); }; const TimezoneDisplay = ({ date }: { date: Date }) => { const { locale } = useLocale(); const localTimezone = Intl.DateTimeFormat(locale).resolvedOptions().timeZone; const hasSubSeconds = date.getUTCMilliseconds() !== 0; const utcFormatter = useDateFormatter( hasSubSeconds ? { timeZone: "UTC", year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit", fractionalSecondDigits: 3, } : { timeZone: "UTC", dateStyle: "long", timeStyle: "medium", }, ); const localFormatter = useDateFormatter( hasSubSeconds ? { timeZone: localTimezone, year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit", fractionalSecondDigits: 3, } : { timeZone: localTimezone, dateStyle: "long", timeStyle: "medium", }, ); return ( <>
UTC {utcFormatter.format(date)}
{localTimezone} {localFormatter.format(date)}
); }; const DateDisplay = ({ date }: { date: Date }) => { const dateFormatter = useDateFormatter({ timeZone: "UTC", dateStyle: "long", }); return {dateFormatter.format(date)}; }; const RelativeTime = ({ date }: { date: Date }) => { const { locale } = useLocale(); // Initialize relative time formatter with current locale and "auto" numeric style const relativeTimeFormatter = new Intl.RelativeTimeFormat(locale, { numeric: "auto", }); const currentTime = new Date(); // Get difference in seconds between now and the input date const differenceInSeconds = (currentTime.getTime() - date.getTime()) / 1000; // Define time units with their thresholds and conversion factors const timeUnits: [number, number, string][] = [ [60, 1, "second"], // Less than 60 seconds [60, 60, "minute"], // Less than 60 minutes [24, 3600, "hour"], // Less than 24 hours [365, 86_400, "day"], // Less than 365 days [Number.POSITIVE_INFINITY, 31_536_000, "year"], // Everything else in years ]; // Find the appropriate unit to use for (const [unitThreshold, secondsInUnit, unitName] of timeUnits) { const valueInUnits = differenceInSeconds / secondsInUnit; if (valueInUnits < unitThreshold) { // Convert to fixed decimal and negate since RelativeTimeFormat expects // negative values for past times and positive for future times const roundedValue = -Number(valueInUnits.toFixed(1)); return ( {relativeTimeFormatter.format( roundedValue, unitName as Intl.RelativeTimeFormatUnit, )} ); } } // Should never reach here due to Infinity threshold, but provide fallback return {relativeTimeFormatter.format(0, "second")}; };