Skip to content

Commit

Permalink
fix(date-picker): picker support timezone (#2106)
Browse files Browse the repository at this point in the history
Co-authored-by: YanHui <[email protected]>
  • Loading branch information
hiker90 and YanHui authored Oct 21, 2022
1 parent cb3ffe7 commit ab64c29
Show file tree
Hide file tree
Showing 9 changed files with 47 additions and 26 deletions.
11 changes: 9 additions & 2 deletions src/date-range-picker/Picker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,18 @@ export const DateRangePicker: React.FC<DateRangePickerProps> = (props) => {

const handleOnSelect = (currentValue: [NullableDate, NullableDate], index: number) => {
setControlledValue(currentValue);
onSelect?.(currentValue, formatDates(currentValue, formatString));
const newValue = currentValue?.map((date, i) => {
if(i === 1 && !!date) {
return new Date(new Date(date).valueOf() + 86399999)
}
return date;
})
onSelect?.(newValue as [Date, Date], formatDates(newValue as [Date, Date], formatString));

if (index) {
setVisible(false);
}
};
};

const handleClear = (e?: React.MouseEvent<Element, MouseEvent>) => {
e?.stopPropagation();
Expand Down
10 changes: 6 additions & 4 deletions src/static-date-picker/DatePicker.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import React, { useEffect, useRef, useState } from 'react';
import React, { useEffect, useRef, useState, useMemo } from 'react';
import { useControlledState, useLocale, usePrefixCls } from '@gio-design/utils';
import { LeftDoubleOutlined, LeftOutlined, RightOutlined, RightDoubleOutlined } from '@gio-design/icons';
import PickerPanel from 'rc-picker/lib/PickerPanel';
import generateDateFns from 'rc-picker/lib/generate/dateFns';
import { Locale, PickerMode } from 'rc-picker/lib/interface';
import defaultLocale from './locales/zh-CN';
import { StaticDatePickerProps } from './interfaces';
import { exportDateToZonedDate } from '../utils/timeHelper';
import { exportDateToZonedDate, exportZonedDateToDate } from '../utils/timeHelper';

const Cell: React.FC<{ visible: boolean; prefixCls: string; currentDate: Date }> = ({
visible,
Expand Down Expand Up @@ -42,9 +42,11 @@ const DatePicker: React.FC<StaticDatePickerProps> = ({
...restProps
}) => {
const locale = useLocale<Locale>('DatePicker') || defaultLocale;
const convertedValue = useMemo(() => exportZonedDateToDate(value), [value]);
const covertedDefaultValue = useMemo(() => exportZonedDateToDate(defaultValue), [defaultValue]);

const [viewDate, setViewDate] = useControlledState(viewDateProp, value ?? defaultValue ?? new Date());
const [innerValue] = useControlledState(value, defaultValue);
const [viewDate, setViewDate] = useControlledState(viewDateProp, convertedValue ?? covertedDefaultValue ?? new Date());
const [innerValue] = useControlledState(convertedValue, covertedDefaultValue);
const [mode] = useState<PickerMode>('date');
const currentPickerMode = useRef(mode);

Expand Down
4 changes: 2 additions & 2 deletions src/static-date-range-picker/StaticDateRangePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import isBefore from 'date-fns/isBefore';
import StaticDatePicker, { StaticDatePickerContext } from '../static-date-picker';
import { StaticDateRangePickerProps } from './interfaces';
import { getDefaultViewDates, calcClosingViewDate, mergeDates } from './utils';
import { exportDateToZonedDate } from '../utils/timeHelper';
import { exportDateToZonedDate, exportZonedDateToDate } from '../utils/timeHelper';

function StaticDateRangePicker({
className,
Expand Down Expand Up @@ -40,7 +40,7 @@ function StaticDateRangePicker({
value={{
inRange: true,
panelPosition: position,
rangedValue: selectedValue,
rangedValue: selectedValue?.map((date) => exportZonedDateToDate(date)) as [any, any],
hoverRangedValue: hoveredDates,
}}
>
Expand Down
5 changes: 2 additions & 3 deletions src/static-past-time-picker/AbsoluteRangePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import InnerRangePanel from './InnerRangePanel';
import { RangePickerProps } from './interfaces';
import { parseStartAndEndDate } from './utils';
import defaultLocale from './locales/zh-CN';
import { exportDateToZonedDate } from '../utils/timeHelper';

function AbsoluteRangePicker({ disabledDate, timeRange, onSelect, onRangeSelect, onCancel }: RangePickerProps) {
const [dates, setDates] = React.useState<[Date | undefined, Date | undefined]>(parseStartAndEndDate(timeRange));
Expand All @@ -30,8 +29,8 @@ function AbsoluteRangePicker({ disabledDate, timeRange, onSelect, onRangeSelect,
const handleDisabledDate = (current: Date) => disabledDate?.(current) || isAfter(current, startOfToday());
const handleOnOK = () => {
onSelect(
`abs:${getTime(exportDateToZonedDate(dates[0] as Date))},${
getTime(exportDateToZonedDate(dates[1] as Date)) + 86399999
`abs:${getTime(dates[0] as Date)},${
getTime(dates[1] as Date) + 86399999
}`
);
};
Expand Down
4 changes: 2 additions & 2 deletions src/static-past-time-picker/RelativeRangeBody.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import React from 'react';
import { startOfDay, startOfYesterday } from 'date-fns';
import { startOfYesterdayInTimezone } from './utils';
import DatePicker from '../static-date-picker';
import DateRangePicker from '../static-date-range-picker';
import { RelativeRangeBodyProps } from './interfaces';

function RelativeRangeBody({ dateRange, fixedMode, onRangeChange, disabledDate }: RelativeRangeBodyProps) {
if (fixedMode) {
const handleOnSelect = (current: Date) => {
onRangeChange([startOfDay(current), startOfYesterday()]);
onRangeChange([current, startOfYesterdayInTimezone()]);
};
return <DatePicker disabledDate={disabledDate} value={dateRange[0]} onSelect={handleOnSelect} />;
}
Expand Down
4 changes: 2 additions & 2 deletions src/static-past-time-picker/RelativeRangePicker.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React from 'react';
import { differenceInDays, startOfToday, startOfDay, isValid, isYesterday, startOfYesterday, isAfter } from 'date-fns';
import { startOfTodayInTimezone , parseStartAndEndDate } from './utils';
import RelativeRangeBody from './RelativeRangeBody';
import RelativeRangeHeader from './RelativeRangeHeader';
import InnerRangePanel from './InnerRangePanel';
import { RangePickerProps } from './interfaces';
import { parseStartAndEndDate } from './utils';

function RelativeRangePciker({ disabledDate, timeRange, onSelect, onCancel, ...rest }: RangePickerProps) {
const defaultDates = parseStartAndEndDate(timeRange ?? 'day:2,1');
Expand All @@ -14,7 +14,7 @@ function RelativeRangePciker({ disabledDate, timeRange, onSelect, onCancel, ...r
const handleDisabledDate = (current: Date) =>
disabledDate?.(current) || isAfter(startOfDay(current), endDateHidden ? startOfYesterday() : startOfToday());
const handleOnOK = () => {
onSelect(`day:${differenceInDays(startOfToday(), dates[0]) + 1},${differenceInDays(startOfToday(), dates[1])}`);
onSelect(`day:${differenceInDays(startOfTodayInTimezone(), dates[0]) + 1},${differenceInDays(startOfTodayInTimezone(), dates[1])}`);
};
return (
<InnerRangePanel
Expand Down
4 changes: 2 additions & 2 deletions src/static-past-time-picker/SinceRangePicker.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import { getTime, startOfToday, startOfYesterday, isValid, isAfter } from 'date-fns';
import { usePrefixCls, useLocale } from '@gio-design/utils';
import { parseFnsTimeZone, exportDateToZonedDate } from '../utils/timeHelper'
import { parseFnsTimeZone } from '../utils/timeHelper'
import SwitchGroup from '../switchGroup';
import DatePicker from '../static-date-picker';
import InnerRangePanel from './InnerRangePanel';
Expand Down Expand Up @@ -60,7 +60,7 @@ function SinceRangePicker({ disabledDate, timeRange, onSelect, onCancel, experim
disabledDate?.(current) || isAfter(current, endKey === 'yesterday' ? startOfYesterday() : startOfToday());

const handleOnOK = () => {
onSelect(`${endKey === 'yesterday' ? 'since-lt-today' : 'since'}:${getTime(exportDateToZonedDate(startDate as Date))}`);
onSelect(`${endKey === 'yesterday' ? 'since-lt-today' : 'since'}:${getTime(startDate as Date)}`);
};
return (
<InnerRangePanel
Expand Down
10 changes: 7 additions & 3 deletions src/static-past-time-picker/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import has from 'lodash/has';
import { startOfToday, sub, startOfYesterday } from 'date-fns';
import { sub } from 'date-fns';
import momentTZ from 'moment-timezone';
import { TimeMode } from './interfaces';
import { QUICK_MAPPING } from './constant';

Expand All @@ -25,14 +26,17 @@ export const parseTimeMode = (timeRange: string | undefined) => {
}
};

export const startOfTodayInTimezone = () => new Date(momentTZ.tz(`${(momentTZ.tz(new Date(), localStorage.getItem('timezone') || 'UTC').format()).substring(0, 10)} 00:00:00`, localStorage.getItem('timezone') || 'UTC').format());
export const startOfYesterdayInTimezone = () => sub(startOfTodayInTimezone(), { days: 1 });

export const parseStartAndEndDate = (timeRange: string | undefined): [Date | undefined, Date | undefined] => {
if (!timeRange || timeRange.split(':').length !== 2) {
return [undefined, undefined];
}
const items = timeRange.split(':');
const times = items[1].split(',').map((str) => parseInt(str, 10));
const today = startOfToday();
const yesterday = startOfYesterday();
const today = startOfTodayInTimezone();
const yesterday = startOfYesterdayInTimezone();
if (items[0] === 'since') {
if (times.length === 1) {
return [new Date(times[0]), today];
Expand Down
21 changes: 15 additions & 6 deletions src/utils/timeHelper.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { isNumber } from 'lodash';
import momentTZ from 'moment-timezone';
import moment from 'moment';
import { format as dateFnsFormat } from 'date-fns-tz';
import { format as dateFnsFormat, utcToZonedTime } from 'date-fns-tz';

// 时间日期转换时区 moment
export const parseTimeZone = (data?: any, format?: string) =>
Expand All @@ -14,17 +14,26 @@ export const parseFnsTimeZone = (date: number | Date | string, format: string) =
finalDate = new Date(date);
}

return dateFnsFormat(finalDate, format, {
return dateFnsFormat(utcToZonedTime(finalDate, localStorage.getItem('timezone') || 'UTC'), format, {
timeZone: localStorage.getItem('timezone') || 'UTC',
});
};

// 选择器时间按时区转化
// 例: date: Fri Oct 21 2022 09:00:00 GMT+0800 (中国标准时间) format: 'yyyy-MM-DD';
// return
// 选择器时间字符串按时区转化
// 例: date: Fri Oct 21 2022 09:00:00 GMT+0800 (中国标准时间) format: 'yyyy-MM-DD' 浏览器TZ: 北京 localstorage: UTC;
// return Fri Oct 21 2022 17:00:00 GMT+0800 (中国标准时间)
export const exportDateToZonedDate = (date: any, format?: string) => {
if (!date) return date;
return new Date(
momentTZ.tz(moment(date).format(format || 'yyyy-MM-DD'), localStorage.getItem('timezone') || 'UTC').format()
momentTZ.tz(moment(date).format(format || 'yyyy-MM-DD HH:mm:ss'), localStorage.getItem('timezone') || 'UTC').format()
);
};
// 选择器时间字符串按local时区转化
// 例: date: Fri Oct 21 2022 09:00:00 GMT+0800 (中国标准时间) format: 'yyyy-MM-DD' 浏览器TZ: 北京 localstorage: UTC;
// return Fri Oct 21 2022 01:00:00 GMT+0800 (中国标准时间)
export const exportZonedDateToDate = (date: any, format?: string) => {
if (!date) return date;
const arr = (momentTZ.tz(date, localStorage.getItem('timezone') || 'UTC').format()).split('T');

return new Date(moment(`${arr[0]} ${arr[1].substring(0, 8)}`).format(format || 'yyyy-MM-DD HH:mm:ss'));
};

0 comments on commit ab64c29

Please sign in to comment.