Fixes #4844 with more defensive date parsing
All checks were successful
Close stale issues and PRs / stale (push) Successful in 23s

This commit is contained in:
Jamie Curnow
2025-11-07 21:37:22 +10:00
parent 8eba31913f
commit 3c252db46f
9 changed files with 38 additions and 34 deletions

View File

@@ -1,77 +0,0 @@
import { DateTimeFormat } from "src/locale";
import { afterAll, beforeAll, describe, expect, it } from "vitest";
describe("DateFormatter", () => {
// Keep a reference to the real Intl to restore later
const RealIntl = global.Intl;
const desiredTimeZone = "Europe/London";
const desiredLocale = "en-GB";
beforeAll(() => {
// Ensure Node-based libs using TZ behave deterministically
try {
process.env.TZ = desiredTimeZone;
} catch {
// ignore if not available
}
// Mock Intl.DateTimeFormat so formatting is stable regardless of host
const MockedDateTimeFormat = class extends RealIntl.DateTimeFormat {
constructor(
_locales?: string | string[],
options?: Intl.DateTimeFormatOptions,
) {
super(desiredLocale, {
...options,
timeZone: desiredTimeZone,
});
}
} as unknown as typeof Intl.DateTimeFormat;
global.Intl = {
...RealIntl,
DateTimeFormat: MockedDateTimeFormat,
};
});
afterAll(() => {
// Restore original Intl after tests
global.Intl = RealIntl;
});
it("format date from iso date", () => {
const value = "2024-01-01T00:00:00.000Z";
const text = DateTimeFormat(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);
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);
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);
expect(text).toBe("this is not a good date");
});
it("catch bad format from number", () => {
const value = -100;
const text = DateTimeFormat(value);
expect(text).toBe("-100");
});
it("catch bad format from number as string", () => {
const value = "-100";
const text = DateTimeFormat(value);
expect(text).toBe("-100");
});
});

View File

@@ -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 <span className={cl}>{DateTimeFormat(value)}</span>;
return <span className={cl}>{formatDateTime(value)}</span>;
}

View File

@@ -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
<div className="font-weight-medium">{...elms}</div>
{createdOn ? (
<div className="text-secondary mt-1">
<T id="created-on" data={{ date: DateTimeFormat(createdOn) }} />
<T id="created-on" data={{ date: formatDateTime(createdOn) }} />
</div>
) : null}
</div>

View File

@@ -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) {
<T id={`object.event.${row.action}`} tData={{ object: row.objectType }} />
&nbsp; &mdash; <span className="badge">{getEventValue(row)}</span>
</div>
<div className="text-secondary mt-1">{DateTimeFormat(row.createdOn)}</div>
<div className="text-secondary mt-1">{formatDateTime(row.createdOn)}</div>
</div>
);
}

View File

@@ -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) {
</div>
{createdOn ? (
<div className={`text-secondary mt-1 ${disabled ? "text-red" : ""}`}>
<T id={disabled ? "disabled" : "created-on"} data={{ date: DateTimeFormat(createdOn) }} />
<T id={disabled ? "disabled" : "created-on"} data={{ date: formatDateTime(createdOn) }} />
</div>
) : null}
</div>