Skip to content

Commit

Permalink
feat: add timezone converter util
Browse files Browse the repository at this point in the history
  • Loading branch information
dpportet committed Jul 15, 2024
1 parent 19a7fca commit 4e1c5e2
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 10 deletions.
31 changes: 23 additions & 8 deletions package-lock.json

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

3 changes: 2 additions & 1 deletion packages/react-components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,8 @@
"d3-array": "^3.2.3",
"d3-format": "^3.1.0",
"d3-shape": "^3.2.0",
"date-fns": "^3.4.0",
"date-fns": "^3.6.0",
"date-fns-tz": "^3.1.3",
"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,5 +1,5 @@
import { HistoricalViewport } from '@iot-app-kit/core';
import { sub } from 'date-fns/sub';
import { sub } from 'date-fns';

export const DEFAULT_ANOMALY_DATA_SOURCE_VIEWPORT: HistoricalViewport = {
start: sub(Date.now(), { days: 7 }),
Expand Down
24 changes: 24 additions & 0 deletions packages/react-components/src/utils/time.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
SECOND_IN_MS,
toTimestamp,
YEAR_IN_MS,
formatDate
} from './time';

describe('convert from milliseconds', () => {
Expand Down Expand Up @@ -285,3 +286,26 @@ describe('toTimestamp', () => {
).toBe(0);
});
});

describe('formatDate', () => {
it('correctly converts to a different timezone', () => {
const date = new Date(0).getTime();

const formattedDate = formatDate(date, { timeZone: 'America/Denver' });

// UTC-7
expect(formattedDate).toBe('1969-12-31, 05:00:00 p.m.');

const formattedDate2 = formatDate(date, { timeZone: 'Asia/Tokyo' });

// UTC+9
expect(formattedDate2).toBe('1970-01-01, 09:00:00 a.m.');
});

it('converts date to specified pattern', () => {
const date = new Date(0).getTime();

const formattedDate = formatDate(date, { timeZone: 'America/Denver', pattern: 'hh:mm a' });
expect(formattedDate).toBe('05:00 PM');
});
});
22 changes: 22 additions & 0 deletions packages/react-components/src/utils/time.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { TimeInNanos } from '@aws-sdk/client-iotsitewise';
import { NANO_SECOND_IN_MS } from '@iot-app-kit/core';
import parse from 'parse-duration';
import { toZonedTime, format } from 'date-fns-tz';

export const SECOND_IN_MS = 1000;
export const MINUTE_IN_MS = 60 * SECOND_IN_MS;
Expand All @@ -9,6 +10,7 @@ export const DAY_IN_MS = 24 * HOUR_IN_MS;
// Not precisely accurate, only estimates. exact duration depends on start date. use with care.
export const MONTH_IN_MS = 30 * DAY_IN_MS;
export const YEAR_IN_MS = 12 * MONTH_IN_MS;
export const DEFAULT_DATE_TIME = 'yyy-MM-dd, hh:mm:ss aaaa';

// Global time format strings
export const SHORT_TIME = 'hh:mm a';
Expand Down Expand Up @@ -166,3 +168,23 @@ export const toTimestamp = (time: TimeInNanos | undefined): number =>
(time.offsetInNanos || 0) * NANO_SECOND_IN_MS
)) ||
0;

// https://date-fns.org/v3.6.0/docs/Time-Zones#date-fns-tz
// converts an epoch date to a formatted string in a specific timeZone
export const formatDate = (
dateTime: number,
options?: { timeZone?: string, pattern?: string }
) => {
const formatPattern = options?.pattern ?? DEFAULT_DATE_TIME;

const userTimeZone = options?.timeZone ?? Intl.DateTimeFormat().resolvedOptions().timeZone;;

// Convert epoch time to a zoned date object
const zonedDate = toZonedTime(new Date(dateTime), userTimeZone);

// Take offset from zoned date to get a date with the days/hours modified based on offset for display
const dateWithOffset = new Date(zonedDate.valueOf() + (zonedDate.getTimezoneOffset() * 60 * 1000));

const formattedString = format(dateWithOffset, formatPattern, { timeZone: userTimeZone });
return formattedString;
};

0 comments on commit 4e1c5e2

Please sign in to comment.