← Back to team overview

sts-sponsors team mailing list archive

[Merge] ~petermakowski/maas-site-manager:update-timezone-MAASENG-1592 into maas-site-manager:main

 

Peter Makowski has proposed merging ~petermakowski/maas-site-manager:update-timezone-MAASENG-1592 into maas-site-manager:main.

Commit message:
update timezone MAASENG-1592
- replace timezone-mock with full date string in tests

Requested reviews:
  MAAS Committers (maas-committers)

For more details, see:
https://code.launchpad.net/~petermakowski/maas-site-manager/+git/site-manager/+merge/441671

QA Steps
go to /sites
verify that each site has a time in a correct format (time followed by short UTC string)
-- 
Your team MAAS Committers is requested to review the proposed merge of ~petermakowski/maas-site-manager:update-timezone-MAASENG-1592 into maas-site-manager:main.
diff --git a/frontend/src/api/types.ts b/frontend/src/api/types.ts
index d8d5ff5..edf349e 100644
--- a/frontend/src/api/types.ts
+++ b/frontend/src/api/types.ts
@@ -15,7 +15,7 @@ export type Site = {
     zip: string;
     street: string;
   };
-  timezone: number;
+  timezone: string; // IANA time zone name,
   stats: {
     machines: number;
     occupied_machines: number;
diff --git a/frontend/src/components/SitesList/SitesTable/SitesTable.test.tsx b/frontend/src/components/SitesList/SitesTable/SitesTable.test.tsx
index f8e3468..1b99229 100644
--- a/frontend/src/components/SitesList/SitesTable/SitesTable.test.tsx
+++ b/frontend/src/components/SitesList/SitesTable/SitesTable.test.tsx
@@ -1,5 +1,3 @@
-import * as timezoneMock from "timezone-mock";
-
 import SitesTable from "./SitesTable";
 
 import urls from "@/api/urls";
@@ -16,12 +14,10 @@ const mockServer = createMockGetServer(
 
 beforeEach(() => {
   vi.useFakeTimers();
-  timezoneMock.register("Etc/GMT");
   mockServer.listen();
 });
 
 afterEach(() => {
-  timezoneMock.unregister();
   vi.useRealTimers();
   mockServer.resetHandlers();
 });
@@ -80,10 +76,10 @@ it("displays correctly paginated results", () => {
 });
 
 it("displays correct local time", () => {
-  const date = new Date("2000-01-01T12:00:00Z");
+  const date = new Date("Fri Apr 21 2023 12:00:00 GMT+0000 (GMT)");
   vi.setSystemTime(date);
 
-  const item = siteFactory.build({ timezone: 1 });
+  const item = siteFactory.build({ timezone: "Europe/London" });
   renderWithMemoryRouter(
     <SitesTable
       data={sitesQueryResultFactory.build({ items: [item], total: 1, page: 1, size: 1 })}
@@ -94,7 +90,7 @@ it("displays correct local time", () => {
   );
 
   expect(screen.getByRole("table", { name: /sites/i })).toBeInTheDocument();
-  expect(screen.getByText(/13:00 UTC\+01:00/i)).toBeInTheDocument();
+  expect(screen.getByText(/13:00 UTC\+1/i)).toBeInTheDocument();
 });
 
 it("displays full name of the country", () => {
diff --git a/frontend/src/components/SitesList/SitesTable/SitesTable.tsx b/frontend/src/components/SitesList/SitesTable/SitesTable.tsx
index e4a8362..38d39fd 100644
--- a/frontend/src/components/SitesList/SitesTable/SitesTable.tsx
+++ b/frontend/src/components/SitesList/SitesTable/SitesTable.tsx
@@ -15,7 +15,7 @@ import SelectAllCheckbox from "@/components/SelectAllCheckbox";
 import { isDev } from "@/constants";
 import { useAppContext } from "@/context";
 import type { UseSitesQueryResult } from "@/hooks/api";
-import { getCountryName, getTimeByUTCOffset, getTimezoneUTCString } from "@/utils";
+import { getCountryName, getTimezoneUTCString, getTimeInTimezone } from "@/utils";
 
 const createAccessor =
   <T, K extends keyof T>(keys: K[] | K) =>
@@ -131,12 +131,10 @@ const SitesTable = ({
         ),
         cell: ({ getValue }) => {
           const { timezone } = getValue();
-          return Number.isInteger(timezone) ? (
-            <>
-              <div>
-                {getTimeByUTCOffset(new Date(), timezone!)} UTC{getTimezoneUTCString(timezone!)}
-              </div>
-            </>
+          return timezone ? (
+            <div>
+              {getTimeInTimezone(new Date(), timezone)} UTC{getTimezoneUTCString(timezone)}
+            </div>
           ) : null;
         },
       },
diff --git a/frontend/src/mocks/factories.ts b/frontend/src/mocks/factories.ts
index 9b6fb7d..3cc0f05 100644
--- a/frontend/src/mocks/factories.ts
+++ b/frontend/src/mocks/factories.ts
@@ -30,7 +30,7 @@ export const siteFactory = Factory.define<Site>(({ sequence }) => {
       zip: chance.zip(),
       street: chance.address(),
     },
-    timezone: chance.integer({ min: -12, max: 14 }),
+    timezone: chance.timezone().utc[0],
     stats: {
       machines: chance.integer({ min: 0, max: 1500 }),
       occupied_machines: chance.integer({ min: 0, max: 500 }),
diff --git a/frontend/src/utils.test.ts b/frontend/src/utils.test.ts
index d453af6..83422b5 100644
--- a/frontend/src/utils.test.ts
+++ b/frontend/src/utils.test.ts
@@ -1,4 +1,4 @@
-import { customParamSerializer, getTimeByUTCOffset, getTimezoneUTCString, parseSearchTextToQueryParams } from "./utils";
+import { customParamSerializer, getTimezoneUTCString, parseSearchTextToQueryParams, getTimeInTimezone } from "./utils";
 
 describe("parseSearchTextToQueryParams tests", () => {
   it('should modify search params from "label:value" to "label=value"', () => {
@@ -34,37 +34,29 @@ describe("customParamSerializer", () => {
   });
 });
 
-describe("getTimezoneUTCString", () => {
-  it("should return the correct positive UTC string", () => {
-    const offset = 2;
-    const result = getTimezoneUTCString(offset);
-    expect(result).toBe("+02:00");
-  });
-
-  it("should return the correct negative UTC string", () => {
-    const offset = -5;
-    const result = getTimezoneUTCString(offset);
-    expect(result).toBe("-05:00");
-  });
-
-  it("should return the correct UTC string for '0' offset", () => {
-    const offset = 0;
-    const result = getTimezoneUTCString(offset);
-    expect(result).toBe("+00:00");
+describe("getTimezoneUTCString returns simplified UTC timezone string for each timezone", () => {
+  [
+    ["Canada/Newfoundland", "-2:30"],
+    ["UTC", ""],
+    ["Europe/London", "+1"],
+  ].forEach(([timezone, expected]) => {
+    it(`returns ${expected} for ${timezone}`, () => {
+      const result = getTimezoneUTCString(timezone);
+      expect(result).toBe(expected);
+    });
   });
 });
 
-describe("getTimeByUTCOffset", () => {
-  it("should display correct positive time offset", () => {
-    const date = new Date("2000-01-01T12:00:00Z");
-    const offset = 2;
-    const result = getTimeByUTCOffset(date, offset);
-    expect(result).toBe("14:00");
-  });
-  it("should display correct negative time offset", () => {
-    const date = new Date("2000-01-01T12:00:00Z");
-    const offset = -2;
-    const result = getTimeByUTCOffset(date, offset);
-    expect(result).toBe("10:00");
+describe("getTimeInTimezone", () => {
+  [
+    ["Canada/Newfoundland", "09:30"],
+    ["UTC", "12:00"],
+    ["Europe/Warsaw", "14:00"],
+    ["Europe/London", "13:00"],
+  ].forEach(([timezone, expected]) => {
+    it(`returns ${expected} for ${timezone}`, () => {
+      const result = getTimeInTimezone(new Date("2000-01-01T12:00:00Z"), timezone);
+      expect(result).toBe(expected);
+    });
   });
 });
diff --git a/frontend/src/utils.ts b/frontend/src/utils.ts
index 243c513..0cc5a22 100644
--- a/frontend/src/utils.ts
+++ b/frontend/src/utils.ts
@@ -34,21 +34,23 @@ export const customParamSerializer = (params: Record<string, string>, queryText?
   );
 };
 
-export const getTimezoneUTCString = (offset: number) => {
+export const getTimezoneUTCString = (timezone: string, date?: Date | number) => {
+  const offset = getTimezoneOffset(timezone, date);
   const sign = offset < 0 ? "-" : "+";
   const absOffset = Math.abs(offset);
-  const hours = Math.floor(absOffset);
-  const minutes = Math.floor((absOffset - hours) * 60);
-  const paddedHours = String(hours).padStart(2, "0");
-  const paddedMinutes = String(minutes).padStart(2, "0");
-  return sign + paddedHours + ":" + paddedMinutes;
+  const minutes = Math.floor(absOffset / 60000) % 60;
+  const hours = Math.floor(absOffset / 60000 / 60);
+  if (absOffset > 0) {
+    return `${sign}${hours}` + (minutes > 0 ? `:${minutes}` : "");
+  } else {
+    return "";
+  }
 };
 
-export const getTimeByUTCOffset = (date: Date, offset: number) => {
-  const utcString = getTimezoneUTCString(offset);
-  const updatedTime = date.getTime() + getTimezoneOffset(utcString);
-  const hours = `${new Date(updatedTime).getUTCHours()}`.padStart(2, "0");
-  const minutes = `${new Date(updatedTime).getUTCMinutes()}`.padStart(2, "0");
+export const getTimeInTimezone = (date: Date, timezone: string) => {
+  const time = date.getTime() + getTimezoneOffset(timezone);
+  const hours = `${new Date(time).getUTCHours()}`.padStart(2, "0");
+  const minutes = `${new Date(time).getUTCMinutes()}`.padStart(2, "0");
   return hours + ":" + minutes;
 };
 

Follow ups