feat: implement customised formatDate() function (#21423)

Closes #21301

This pull-request adds a very simple `function formatDate(date: Date,
options: Intl.DateTimeFormatOptions)` function which will allow us to
standardise the rendering of dates across the application. This is
inline with the content of #21252.
This commit is contained in:
Jake Howell
2026-01-08 10:52:23 +11:00
committed by GitHub
parent 17ba151ed2
commit 1a9a1106ca
10 changed files with 62 additions and 40 deletions
@@ -13,6 +13,7 @@ import {
} from "components/HelpTooltip/HelpTooltip";
import type { FC } from "react";
import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from "recharts";
import { formatDate } from "utils/time";
const chartConfig = {
amount: {
@@ -43,9 +44,13 @@ export const ActiveUserChart: FC<ActiveUserChartProps> = ({ data }) => {
tickMargin={12}
minTickGap={24}
tickFormatter={(value: string) =>
new Date(value).toLocaleDateString(undefined, {
formatDate(new Date(value), {
month: "short",
day: "numeric",
year: undefined,
hour: undefined,
minute: undefined,
second: undefined,
})
}
/>
@@ -1,6 +1,7 @@
import { css, useTheme } from "@emotion/react";
import { TableCell, TableRow } from "components/Table/Table";
import type { FC } from "react";
import { formatDate } from "utils/time";
import { createDisplayDate } from "./utils";
export interface TimelineDateRow {
@@ -27,7 +28,7 @@ export const TimelineDateRow: FC<TimelineDateRow> = ({ date }) => {
color: theme.palette.text.secondary,
textTransform: "capitalize",
}}
title={date.toLocaleDateString()}
title={formatDate(date)}
>
{createDisplayDate(date)}
</TableCell>
+6 -1
View File
@@ -1,5 +1,6 @@
import dayjs from "dayjs";
import calendar from "dayjs/plugin/calendar";
import { formatDate } from "utils/time";
dayjs.extend(calendar);
@@ -16,5 +17,9 @@ export const createDisplayDate = (
sameElse: "MM/DD/YYYY",
});
}
return date.toLocaleDateString();
return formatDate(date, {
hour: undefined,
minute: undefined,
second: undefined,
});
};
@@ -16,23 +16,13 @@ import {
} from "lucide-react";
import { type FC, Fragment, useState } from "react";
import { cn } from "utils/cn";
import { humanDuration } from "utils/time";
import { formatDate, humanDuration } from "utils/time";
import { AIBridgeProviderIcon } from "../AIBridgeProviderIcon";
type RequestLogsRowProps = {
interception: AIBridgeInterception;
};
const customisedDateLocale: Intl.DateTimeFormatOptions = {
second: "2-digit",
minute: "2-digit",
hour: "2-digit",
day: "numeric",
// Show the month as a short name
month: "short",
year: "numeric",
};
type TokenUsageMetadataMerged =
| null
| Record<string, unknown>
@@ -161,10 +151,7 @@ export const RequestLogsRow: FC<RequestLogsRowProps> = ({ interception }) => {
<ChevronRightIcon className="size-icon-xs" />
)}
<span className="sr-only">({isOpen ? "Hide" : "Show more"})</span>
{new Date(interception.started_at).toLocaleString(
undefined,
customisedDateLocale,
)}
{formatDate(new Date(interception.started_at))}
</div>
</TableCell>
<TableCell className="w-48 max-w-48">
@@ -268,20 +255,14 @@ export const RequestLogsRow: FC<RequestLogsRowProps> = ({ interception }) => {
<dt>Start Time:</dt>
<dd data-chromatic="ignore">
{new Date(interception.started_at).toLocaleString(
undefined,
customisedDateLocale,
)}
{formatDate(new Date(interception.started_at))}
</dd>
{interception.ended_at && (
<>
<dt>End Time:</dt>
<dd data-chromatic="ignore">
{new Date(interception.ended_at).toLocaleString(
undefined,
customisedDateLocale,
)}
{formatDate(new Date(interception.ended_at))}
</dd>
</>
)}
@@ -24,6 +24,7 @@ import {
YAxis,
} from "recharts";
import { docs } from "utils/docs";
import { formatDate } from "utils/time";
const chartConfig = {
users: {
@@ -153,9 +154,13 @@ export const LicenseSeatConsumptionChart: FC<
tickMargin={12}
minTickGap={24}
tickFormatter={(value: string) =>
new Date(value).toLocaleDateString(undefined, {
formatDate(new Date(value), {
month: "short",
day: "numeric",
year: undefined,
hour: undefined,
minute: undefined,
second: undefined,
})
}
/>
@@ -16,6 +16,7 @@ import { ChevronRightIcon } from "lucide-react";
import type { FC } from "react";
import { Link as RouterLink } from "react-router";
import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from "recharts";
import { formatDate } from "utils/time";
const chartConfig = {
users: {
@@ -105,9 +106,13 @@ export const UserEngagementChart: FC<UserEngagementChartProps> = ({ data }) => {
tickMargin={12}
minTickGap={24}
tickFormatter={(value: string) =>
new Date(value).toLocaleDateString(undefined, {
formatDate(new Date(value), {
month: "short",
day: "numeric",
year: undefined,
hour: undefined,
minute: undefined,
second: undefined,
})
}
/>
@@ -34,6 +34,7 @@ import {
} from "react";
import { Link } from "react-router";
import { cn } from "utils/cn";
import { formatDate } from "utils/time";
import { displayWorkspaceBuildDuration } from "utils/workspace";
import { Sidebar, SidebarCaption, SidebarItem } from "./Sidebar";
@@ -118,7 +119,7 @@ export const WorkspaceBuildPageView: FC<WorkspaceBuildPageViewProps> = ({
{displayWorkspaceBuildDuration(build)}
</BuildStatsItem>
<BuildStatsItem label="Started at">
{new Date(build.created_at).toLocaleString()}
{formatDate(new Date(build.created_at))}
</BuildStatsItem>
<BuildStatsItem label="Action">
<span className="capitalize">{build.transition}</span>
@@ -17,6 +17,7 @@ import { type FC, useEffect, useState } from "react";
dayjs.extend(relativeTime);
import { useQuery } from "react-query";
import { formatDate } from "utils/time";
import type { WorkspacePermissions } from "../../../modules/workspaces/permissions";
import {
NotificationActionButton,
@@ -128,9 +129,9 @@ export const WorkspaceNotifications: FC<WorkspaceNotificationsProps> = ({
const advancedSchedulingEnabled =
entitlements.features.advanced_template_scheduling.enabled;
if (advancedSchedulingEnabled && workspace.dormant_at) {
const formatDate = (dateStr: string, timestamp: boolean): string => {
const formatDateTime = (dateStr: string, timestamp: boolean): string => {
const date = new Date(dateStr);
return date.toLocaleDateString(undefined, {
return formatDate(date, {
month: "long",
day: "numeric",
year: "numeric",
@@ -150,17 +151,17 @@ export const WorkspaceNotifications: FC<WorkspaceNotificationsProps> = ({
<>
This workspace has not been used for{" "}
{dayjs(workspace.last_used_at).fromNow(true)} and was marked dormant
on {formatDate(workspace.dormant_at, false)}. It is scheduled to be
deleted on {formatDate(workspace.deleting_at, true)}. To keep it you
must activate the workspace.
on {formatDateTime(workspace.dormant_at, false)}. It is scheduled to
be deleted on {formatDateTime(workspace.deleting_at, true)}. To keep
it you must activate the workspace.
</>
) : (
<>
This workspace has not been used for{" "}
{dayjs(workspace.last_used_at).fromNow(true)} and was marked dormant
on {formatDate(workspace.dormant_at, false)}. It is not scheduled for
auto-deletion but will become a candidate if auto-deletion is enabled
on this template. To keep it you must activate the workspace.
on {formatDateTime(workspace.dormant_at, false)}. It is not scheduled
for auto-deletion but will become a candidate if auto-deletion is
enabled on this template. To keep it you must activate the workspace.
</>
),
});
@@ -31,6 +31,7 @@ import type { FC } from "react";
import { useQuery } from "react-query";
import { Link as RouterLink } from "react-router";
import { displayDormantDeletion } from "utils/dormant";
import { formatDate } from "utils/time";
import type { WorkspacePermissions } from "../../modules/workspaces/permissions";
import { WorkspaceActions } from "./WorkspaceActions/WorkspaceActions";
import { WorkspaceNotifications } from "./WorkspaceNotifications/WorkspaceNotifications";
@@ -201,9 +202,7 @@ export const WorkspaceTopbar: FC<WorkspaceProps> = ({
css={{ color: "inherit" }}
>
{workspace.deleting_at ? (
<>
Deletion on {new Date(workspace.deleting_at).toLocaleString()}
</>
<>Deletion on {formatDate(new Date(workspace.deleting_at))}</>
) : (
"Deletion soon"
)}
+19
View File
@@ -48,6 +48,25 @@ export function formatDateTime(
return dayjs(date).format(format);
}
const defaultDateLocaleOptions: Intl.DateTimeFormatOptions = {
second: "2-digit",
minute: "2-digit",
hour: "2-digit",
day: "numeric",
month: "short",
year: "numeric",
};
export function formatDate(
date: Date,
options?: { locale?: Intl.LocalesArgument } & Intl.DateTimeFormatOptions,
) {
return date.toLocaleDateString(options?.locale, {
...defaultDateLocaleOptions,
...options,
});
}
// Duration functions
export function humanDuration(durationInMs: number) {
return humanizeDuration(durationInMs, {