From 0ff15f96bd2c100091cbb5273f7624f6c8ec7a1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81d=C3=A1m=20Kleizer?= Date: Wed, 20 Nov 2024 08:47:02 +0100 Subject: [PATCH] fix: #321 use date-fns for localized datetime formatting --- frontend/components/global/DateTime.vue | 2 +- frontend/composables/use-formatters.ts | 110 +++++++----------------- 2 files changed, 32 insertions(+), 80 deletions(-) diff --git a/frontend/components/global/DateTime.vue b/frontend/components/global/DateTime.vue index b23cac2b..b82cf5c6 100644 --- a/frontend/components/global/DateTime.vue +++ b/frontend/components/global/DateTime.vue @@ -22,6 +22,6 @@ return ""; } - return fmtDate(props.date, props.format); + return fmtDate(props.date, props.format, props.datetimeType); }); diff --git a/frontend/composables/use-formatters.ts b/frontend/composables/use-formatters.ts index 602a7108..4d4533ed 100644 --- a/frontend/composables/use-formatters.ts +++ b/frontend/composables/use-formatters.ts @@ -1,5 +1,6 @@ import { useI18n } from "vue-i18n"; -import { type UseTimeAgoMessages, type UseTimeAgoUnitNamesDefault } from "@vueuse/core"; +import { format, formatDistance } from "date-fns"; +import * as Locales from "date-fns/locale"; const cache = { currency: "", @@ -26,99 +27,50 @@ export async function useFormatCurrency() { export type DateTimeFormat = "relative" | "long" | "short" | "human"; export type DateTimeType = "date" | "time" | "datetime"; -function ordinalIndicator(num: number) { - if (num > 3 && num < 21) return "th"; - switch (num % 10) { - case 1: - return "st"; - case 2: - return "nd"; - case 3: - return "rd"; - default: - return "th"; - } +function getLocale() { + const t = useI18n(); + const localeCode = t?.locale?.value as string ?? "en-US"; + const region = localeCode.length>2 ? localeCode.substring(3) : ""; + return (Locales[(localeCode.substring(0,2) + region) as keyof typeof Locales] ?? Locales[localeCode.substring(0,2) as keyof typeof Locales] ?? Locales.enUS); } export function useLocaleTimeAgo(date: Date) { - const { t } = useI18n(); - - const I18N_MESSAGES: UseTimeAgoMessages = { - justNow: t("components.global.date_time.just-now"), - past: n => (n.match(/\d/) ? t("components.global.date_time.ago", [n]) : n), - future: n => (n.match(/\d/) ? t("components.global.date_time.in", [n]) : n), - month: (n, past) => - n === 1 - ? past - ? t("components.global.date_time.last-month") - : t("components.global.date_time.next-month") - : `${n} ${t(`components.global.date_time.months`)}`, - year: (n, past) => - n === 1 - ? past - ? t("components.global.date_time.last-year") - : t("components.global.date_time.next-year") - : `${n} ${t(`components.global.date_time.years`)}`, - day: (n, past) => - n === 1 - ? past - ? t("components.global.date_time.yesterday") - : t("components.global.date_time.tomorrow") - : `${n} ${t(`components.global.date_time.days`)}`, - week: (n, past) => - n === 1 - ? past - ? t("components.global.date_time.last-week") - : t("components.global.date_time.next-week") - : `${n} ${t(`components.global.date_time.weeks`)}`, - hour: n => `${n} ${n === 1 ? t("components.global.date_time.hour") : t("components.global.date_time.hours")}`, - minute: n => `${n} ${n === 1 ? t("components.global.date_time.minute") : t("components.global.date_time.minutes")}`, - second: n => `${n} ${n === 1 ? t("components.global.date_time.second") : t("components.global.date_time.seconds")}`, - invalid: "", - }; - - return useTimeAgo(date, { - fullDateFormatter: (date: Date) => date.toLocaleDateString(), - messages: I18N_MESSAGES, - }); + return formatDistance(date, new Date(), {locale: getLocale()}); } -export function fmtDate(value: string | Date, fmt: DateTimeFormat = "human"): string { - const months = [ - "January", - "February", - "March", - "April", - "May", - "June", - "July", - "August", - "September", - "October", - "November", - "December", - ]; - +export function fmtDate(value: string | Date, fmt: DateTimeFormat = "human", type: DateTimeType = "date"): string { const dt = typeof value === "string" ? new Date(value) : value; - if (!dt) { + + if (!dt || !validDate(dt)) { return ""; } - if (!validDate(dt)) { - return ""; + if (fmt === "relative") { + return useLocaleTimeAgo(dt); + } + + if (type === "time") { + return format(dt, "p", { locale: getLocale() }); } + let formatStr = ""; + switch (fmt) { - case "relative": - return useLocaleTimeAgo(dt).value + useDateFormat(dt, " (YYYY-MM-DD)").value; + case "human": + formatStr = "PPP" + break; case "long": - return useDateFormat(dt, "YYYY-MM-DD (dddd)").value; + formatStr = "PP"; + break; case "short": - return useDateFormat(dt, "YYYY-MM-DD").value; - case "human": - // January 1st, 2021 - return `${months[dt.getMonth()]} ${dt.getDate()}${ordinalIndicator(dt.getDate())}, ${dt.getFullYear()}`; + formatStr = "P"; + break; default: return ""; } + if (type === "datetime") { + formatStr += "p"; + } + + return format(dt, formatStr, { locale: getLocale() }); }