blob: 5a733ae376d3d13f5df6044da794d0ab3d094889 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { useEffect, useMemo } from 'react';
import { t } from '@superset-ui/core';
import { Select } from 'src/components';
import { isDST, extendedDayjs } from 'src/utils/dates';
const DEFAULT_TIMEZONE = {
name: 'GMT Standard Time',
value: 'Africa/Abidjan', // timezones are deduped by the first alphabetical value
};
const MIN_SELECT_WIDTH = '400px';
const offsetsToName = {
'-300-240': ['Eastern Standard Time', 'Eastern Daylight Time'],
'-360-300': ['Central Standard Time', 'Central Daylight Time'],
'-420-360': ['Mountain Standard Time', 'Mountain Daylight Time'],
'-420-420': [
'Mountain Standard Time - Phoenix',
'Mountain Standard Time - Phoenix',
],
'-480-420': ['Pacific Standard Time', 'Pacific Daylight Time'],
'-540-480': ['Alaska Standard Time', 'Alaska Daylight Time'],
'-600-600': ['Hawaii Standard Time', 'Hawaii Daylight Time'],
'60120': ['Central European Time', 'Central European Daylight Time'],
'00': [DEFAULT_TIMEZONE.name, DEFAULT_TIMEZONE.name],
'060': ['GMT Standard Time - London', 'British Summer Time'],
};
export type TimezoneSelectorProps = {
onTimezoneChange: (value: string) => void;
timezone?: string | null;
minWidth?: string;
};
export default function TimezoneSelector({
onTimezoneChange,
timezone,
minWidth = MIN_SELECT_WIDTH, // smallest size for current values
}: TimezoneSelectorProps) {
const { TIMEZONE_OPTIONS, TIMEZONE_OPTIONS_SORT_COMPARATOR, validTimezone } =
useMemo(() => {
const currentDate = extendedDayjs();
const JANUARY = extendedDayjs.tz('2021-01-01');
const JULY = extendedDayjs.tz('2021-07-01');
const getOffsetKey = (name: string) =>
JANUARY.tz(name).utcOffset().toString() +
JULY.tz(name).utcOffset().toString();
const getTimezoneName = (name: string) => {
const offsets = getOffsetKey(name);
return (
(isDST(currentDate.tz(name), name)
? offsetsToName[offsets]?.[1]
: offsetsToName[offsets]?.[0]) || name
);
};
const dedupedTimezones = new Map();
// TODO: remove this ts-ignore when typescript is upgraded to 5.1
// @ts-ignore
const ALL_ZONES: string[] = Intl.supportedValuesOf('timeZone');
ALL_ZONES.forEach(zone => {
const offsetKey = getOffsetKey(zone);
if (!dedupedTimezones.has(offsetKey)) {
dedupedTimezones.set(offsetKey, zone);
}
});
const TIMEZONES: string[] = Array.from(dedupedTimezones.values());
const TIMEZONE_OPTIONS = TIMEZONES.map(zone => ({
label: `GMT ${extendedDayjs
.tz(currentDate, zone)
.format('Z')} (${getTimezoneName(zone)})`,
value: zone,
offsets: getOffsetKey(zone),
timezoneName: zone,
}));
const TIMEZONE_OPTIONS_SORT_COMPARATOR = (
a: (typeof TIMEZONE_OPTIONS)[number],
b: (typeof TIMEZONE_OPTIONS)[number],
) =>
extendedDayjs.tz(currentDate, a.timezoneName).utcOffset() -
extendedDayjs.tz(currentDate, b.timezoneName).utcOffset();
// pre-sort timezone options by time offset
TIMEZONE_OPTIONS.sort(TIMEZONE_OPTIONS_SORT_COMPARATOR);
const matchTimezoneToOptions = (timezone: string) =>
TIMEZONE_OPTIONS.find(
option => option.offsets === getOffsetKey(timezone),
)?.value || DEFAULT_TIMEZONE.value;
const validTimezone = matchTimezoneToOptions(
timezone || extendedDayjs.tz.guess(),
);
return {
TIMEZONE_OPTIONS,
TIMEZONE_OPTIONS_SORT_COMPARATOR,
validTimezone,
};
}, [timezone]);
// force trigger a timezone update if provided `timezone` is not invalid
useEffect(() => {
if (validTimezone && timezone !== validTimezone) {
onTimezoneChange(validTimezone);
}
}, [validTimezone, onTimezoneChange, timezone]);
return (
<Select
ariaLabel={t('Timezone selector')}
css={{ minWidth }}
onChange={tz => onTimezoneChange(tz as string)}
value={validTimezone}
options={TIMEZONE_OPTIONS}
sortComparator={TIMEZONE_OPTIONS_SORT_COMPARATOR}
/>
);
}