Skip to content

Commit

Permalink
[datetime2] fix(DateInput2): handle defaultTimezone prop changes
Browse files Browse the repository at this point in the history
  • Loading branch information
adidahiya committed Apr 19, 2023
1 parent ad6cc22 commit 66111a0
Show file tree
Hide file tree
Showing 7 changed files with 54 additions and 20 deletions.
4 changes: 3 additions & 1 deletion packages/datetime2/src/common/getTimezone.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

import memoize from "lodash/memoize";

import { UTC_TIME } from "./timezoneItems";

/**
* Gets the users current time zone, for example "Europe/Oslo".
* This is currently backed by the browser or computer's locale setting.
Expand All @@ -29,5 +31,5 @@ export const getCurrentTimezone: () => string = memoize(guessTimezone);
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/resolvedOptions
*/
function guessTimezone(): string {
return Intl.DateTimeFormat().resolvedOptions().timeZone ?? "Etc/UTC";
return Intl.DateTimeFormat().resolvedOptions().timeZone ?? UTC_TIME.ianaCode;
}
4 changes: 2 additions & 2 deletions packages/datetime2/src/common/timezoneUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ import isEmpty from "lodash/isEmpty";
import { TimePrecision } from "@blueprintjs/datetime";

import { getCurrentTimezone } from "./getTimezone";
import { UTC_TIME } from "./timezoneItems";

const NO_TIME_PRECISION = "date";
const UTC_IANA_LABEL = "Etc/UTC";

const TIME_FORMAT_TO_ISO_FORMAT: Record<TimePrecision | "date", string> = {
[TimePrecision.MILLISECOND]: "yyyy-MM-dd'T'HH:mm:ss.SSSxxx",
Expand Down Expand Up @@ -75,7 +75,7 @@ export function getDateObjectFromIsoString(
// If the value is just a date format then we convert it to midnight in local time to avoid weird things happening
if (value.length === 10) {
// If it's just a date, we know it's interpreted as midnight UTC so we convert it to local time of that UTC time
return convertLocalDateToTimezoneTime(date, UTC_IANA_LABEL);
return convertLocalDateToTimezoneTime(date, UTC_TIME.ianaCode);
}
return convertLocalDateToTimezoneTime(date, timezone);
}
Expand Down
9 changes: 8 additions & 1 deletion packages/datetime2/src/components/date-input2/dateInput2.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import { DatetimePopoverProps } from "../../common/datetimePopoverProps";
import { isDateValid, isDayInRange } from "../../common/dateUtils";
import * as Errors from "../../common/errors";
import { getCurrentTimezone } from "../../common/getTimezone";
import { UTC_TIME } from "../../common/timezoneItems";
import { getTimezoneShortName, isValidTimezone } from "../../common/timezoneNameUtils";
import {
convertLocalDateToTimezoneTime,
Expand Down Expand Up @@ -258,6 +259,12 @@ export const DateInput2: React.FC<DateInput2Props> = React.memo(function _DateIn
}
}, [valueFromProps]);

React.useEffect(() => {
if (defaultTimezone !== undefined && isValidTimezone(defaultTimezone)) {
setTimezoneValue(defaultTimezone);
}
}, [defaultTimezone]);

React.useEffect(() => {
if (isControlled && !isInputFocused) {
setInputValue(formattedDateString);
Expand Down Expand Up @@ -650,7 +657,7 @@ function getInitialTimezoneValue({ defaultTimezone }: DateInput2Props) {
return defaultTimezone;
} else {
console.error(Errors.DATEINPUT_INVALID_DEFAULT_TIMEZONE);
return "Etc/UTC";
return UTC_TIME.ianaCode;
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions packages/datetime2/test/common/timezoneMetadataTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@

import { expect } from "chai";

import { UTC_TIME } from "../../src/common/timezoneItems";
import { getTimezoneMetadata } from "../../src/common/timezoneMetadata";

const UTC_TIME = "Etc/UTC";
const LONDON_TIME = "Europe/London";
const NEW_YORK = "America/New_York";

describe("getTimezoneMetadata", () => {
it("Returns valid metadata for common timezones", () => {
for (const tzCode of [UTC_TIME, LONDON_TIME, NEW_YORK]) {
for (const tzCode of [UTC_TIME.ianaCode, LONDON_TIME, NEW_YORK]) {
const metadata = getTimezoneMetadata(tzCode);
expect(metadata).not.to.be.undefined;
expect(metadata?.label).to.exist;
Expand Down
4 changes: 2 additions & 2 deletions packages/datetime2/test/common/timezoneUtilsTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@

import { expect } from "chai";

import { UTC_TIME } from "../../src/common/timezoneItems";
import {
convertDateToLocalEquivalentOfTimezoneTime,
convertLocalDateToTimezoneTime,
} from "../../src/common/timezoneUtils";

const OSLO_TIME = "Europe/Oslo";
const UTC_TIME = "Etc/UTC";
const LONDON_TIME = "Europe/London";
const NEW_YORK = "America/New_York";

Expand All @@ -31,7 +31,7 @@ const MOCK_WINTER_DATE = new Date(2022, 0, 1, 12);

describe("convertLocalDateToTimezoneTime", () => {
it("Returns the same date when current time is the same as passed", () => {
const convertedDate = convertLocalDateToTimezoneTime(MOCK_SUMMER_DATE, UTC_TIME);
const convertedDate = convertLocalDateToTimezoneTime(MOCK_SUMMER_DATE, UTC_TIME.ianaCode);
expect(checkIfDatesAreEqual(MOCK_SUMMER_DATE, convertedDate)).to.equal(true);
});

Expand Down
45 changes: 35 additions & 10 deletions packages/datetime2/test/components/dateInput2Tests.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,17 @@ import { Popover2, Classes as Popover2Classes } from "@blueprintjs/popover2";

import { Classes, DateInput2, DateInput2Props, TimezoneSelect } from "../../src";
import { getCurrentTimezone } from "../../src/common/getTimezone";
import { TIMEZONE_ITEMS, UTC_TIME } from "../../src/common/timezoneItems";
import { getTimezoneShortName } from "../../src/common/timezoneNameUtils";

const NEW_YORK_TIMEZONE = TIMEZONE_ITEMS.find(item => item.label === "New York")!;
const PARIS_TIMEZONE = TIMEZONE_ITEMS.find(item => item.label === "Paris")!;
const TOKYO_TIMEZONE = TIMEZONE_ITEMS.find(item => item.label === "Tokyo")!;

const VALUE = "2021-11-29T10:30:00z";

const DEFAULT_PROPS = {
defaultTimezone: "Etc/UTC",
defaultTimezone: UTC_TIME.ianaCode,
formatDate: (date: Date | null | undefined, locale?: string) => {
if (date == null) {
return "";
Expand Down Expand Up @@ -220,7 +226,7 @@ describe("<DateInput2>", () => {

it("calls onChange on timezone changes", () => {
const wrapper = mount(<DateInput2 {...DEFAULT_PROPS_UNCONTROLLED} />, { attachTo: containerElement });
clickTimezoneItem(wrapper, "New York");
clickTimezoneItem(wrapper, NEW_YORK_TIMEZONE.label);
assert.isTrue(onChange.calledOnce);
// New York is UTC-5
assert.strictEqual(onChange.firstCall.args[0], "2021-11-29T10:30:00-05:00");
Expand Down Expand Up @@ -476,24 +482,33 @@ describe("<DateInput2>", () => {
focusInput(wrapper);
// Japan is one of the few countries that does not have any kind of daylight savings, so this unit test
// keeps working all year round
clickTimezoneItem(wrapper, "Tokyo");
clickTimezoneItem(wrapper, TOKYO_TIMEZONE.label);
assertTimezoneIsSelected(wrapper, "GMT+9");
});

it("after selecting a date", () => {
const wrapper = mount(<DateInput2 {...DEFAULT_PROPS} />, { attachTo: containerElement });
focusInput(wrapper);
clickCalendarDay(wrapper, 1);
clickTimezoneItem(wrapper, "Tokyo");
clickTimezoneItem(wrapper, TOKYO_TIMEZONE.label);
assertTimezoneIsSelected(wrapper, "GMT+9");
});
});

describe("allows changing defaultTimezone", () => {
const wrapper = mount(<DateInput2 {...DEFAULT_PROPS_UNCONTROLLED} />, { attachTo: containerElement });
assert.strictEqual(wrapper.find(TimezoneSelect).text(), getTimezoneShortName(UTC_TIME.ianaCode, undefined));
wrapper.setProps({ defaultTimezone: TOKYO_TIMEZONE.ianaCode });
assert.strictEqual(
wrapper.find(TimezoneSelect).text(),
getTimezoneShortName(TOKYO_TIMEZONE.ianaCode, undefined),
);
});
});

describe("controlled usage", () => {
const DEFAULT_PROPS_CONTROLLED = {
...DEFAULT_PROPS,
defaultTimezone: "Etc/UTC",
onChange,
value: VALUE,
};
Expand Down Expand Up @@ -545,7 +560,7 @@ describe("<DateInput2>", () => {
assert.isTrue(onChange.calledTwice, "onChange called twice");
assert.strictEqual(
onChange.args[1][0],
formatInTimeZone(parseISO(DATE2_VALUE), "Etc/UTC", "yyyy-MM-dd'T'HH:mm:ssxxx"),
formatInTimeZone(parseISO(DATE2_VALUE), UTC_TIME.ianaCode, "yyyy-MM-dd'T'HH:mm:ssxxx"),
);
assert.isTrue(onKeyDown.calledOnce, "onKeyDown called once");
assert.strictEqual(
Expand Down Expand Up @@ -671,7 +686,7 @@ describe("<DateInput2>", () => {
describe("when changing timezone", () => {
it("calls onChange with the updated ISO string", () => {
const wrapper = mount(<DateInput2 {...DEFAULT_PROPS_CONTROLLED} />, { attachTo: containerElement });
clickTimezoneItem(wrapper, "Paris");
clickTimezoneItem(wrapper, PARIS_TIMEZONE.label);
assert.isTrue(onChange.calledOnce);
assert.strictEqual(onChange.firstCall.args[0], "2021-11-29T10:30:00+01:00");
});
Expand All @@ -681,23 +696,33 @@ describe("<DateInput2>", () => {
<DateInput2 {...DEFAULT_PROPS_CONTROLLED} timePrecision={TimePrecision.MINUTE} />,
{ attachTo: containerElement },
);
clickTimezoneItem(wrapper, "Paris");
clickTimezoneItem(wrapper, PARIS_TIMEZONE.label);
assert.isTrue(onChange.calledOnce);
assert.strictEqual(onChange.firstCall.args[0], "2021-11-29T10:30+01:00");
});

it("updates the displayed timezone", () => {
const wrapper = mount(<DateInput2 {...DEFAULT_PROPS_CONTROLLED} />, { attachTo: containerElement });
clickTimezoneItem(wrapper, "Tokyo");
clickTimezoneItem(wrapper, TOKYO_TIMEZONE.label);
assertTimezoneIsSelected(wrapper, "GMT+9");
});

it("before selecting a date (initial value={null})", () => {
const wrapper = mount(<DateInput2 {...DEFAULT_PROPS} value={null} />, { attachTo: containerElement });
clickTimezoneItem(wrapper, "Tokyo");
clickTimezoneItem(wrapper, TOKYO_TIMEZONE.label);
assertTimezoneIsSelected(wrapper, "GMT+9");
});
});

describe("allows changing defaultTimezone", () => {
const wrapper = mount(<DateInput2 {...DEFAULT_PROPS_CONTROLLED} />, { attachTo: containerElement });
assert.strictEqual(wrapper.find(TimezoneSelect).text(), getTimezoneShortName(UTC_TIME.ianaCode, undefined));
wrapper.setProps({ defaultTimezone: TOKYO_TIMEZONE.ianaCode });
assert.strictEqual(
wrapper.find(TimezoneSelect).text(),
getTimezoneShortName(TOKYO_TIMEZONE.ianaCode, undefined),
);
});
});

describe("date formatting", () => {
Expand Down
4 changes: 2 additions & 2 deletions packages/datetime2/test/components/timezoneSelectTests.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { Popover2, Popover2Props } from "@blueprintjs/popover2";
import { QueryList, Select2 } from "@blueprintjs/select";

import { TimezoneSelect, TimezoneSelectProps } from "../../src";
import { TIMEZONE_ITEMS } from "../../src/common/timezoneItems";
import { TIMEZONE_ITEMS, UTC_TIME } from "../../src/common/timezoneItems";
import { getInitialTimezoneItems, mapTimezonesWithNames, TimezoneWithNames } from "../../src/common/timezoneNameUtils";

const VALUE = "America/Los_Angeles";
Expand Down Expand Up @@ -84,7 +84,7 @@ describe("<TimezoneSelect>", () => {
const items = findSelect(timezoneSelect).prop("items");
assert.isTrue(items.length > 0);
const firstItem = items[0];
assert.strictEqual(firstItem.ianaCode, "Etc/UTC");
assert.strictEqual(firstItem.ianaCode, UTC_TIME.ianaCode);
});

it("if showLocalTimezone=false, the local timezone is not rendered at the top of the item list", () => {
Expand Down

0 comments on commit 66111a0

Please sign in to comment.