2026-01-23 23:54:58 +00:00
|
|
|
import dayjs from "dayjs";
|
|
|
|
|
import duration from "dayjs/plugin/duration";
|
|
|
|
|
import utc from "dayjs/plugin/utc";
|
|
|
|
|
import timezone from "dayjs/plugin/timezone";
|
|
|
|
|
import customParseFormat from "dayjs/plugin/customParseFormat";
|
2026-07-31 11:55:05 -07:00
|
|
|
import prettyMilliseconds from "pretty-ms";
|
2026-01-23 23:54:58 +00:00
|
|
|
|
|
|
|
|
dayjs.extend(utc);
|
|
|
|
|
dayjs.extend(timezone);
|
|
|
|
|
dayjs.extend(customParseFormat);
|
|
|
|
|
dayjs.extend(duration);
|
|
|
|
|
|
|
|
|
|
export const MS_PER_SECOND = 1000;
|
|
|
|
|
export const MS_PER_MINUTE = 60 * MS_PER_SECOND;
|
|
|
|
|
export const MS_PER_HOUR = 60 * MS_PER_MINUTE;
|
|
|
|
|
export const MS_PER_DAY = 24 * MS_PER_HOUR;
|
|
|
|
|
export const MS_PER_WEEK = MS_PER_DAY * 7;
|
|
|
|
|
|
2026-05-01 22:28:12 -04:00
|
|
|
const resolveSystemTimezone = (): string => {
|
|
|
|
|
try {
|
|
|
|
|
return Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
|
|
|
|
|
} catch {
|
|
|
|
|
return "UTC";
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const SYSTEM_TIMEZONE = resolveSystemTimezone();
|
|
|
|
|
|
|
|
|
|
export const resolveTimezone = (preferred?: string | null): string =>
|
|
|
|
|
preferred?.trim() || SYSTEM_TIMEZONE;
|
|
|
|
|
|
|
|
|
|
export const formatDateWithTz = (
|
|
|
|
|
timestamp: string,
|
|
|
|
|
format: string,
|
|
|
|
|
timezone?: string
|
|
|
|
|
) => {
|
2026-01-23 23:54:58 +00:00
|
|
|
if (!timestamp) {
|
|
|
|
|
return "Unknown time";
|
|
|
|
|
}
|
2026-05-01 22:28:12 -04:00
|
|
|
const tz = resolveTimezone(timezone);
|
2026-04-26 18:25:53 +01:00
|
|
|
try {
|
2026-05-01 22:28:12 -04:00
|
|
|
return dayjs(timestamp).tz(tz).format(format);
|
2026-04-26 18:25:53 +01:00
|
|
|
} catch {
|
|
|
|
|
return dayjs(timestamp).utc().format(format);
|
|
|
|
|
}
|
2026-01-23 23:54:58 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const tickDateFormatLookup = (range: string) => {
|
|
|
|
|
const tickFormatLookup: Record<string, string> = {
|
2026-01-26 02:06:37 +00:00
|
|
|
recent: "h:mm A",
|
|
|
|
|
day: "h:mm A",
|
|
|
|
|
week: "MM/D, h:mm A",
|
|
|
|
|
month: "ddd. M/D",
|
2026-01-23 23:54:58 +00:00
|
|
|
};
|
|
|
|
|
const format = tickFormatLookup[range];
|
|
|
|
|
if (format === undefined) {
|
|
|
|
|
return "";
|
|
|
|
|
}
|
|
|
|
|
return format;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const tooltipDateFormatLookup = (range: string) => {
|
|
|
|
|
const dateFormatLookup: Record<string, string> = {
|
2026-01-26 02:06:37 +00:00
|
|
|
recent: "ddd. MMMM D, YYYY, hh:mm A",
|
|
|
|
|
day: "ddd. MMMM D, YYYY, hh:mm A",
|
|
|
|
|
week: "ddd. MMMM D, YYYY, hh:mm A",
|
|
|
|
|
month: "ddd. MMMM D, YYYY",
|
2026-01-23 23:54:58 +00:00
|
|
|
};
|
|
|
|
|
const format = dateFormatLookup[range];
|
|
|
|
|
if (format === undefined) {
|
|
|
|
|
return "";
|
|
|
|
|
}
|
|
|
|
|
return format;
|
|
|
|
|
};
|
2026-02-05 21:40:36 +00:00
|
|
|
|
|
|
|
|
export const formatTimestamp = (timestamp: string | number | null): string => {
|
|
|
|
|
if (!timestamp) return "-";
|
|
|
|
|
const date = new Date(timestamp);
|
|
|
|
|
return date.toLocaleString();
|
|
|
|
|
};
|
2026-07-31 11:55:05 -07:00
|
|
|
|
2026-07-31 12:40:18 -07:00
|
|
|
const DISPLAY_UNITS = [MS_PER_DAY, MS_PER_HOUR, MS_PER_MINUTE, MS_PER_SECOND];
|
|
|
|
|
|
|
|
|
|
// pretty-ms floors seconds-and-above and truncates fractional ms, so values
|
|
|
|
|
// must be rounded to their display unit first to land on the nearest whole
|
|
|
|
|
// unit (53.6 → "54 ms", 1937 → "2 s", 59500 → "1 m").
|
|
|
|
|
const roundToDisplayUnit = (ms: number): number => {
|
|
|
|
|
for (const unit of DISPLAY_UNITS) {
|
|
|
|
|
if (ms >= unit) return Math.round(ms / unit) * unit;
|
|
|
|
|
}
|
|
|
|
|
return ms >= 1 ? Math.round(ms) : ms;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const formatMs = (ms: number, hasSpace: boolean = true): string => {
|
|
|
|
|
const formatted = prettyMilliseconds(roundToDisplayUnit(ms), {
|
2026-07-31 11:55:05 -07:00
|
|
|
compact: true,
|
|
|
|
|
formatSubMilliseconds: true,
|
|
|
|
|
secondsDecimalDigits: 0,
|
|
|
|
|
});
|
2026-07-31 12:40:18 -07:00
|
|
|
|
|
|
|
|
if (hasSpace) {
|
|
|
|
|
return formatted.replace(/^(\d+)(\D+)$/, "$1 $2");
|
|
|
|
|
}
|
|
|
|
|
return formatted;
|
2026-07-31 11:55:05 -07:00
|
|
|
};
|