diff --git a/packages/ava/__tests__/unit/data/utils/isType/isDateString.test.ts b/packages/ava/__tests__/unit/data/utils/isType/isDateString.test.ts index 3c7217c75..8f7c1df3c 100644 --- a/packages/ava/__tests__/unit/data/utils/isType/isDateString.test.ts +++ b/packages/ava/__tests__/unit/data/utils/isType/isDateString.test.ts @@ -2,6 +2,7 @@ import { getIsoDatePatterns, getIsoTimePatterns, isDateString, + parseIsoDateString, } from '../../../../../src/data/utils/isType/isDateString'; describe('func: isDateString', () => { @@ -38,21 +39,50 @@ describe('func: isDateString', () => { }); }); +describe('func: parseIsoDateString', () => { + test('parse a date string to date type', () => { + expect(parseIsoDateString('1991')).toStrictEqual(new Date('1991-01-01 00:00:00.000')); + expect(parseIsoDateString('2010 W08 1')).toStrictEqual(new Date('2010-02-15 00:00:00.000')); + expect(parseIsoDateString('2010-W08')).toStrictEqual(new Date('2010-02-15 00:00:00.000')); + expect(parseIsoDateString('2010-08-01')).toStrictEqual(new Date('2010-08-01 00:00:00.000')); + expect(parseIsoDateString('20100801')).toStrictEqual(new Date('2010-08-01 00:00:00.000')); + expect(parseIsoDateString('2010/08/01')).toStrictEqual(new Date('2010-08-01 00:00:00.000')); + expect(parseIsoDateString('12/08/1999')).toStrictEqual(new Date('1999-12-08 00:00:00.000')); + expect(parseIsoDateString('1/1/1999')).toStrictEqual(new Date('1999-01-01 00:00:00.000')); + expect(parseIsoDateString('1999-01')).toStrictEqual(new Date('1999-01 00:00:00.000')); + expect(parseIsoDateString('1999-200')).toStrictEqual(new Date(1999, 0, 200)); + expect(parseIsoDateString('1999-367')).toBe(null); + expect(parseIsoDateString('2010-08-01 20:00:00')).toStrictEqual(new Date('2010-08-01 20:00:00.000')); + expect(parseIsoDateString('20:00:00')).toStrictEqual(new Date('01-01 20:00:00')); + expect(parseIsoDateString('20:00:00Z')).toStrictEqual(new Date('01-01 20:00:00Z')); + expect(parseIsoDateString('20:00:00+08:00')).toStrictEqual(new Date('01-01 20:00:00+08:00')); + expect(parseIsoDateString('20:00:00-08:00')).toStrictEqual(new Date('01-01 20:00:00-08:00')); + expect(parseIsoDateString('20:00:00.299-08:00')).toStrictEqual(new Date('01-01 20:00:00.299-08:00')); + expect(parseIsoDateString('20:00:00.299')).toStrictEqual(new Date('01-01 20:00:00.299')); + }); + + test('strictly recognize number as date', () => { + expect(isDateString('997815')).toBe(false); + expect(isDateString('1135028')).toBe(false); + expect(isDateString('5388715')).toBe(false); + }); +}); + describe('Not strict', () => { const isoDatePatternsNotStrict = getIsoDatePatterns(false); const isoTimePatternsNotStrict = getIsoTimePatterns(false); expect(isoDatePatternsNotStrict).toStrictEqual([ - '(18|19|20)\\d{2}', - '(18|19|20)\\d{2}([-_./\\s])?W([0-4]\\d|5[0-2])(([-_./\\s])?([1-7]))?', - '(0?[1-9]|1[012])([-_./\\s])?(0?[1-9]|[12]\\d|3[01])([-_./\\s])?(18|19|20)\\d{2}', - '(18|19|20)\\d{2}([-_./\\s])?(0?[1-9]|1[012])([-_./\\s])?(0?[1-9]|[12]\\d|3[01])', - '(18|19|20)\\d{2}([-_./\\s])?(0?[1-9]|1[012])', - '(18|19|20)\\d{2}([-_./\\s])?((([0-2]\\d|3[0-5])\\d)|36[0-6])', + '(?(18|19|20)\\d{2})', + '(?(18|19|20)\\d{2})([-_./\\s])?W(?[0-4]\\d|5[0-2])(([-_./\\s])?(?[1-7]))?', + '(?0?[1-9]|1[012])([-_./\\s])?(?0?[1-9]|[12]\\d|3[01])([-_./\\s])?(?(18|19|20)\\d{2})', + '(?(18|19|20)\\d{2})([-_./\\s])?(?0?[1-9]|1[012])([-_./\\s])?(?0?[1-9]|[12]\\d|3[01])', + '(?(18|19|20)\\d{2})([-_./\\s])?(?0?[1-9]|1[012])', + '(?(18|19|20)\\d{2})([-_./\\s])?(?(([0-2]\\d|3[0-5])\\d)|36[0-6])', ]); expect(isoTimePatternsNotStrict).toStrictEqual([ - '(0?\\d|1\\d|2[0-4]):?(0?\\d|[012345]\\d):?(0?\\d|[012345]\\d)([.,]\\d{1,4})?(Z|[+-](0?\\d|1\\d|2[0-4])(:(0?\\d|[012345]\\d))?)?', - '(0?\\d|1\\d|2[0-4]):?(0?\\d|[012345]\\d)?(Z|[+-](0?\\d|1\\d|2[0-4])(:(0?\\d|[012345]\\d))?)', + '(?(0?\\d|[012345]\\d)):?(?(0?\\d|[012345]\\d)):?(?(0?\\d|[012345]\\d))([.,](?\\d{1,4}))?(?Z|[+-](0?\\d|1\\d|2[0-4])(:(0?\\d|[012345]\\d))?)?', + '(?(0?\\d|[012345]\\d)):?(?(0?\\d|[012345]\\d))?(?Z|[+-](0?\\d|1\\d|2[0-4])(:(0?\\d|[012345]\\d))?)', ]); }); diff --git a/packages/ava/src/advisor/advise-pipeline/advicesForChart.ts b/packages/ava/src/advisor/advise-pipeline/advicesForChart.ts index b7547ba78..6007151eb 100644 --- a/packages/ava/src/advisor/advise-pipeline/advicesForChart.ts +++ b/packages/ava/src/advisor/advise-pipeline/advicesForChart.ts @@ -92,6 +92,6 @@ export function advicesForChart( // if the input data cannot be transformed into DataFrame // eslint-disable-next-line no-console console.error('error: ', error); - return null; + return { advices: [], log: [] }; } } diff --git a/packages/ava/src/data/utils/isType/constants.ts b/packages/ava/src/data/utils/isType/constants.ts index 46813b672..6faf6ab17 100644 --- a/packages/ava/src/data/utils/isType/constants.ts +++ b/packages/ava/src/data/utils/isType/constants.ts @@ -10,14 +10,16 @@ export const SPECIAL_BOOLEANS = [ // For isDateString.ts export const DELIMITER = '([-_./\\s])'; -export const YEAR = '(18|19|20)\\d{2}'; -export const MONTH = '(0?[1-9]|1[012])'; -export const DAY = '(0?[1-9]|[12]\\d|3[01])'; -export const WEEK = '([0-4]\\d|5[0-2])'; -export const WEEKDAY = '([1-7])'; -export const HOUR = '(0?\\d|1\\d|2[0-4])'; -export const MINUTE = '(0?\\d|[012345]\\d)'; -export const SECOND = MINUTE; -export const MILLISECOND = '\\d{1,4}'; -export const YEARDAY = '((([0-2]\\d|3[0-5])\\d)|36[0-6])'; -export const OFFSET = `(Z|[+-]${HOUR}(:${MINUTE})?)`; +export const YEAR = '(?(18|19|20)\\d{2})'; +export const MONTH = '(?0?[1-9]|1[012])'; +export const DAY = '(?0?[1-9]|[12]\\d|3[01])'; +export const WEEK = '(?[0-4]\\d|5[0-2])'; +export const WEEKDAY = '(?[1-7])'; +export const BASE_HOUR = '(0?\\d|1\\d|2[0-4])'; +export const BASE_MINUTE = '(0?\\d|[012345]\\d)'; +export const HOUR = `(?${BASE_MINUTE})`; +export const MINUTE = `(?${BASE_MINUTE})`; +export const SECOND = `(?${BASE_MINUTE})`; +export const MILLISECOND = '(?\\d{1,4})'; +export const YEARDAY = '(?(([0-2]\\d|3[0-5])\\d)|36[0-6])'; +export const OFFSET = `(?Z|[+-]${BASE_HOUR}(:${BASE_MINUTE})?)`; diff --git a/packages/ava/src/data/utils/isType/isDateString.ts b/packages/ava/src/data/utils/isType/isDateString.ts index 7608c1f73..dde7e1daf 100644 --- a/packages/ava/src/data/utils/isType/isDateString.ts +++ b/packages/ava/src/data/utils/isType/isDateString.ts @@ -81,3 +81,45 @@ export function isDateString(value: unknown, strictDatePattern?: boolean): value } return false; } + +/** parse ISO 8601 date string to standard Date type, if month and date is missing, will use new Date(01-01) + * Reference: https://www.cl.cam.ac.uk/~mgk25/iso-time.html + * 将日期字符串转为标准 Date 类型,如果没有月日,只有时间信息,会默认为 01-01 + */ +export function parseIsoDateString(value: string, strictDatePattern: boolean = false): Date | null { + const isoDateAndTimeRegs = getIsoDateAndTimeRegs(strictDatePattern); + for (let i = 0; i < isoDateAndTimeRegs.length; i += 1) { + const reg = isoDateAndTimeRegs[i]; + if (reg.test(value.trim())) { + const matches = value.trim().match(reg); + if (matches.groups) { + const { year, month, day, week, weekday, hour, minute, second, millisecond, yearDay, offset } = + matches.groups || {}; + const yearNum = parseInt(year, 10); + if (yearDay) { + return new Date(yearNum, 0, parseInt(yearDay, 10)); + } + + if (week) { + const weekNum = parseInt(week, 10); + const weekDayNum = weekday ? parseInt(weekday, 10) : 1; + const firstDayOfYear = new Date(yearNum, 0, 1); + // 给定年份的第一天是周几 + const firstDayOfYearDayOfWeek = firstDayOfYear.getDay() === 0 ? 7 : firstDayOfYear.getDay(); + // 计算第一周的第一天相对 firstDayOfYear 的偏移量 + const firstWeekStartDayOffset = firstDayOfYearDayOfWeek === 1 ? 1 : 1 - firstDayOfYearDayOfWeek; + // 目标日期偏移量 + const targetDateOffset = (weekNum - 1) * 7 + (weekDayNum + firstWeekStartDayOffset); + return new Date(yearNum, 0, targetDateOffset); + } + + const formattedDateString = [year, month ?? '01', day ?? '01'].join('-'); + const formattedTimeString = `${[hour ?? '00', minute ?? '00', second ?? '00'].join(':')}.${ + millisecond ?? '000' + }${offset ?? ''}`; + return new Date(`${formattedDateString} ${formattedTimeString}`); + } + } + } + return null; +}