瀏覽代碼

fix: fixed DatePicker timeZone bug #1522 (#1523)

Co-authored-by: shijia.me <[email protected]>
走鹃 2 年之前
父節點
當前提交
0334d30d26

+ 3 - 0
packages/semi-foundation/datePicker/_utils/isValidTimeZone.ts

@@ -0,0 +1,3 @@
+export default function isValidTimeZone(timeZone?: string | number) {
+    return ['string', 'number'].includes(typeof timeZone) && timeZone !== '';
+}

+ 6 - 13
packages/semi-foundation/datePicker/foundation.ts

@@ -19,6 +19,7 @@ import type { ArrayElement, Motion } from '../utils/type';
 import type { Type, DateInputFoundationProps, InsetInputValue } from './inputFoundation';
 import type { MonthsGridFoundationProps } from './monthsGridFoundation';
 import type { WeekStartNumber } from './_utils/getMonthTable';
+import isValidTimeZone from './_utils/isValidTimeZone';
 
 export type ValidateStatus = ArrayElement<typeof strings.STATUS>;
 export type InputSize = ArrayElement<typeof strings.SIZE_SET>;
@@ -238,13 +239,6 @@ export default class DatePickerFoundation extends BaseFoundation<DatePickerAdapt
         this.initPanelOpenStatus(this.getProp('defaultOpen'));
     }
 
-    isValidTimeZone(timeZone?: string | number) {
-        const propTimeZone = this.getProp('timeZone');
-        const _timeZone = isNullOrUndefined(timeZone) ? propTimeZone : timeZone;
-
-        return ['string', 'number'].includes(typeof _timeZone) && _timeZone !== '';
-    }
-
     initFromProps({ value, timeZone, prevTimeZone }: Pick<DatePickerFoundationProps, 'value' | 'timeZone'> & { prevTimeZone?: string | number }) {
         const _value = (Array.isArray(value) ? [...value] : (value || value === 0) && [value]) || [];
 
@@ -280,11 +274,10 @@ export default class DatePickerFoundation extends BaseFoundation<DatePickerAdapt
             for (const v of value) {
                 let parsedV = (v || v === 0) && this._parseValue(v);
                 if (parsedV) {
-                    if (this.isValidTimeZone(prevTimeZone)) {
-                        parsedV = zonedTimeToUtc(parsedV, prevTimeZone as string);
+                    if (isValidTimeZone(prevTimeZone)) {
+                        parsedV = zonedTimeToUtc(parsedV, prevTimeZone);
                     }
-
-                    result.push(this.isValidTimeZone(timeZone) ? utcToZonedTime(parsedV, timeZone as string) : parsedV);
+                    result.push(isValidTimeZone(timeZone) ? utcToZonedTime(parsedV, timeZone) : parsedV);
                 }
             }
         }
@@ -1098,9 +1091,9 @@ export default class DatePickerFoundation extends BaseFoundation<DatePickerAdapt
      */
     disposeCallbackArgs(value: Date | Date[]) {
         let _value = Array.isArray(value) ? value : (value && [value]) || [];
+        const timeZone = this.getProp('timeZone');
 
-        if (this.isValidTimeZone()) {
-            const timeZone = this.getProp('timeZone');
+        if (isValidTimeZone(timeZone)) {
             _value = _value.map(date => zonedTimeToUtc(date, timeZone));
         }
         const type = this.getProp('type');

+ 3 - 9
packages/semi-foundation/datePicker/monthsGridFoundation.ts

@@ -23,6 +23,7 @@ import isNullOrUndefined from '../utils/isNullOrUndefined';
 import { BaseValueType, DateInputFoundationProps, PresetPosition, ValueType } from './foundation';
 import { MonthDayInfo } from './monthFoundation';
 import { ArrayElement } from '../utils/type';
+import isValidTimeZone from './_utils/isValidTimeZone';
 
 const dateDiffFns = {
     month: differenceInCalendarMonths,
@@ -448,13 +449,6 @@ export default class MonthsGridFoundation extends BaseFoundation<MonthsGridAdapt
         return format(date, token, { locale: dateFnsLocale });
     }
 
-    isValidTimeZone(timeZone?: string | number) {
-        const propTimeZone = this.getProp('timeZone');
-        const _timeZone = isNullOrUndefined(timeZone) ? propTimeZone : timeZone;
-
-        return ['string', 'number'].includes(typeof _timeZone) && _timeZone !== '';
-    }
-
     /**
      * 根据 type 处理 onChange 返回的参数
      *
@@ -484,9 +478,9 @@ export default class MonthsGridFoundation extends BaseFoundation<MonthsGridAdapt
      */
     disposeCallbackArgs(value: Date | Date[]) {
         let _value = Array.isArray(value) ? value : (value && [value]) || [];
+        const timeZone = this.getProp('timeZone');
 
-        if (this.isValidTimeZone()) {
-            const timeZone = this.getProp('timeZone');
+        if (isValidTimeZone(timeZone)) {
             _value = _value.map(date => zonedTimeToUtc(date, timeZone));
         }
         const type = this.getProp('type');

+ 3 - 3
packages/semi-foundation/package.json

@@ -10,8 +10,8 @@
         "@douyinfe/semi-animation": "2.12.0",
         "async-validator": "^3.5.0",
         "classnames": "^2.2.6",
-        "date-fns": "^2.9.0",
-        "date-fns-tz": "^1.0.10",
+        "date-fns": "^2.29.3",
+        "date-fns-tz": "^1.3.8",
         "lodash": "^4.17.21",
         "memoize-one": "^5.2.1",
         "scroll-into-view-if-needed": "^2.2.24"
@@ -38,4 +38,4 @@
         "merge2": "^1.4.1",
         "through2": "^4.0.2"
     }
-}
+}

+ 25 - 12
packages/semi-foundation/utils/date-fns-extra.ts

@@ -120,22 +120,35 @@ const format = (date: string | number | Date, formatToken: string, options?: any
 };
 
 /**
- *
- * @param {string | number | Date} date
- * @param {string} timeZone
- * @param {object} options
- * @returns {Date}
+ * Given a date and any time zone, returns a Date with the equivalent UTC time
+ * 
+ * @example
+ * ```javascript
+ * import { zonedTimeToUtc } from 'date-fns-tz'
+ * const date = getDatePickerValue() // e.g. 2014-06-25 10:00:00 (picked in any time zone
+ * const timeZone = getTimeZoneValue() // e.g. America/Los_Angeles
+ * const utcDate = zonedTimeToUtc(date, timeZone) // In June 10am in Los Angeles is 5pm UTC
+ * ```
+ * 
+ * @see https://github.com/marnusw/date-fns-tz#zonedtimetoutc
  */
-const utcToZonedTime = (date: string | number | Date, timeZone: string, options?: OptionsWithTZ) => dateFnsUtcToZonedTime(date, toIANA(timeZone), options);
+const utcToZonedTime = (date: string | number | Date, timeZone: string | number, options?: OptionsWithTZ) => dateFnsUtcToZonedTime(date, toIANA(timeZone), options);
 
 /**
- *
- * @param {string | number | Date} date
- * @param {string} timeZone
- * @param {object} options
- * @returns {Date}
+ * Get a date/time representing local time in a given time zone from the UTC date
+ * 
+ * @example
+ * ```
+ * import { utcToZonedTime } from 'date-fns-tz'
+ * const { isoDate, timeZone } = fetchInitialValues() // 2014-06-25T10:00:00.000Z, America/New_York
+ * const date = utcToZonedTime(isoDate, timeZone) // In June 10am UTC is 6am in New York (-04:00)
+ * renderDatePicker(date) // 2014-06-25 06:00:00 (in the system time zone)
+ * renderTimeZoneSelect(timeZone) // America/New_York
+ * ```
+ * 
+ * @see https://github.com/marnusw/date-fns-tz#utctozonedtime
  */
-const zonedTimeToUtc = (date: string | number | Date, timeZone: string, options?: OptionsWithTZ) => dateFnsZonedTimeToUtc(date, toIANA(timeZone), options);
+const zonedTimeToUtc = (date: string | number | Date, timeZone: string | number, options?: OptionsWithTZ) => dateFnsZonedTimeToUtc(date, toIANA(timeZone), options);
 
 /**
  * return current system hour offset based on utc:

+ 56 - 0
packages/semi-ui/datePicker/_story/v2/FixTimeZone.tsx

@@ -0,0 +1,56 @@
+import React, { useMemo, useState } from "react";
+import { Select, DatePicker } from "@douyinfe/semi-ui";
+
+export default function Demo(props = {}) {
+    const defaultTimestamp = 1581599305265;
+    const [timeZone, setTimeZone] = useState("GMT+00:00");
+    const [value, setValue] = useState(defaultTimestamp);
+    const [dateValue, setDateValue] = useState();
+
+    const gmtList = useMemo(() => {
+        const list = [];
+        for (let hourOffset = -11; hourOffset <= 14; hourOffset++) {
+            const prefix = hourOffset >= 0 ? "+" : "-";
+            const hOffset = Math.abs(parseInt(hourOffset, 10));
+            list.push(`GMT${prefix}${String(hOffset).padStart(2, "0")}:00`);
+        }
+        return list;
+    }, []);
+
+    const handleChange = (date) => {
+        if (date instanceof Date) {
+            const timeStamp = date.valueOf();
+            console.log(date, timeStamp);
+            setValue(timeStamp);
+            setDateValue(date);
+        }
+    };
+
+    return (
+        <div style={{ width: 300 }}>
+            <h5 style={{ margin: 10 }}>Select Time Zone:</h5>
+            <Select
+                placeholder={"请选择时区"}
+                style={{ width: 300 }}
+                value={timeZone}
+                showClear={true}
+                onSelect={(value) => setTimeZone(value)}
+            >
+                {gmtList.map((gmt) => (
+                    <Select.Option key={gmt} value={gmt}>
+                        {gmt}
+                    </Select.Option>
+                ))}
+            </Select>
+            <br />
+            <br />
+            <DatePicker
+                timeZone={timeZone}
+                type={"dateTime"}
+                // value={value}
+                value={dateValue}
+                onChange={handleChange}
+            />
+        </div>
+    );
+}

+ 6 - 0
packages/semi-ui/datePicker/_story/v2/index.js

@@ -15,3 +15,9 @@ export { default as FixRangePanelShift } from './FixRangePanelShift';
 export { default as InsetInputControlled } from './InsetInputControlled';
 export { default as FeatInsetInputProps } from './FeatInsetInputProps';
 export { default as FixMultiplePanelShift } from './FixMultiplePanelShift';
+export { default as FeatRefOpen } from './FeatRefOpen';
+export { default as FeatRefFocus } from './FeatRefFocus';
+export { default as FeatOnClickOutside } from './FeatOnClickOutside';
+export { default as FeatRefClass } from './FeatRefClass';
+export { default as FixNeedConfirmInTabs } from './FixNeedConfirmInTabs';
+export { default as FixTimeZone } from './FixTimeZone';

+ 3 - 3
packages/semi-ui/package.json

@@ -26,8 +26,8 @@
         "async-validator": "^3.5.0",
         "classnames": "^2.2.6",
         "copy-text-to-clipboard": "^2.1.1",
-        "date-fns": "^2.9.0",
-        "date-fns-tz": "^1.0.10",
+        "date-fns": "^2.29.3",
+        "date-fns-tz": "^1.3.8",
         "lodash": "^4.17.21",
         "prop-types": "^15.7.2",
         "react-resizable": "^1.8.0",
@@ -110,4 +110,4 @@
         "webpack": "^4.46.0",
         "webpackbar": "^5.0.0-3"
     }
-}
+}

+ 84 - 0
yarn.lock

@@ -1465,6 +1465,15 @@
     "@douyinfe/semi-animation-styled" "2.23.2"
     classnames "^2.2.6"
 
+"@douyinfe/[email protected]":
+  version "2.31.2"
+  resolved "https://registry.yarnpkg.com/@douyinfe/semi-animation-react/-/semi-animation-react-2.31.2.tgz#d3eb6782400dbdb97ba10878eb91cd1523c11a7d"
+  integrity sha512-TTsxPbDnT7DesjpaYpEWPtdIZJYH8qpLhrDbX9CxjShHoHuZ58XcW+cuukAIRIVVezLIwXrt5ZkIhO0R/094Ww==
+  dependencies:
+    "@douyinfe/semi-animation" "2.12.0"
+    "@douyinfe/semi-animation-styled" "2.23.2"
+    classnames "^2.2.6"
+
 "@douyinfe/[email protected]":
   version "2.23.2"
   resolved "https://registry.npmjs.org/@douyinfe/semi-animation-styled/-/semi-animation-styled-2.23.2.tgz"
@@ -1485,6 +1494,13 @@
   dependencies:
     bezier-easing "^2.1.0"
 
+"@douyinfe/[email protected]":
+  version "2.31.2"
+  resolved "https://registry.yarnpkg.com/@douyinfe/semi-animation/-/semi-animation-2.31.2.tgz#be95fe051f7ad764fb4c283a3c0b0f1a0bc034fc"
+  integrity sha512-AzI2yZUC1Ep4a0B0dcJbTnL3ufFYM/07nPEdO9l9Jf6Ej5b+LDlOR/mGFt95+rG9o2WClipe01QVPyfG6x3G1A==
+  dependencies:
+    bezier-easing "^2.1.0"
+
 "@douyinfe/[email protected]":
   version "2.30.2"
   resolved "https://registry.yarnpkg.com/@douyinfe/semi-foundation/-/semi-foundation-2.30.2.tgz#f7a3f983da02a2843c8e95604f10c08d5c8e922c"
@@ -1499,6 +1515,20 @@
     memoize-one "^5.2.1"
     scroll-into-view-if-needed "^2.2.24"
 
+"@douyinfe/[email protected]":
+  version "2.31.2"
+  resolved "https://registry.yarnpkg.com/@douyinfe/semi-foundation/-/semi-foundation-2.31.2.tgz#4ff893b106447ac815879bf23ca25b7b63ded021"
+  integrity sha512-64L0mLRnB6yJd/Gt1WeC6kKOER50tG5X2TfJ86Tf1P/Yi6006oqYAaccdRnx/cET0A0gAjFcCcyTHtzv7LKfYQ==
+  dependencies:
+    "@douyinfe/semi-animation" "2.12.0"
+    async-validator "^3.5.0"
+    classnames "^2.2.6"
+    date-fns "^2.9.0"
+    date-fns-tz "^1.0.10"
+    lodash "^4.17.21"
+    memoize-one "^5.2.1"
+    scroll-into-view-if-needed "^2.2.24"
+
 "@douyinfe/[email protected]":
   version "2.30.2"
   resolved "https://registry.yarnpkg.com/@douyinfe/semi-icons/-/semi-icons-2.30.2.tgz#03bf9a5ef99bf3b77557379d631908889767b8aa"
@@ -1506,6 +1536,13 @@
   dependencies:
     classnames "^2.2.6"
 
+"@douyinfe/[email protected]", "@douyinfe/semi-icons@^2.0.0":
+  version "2.31.2"
+  resolved "https://registry.yarnpkg.com/@douyinfe/semi-icons/-/semi-icons-2.31.2.tgz#ac78814efd9c161995fd7be60d9e6619c9f70c48"
+  integrity sha512-LAEtAt5CDKjolS9QqI73rQXYUNmLLZYJSbjsilaZRVsFfj7/fSjAG/jrx1NmY7sUkznZ40yQD8380QB1UkHzdQ==
+  dependencies:
+    classnames "^2.2.6"
+
 "@douyinfe/semi-icons@latest":
   version "2.9.1"
   resolved "https://registry.yarnpkg.com/@douyinfe/semi-icons/-/semi-icons-2.9.1.tgz#7a04e1a77070220b04f63e6f65aac30155ed8ddd"
@@ -1519,6 +1556,11 @@
   resolved "https://registry.yarnpkg.com/@douyinfe/semi-illustrations/-/semi-illustrations-2.30.2.tgz#e31007086d75c98c6b87789945bffcdb72cf195e"
   integrity sha512-cjJ02F4Fg6wfWgVCpGSEUU/MDg/fc6zy2eAxMJ2yMqbERYI99y3Qk4RlckPcUnXx5LzfFAeFtM6E/ri+xMtIKQ==
 
+"@douyinfe/[email protected]":
+  version "2.31.2"
+  resolved "https://registry.yarnpkg.com/@douyinfe/semi-illustrations/-/semi-illustrations-2.31.2.tgz#6acc0e3f70af454381ed446d53202feb9e2ae502"
+  integrity sha512-6iHBs30MRhPFG/sesk8a4Csh0R84vCXduAJSwoUmDLvRtFl/nLAVb0i1rDaGb2z+Q83ZepCOz24ulzVr+/lUbA==
+
 "@douyinfe/[email protected]":
   version "2.23.2"
   resolved "https://registry.yarnpkg.com/@douyinfe/semi-scss-compile/-/semi-scss-compile-2.23.2.tgz#30884bb194ee9ae1e81877985e5663c3297c1ced"
@@ -1592,6 +1634,38 @@
   dependencies:
     glob "^7.1.6"
 
+"@douyinfe/[email protected]":
+  version "2.31.2"
+  resolved "https://registry.yarnpkg.com/@douyinfe/semi-theme-default/-/semi-theme-default-2.31.2.tgz#8879f23e1b7cae763e398ac234cf1df624a0c95a"
+  integrity sha512-2VdyWTpRg61/LTUqiYsZ59UfaBYBYHMvlut0N3NFq21uv8csV7MOGJEPkAWinMgY5yFj2wxxcbeTRzwJi1m+2g==
+  dependencies:
+    glob "^7.1.6"
+
+"@douyinfe/semi-ui@^2.0.0":
+  version "2.31.2"
+  resolved "https://registry.yarnpkg.com/@douyinfe/semi-ui/-/semi-ui-2.31.2.tgz#36dda1676a613adc0a47596c18c96d0ce0ed8d19"
+  integrity sha512-GvnqTVzWoxoK28nUbspNnS2LIHmUxMSzr/vF+C74JTrZyjXQnfdIjqIFiFvRRpfMX3FWSvmcIe9hto//VQafmw==
+  dependencies:
+    "@douyinfe/semi-animation" "2.31.2"
+    "@douyinfe/semi-animation-react" "2.31.2"
+    "@douyinfe/semi-foundation" "2.31.2"
+    "@douyinfe/semi-icons" "2.31.2"
+    "@douyinfe/semi-illustrations" "2.31.2"
+    "@douyinfe/semi-theme-default" "2.31.2"
+    async-validator "^3.5.0"
+    classnames "^2.2.6"
+    copy-text-to-clipboard "^2.1.1"
+    date-fns "^2.9.0"
+    date-fns-tz "^1.0.10"
+    lodash "^4.17.21"
+    prop-types "^15.7.2"
+    react-resizable "^1.8.0"
+    react-sortable-hoc "^2.0.0"
+    react-window "^1.8.2"
+    resize-observer-polyfill "^1.5.1"
+    scroll-into-view-if-needed "^2.2.24"
+    utility-types "^3.10.0"
+
 "@douyinfe/semi-ui@latest":
   version "2.30.2"
   resolved "https://registry.yarnpkg.com/@douyinfe/semi-ui/-/semi-ui-2.30.2.tgz#75c2ad58ead2f96845381d8af94d32a58dec3d45"
@@ -9348,11 +9422,21 @@ date-fns-tz@^1.0.10:
   resolved "https://registry.npmjs.org/date-fns-tz/-/date-fns-tz-1.3.4.tgz"
   integrity sha512-O47vEyz85F2ax/ZdhMBJo187RivZGjH6V0cPjPzpm/yi6YffJg4upD/8ibezO11ezZwP3QYlBHh/t4JhRNx0Ow==
 
+date-fns-tz@^1.3.8:
+  version "1.3.8"
+  resolved "https://registry.yarnpkg.com/date-fns-tz/-/date-fns-tz-1.3.8.tgz#083e3a4e1f19b7857fa0c18deea6c2bc46ded7b9"
+  integrity sha512-qwNXUFtMHTTU6CFSFjoJ80W8Fzzp24LntbjFFBgL/faqds4e5mo9mftoRLgr3Vi1trISsg4awSpYVsOQCRnapQ==
+
 date-fns@^2.14.0, date-fns@^2.23.0, date-fns@^2.9.0:
   version "2.28.0"
   resolved "https://registry.npmjs.org/date-fns/-/date-fns-2.28.0.tgz"
   integrity sha512-8d35hViGYx/QH0icHYCeLmsLmMUheMmTyV9Fcm6gvNwdw31yXXH+O85sOBJ+OLnLQMKZowvpKb6FgMIQjcpvQw==
 
+date-fns@^2.29.3:
+  version "2.29.3"
+  resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.29.3.tgz#27402d2fc67eb442b511b70bbdf98e6411cd68a8"
+  integrity sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==
+
 dateformat@^3.0.0:
   version "3.0.3"
   resolved "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz"