From 165ab2b876f6d8494807aefca733ef7f0649ada5 Mon Sep 17 00:00:00 2001 From: SemmyWong <67748948+semmywong@users.noreply.github.com> Date: Thu, 28 Jul 2022 17:32:31 +0800 Subject: [PATCH] fix: fix date format (#686) * fix: fix date format * fix: fix date format * fix: export date * fix: get current timezone * fix: datetime render * fix: gmt * fix: gmt date * fix: utf offset Co-authored-by: chenos --- .../client/src/application/Application.tsx | 7 ++ .../antd/date-picker/DatePicker.tsx | 6 +- .../antd/date-picker/ReadPretty.tsx | 4 +- .../antd/date-picker/__tests__/util.test.ts | 9 ++- .../schema-component/antd/date-picker/util.ts | 75 +----------------- packages/core/utils/package.json | 4 + packages/core/utils/src/client.ts | 1 + packages/core/utils/src/date.ts | 78 +++++++++++++++++++ packages/core/utils/src/index.ts | 1 + packages/core/utils/src/server.ts | 1 + .../export/src/server/renders/renders.ts | 12 ++- 11 files changed, 112 insertions(+), 86 deletions(-) create mode 100644 packages/core/utils/src/date.ts diff --git a/packages/core/client/src/application/Application.tsx b/packages/core/client/src/application/Application.tsx index a59b16379..160330d32 100644 --- a/packages/core/client/src/application/Application.tsx +++ b/packages/core/client/src/application/Application.tsx @@ -37,6 +37,12 @@ export interface ApplicationOptions { plugins?: any[]; } +export const getCurrentTimezone = () => { + const timezoneOffset = new Date().getTimezoneOffset() / -60; + const timezone = String(timezoneOffset).padStart(2, '0') + ':00'; + return (timezoneOffset > 0 ? '+' : '-') + timezone; +}; + export type PluginCallback = () => Promise; export class Application { @@ -51,6 +57,7 @@ export class Application { baseURL: process.env.API_BASE_URL, headers: { 'X-Hostname': window?.location?.hostname, + 'X-Timezone': getCurrentTimezone(), }, ...options.apiClient, }); diff --git a/packages/core/client/src/schema-component/antd/date-picker/DatePicker.tsx b/packages/core/client/src/schema-component/antd/date-picker/DatePicker.tsx index 7085e6353..e181aee0c 100644 --- a/packages/core/client/src/schema-component/antd/date-picker/DatePicker.tsx +++ b/packages/core/client/src/schema-component/antd/date-picker/DatePicker.tsx @@ -1,10 +1,10 @@ -import React from 'react'; +import { connect, mapProps, mapReadPretty } from '@formily/react'; import { DatePicker as AntdDatePicker } from 'antd'; import type { DatePickerProps as AntdDatePickerProps, - RangePickerProps as AntdRangePickerProps, + RangePickerProps as AntdRangePickerProps } from 'antd/lib/date-picker'; -import { connect, mapProps, mapReadPretty } from '@formily/react'; +import React from 'react'; import { ReadPretty } from './ReadPretty'; import { mapDateFormat } from './util'; diff --git a/packages/core/client/src/schema-component/antd/date-picker/ReadPretty.tsx b/packages/core/client/src/schema-component/antd/date-picker/ReadPretty.tsx index 158c19a6e..6b100dfbe 100644 --- a/packages/core/client/src/schema-component/antd/date-picker/ReadPretty.tsx +++ b/packages/core/client/src/schema-component/antd/date-picker/ReadPretty.tsx @@ -1,5 +1,6 @@ import { usePrefixCls } from '@formily/antd/lib/__builtins__'; import { isArr } from '@formily/shared'; +import { getDefaultFormat, str2moment } from '@nocobase/utils/client'; import type { DatePickerProps as AntdDatePickerProps, RangePickerProps as AntdRangePickerProps @@ -7,7 +8,6 @@ import type { import cls from 'classnames'; import moment from 'moment'; import React from 'react'; -import { getDefaultFormat, str2moment } from './util'; type Composed = { DatePicker: React.FC; @@ -38,7 +38,7 @@ ReadPretty.DateRangePicker = (props: any) => { if (!m) { return ''; } - const labels = m.map(m => m.format(format)); + const labels = m.map((m) => m.format(format)); return isArr(labels) ? labels.join('~') : labels; }; return ( diff --git a/packages/core/client/src/schema-component/antd/date-picker/__tests__/util.test.ts b/packages/core/client/src/schema-component/antd/date-picker/__tests__/util.test.ts index 8a058a19c..6ea9f53ff 100644 --- a/packages/core/client/src/schema-component/antd/date-picker/__tests__/util.test.ts +++ b/packages/core/client/src/schema-component/antd/date-picker/__tests__/util.test.ts @@ -1,5 +1,6 @@ +import { str2moment } from '@nocobase/utils'; import moment from 'moment'; -import { moment2str, str2moment } from '../util'; +import { moment2str } from '../util'; describe('str2moment', () => { describe('string value', () => { @@ -7,12 +8,12 @@ describe('str2moment', () => { const m = str2moment('2022-06-21T00:00:00.000Z', { gmt: true }); expect(m.format('YYYY-MM-DD HH:mm:ss')).toBe('2022-06-21 00:00:00'); }); - + test('local date', async () => { const m = str2moment('2022-06-21T00:00:00.000Z'); expect(m.toISOString()).toBe('2022-06-21T00:00:00.000Z'); }); - + test('value is null', async () => { const m = str2moment(null); expect(m).toBeNull(); @@ -31,7 +32,7 @@ describe('str2moment', () => { expect(m.format('YYYY-MM-DD HH:mm:ss')).toBe('2022-06-21 00:00:00'); } }); - + test('local date', async () => { const arr = str2moment(['2022-06-21T00:00:00.000Z', '2022-06-21T00:00:00.000Z']); for (const m of arr) { diff --git a/packages/core/client/src/schema-component/antd/date-picker/util.ts b/packages/core/client/src/schema-component/antd/date-picker/util.ts index 275e7571e..442d56501 100644 --- a/packages/core/client/src/schema-component/antd/date-picker/util.ts +++ b/packages/core/client/src/schema-component/antd/date-picker/util.ts @@ -1,18 +1,6 @@ -import type { DatePickerProps } from 'antd/lib/date-picker'; +import { getDefaultFormat, str2moment, toGmt, toLocal } from '@nocobase/utils/client'; import moment from 'moment'; -const toGmt = (value: moment.Moment | moment.Moment[]) => { - if (!value) { - return value; - } - if (Array.isArray(value)) { - return value.map((val) => `${val.format('YYYY-MM-DD')}T${val.format('HH:mm:ss.SSS')}Z`); - } - if (moment.isMoment(value)) { - return `${value.format('YYYY-MM-DD')}T${value.format('HH:mm:ss.SSS')}Z`; - } -}; - const toStringByPicker = (value, picker) => { if (picker === 'year') { return value.format('YYYY') + '-01-01T00:00:00.000Z'; @@ -41,45 +29,6 @@ const toGmtByPicker = (value: moment.Moment | moment.Moment[], picker?: any) => } }; -const toLocal = (value: moment.Moment | moment.Moment[]) => { - if (!value) { - return value; - } - if (Array.isArray(value)) { - return value.map((val) => val.toISOString()); - } - if (moment.isMoment(value)) { - return value.toISOString(); - } -}; - -export interface Str2momentOptions { - gmt?: boolean; - picker?: 'year' | 'month' | 'week' | 'quarter'; -} - -const toMoment = (val: any, options?: Str2momentOptions) => { - if (moment.isMoment(val)) { - return val; - } - const { gmt, picker } = options; - if (gmt || picker) { - val = val.replace('T', ' ').replace('Z', ''); - return moment(val); - } - return moment(val); -}; - -export const str2moment = (value?: string | string[], options: Str2momentOptions = {}): any => { - return Array.isArray(value) - ? value.map((val) => { - return toMoment(val, options); - }) - : value - ? toMoment(value, options) - : value; -}; - export interface Moment2strOptions { showTime?: boolean; gmt?: boolean; @@ -97,28 +46,6 @@ export const moment2str = (value?: moment.Moment | moment.Moment[], options: Mom return toGmtByPicker(value, picker); }; -export const getDefaultFormat = (props: DatePickerProps & { dateFormat: string; timeFormat: string }) => { - if (props.format) { - return props.format; - } - if (props.dateFormat) { - if (props['showTime']) { - return `${props.dateFormat} ${props.timeFormat || 'HH:mm:ss'}`; - } - return props.dateFormat; - } - if (props['picker'] === 'month') { - return 'YYYY-MM'; - } else if (props['picker'] === 'quarter') { - return 'YYYY-\\QQ'; - } else if (props['picker'] === 'year') { - return 'YYYY'; - } else if (props['picker'] === 'week') { - return 'YYYY-wo'; - } - return props['showTime'] ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD'; -}; - export const mapDateFormat = function () { return (props: any) => { const format = getDefaultFormat(props) as any; diff --git a/packages/core/utils/package.json b/packages/core/utils/package.json index e2cb72b01..62c7478f5 100644 --- a/packages/core/utils/package.json +++ b/packages/core/utils/package.json @@ -14,5 +14,9 @@ "deepmerge": "^4.2.2", "flat-to-nested": "^1.1.1" }, + "peerDependencies": { + "moment": "2.x", + "rc-input-number": "7.x" + }, "gitHead": "7e9556e489007577fc0ed89063b3a9ce2f9aae53" } diff --git a/packages/core/utils/src/client.ts b/packages/core/utils/src/client.ts index e6eb7f346..dea79f636 100644 --- a/packages/core/utils/src/client.ts +++ b/packages/core/utils/src/client.ts @@ -1,3 +1,4 @@ +export * from './date'; export * from './merge'; export * from './number'; export * from './registry'; diff --git a/packages/core/utils/src/date.ts b/packages/core/utils/src/date.ts new file mode 100644 index 000000000..b8eb4b718 --- /dev/null +++ b/packages/core/utils/src/date.ts @@ -0,0 +1,78 @@ +import moment from 'moment'; + +export interface Str2momentOptions { + gmt?: boolean; + picker?: 'year' | 'month' | 'week' | 'quarter'; + utcOffset?: any; +} + +export const getDefaultFormat = (props: any) => { + if (props.format) { + return props.format; + } + if (props.dateFormat) { + if (props['showTime']) { + return `${props.dateFormat} ${props.timeFormat || 'HH:mm:ss'}`; + } + return props.dateFormat; + } + if (props['picker'] === 'month') { + return 'YYYY-MM'; + } else if (props['picker'] === 'quarter') { + return 'YYYY-\\QQ'; + } else if (props['picker'] === 'year') { + return 'YYYY'; + } else if (props['picker'] === 'week') { + return 'YYYY-wo'; + } + return props['showTime'] ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD'; +}; + +export const toGmt = (value: moment.Moment | moment.Moment[]) => { + if (!value) { + return value; + } + if (Array.isArray(value)) { + return value.map((val) => `${val.format('YYYY-MM-DD')}T${val.format('HH:mm:ss.SSS')}Z`); + } + if (moment.isMoment(value)) { + return `${value.format('YYYY-MM-DD')}T${value.format('HH:mm:ss.SSS')}Z`; + } +}; + +export const toLocal = (value: moment.Moment | moment.Moment[]) => { + if (!value) { + return value; + } + if (Array.isArray(value)) { + return value.map((val) => val.toISOString()); + } + if (moment.isMoment(value)) { + return value.toISOString(); + } +}; + +const toMoment = (val: any, options?: Str2momentOptions) => { + if (!val) { + return; + } + const offset = options.utcOffset || -1 * new Date().getTimezoneOffset(); + if (moment.isMoment(val)) { + return val.utcOffset(offset); + } + const { gmt, picker } = options; + if (gmt || picker) { + return moment(val).utcOffset(0); + } + return moment(val).utcOffset(offset); +}; + +export const str2moment = (value?: string | string[], options: Str2momentOptions = {}): any => { + return Array.isArray(value) + ? value.map((val) => { + return toMoment(val, options); + }) + : value + ? toMoment(value, options) + : value; +}; diff --git a/packages/core/utils/src/index.ts b/packages/core/utils/src/index.ts index 096103c48..c19e16520 100644 --- a/packages/core/utils/src/index.ts +++ b/packages/core/utils/src/index.ts @@ -1,3 +1,4 @@ +export * from './date'; export * from './merge'; export * from './mixin'; export * from './mixin/AsyncEmitter'; diff --git a/packages/core/utils/src/server.ts b/packages/core/utils/src/server.ts index f50ad7d92..14daaa3de 100644 --- a/packages/core/utils/src/server.ts +++ b/packages/core/utils/src/server.ts @@ -1,3 +1,4 @@ +export * from './date'; export * from './merge'; export * from './mixin'; export * from './mixin/AsyncEmitter'; diff --git a/packages/plugins/export/src/server/renders/renders.ts b/packages/plugins/export/src/server/renders/renders.ts index f590fc82e..b5f38610b 100644 --- a/packages/plugins/export/src/server/renders/renders.ts +++ b/packages/plugins/export/src/server/renders/renders.ts @@ -1,5 +1,4 @@ -import { toFixedByStep } from '@nocobase/utils'; -import moment from 'moment'; +import { getDefaultFormat, str2moment } from '@nocobase/utils'; export async function _(field, row, ctx, column?: any) { if (column?.dataIndex.length > 1) { @@ -34,7 +33,14 @@ export async function _(field, row, ctx, column?: any) { export async function datetime(field, row, ctx) { const value = row.get(field.name); - return moment(value).format(field.showTime ? `${field.dateFormat} ${field.timeFormat}` : field.dateFormat); + if (!value) { + return ''; + } + const utcOffset = ctx.get('X-Timezone'); + const props = field.options?.uiSchema?.['x-component-props'] ?? {}; + const format = getDefaultFormat(props); + const m = str2moment(value, { ...props, utcOffset }); + return m ? m.format(format) : ''; } export async function percent(field, row, ctx) {