diff --git a/apps/governance/src/routes/proposals/components/proposal-change-table/proposal-change-table.spec.tsx b/apps/governance/src/routes/proposals/components/proposal-change-table/proposal-change-table.spec.tsx
index 63d9ca8e1..1b538be80 100644
--- a/apps/governance/src/routes/proposals/components/proposal-change-table/proposal-change-table.spec.tsx
+++ b/apps/governance/src/routes/proposals/components/proposal-change-table/proposal-change-table.spec.tsx
@@ -1,8 +1,7 @@
import { render, screen } from '@testing-library/react';
import { ProposalRejectionReason, ProposalState } from '@vegaprotocol/types';
-import { format } from 'date-fns';
+import { formatDateWithLocalTimezone } from '@vegaprotocol/utils';
-import { DATE_FORMAT_DETAILED } from '../../../../lib/date-formats';
import { generateProposal } from '../../test-helpers/generate-proposals';
import { ProposalChangeTable } from './proposal-change-table';
@@ -24,16 +23,15 @@ it('Renders all data for table', () => {
expect(screen.getByText('Closes on')).toBeInTheDocument();
expect(
screen.getByText(
- format(new Date(proposal?.terms.closingDatetime), DATE_FORMAT_DETAILED)
+ formatDateWithLocalTimezone(new Date(proposal?.terms.closingDatetime))
)
).toBeInTheDocument();
expect(screen.getByText('Proposed enactment')).toBeInTheDocument();
expect(
screen.getByText(
- format(
- new Date(proposal?.terms.enactmentDatetime || 0),
- DATE_FORMAT_DETAILED
+ formatDateWithLocalTimezone(
+ new Date(proposal?.terms.enactmentDatetime || 0)
)
)
).toBeInTheDocument();
@@ -43,7 +41,7 @@ it('Renders all data for table', () => {
expect(screen.getByText('Proposed on')).toBeInTheDocument();
expect(
- screen.getByText(format(new Date(proposal?.datetime), DATE_FORMAT_DETAILED))
+ screen.getByText(formatDateWithLocalTimezone(new Date(proposal?.datetime)))
).toBeInTheDocument();
expect(screen.getByText('Type')).toBeInTheDocument();
@@ -62,16 +60,15 @@ it('Changes data based on if data is in future or past', () => {
expect(screen.getByText('Closed on')).toBeInTheDocument();
expect(
screen.getByText(
- format(new Date(proposal?.terms.closingDatetime), DATE_FORMAT_DETAILED)
+ formatDateWithLocalTimezone(new Date(proposal?.terms.closingDatetime))
)
).toBeInTheDocument();
expect(screen.getByText('Enacted on')).toBeInTheDocument();
expect(
screen.getByText(
- format(
- new Date(proposal?.terms.enactmentDatetime || 0),
- DATE_FORMAT_DETAILED
+ formatDateWithLocalTimezone(
+ new Date(proposal?.terms.enactmentDatetime || 0)
)
)
).toBeInTheDocument();
@@ -91,9 +88,8 @@ it('Does not render enactment time for freeform proposal', () => {
expect(screen.queryByText('Enacted on')).not.toBeInTheDocument();
expect(
screen.queryByText(
- format(
- new Date(proposal?.terms.enactmentDatetime || 0),
- DATE_FORMAT_DETAILED
+ formatDateWithLocalTimezone(
+ new Date(proposal?.terms.enactmentDatetime || 0)
)
)
).not.toBeInTheDocument();
diff --git a/apps/governance/src/routes/proposals/components/proposal-change-table/proposal-change-table.tsx b/apps/governance/src/routes/proposals/components/proposal-change-table/proposal-change-table.tsx
index 36992991f..fd10d8537 100644
--- a/apps/governance/src/routes/proposals/components/proposal-change-table/proposal-change-table.tsx
+++ b/apps/governance/src/routes/proposals/components/proposal-change-table/proposal-change-table.tsx
@@ -1,12 +1,11 @@
-import { format, isFuture } from 'date-fns';
+import { isFuture } from 'date-fns';
import { useTranslation } from 'react-i18next';
-
+import { formatDateWithLocalTimezone } from '@vegaprotocol/utils';
import {
KeyValueTable,
KeyValueTableRow,
RoundedWrapper,
} from '@vegaprotocol/ui-toolkit';
-import { DATE_FORMAT_DETAILED } from '../../../../lib/date-formats';
import { CurrentProposalState } from '../current-proposal-state';
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
@@ -35,16 +34,15 @@ export const ProposalChangeTable = ({ proposal }: ProposalChangeTableProps) => {
{isFuture(new Date(terms?.closingDatetime))
? t('closesOn')
: t('closedOn')}
- {format(new Date(terms?.closingDatetime), DATE_FORMAT_DETAILED)}
+ {formatDateWithLocalTimezone(new Date(terms?.closingDatetime))}
{terms?.change.__typename !== 'NewFreeform' ? (
{isFuture(new Date(terms?.enactmentDatetime || 0))
? t('proposedEnactment')
: t('enactedOn')}
- {format(
- new Date(terms?.enactmentDatetime || 0),
- DATE_FORMAT_DETAILED
+ {formatDateWithLocalTimezone(
+ new Date(terms?.enactmentDatetime || 0)
)}
) : null}
@@ -54,7 +52,7 @@ export const ProposalChangeTable = ({ proposal }: ProposalChangeTableProps) => {
{t('proposedOn')}
- {format(new Date(proposal?.datetime), DATE_FORMAT_DETAILED)}
+ {formatDateWithLocalTimezone(new Date(proposal?.datetime))}
{proposal?.rejectionReason ? (
diff --git a/libs/utils/src/lib/format/date.ts b/libs/utils/src/lib/format/date.ts
index 151d34410..46518d9f2 100644
--- a/libs/utils/src/lib/format/date.ts
+++ b/libs/utils/src/lib/format/date.ts
@@ -1,5 +1,6 @@
import once from 'lodash/once';
import { getUserLocale } from '../get-user-locale';
+import { utcToZonedTime, format as tzFormat } from 'date-fns-tz';
export const isValidDate = (date: Date) =>
date instanceof Date && !isNaN(date.getTime());
@@ -51,3 +52,16 @@ export const formatForInput = (date: Date) => {
return `${year}-${month}-${day}T${hours}:${minutes}:${secs}`;
};
+
+/** Format a user's local date and time with the time zone abbreviation */
+export const formatDateWithLocalTimezone = (
+ date: Date,
+ formatStr = 'dd MMMM yyyy HH:mm (z)'
+) => {
+ const userTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
+ const localDatetime = utcToZonedTime(date, userTimeZone);
+
+ return tzFormat(localDatetime, formatStr, {
+ timeZone: userTimeZone,
+ });
+};
diff --git a/package.json b/package.json
index d2003915a..721c29aed 100644
--- a/package.json
+++ b/package.json
@@ -56,6 +56,7 @@
"classnames": "^2.3.1",
"core-js": "^3.6.5",
"date-fns": "^2.28.0",
+ "date-fns-tz": "^2.0.0",
"duration-js": "^4.0.0",
"ethers": "^5.6.0",
"graphql": "^15.7.2",
@@ -154,8 +155,8 @@
"autoprefixer": "10.4.8",
"babel-jest": "27.5.1",
"babel-loader": "8.1.0",
- "cypress-mochawesome-reporter": "^3.3.0",
"cypress": "^12.9.0",
+ "cypress-mochawesome-reporter": "^3.3.0",
"cypress-real-events": "^1.7.6",
"dotenv": "^16.0.1",
"eslint": "8.15.0",
diff --git a/yarn.lock b/yarn.lock
index d456252e8..e7e9b910f 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -12070,6 +12070,11 @@ dataloader@2.1.0:
resolved "https://registry.yarnpkg.com/dataloader/-/dataloader-2.1.0.tgz#c69c538235e85e7ac6c6c444bae8ecabf5de9df7"
integrity sha512-qTcEYLen3r7ojZNgVUaRggOI+KM7jrKxXeSHhogh/TWxYMeONEMqY+hmkobiYQozsGIyg9OYVzO4ZIfoB4I0pQ==
+date-fns-tz@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/date-fns-tz/-/date-fns-tz-2.0.0.tgz#1b14c386cb8bc16fc56fe333d4fc34ae1d1099d5"
+ integrity sha512-OAtcLdB9vxSXTWHdT8b398ARImVwQMyjfYGkKD2zaGpHseG2UPHbHjXELReErZFxWdSLph3c2zOaaTyHfOhERQ==
+
date-fns@^1.27.2:
version "1.30.1"
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.30.1.tgz#2e71bf0b119153dbb4cc4e88d9ea5acfb50dc05c"