diff --git a/frontend/src/components/Form/AccessField.tsx b/frontend/src/components/Form/AccessField.tsx
index c72ef588..16059ac3 100644
--- a/frontend/src/components/Form/AccessField.tsx
+++ b/frontend/src/components/Form/AccessField.tsx
@@ -4,7 +4,7 @@ import type { ReactNode } from "react";
import Select, { type ActionMeta, components, type OptionProps } from "react-select";
import type { AccessList } from "src/api/backend";
import { useAccessLists } from "src/hooks";
-import { DateTimeFormat, intl, T } from "src/locale";
+import { formatDateTime, intl, T } from "src/locale";
interface AccessOption {
readonly value: number;
@@ -48,7 +48,7 @@ export function AccessField({ name = "accessListId", label = "access-list", id =
{
users: item?.items?.length,
rules: item?.clients?.length,
- date: item?.createdOn ? DateTimeFormat(item?.createdOn) : "N/A",
+ date: item?.createdOn ? formatDateTime(item?.createdOn) : "N/A",
},
),
icon: ,
diff --git a/frontend/src/components/Form/SSLCertificateField.tsx b/frontend/src/components/Form/SSLCertificateField.tsx
index c4767509..0e7ce337 100644
--- a/frontend/src/components/Form/SSLCertificateField.tsx
+++ b/frontend/src/components/Form/SSLCertificateField.tsx
@@ -3,7 +3,7 @@ import { Field, useFormikContext } from "formik";
import Select, { type ActionMeta, components, type OptionProps } from "react-select";
import type { Certificate } from "src/api/backend";
import { useCertificates } from "src/hooks";
-import { DateTimeFormat, intl, T } from "src/locale";
+import { formatDateTime, intl, T } from "src/locale";
interface CertOption {
readonly value: number | "new";
@@ -75,7 +75,7 @@ export function SSLCertificateField({
data?.map((cert: Certificate) => ({
value: cert.id,
label: cert.niceName,
- subLabel: `${cert.provider === "letsencrypt" ? intl.formatMessage({ id: "lets-encrypt" }) : cert.provider} — ${intl.formatMessage({ id: "expires.on" }, { date: cert.expiresOn ? DateTimeFormat(cert.expiresOn) : "N/A" })}`,
+ subLabel: `${cert.provider === "letsencrypt" ? intl.formatMessage({ id: "lets-encrypt" }) : cert.provider} — ${intl.formatMessage({ id: "expires.on" }, { date: cert.expiresOn ? formatDateTime(cert.expiresOn) : "N/A" })}`,
icon: ,
})) || [];
diff --git a/frontend/src/components/Table/Formatter/DateFormatter.tsx b/frontend/src/components/Table/Formatter/DateFormatter.tsx
index e8c2c6c7..acb96461 100644
--- a/frontend/src/components/Table/Formatter/DateFormatter.tsx
+++ b/frontend/src/components/Table/Formatter/DateFormatter.tsx
@@ -1,6 +1,6 @@
import cn from "classnames";
-import { differenceInDays, isPast, parseISO } from "date-fns";
-import { DateTimeFormat } from "src/locale";
+import { differenceInDays, isPast } from "date-fns";
+import { formatDateTime, parseDate } from "src/locale";
interface Props {
value: string;
@@ -8,11 +8,12 @@ interface Props {
highlistNearlyExpired?: boolean;
}
export function DateFormatter({ value, highlightPast, highlistNearlyExpired }: Props) {
- const dateIsPast = isPast(parseISO(value));
- const days = differenceInDays(parseISO(value), new Date());
+ const d = parseDate(value);
+ const dateIsPast = d ? isPast(d) : false;
+ const days = d ? differenceInDays(d, new Date()) : 0;
const cl = cn({
"text-danger": highlightPast && dateIsPast,
"text-warning": highlistNearlyExpired && !dateIsPast && days <= 30 && days >= 0,
});
- return {DateTimeFormat(value)};
+ return {formatDateTime(value)};
}
diff --git a/frontend/src/components/Table/Formatter/DomainsFormatter.tsx b/frontend/src/components/Table/Formatter/DomainsFormatter.tsx
index 8560eae2..4cc50475 100644
--- a/frontend/src/components/Table/Formatter/DomainsFormatter.tsx
+++ b/frontend/src/components/Table/Formatter/DomainsFormatter.tsx
@@ -1,6 +1,6 @@
import cn from "classnames";
import type { ReactNode } from "react";
-import { DateTimeFormat, T } from "src/locale";
+import { formatDateTime, T } from "src/locale";
interface Props {
domains: string[];
@@ -53,7 +53,7 @@ export function DomainsFormatter({ domains, createdOn, niceName, provider, color
{...elms}
{createdOn ? (
-
+
) : null}
diff --git a/frontend/src/components/Table/Formatter/EventFormatter.tsx b/frontend/src/components/Table/Formatter/EventFormatter.tsx
index f6f7787d..ff37ecbe 100644
--- a/frontend/src/components/Table/Formatter/EventFormatter.tsx
+++ b/frontend/src/components/Table/Formatter/EventFormatter.tsx
@@ -1,7 +1,7 @@
import { IconArrowsCross, IconBolt, IconBoltOff, IconDisc, IconLock, IconShield, IconUser } from "@tabler/icons-react";
import cn from "classnames";
import type { AuditLog } from "src/api/backend";
-import { DateTimeFormat, T } from "src/locale";
+import { formatDateTime, T } from "src/locale";
const getEventValue = (event: AuditLog) => {
switch (event.objectType) {
@@ -73,7 +73,7 @@ export function EventFormatter({ row }: Props) {
— {getEventValue(row)}
- {DateTimeFormat(row.createdOn)}
+ {formatDateTime(row.createdOn)}
);
}
diff --git a/frontend/src/components/Table/Formatter/ValueWithDateFormatter.tsx b/frontend/src/components/Table/Formatter/ValueWithDateFormatter.tsx
index e4e7fb27..38352b31 100644
--- a/frontend/src/components/Table/Formatter/ValueWithDateFormatter.tsx
+++ b/frontend/src/components/Table/Formatter/ValueWithDateFormatter.tsx
@@ -1,4 +1,4 @@
-import { DateTimeFormat, T } from "src/locale";
+import { formatDateTime, T } from "src/locale";
interface Props {
value: string;
@@ -13,7 +13,7 @@ export function ValueWithDateFormatter({ value, createdOn, disabled }: Props) {
{createdOn ? (
-
+
) : null}
diff --git a/frontend/src/components/Table/Formatter/DateFormatter.test.tsx b/frontend/src/locale/Utils.test.tsx
similarity index 81%
rename from frontend/src/components/Table/Formatter/DateFormatter.test.tsx
rename to frontend/src/locale/Utils.test.tsx
index 33c561c7..fb262501 100644
--- a/frontend/src/components/Table/Formatter/DateFormatter.test.tsx
+++ b/frontend/src/locale/Utils.test.tsx
@@ -1,4 +1,4 @@
-import { DateTimeFormat } from "src/locale";
+import { formatDateTime } from "src/locale";
import { afterAll, beforeAll, describe, expect, it } from "vitest";
describe("DateFormatter", () => {
@@ -17,10 +17,7 @@ describe("DateFormatter", () => {
// Mock Intl.DateTimeFormat so formatting is stable regardless of host
const MockedDateTimeFormat = class extends RealIntl.DateTimeFormat {
- constructor(
- _locales?: string | string[],
- options?: Intl.DateTimeFormatOptions,
- ) {
+ constructor(_locales?: string | string[], options?: Intl.DateTimeFormatOptions) {
super(desiredLocale, {
...options,
timeZone: desiredTimeZone,
@@ -41,37 +38,37 @@ describe("DateFormatter", () => {
it("format date from iso date", () => {
const value = "2024-01-01T00:00:00.000Z";
- const text = DateTimeFormat(value);
+ const text = formatDateTime(value);
expect(text).toBe("Monday, 01/01/2024, 12:00:00 am");
});
it("format date from unix timestamp number", () => {
const value = 1762476112;
- const text = DateTimeFormat(value);
+ const text = formatDateTime(value);
expect(text).toBe("Friday, 07/11/2025, 12:41:52 am");
});
it("format date from unix timestamp string", () => {
const value = "1762476112";
- const text = DateTimeFormat(value);
+ const text = formatDateTime(value);
expect(text).toBe("Friday, 07/11/2025, 12:41:52 am");
});
it("catch bad format from string", () => {
const value = "this is not a good date";
- const text = DateTimeFormat(value);
+ const text = formatDateTime(value);
expect(text).toBe("this is not a good date");
});
it("catch bad format from number", () => {
const value = -100;
- const text = DateTimeFormat(value);
+ const text = formatDateTime(value);
expect(text).toBe("-100");
});
it("catch bad format from number as string", () => {
const value = "-100";
- const text = DateTimeFormat(value);
+ const text = formatDateTime(value);
expect(text).toBe("-100");
});
});
diff --git a/frontend/src/locale/DateTimeFormat.ts b/frontend/src/locale/Utils.ts
similarity index 70%
rename from frontend/src/locale/DateTimeFormat.ts
rename to frontend/src/locale/Utils.ts
index 93c2121d..018efd0b 100644
--- a/frontend/src/locale/DateTimeFormat.ts
+++ b/frontend/src/locale/Utils.ts
@@ -11,13 +11,19 @@ const isUnixTimestamp = (value: unknown): boolean => {
return false;
};
-const DateTimeFormat = (value: string | number): string => {
- if (typeof value !== "number" && typeof value !== "string") return `${value}`;
-
+const parseDate = (value: string | number): Date | null => {
+ if (typeof value !== "number" && typeof value !== "string") return null;
+ try {
+ return isUnixTimestamp(value) ? fromUnixTime(+value) : parseISO(`${value}`);
+ } catch {
+ return null;
+ }
+};
+
+const formatDateTime = (value: string | number): string => {
+ const d = parseDate(value);
+ if (!d) return `${value}`;
try {
- const d = isUnixTimestamp(value)
- ? fromUnixTime(+value)
- : parseISO(`${value}`);
return intlFormat(d, {
weekday: "long",
year: "numeric",
@@ -33,4 +39,4 @@ const DateTimeFormat = (value: string | number): string => {
}
};
-export { DateTimeFormat };
+export { formatDateTime, parseDate, isUnixTimestamp };
diff --git a/frontend/src/locale/index.ts b/frontend/src/locale/index.ts
index 6d9ac03c..bdd1343e 100644
--- a/frontend/src/locale/index.ts
+++ b/frontend/src/locale/index.ts
@@ -1,2 +1,2 @@
-export * from "./DateTimeFormat";
export * from "./IntlProvider";
+export * from "./Utils";