From c7527f7b1c252be504ee432c4ff96f7d899bc27f Mon Sep 17 00:00:00 2001 From: Peter Makowski Date: Thu, 4 Apr 2024 12:17:32 +0200 Subject: [PATCH] add UTC timestamps to discovery list --- .../Notification/Notification.tsx | 4 +- .../base/components/StatusBar/StatusBar.tsx | 9 +-- .../node/NodeTestsTable/NodeTestsTable.tsx | 4 +- .../views/DiscoveriesList/DiscoveriesList.tsx | 7 ++- .../settings/views/Dhcp/DhcpList/DhcpList.tsx | 4 +- .../Scripts/ScriptsList/ScriptsList.test.tsx | 4 +- .../views/Scripts/ScriptsList/ScriptsList.tsx | 5 +- .../views/Users/UsersList/UsersList.tsx | 4 +- src/app/store/bootresource/types/base.ts | 6 +- src/app/store/controller/types/base.ts | 4 +- src/app/store/discovery/types/base.ts | 4 +- src/app/store/machine/types/base.ts | 8 +-- src/app/store/scriptresult/types/base.ts | 6 +- src/app/store/types/model.ts | 7 ++- src/app/store/user/types/base.ts | 4 +- .../tags/views/TagList/TagTable/TagTable.tsx | 4 +- .../TagUpdateFormFields.tsx | 4 +- src/app/utils/time.test.ts | 55 ++++++++++++------- src/app/utils/time.ts | 50 ++++++++--------- src/testing/factories/discovery.ts | 3 +- src/testing/factories/general.ts | 4 +- 21 files changed, 112 insertions(+), 88 deletions(-) diff --git a/src/app/base/components/NotificationGroup/Notification/Notification.tsx b/src/app/base/components/NotificationGroup/Notification/Notification.tsx index 46bcb5fcb92..5105aeee264 100644 --- a/src/app/base/components/NotificationGroup/Notification/Notification.tsx +++ b/src/app/base/components/NotificationGroup/Notification/Notification.tsx @@ -13,7 +13,7 @@ import { isUpgradeNotification, } from "@/app/store/notification/utils"; import type { RootState } from "@/app/store/root/types"; -import { getUtcTimestamp } from "@/app/utils/time"; +import { formatUtcDatetime } from "@/app/utils/time"; type Props = { className?: string | null; @@ -32,7 +32,7 @@ const NotificationGroupNotification = ({ const notification = useSelector((state: RootState) => notificationSelectors.getById(state, id) ); - const createdTimestamp = getUtcTimestamp(notification?.created); + const createdTimestamp = formatUtcDatetime(notification?.created); if (!notification) { return null; } diff --git a/src/app/base/components/StatusBar/StatusBar.tsx b/src/app/base/components/StatusBar/StatusBar.tsx index 3155c4af88f..26eaeb80b31 100644 --- a/src/app/base/components/StatusBar/StatusBar.tsx +++ b/src/app/base/components/StatusBar/StatusBar.tsx @@ -18,9 +18,9 @@ import { isDeployedWithHardwareSync, isMachineDetails, } from "@/app/store/machine/utils"; -import type { UtcTimestamp } from "@/app/store/types/model"; +import type { UtcDatetime } from "@/app/store/types/model"; import { NodeStatus } from "@/app/store/types/node"; -import { getUtcTimestamp, getTimeDistanceString } from "@/app/utils/time"; +import { formatUtcDatetime, getTimeDistanceString } from "@/app/utils/time"; const getLastCommissionedString = (machine: MachineDetails) => { if (machine.status === NodeStatus.COMMISSIONING) { @@ -38,7 +38,7 @@ const getLastCommissionedString = (machine: MachineDetails) => { } }; -const getSyncStatusString = (syncStatus: UtcTimestamp) => { +const getSyncStatusString = (syncStatus: UtcDatetime) => { if (syncStatus === "") { return "Never"; } @@ -88,7 +88,8 @@ export const StatusBar = (): JSX.Element | null => { isControllerDetails(activeController) && (isRack(activeController) || isRegionAndRack(activeController)) ) { - status = `Last image sync: ${getUtcTimestamp( + console.log(activeController.last_image_sync); + status = `Last image sync: ${formatUtcDatetime( activeController.last_image_sync )}`; } diff --git a/src/app/base/components/node/NodeTestsTable/NodeTestsTable.tsx b/src/app/base/components/node/NodeTestsTable/NodeTestsTable.tsx index 77b283f1035..446afffb471 100644 --- a/src/app/base/components/node/NodeTestsTable/NodeTestsTable.tsx +++ b/src/app/base/components/node/NodeTestsTable/NodeTestsTable.tsx @@ -19,7 +19,7 @@ import type { ScriptResult } from "@/app/store/scriptresult/types"; import { ScriptResultType } from "@/app/store/scriptresult/types"; import { canBeSuppressed } from "@/app/store/scriptresult/utils"; import { nodeIsMachine } from "@/app/store/utils"; -import { getUtcTimestamp } from "@/app/utils/time"; +import { formatUtcDatetime } from "@/app/utils/time"; export enum ScriptResultAction { VIEW_METRICS = "viewMetrics", @@ -132,7 +132,7 @@ const NodeTestsTable = ({ node, scriptResults }: Props): JSX.Element => { }, { className: "date-col", - content: getUtcTimestamp(result.updated), + content: formatUtcDatetime(result.updated), }, { className: "runtime-col", diff --git a/src/app/networkDiscovery/views/DiscoveriesList/DiscoveriesList.tsx b/src/app/networkDiscovery/views/DiscoveriesList/DiscoveriesList.tsx index 3da80adfd54..8f667773ef7 100644 --- a/src/app/networkDiscovery/views/DiscoveriesList/DiscoveriesList.tsx +++ b/src/app/networkDiscovery/views/DiscoveriesList/DiscoveriesList.tsx @@ -25,6 +25,7 @@ import type { Discovery } from "@/app/store/discovery/types"; import { DiscoveryMeta } from "@/app/store/discovery/types"; import type { RootState } from "@/app/store/root/types"; import { generateEmptyStateMsg, getTableStatus } from "@/app/utils"; +import { formatUtcDatetime } from "@/app/utils/time"; export enum Labels { DiscoveriesList = "Discoveries list", @@ -78,7 +79,11 @@ const generateRows = ( content: discovery.observer_hostname, }, { - content:
{discovery.last_seen}
, + content: ( +
+ {formatUtcDatetime(discovery.last_seen)} +
+ ), }, { content: ( diff --git a/src/app/settings/views/Dhcp/DhcpList/DhcpList.tsx b/src/app/settings/views/Dhcp/DhcpList/DhcpList.tsx index 69c379a7e75..5de926aa5ec 100644 --- a/src/app/settings/views/Dhcp/DhcpList/DhcpList.tsx +++ b/src/app/settings/views/Dhcp/DhcpList/DhcpList.tsx @@ -33,7 +33,7 @@ import type { RootState } from "@/app/store/root/types"; import { subnetActions } from "@/app/store/subnet"; import subnetSelectors from "@/app/store/subnet/selectors"; import type { Subnet } from "@/app/store/subnet/types"; -import { formatUtcTimestamp } from "@/app/utils/time"; +import { formatUtcDatetime } from "@/app/utils/time"; const getTargetName = ( controllers: Controller[], @@ -80,7 +80,7 @@ const generateRows = ( const expanded = expandedId === dhcpsnippet.id; // Dates are in the format: Thu, 15 Aug. 2019 06:21:39. const updated = dhcpsnippet.updated - ? formatUtcTimestamp(dhcpsnippet.updated) + ? formatUtcDatetime(dhcpsnippet.updated) : "Never"; const enabled = dhcpsnippet.enabled ? "Yes" : "No"; const showDelete = expandedType === "delete"; diff --git a/src/app/settings/views/Scripts/ScriptsList/ScriptsList.test.tsx b/src/app/settings/views/Scripts/ScriptsList/ScriptsList.test.tsx index 8bb51eb836b..daabc35bf73 100644 --- a/src/app/settings/views/Scripts/ScriptsList/ScriptsList.test.tsx +++ b/src/app/settings/views/Scripts/ScriptsList/ScriptsList.test.tsx @@ -315,7 +315,7 @@ describe("ScriptsList", () => { ); expect( within(screen.getByRole("row", { name: "test name 33" })).getByText( - "2020-12-31 22:59" + "Thu, 31 Dec. 2020 22:59:00" ) ).toBeInTheDocument(); }); @@ -326,7 +326,7 @@ describe("ScriptsList", () => { loaded: true, items: [ factory.script({ - created: factory.timestamp(""), + created: () => factory.timestamp(""), script_type: ScriptType.TESTING, }), ], diff --git a/src/app/settings/views/Scripts/ScriptsList/ScriptsList.tsx b/src/app/settings/views/Scripts/ScriptsList/ScriptsList.tsx index 3ac9498df3d..05c3fee0bae 100644 --- a/src/app/settings/views/Scripts/ScriptsList/ScriptsList.tsx +++ b/src/app/settings/views/Scripts/ScriptsList/ScriptsList.tsx @@ -1,7 +1,6 @@ import { useEffect, useState } from "react"; import { ContentSection } from "@canonical/maas-react-components"; -import { format } from "date-fns"; import { useDispatch, useSelector } from "react-redux"; import type { Dispatch } from "redux"; @@ -17,7 +16,7 @@ import { scriptActions } from "@/app/store/script"; import scriptSelectors from "@/app/store/script/selectors"; import type { Script } from "@/app/store/script/types"; import { generateEmptyStateMsg, getTableStatus } from "@/app/utils"; -import { parseUtcDatetime } from "@/app/utils/time"; +import { formatUtcDatetime } from "@/app/utils/time"; export enum Labels { Actions = "Table actions", @@ -49,7 +48,7 @@ const generateRows = ( // history timestamps are in the format: Mon, 02 Sep 2019 02:02:39 -0000 let uploadedOn: string; try { - uploadedOn = format(parseUtcDatetime(script.created), "yyyy-LL-dd H:mm"); + uploadedOn = formatUtcDatetime(script.created); } catch (error) { uploadedOn = "Never"; } diff --git a/src/app/settings/views/Users/UsersList/UsersList.tsx b/src/app/settings/views/Users/UsersList/UsersList.tsx index 66d235a145f..3247dfdfbd1 100644 --- a/src/app/settings/views/Users/UsersList/UsersList.tsx +++ b/src/app/settings/views/Users/UsersList/UsersList.tsx @@ -22,7 +22,7 @@ import { userActions } from "@/app/store/user"; import userSelectors from "@/app/store/user/selectors"; import type { User } from "@/app/store/user/types"; import { isComparable } from "@/app/utils"; -import { formatUtcTimestamp } from "@/app/utils/time"; +import { formatUtcDatetime } from "@/app/utils/time"; type SortKey = keyof User; @@ -35,7 +35,7 @@ const generateUserRows = ( const isAuthUser = user.id === authUser?.id; // Dates are in the format: Thu, 15 Aug. 2019 06:21:39. const last_login = user.last_login - ? formatUtcTimestamp(user.last_login) + ? formatUtcDatetime(user.last_login) : "Never"; const fullName = user.last_name; return { diff --git a/src/app/store/bootresource/types/base.ts b/src/app/store/bootresource/types/base.ts index 08402eeb724..6a73e9b0e71 100644 --- a/src/app/store/bootresource/types/base.ts +++ b/src/app/store/bootresource/types/base.ts @@ -1,6 +1,6 @@ import type { BootResourceSourceType, BootResourceType } from "./enum"; -import type { Model, UtcTimestamp } from "@/app/store/types/model"; +import type { Model, UtcDatetime } from "@/app/store/types/model"; export type BaseImageFields = { checked: boolean; @@ -15,8 +15,8 @@ export type BootResource = Model & { complete: boolean; downloading: boolean; icon: "in-progress" | "queued" | "succeeded" | "waiting"; - lastUpdate: UtcTimestamp; - lastDeployed: UtcTimestamp; + lastUpdate: UtcDatetime; + lastDeployed: UtcDatetime; machineCount: number; name: string; numberOfNodes: number; diff --git a/src/app/store/controller/types/base.ts b/src/app/store/controller/types/base.ts index e9768088684..0a65a5a6901 100644 --- a/src/app/store/controller/types/base.ts +++ b/src/app/store/controller/types/base.ts @@ -11,7 +11,7 @@ import type { PowerState, StorageLayout } from "@/app/store/types/enum"; import type { ModelRef, TimestampFields, - UtcTimestamp, + UtcDatetime, } from "@/app/store/types/model"; import type { NodeActions, @@ -69,7 +69,7 @@ export type ControllerActions = export type BaseController = BaseNode & { actions: ControllerActions[]; - last_image_sync: UtcTimestamp; + last_image_sync: UtcDatetime; link_type: NodeLinkType.CONTROLLER; node_type_display: | NodeTypeDisplay.RACK_CONTROLLER diff --git a/src/app/store/discovery/types/base.ts b/src/app/store/discovery/types/base.ts index b4ed8ba6450..a600fb43f20 100644 --- a/src/app/store/discovery/types/base.ts +++ b/src/app/store/discovery/types/base.ts @@ -1,5 +1,5 @@ import type { APIError } from "@/app/base/types"; -import type { Model } from "@/app/store/types/model"; +import type { Model, UtcDatetime } from "@/app/store/types/model"; import type { GenericState } from "@/app/store/types/state"; export type Discovery = Model & { @@ -10,7 +10,7 @@ export type Discovery = Model & { hostname: string | null; ip: string | null; is_external_dhcp: boolean | null; - last_seen: string; + last_seen: UtcDatetime; mac_address: string | null; mac_organization: string; mdns: number | null; diff --git a/src/app/store/machine/types/base.ts b/src/app/store/machine/types/base.ts index e265e42406a..b53e6b4401b 100644 --- a/src/app/store/machine/types/base.ts +++ b/src/app/store/machine/types/base.ts @@ -8,7 +8,7 @@ import type { PowerState, StorageLayout } from "@/app/store/types/enum"; import type { ModelRef, TimestampFields, - UtcTimestamp, + UtcDatetime, } from "@/app/store/types/model"; import type { BaseNode, @@ -73,7 +73,7 @@ export type MachineDetails = BaseMachine & bmc: number; boot_disk: Disk | null; certificate?: CertificateMetadata; - commissioning_start_time: UtcTimestamp; + commissioning_start_time: UtcDatetime; commissioning_status: TestStatus; cpu_speed: BaseNode["cpu_speed"]; cpu_test_status: TestStatus; @@ -126,8 +126,8 @@ type HardwareSyncFields = } | { enable_hw_sync: true; - last_sync: UtcTimestamp; - next_sync: UtcTimestamp; + last_sync: UtcDatetime; + next_sync: UtcDatetime; is_sync_healthy: boolean; sync_interval: Seconds; }; diff --git a/src/app/store/scriptresult/types/base.ts b/src/app/store/scriptresult/types/base.ts index b81a85567b4..54afbdcb486 100644 --- a/src/app/store/scriptresult/types/base.ts +++ b/src/app/store/scriptresult/types/base.ts @@ -9,7 +9,7 @@ import type { import type { HardwareType } from "@/app/base/enum"; import type { APIError } from "@/app/base/types"; -import type { Model, UtcTimestamp } from "@/app/store/types/model"; +import type { Model, UtcDatetime } from "@/app/store/types/model"; import type { NetworkInterface } from "@/app/store/types/node"; import type { GenericState } from "@/app/store/types/state"; @@ -29,7 +29,7 @@ export type PartialScriptResult = Model & { status: ScriptResultStatus; status_name: string; suppressed: boolean; - updated?: UtcTimestamp; + updated?: UtcDatetime; }; export type ScriptResult = PartialScriptResult & { @@ -76,7 +76,7 @@ export type ScriptResult = PartialScriptResult & { results: ScriptResultResult[]; script?: number; script_version?: number | null; - started?: UtcTimestamp; + started?: UtcDatetime; tags: string; }; diff --git a/src/app/store/types/model.ts b/src/app/store/types/model.ts index 8d48cdd8fe7..e3630552a32 100644 --- a/src/app/store/types/model.ts +++ b/src/app/store/types/model.ts @@ -3,10 +3,11 @@ export type Model = { }; // Expected format: "Thu, 15 Aug. 2019 06:21:39" or "" -export type UtcTimestamp = string & { readonly __brand: unique symbol }; +export type UtcDatetime = string & { readonly __brand: unique symbol }; +export type UtcDatetimeDisplay = `${string} (UTC)` | "Never"; export type TimestampFields = { - created: UtcTimestamp; - updated: UtcTimestamp; + created: UtcDatetime; + updated: UtcDatetime; }; export type TimestampedModel = Model & TimestampFields; diff --git a/src/app/store/user/types/base.ts b/src/app/store/user/types/base.ts index 8891dbfef2d..5b9f9f9e312 100644 --- a/src/app/store/user/types/base.ts +++ b/src/app/store/user/types/base.ts @@ -1,5 +1,5 @@ import type { APIError } from "@/app/base/types"; -import type { Model, UtcTimestamp } from "@/app/store/types/model"; +import type { Model, UtcDatetime } from "@/app/store/types/model"; import type { GenericState } from "@/app/store/types/state"; export type User = Model & { @@ -9,7 +9,7 @@ export type User = Model & { is_local: boolean; is_superuser: boolean; last_name: string; - last_login: UtcTimestamp; + last_login: UtcDatetime; machines_count: number; sshkeys_count: number; username: string; diff --git a/src/app/tags/views/TagList/TagTable/TagTable.tsx b/src/app/tags/views/TagList/TagTable/TagTable.tsx index 4ff736f2faf..539eab37758 100644 --- a/src/app/tags/views/TagList/TagTable/TagTable.tsx +++ b/src/app/tags/views/TagList/TagTable/TagTable.tsx @@ -24,7 +24,7 @@ import type { Tag } from "@/app/store/tag/types"; import { TagMeta } from "@/app/store/tag/types"; import AppliedTo from "@/app/tags/components/AppliedTo"; import { isComparable } from "@/app/utils"; -import { getUtcTimestamp } from "@/app/utils/time"; +import { formatUtcDatetime } from "@/app/utils/time"; type Props = PropsWithSpread< { @@ -76,7 +76,7 @@ const generateRows = ( }, { "aria-label": Label.Updated, - content: getUtcTimestamp(tag.updated), + content: formatUtcDatetime(tag.updated), }, { "aria-label": Label.Auto, diff --git a/src/app/tags/views/TagUpdate/TagUpdateFormFields/TagUpdateFormFields.tsx b/src/app/tags/views/TagUpdate/TagUpdateFormFields/TagUpdateFormFields.tsx index 17e06ab8359..ea8fac66865 100644 --- a/src/app/tags/views/TagUpdate/TagUpdateFormFields/TagUpdateFormFields.tsx +++ b/src/app/tags/views/TagUpdate/TagUpdateFormFields/TagUpdateFormFields.tsx @@ -12,7 +12,7 @@ import AppliedTo from "@/app/tags/components/AppliedTo"; import DefinitionField from "@/app/tags/components/DefinitionField"; import KernelOptionsField from "@/app/tags/components/KernelOptionsField"; import { Label } from "@/app/tags/views/TagDetails"; -import { getUtcTimestamp } from "@/app/utils/time"; +import { getUtcDatetime } from "@/app/utils/time"; type Props = { id: Tag[TagMeta.PK]; @@ -41,7 +41,7 @@ export const TagUpdateFormFields = ({ id }: Props): JSX.Element | null => { diff --git a/src/app/utils/time.test.ts b/src/app/utils/time.test.ts index 62944bbd326..510c5d4bae5 100644 --- a/src/app/utils/time.test.ts +++ b/src/app/utils/time.test.ts @@ -2,59 +2,74 @@ import MockDate from "mockdate"; import timezoneMock from "timezone-mock"; import { - formatUtcTimestamp, + formatUtcDatetime, getTimeDistanceString, - getUtcTimestamp, + parseUtcDatetime, } from "./time"; -import type { UtcTimestamp } from "@/app/store/types/model"; -import * as factory from "@/testing/factories"; +import type { UtcDatetime } from "@/app/store/types/model"; beforeEach(() => { MockDate.set("Fri, 18 Nov. 2022 01:01:00"); + timezoneMock.register("Etc/GMT+5"); }); afterEach(() => { MockDate.reset(); + timezoneMock.unregister(); }); describe("getTimeDistanceString", () => { it("returns time distance for UTC TimeString in the past", () => { expect( - getTimeDistanceString("Fri, 18 Nov. 2022 01:00:50" as UtcTimestamp) + getTimeDistanceString("Fri, 18 Nov. 2022 01:00:50" as UtcDatetime) ).toEqual("less than a minute ago"); }); it("returns time distance for UTC TimeString in the future", () => { expect( - getTimeDistanceString("Fri, 18 Nov. 2022 01:01:10" as UtcTimestamp) + getTimeDistanceString("Fri, 18 Nov. 2022 01:01:10" as UtcDatetime) ).toEqual("in less than a minute"); }); }); -describe("formatUtcTimestamp", () => { +describe("formatUtcDatetime", () => { it("returns UTC date time in a correct format", () => { + timezoneMock.register("Etc/GMT+0"); expect( - formatUtcTimestamp("Fri, 18 Nov. 2022 01:00:50" as UtcTimestamp) - ).toEqual("Fri, 18 Nov. 2022 01:00:50"); + formatUtcDatetime("Fri, 18 Nov. 2022 01:00:50" as UtcDatetime) + ).toEqual("Fri, 18 Nov. 2022 01:00:50 (UTC)"); }); - it("returns UTC date time in local time", () => { + it("returns UTC date time in UTC regardless of timezone", () => { timezoneMock.register("Etc/GMT-1"); expect( - formatUtcTimestamp("Fri, 18 Nov. 2022 03:00:00" as UtcTimestamp) - ).toEqual("Fri, 18 Nov. 2022 04:00:00"); + formatUtcDatetime("Fri, 18 Nov. 2022 03:00:00" as UtcDatetime) + ).toEqual("Fri, 18 Nov. 2022 03:00:00 (UTC)"); + }); + it("returns Never if no time is provided", () => { + const inputTimeString = "" as UtcDatetime; + const expectedOutput = "Never"; + expect(formatUtcDatetime(inputTimeString)).toEqual(expectedOutput); }); -}); -describe("getUtcTimestamp", () => { it("appends (UTC) to the given time string", () => { - const inputTimeString = "Fri, 18 Nov. 2022 01:00:50" as UtcTimestamp; + const inputTimeString = "Fri, 18 Nov. 2022 01:00:50" as UtcDatetime; const expectedOutput = "Fri, 18 Nov. 2022 01:00:50 (UTC)"; - expect(getUtcTimestamp(inputTimeString)).toEqual(expectedOutput); + expect(formatUtcDatetime(inputTimeString)).toEqual(expectedOutput); + }); +}); + +describe("parseUtcDatetime", () => { + it("parses UTC time string into Date object correctly", () => { + const utcTimeString = "Fri, 18 Nov. 2022 01:00:50" as UtcDatetime; + const expectedDate = new Date(Date.UTC(2022, 10, 18, 1, 0, 50)); // Fri, 18 Nov. 2022 01:00:50 + const result = parseUtcDatetime(utcTimeString); + expect(result).toEqual(expectedDate); }); - it("works with different date formats", () => { - const inputTimeString = factory.timestamp("2022-11-18T01:00:50Z"); - const expectedOutput = "2022-11-18T01:00:50Z (UTC)"; - expect(getUtcTimestamp(inputTimeString)).toEqual(expectedOutput); + it("handles leap years correctly", () => { + const utcTimeString = "Mon, 29 Feb. 2016 12:00:00" as UtcDatetime; + const expectedDate = new Date(Date.UTC(2016, 1, 29, 12, 0, 0)); // Mon, 29 Feb. 2016 12:00:00 + const result = parseUtcDatetime(utcTimeString); + expect(result).toEqual(expectedDate); }); }); diff --git a/src/app/utils/time.ts b/src/app/utils/time.ts index e342ecf8223..4706341a9d1 100644 --- a/src/app/utils/time.ts +++ b/src/app/utils/time.ts @@ -1,40 +1,40 @@ -import { format, formatDistance, parse } from "date-fns"; +import { formatDistance, parse } from "date-fns"; +import { formatInTimeZone } from "date-fns-tz"; -import type { UtcTimestamp } from "@/app/store/types/model"; +import type { UtcDatetime, UtcDatetimeDisplay } from "@/app/store/types/model"; -const DATETIME_FORMAT = "E, dd LLL. yyyy HH:mm:ss"; -const UTC_DATETIME_FORMAT = `${DATETIME_FORMAT} x`; +const DATETIME_DISPLAY_FORMAT = "E, dd LLL. yyyy HH:mm:ss"; -export const parseUtcDatetime = (utcTimeString: UtcTimestamp): Date => +export const parseUtcDatetime = (utcTimeString: UtcDatetime): Date => parse( `${utcTimeString} +00`, // let parse fn know it's UTC - UTC_DATETIME_FORMAT, + `${DATETIME_DISPLAY_FORMAT} x`, new Date() ); -export const getTimeDistanceString = (utcTimeString: UtcTimestamp): string => +export const getTimeDistanceString = (utcTimeString: UtcDatetime) => formatDistance(parseUtcDatetime(utcTimeString), new Date(), { addSuffix: true, }); /** - * Appends "(UTC)" to a given string to indicate the time zone explicitly. - * @param utcTimeString - time string in UTC - * @returns Time string with appended "(UTC)" + * @param utcTimeString - time string in UTC_DATETIME_FORMAT + * @returns time string adjusted for local time zone in DATETIME_FORMAT */ -export const getUtcTimestamp = (utcTimeString?: UtcTimestamp): string => - utcTimeString ? utcTimeString + " (UTC)" : ""; +export const formatUtcDatetime = ( + utcTimeString?: UtcDatetime +): UtcDatetimeDisplay => { + if (!utcTimeString) return "Never"; -/** - * Formats a given UTC time string into a more readable format and appends "(UTC)" to indicate the time zone explicitly. - * It converts the time string from "E, dd LLL. yyyy HH:mm:ss" format to "yyyy-LL-dd H:mm" format. - * @param utcTimeString - time string in UTC to be formatted - * @returns Formatted time string with appended "(UTC)" - */ -export const formatUtcTimestamp = (utcTimeString?: UtcTimestamp): string => - utcTimeString - ? format( - parse(utcTimeString, "E, dd LLL. yyyy HH:mm:ss", new Date()), - "yyyy-LL-dd H:mm" - ) + " (UTC)" - : ""; + try { + const utcTime = `${formatInTimeZone( + parseUtcDatetime(utcTimeString), + "UTC", + DATETIME_DISPLAY_FORMAT, + { addSuffix: true } + )} (UTC)` as const; + return utcTime; + } catch (error) { + return "Never"; + } +}; diff --git a/src/testing/factories/discovery.ts b/src/testing/factories/discovery.ts index 82dba7bd46f..6023943c724 100644 --- a/src/testing/factories/discovery.ts +++ b/src/testing/factories/discovery.ts @@ -4,6 +4,7 @@ import { model } from "./model"; import type { Discovery } from "@/app/store/discovery/types"; import type { Model } from "@/app/store/types/model"; +import { timestamp } from "./general"; export const discovery = extend(model, { discovery_id: () => `discovery-${random()}`, @@ -13,7 +14,7 @@ export const discovery = extend(model, { hostname: "discovery-hostname", ip: "192.168.1.1", is_external_dhcp: false, - last_seen: "Wed, 08 Jul. 2020 05:35:4", + last_seen: () => timestamp("Wed, 08 Jul. 2020 05:35:4"), mac_address: "00:00:00:00:00:00", mac_organization: "Business Corp, Inc.", mdns: 2, diff --git a/src/testing/factories/general.ts b/src/testing/factories/general.ts index 5c1acaf7c92..cffaf2298d8 100644 --- a/src/testing/factories/general.ts +++ b/src/testing/factories/general.ts @@ -129,4 +129,6 @@ export const tlsCertificate = define({ export const version = define("test version"); export const timestamp = (timestamp: string) => - (timestamp as UtcTimestamp) || ("Wed, 08 Jul. 2020 05:35:4" as UtcTimestamp); + timestamp !== undefined + ? (timestamp as UtcTimestamp) + : ("Wed, 08 Jul. 2020 05:35:4" as UtcTimestamp);