Skip to content

Commit

Permalink
Give non-misleading labels to Etc/GMT timezone options
Browse files Browse the repository at this point in the history
  • Loading branch information
DJDavid98 committed Sep 16, 2021
1 parent bb1dd2c commit 1638ded
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 21 deletions.
10 changes: 5 additions & 5 deletions src/components/TimestampPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { TFunction } from 'i18next';
import styles from 'modules/TimestampPicker.module.scss';
import moment, { Moment } from 'moment-timezone';
import { useCallback, VFC } from 'react';
import { useCallback, useMemo, VFC } from 'react';
import Datetime from 'react-datetime';
import Select from 'react-select';
import { StylesConfig } from 'react-select/src/styles';
import { ThemeConfig } from 'react-select/src/theme';
import { Col, FormGroup, Input, InputGroup, InputGroupAddon, InputGroupText, Label, Row } from 'reactstrap';
import { getTimezoneValue } from 'src/util/timezone';

const dateInputId = 'date-input';
const timezoneSelectId = 'timezone-input';
Expand Down Expand Up @@ -109,6 +110,8 @@ export const TimestampPicker: VFC<PropTypes> = ({
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const renderDateInput = useCallback((props: any) => <DateInput {...props} />, []);

const timezoneSelectValue = useMemo(() => getTimezoneValue(timezone), [timezone]);

return (
<div className={styles.datepicker}>
<Row form>
Expand Down Expand Up @@ -136,10 +139,7 @@ export const TimestampPicker: VFC<PropTypes> = ({
</Label>
<Select
inputId={timezoneSelectId}
value={{
label: timezone,
value: timezone,
}}
value={timezoneSelectValue}
options={timezoneNames}
onChange={handleTimezoneChange}
className="w-100"
Expand Down
24 changes: 8 additions & 16 deletions src/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,19 @@ import { useCallback, useEffect, useMemo, useState, VFC } from 'react';
import { SITE_TITLE } from 'src/config';
import { useLocale } from 'src/util/common';
import { typedServerSideTranslations } from 'src/util/i18n-server';
import { getSortedNormalizedTimezoneNames, getTimezoneValue } from 'src/util/timezone';

const forceFirstTimezoneSet = 'GMT';
interface IndexPageProps {
tzNames: string[];
}

const IndexPage: VFC = () => {
const IndexPage: VFC<IndexPageProps> = ({ tzNames }) => {
const {
t,
i18n: { language },
} = useTranslation();
const locale = useLocale(language);
const timezoneNames = useMemo(() => {
let compare = (a: string, b: string): number => a.localeCompare(b);
try {
const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' });
compare = collator.compare;
} catch (e) {
console.error(e);
}
return moment.tz
.names()
.sort((a, b) => (forceFirstTimezoneSet === a ? -1 : forceFirstTimezoneSet === b ? 1 : compare(a, b)))
.map((value) => ({ label: value, value }));
}, []);
const timezoneNames = useMemo(() => tzNames.map((timezone) => getTimezoneValue(timezone)), [tzNames]);
const [timezone, setTimezone] = useState<string>(() => timezoneNames[0].value);
const [timestamp, setTimestamp] = useState<Moment>(() => moment(new Date(0)).utc());
const [inputValue, setInputValue] = useState<Moment | string>('');
Expand Down Expand Up @@ -93,8 +84,9 @@ const IndexPage: VFC = () => {

export default IndexPage;

export const getStaticProps: GetStaticProps<SSRConfig> = async ({ locale }) => ({
export const getStaticProps: GetStaticProps<IndexPageProps & SSRConfig> = async ({ locale }) => ({
props: {
tzNames: getSortedNormalizedTimezoneNames(),
...(await typedServerSideTranslations(locale, ['common'])),
},
});
31 changes: 31 additions & 0 deletions src/util/timezone.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import moment from 'moment-timezone';

export const gmtZoneRegex = /^Etc\/(GMT([+-]\d+)?)$/;

export const switchGmtZoneName = (value: string): string =>
value.replace(gmtZoneRegex, (_, extractedIdentifier: string) => extractedIdentifier.replace(/([+-])/, (m) => (m === '+' ? '-' : '+')));

const compareGmtStrings = (a: string, b: string) =>
parseInt(a.replace(gmtZoneRegex, '$2'), 10) - parseInt(b.replace(gmtZoneRegex, '$2'), 10);

export const getSortedNormalizedTimezoneNames = (): string[] =>
moment.tz
.names()
.filter((name) => !/^(?:Etc\/)?GMT[+-]0$/.test(name))
.sort((a, b) => {
const isAGmt = gmtZoneRegex.test(a);
const isBGmt = gmtZoneRegex.test(b);
if (isAGmt) return isBGmt ? compareGmtStrings(a, b) : -1;
if (isBGmt) return isAGmt ? compareGmtStrings(a, b) : 1;
return a.localeCompare(b);
});

export const getTimezoneLabel = (timezone: string): string => {
if (!gmtZoneRegex.test(timezone)) return timezone;
return switchGmtZoneName(timezone);
};

export const getTimezoneValue = (timezone: string) => ({
value: timezone,
label: getTimezoneLabel(timezone),
});

0 comments on commit 1638ded

Please sign in to comment.