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"