Skip to content

Commit

Permalink
feat: timezone support via gettimeoffset awslabs#2663
Browse files Browse the repository at this point in the history
  • Loading branch information
chandrashekhara.n authored and chandrashekhara.n committed May 8, 2024
1 parent 7fad147 commit 154ea32
Show file tree
Hide file tree
Showing 22 changed files with 141 additions and 36 deletions.
15 changes: 15 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { AssetSummary } from '@aws-sdk/client-iotsitewise';
import { type TableProps } from '@cloudscape-design/components/table';
import React from 'react';

import { DateTime } from '@iot-app-kit/react-components';
import type { AssetTableNameLinkProps } from './assetTableNameLink';
import { getFormattedDateTime } from '~/components/util/dateTimeUtil';

type AssetTableColumnDefinitions =
TableProps<AssetSummary>['columnDefinitions'];
Expand Down Expand Up @@ -87,7 +87,7 @@ export class AssetTableColumnDefinitionsFactory {
id: 'creationDate',
header: 'Creation Date',
cell: ({ creationDate }) =>
creationDate ? getFormattedDateTime(creationDate) : '-',
creationDate ? <DateTime dateTime={creationDate.getTime()} /> : '-',
sortingField: 'creationDate',
};
}
Expand All @@ -97,7 +97,7 @@ export class AssetTableColumnDefinitionsFactory {
id: 'lastUpdateDate',
header: 'Last Update Date',
cell: ({ lastUpdateDate }) =>
lastUpdateDate ? getFormattedDateTime(lastUpdateDate) : '-',
lastUpdateDate ? <DateTime dateTime={lastUpdateDate.getTime()} /> : '-',
sortingField: 'lastUpdateDate',
};
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import React from 'react';
import { isNumeric, round } from '@iot-app-kit/core-util';
import { DateTime } from '@iot-app-kit/react-components';
import { type TableProps } from '@cloudscape-design/components/table';
import type { ModeledDataStream } from '../types';
import { getFormattedDateTimeFromEpoch } from '~/components/util/dateTimeUtil';

export function createModeledDataStreamColumnDefinitions(
significantDigits: number
Expand Down Expand Up @@ -33,12 +34,17 @@ export function createModeledDataStreamColumnDefinitions(
id: 'latestValueTime',
header: 'Latest value time',
cell: ({ latestValueTime }) => {
if (!latestValueTime) return '-';
if (latestValueTime && isNumeric(latestValueTime)) {
return getFormattedDateTimeFromEpoch(
Number(round(latestValueTime, significantDigits))
return (
<DateTime
dateTime={
Number(round(latestValueTime, significantDigits)) * 1000
}
/>
);
}
return getFormattedDateTimeFromEpoch(latestValueTime);
return <DateTime dateTime={latestValueTime * 1000} />;
},
sortingField: 'latestValueTime',
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import React from 'react';
import { useSelector } from 'react-redux';
import { type IoTSiteWiseClient } from '@aws-sdk/client-iotsitewise';
import { isNumeric, round } from '@iot-app-kit/core-util';
import { getPlugin } from '@iot-app-kit/core';
import { DateTime } from '@iot-app-kit/react-components';

import { useCollection } from '@cloudscape-design/collection-hooks';
import Box from '@cloudscape-design/components/box';
Expand All @@ -16,10 +18,8 @@ import { useExplorerPreferences } from '../../useExplorerPreferences';
import { SUPPORTED_PAGE_SIZES } from '../../constants';
import { useLatestValues } from '../../useLatestValues';
import { DashboardState } from '~/store/state';
import { getFormattedDateTimeFromEpoch } from '~/components/util/dateTimeUtil';
import { ResourceExplorerFooter } from '../../footer/footer';
import { getPlugin } from '@iot-app-kit/core';
import { disableAdd } from '~/components/queryEditor/iotSiteWiseQueryEditor/footer/disableAdd';
import { ResourceExplorerFooter } from '../../footer/footer';

export interface UnmodeledDataStreamTableProps {
onClickAdd: (unmodeledDataStreams: UnmodeledDataStream[]) => void;
Expand Down Expand Up @@ -162,12 +162,17 @@ export function UnmodeledDataStreamTable({
id: 'latestValueTime',
header: 'Latest value time',
cell: ({ latestValueTime }) => {
if (!latestValueTime) return '-';
if (latestValueTime && isNumeric(latestValueTime)) {
return getFormattedDateTimeFromEpoch(
Number(round(latestValueTime, significantDigits))
return (
<DateTime
dateTime={
Number(round(latestValueTime, significantDigits)) * 1000
}
/>
);
}
return getFormattedDateTimeFromEpoch(latestValueTime);
return <DateTime dateTime={latestValueTime * 1000} />;
},
sortingField: 'latestValueTime',
},
Expand Down
1 change: 1 addition & 0 deletions packages/react-components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@
"d3-array": "^3.2.3",
"d3-format": "^3.1.0",
"d3-shape": "^3.2.0",
"date-fns-tz": "^2.0.1",
"dompurify": "3.0.5",
"echarts": "^5.4.3",
"is-hotkey": "^0.2.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import React from 'react';
import { format } from 'date-fns';
import { fontWeightHeadingS } from '@cloudscape-design/design-tokens';
import { FULL_DATE } from '../../../../utils/time';
import { DateTime } from '../../../timeZone';

export type XYPlotTooltipTimeOptions = {
time?: number;
};
export const XYPlotTooltipTime = ({ time }: XYPlotTooltipTimeOptions) => (
<span style={{ fontWeight: fontWeightHeadingS }}>
{time ? format(new Date(time), FULL_DATE) : ''}
{time ? <DateTime dateTime={time} pattern={FULL_DATE} /> : ''}
</span>
);
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ const createTrendCursorColumnDefinition = ({
date,
}: TrendCursor): LegendTableColumnDefinitions[number] => ({
id: trendCursorId,
header: <TrendCursorColumnHeader color={color} date={new Date(date)} />,
header: <TrendCursorColumnHeader color={color} date={date} />,
sortingComparator: (a, b) => {
const aValue = a.trendCursorValues[trendCursorId] ?? 0;
const bValue = b.trendCursorValues[trendCursorId] ?? 0;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
import React from 'react';
import { useDateTime } from '../../../../../timeZone';
import { FULL_DATE_TIME } from '../../../../../../utils/time';

type TrendCursorColumnHeaderOptions = {
date: Date;
date: number;
color?: string;
};

export const TrendCursorColumnHeader = ({
date,
color,
}: TrendCursorColumnHeaderOptions) => {
const dateTime = useDateTime(date, FULL_DATE_TIME.replace(',', ''));
return (
<div className='base-chart-legend-tc-header-container'>
<div>
<span>{date.toLocaleDateString()}</span>
<span>{dateTime.split(' ')[0]}</span>
<br />
<span>{date.toLocaleTimeString()}</span>
<span>{dateTime.split(' ')[1]}</span>
</div>
<div
className='base-chart-legend-tc-header-color'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';
import { render, renderHook, screen } from '@testing-library/react';
import { format } from 'date-fns-tz';
import { ChartLegendTable } from './table';
import { DataStreamInformation, TrendCursor } from './types';
import { useChartStore } from '../../store';
Expand Down Expand Up @@ -90,10 +91,12 @@ describe('legend table', () => {

expect(table).not.toBeNull();
expect(
screen.getByText(new Date(trendCursors[0].date).toLocaleTimeString())
screen
.getAllByText(format(new Date(trendCursors[0].date), 'dd/MM/yyyy'))
.at(0)
).not.toBeNull();
expect(
screen.getByText(new Date(trendCursors[1].date).toLocaleTimeString())
screen.getByText(format(new Date(trendCursors[1].date), 'hh:mm:ss'))
).not.toBeNull();

expect(screen.getByText('111')).not.toBeNull();
Expand Down
4 changes: 3 additions & 1 deletion packages/react-components/src/components/kpi/kpi.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import { mockTimeSeriesDataQuery } from '@iot-app-kit/testing-util';
import { format } from 'date-fns-tz';
import { KPI } from './kpi';
import { FULL_DATE_TIME } from '../../utils/time';

const VIEWPORT = { duration: '5m' };

Expand Down Expand Up @@ -35,6 +37,6 @@ it('renders', async () => {
`${DATA_STREAM.data[0].y} `
);
expect(screen.getByTestId('kpi-timestamp').textContent).toContain(
new Date(DATA_STREAM.data[0].x).toLocaleString()
format(new Date(DATA_STREAM.data[0].x), FULL_DATE_TIME)
);
});
6 changes: 4 additions & 2 deletions packages/react-components/src/components/kpi/kpiBase.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import React from 'react';
import { format } from 'date-fns-tz';
import { render, screen } from '@testing-library/react';
import { KpiBase } from './kpiBase';
import type { DataPoint } from '@iot-app-kit/core';
import { KpiBase } from './kpiBase';
import { FULL_DATE_TIME } from '../../utils/time';

describe('name', () => {
it('renders name when showName is true', () => {
Expand Down Expand Up @@ -149,7 +151,7 @@ describe('timestamp', () => {
);

expect(
screen.queryByText(PROPERTY_POINT_DATE.toLocaleString())
screen.queryByText(format(PROPERTY_POINT_DATE, FULL_DATE_TIME))
).not.toBeNull();
});

Expand Down
3 changes: 2 additions & 1 deletion packages/react-components/src/components/kpi/kpiBase.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
fontSizeBodyS,
} from '@cloudscape-design/design-tokens';
import { DataQualityText } from '../data-quality/data-quality-text';
import { DateTime } from '../timeZone';

export const KpiBase: React.FC<KPIBaseProperties> = ({
propertyPoint,
Expand Down Expand Up @@ -138,7 +139,7 @@ export const KpiBase: React.FC<KPIBaseProperties> = ({
}}
/>
<div className='timestamp' data-testid='kpi-timestamp'>
{isLoading ? '-' : new Date(point.x).toLocaleString()}
{isLoading ? '-' : <DateTime dateTime={point.x} />}
</div>
</>
)}
Expand Down
1 change: 1 addition & 0 deletions packages/react-components/src/components/timeZone/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './timeZone';
44 changes: 44 additions & 0 deletions packages/react-components/src/components/timeZone/timeZone.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import React, { createContext, useContext } from 'react';

import { utcToZonedTime, format } from 'date-fns-tz';
import { FULL_DATE_TIME } from '../../utils/time';

// https://date-fns.org/v3.6.0/docs/Time-Zones#date-fns-tz
// converts a utc date to a formatted string in a specific timeZone
export const formatDate = (
dateTime: number,
{ timeZone, pattern }: { timeZone: string; pattern: string }
) => {
const zonedDate = utcToZonedTime(new Date(dateTime).toISOString(), timeZone);
const formattedDate = format(zonedDate, pattern, { timeZone: timeZone });

return formattedDate;
};

// Helper components for use in a React Context
export type DateTimeFormatContextOptions = {
timeZone: string;
};
export const DateTimeFormatContext =
createContext<DateTimeFormatContextOptions>({
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
});

export type DateTimeOptions = {
dateTime: number;
pattern?: string;
};
export const DateTime = ({ dateTime, pattern }: DateTimeOptions) => {
const formattedDate = useDateTime(dateTime, pattern);

return <>{formattedDate}</>;
};

export const useDateTime = (dateTime: number, pattern?: string) => {
const dateTimeFormatPattern = pattern ?? FULL_DATE_TIME;
const { timeZone } = useContext(DateTimeFormatContext);
return formatDate(dateTime, {
timeZone,
pattern: dateTimeFormatPattern,
});
};
14 changes: 10 additions & 4 deletions packages/react-components/src/components/timestampBar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { Spinner } from '@cloudscape-design/components';
import './timestamp.css';
import { useViewport } from '../../hooks/useViewport';
import { convertViewportToMs } from '../../utils/convertViewportToMs';
import { DateTime } from '../timeZone';

type TimestampProps = {
showLoadingIndicator: boolean;
Expand All @@ -33,9 +34,10 @@ export const Timestamp = ({
styleProps,
}: TimestampProps) => {
const { viewport } = useViewport();
// const { initial, end } = convertViewportToMs(viewport);
// const timestampStart = new Date(initial).toLocaleString();
// const timestampEnd = new Date(end).toLocaleString();
const { initial, end } = convertViewportToMs(viewport);
const timestampStart = new Date(initial).toLocaleString();
const timestampEnd = new Date(end).toLocaleString();
const timestampStyle = {
...styleProps,
backgroundColor: showLoadingIndicator ? '' : colorBorderDividerSecondary,
Expand Down Expand Up @@ -65,8 +67,12 @@ export const Timestamp = ({
color: colorTextBodyDefault,
}}
>
<span>{timestampStart}</span>
<span>{timestampEnd}</span>
<span>
<DateTime dateTime={initial} />
</span>
<span>
<DateTime dateTime={end} />
</span>
</div>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ import {
import { AnomalyObjectDataSource } from './datasource';
import { AnomalyObjectDataSourceValue, Diagnostic, Diagnostics } from './input';
import { AnomalyData, AnomalyDescription, DiagnosticData } from './output';
import { isAfter } from 'date-fns/isAfter';
import { isBefore } from 'date-fns/isBefore';
import { isAfter, isBefore } from 'date-fns';

/**
* Transformer for AnomalyResult type responses from
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { formatDate } from '../../../../../components/timeZone';

export const getDateTime = (date: number) => {
const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
const formatedDate = formatDate(date, {
timeZone: timeZone,
pattern: 'dd/MM/yyyy HH:mm:ss',
});
return formatedDate;
};
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * from './coordinateSystem';
export * from './grid';
export * from './polyline';
export * from './xAxis';
export * from './dateTime';
Loading

0 comments on commit 154ea32

Please sign in to comment.