import dayjs, { type Dayjs } from "dayjs"; import customParseFormat from "dayjs/plugin/customParseFormat"; import timezone from "dayjs/plugin/timezone"; import utc from "dayjs/plugin/utc"; import { match, P } from "ts-pattern"; import { computed, toValue, type MaybeRefOrGetter } from "vue"; dayjs.extend(customParseFormat); dayjs.extend(utc); dayjs.extend(timezone); export type TimestampDisplayType = "iso8601" | "unix" | "unixMillis"; export interface TimestampDisplayConfig { valueFormat?: string; valueTz?: string; displayFormat?: string; locale?: string; type?: TimestampDisplayType; } export interface TimestampDisplayInput extends TimestampDisplayConfig { value: string | number; } export type TimestampSimpleConfig = undefined | boolean | string | TimestampDisplayConfig; export const DEFAULT_TIMESTAMP_DISPLAY_FORMAT = "YYYY-MM-DD HH:mm:ss"; function withDefaultDisplayFormat( config: TimestampDisplayConfig, defaultDisplayFormat?: string ): TimestampDisplayConfig { if (!defaultDisplayFormat || config.displayFormat) { return config; } return { ...config, displayFormat: defaultDisplayFormat, }; } export function normalizeTimestampValue( timestamp: TimestampSimpleConfig, defaultDisplayFormat?: string ): TimestampDisplayConfig | null { return match(timestamp) .with(P.nullish, () => null) .with(true, () => withDefaultDisplayFormat({ type: "unixMillis" }, defaultDisplayFormat)) .with(P.union("iso8601", "unix", "unixMillis"), (type) => withDefaultDisplayFormat({ type }, defaultDisplayFormat)) .with(P.string, (valueFormat) => withDefaultDisplayFormat({ valueFormat }, defaultDisplayFormat)) .otherwise((config) => withDefaultDisplayFormat({ ...config }, defaultDisplayFormat)); } export function parseTimestampValue(value: string | number, config: TimestampDisplayConfig): Dayjs { const numericValue = match(value) .with(P.string, parseFloat) .otherwise((n) => n); if (!isNaN(numericValue)) { if (config.type == "unix") return dayjs.unix(numericValue); if (config.type == "unixMillis") return dayjs(numericValue); } if (config.valueFormat && config.valueTz) { return dayjs.tz(value, config.valueFormat, config.valueTz); } if (config.valueTz) { return dayjs.tz(value, config.valueTz); } if (config.valueFormat) { return dayjs(value, config.valueFormat); } return dayjs(value); } export function formatTimestampDisplay(value: string | number, config: TimestampDisplayConfig): string { let parsed = parseTimestampValue(value, config); if (!parsed.isValid()) { return String(value); } if (config.locale) { parsed = parsed.locale(config.locale); } return config.displayFormat ? parsed.format(config.displayFormat) : parsed.format(); } export function formatTimestampFromValue( value: string | number, timestamp: TimestampSimpleConfig, defaultDisplayFormat = DEFAULT_TIMESTAMP_DISPLAY_FORMAT ): string { const config = normalizeTimestampValue(timestamp, defaultDisplayFormat); if (!config) { return String(value); } return formatTimestampDisplay(value, config); } export function useTimestampDisplay(input: MaybeRefOrGetter) { const parsed = computed(() => { const resolved = toValue(input); return parseTimestampValue(resolved.value, resolved); }); const display = computed(() => { const resolved = toValue(input); return formatTimestampDisplay(resolved.value, resolved); }); return { parsed, display, }; }