Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ava/data): add function to parse ISO date string to js Date type #770

Merged
merged 3 commits into from
Mar 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 38 additions & 8 deletions packages/ava/__tests__/unit/data/utils/isType/isDateString.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
getIsoDatePatterns,
getIsoTimePatterns,
isDateString,
parseIsoDateString,
} from '../../../../../src/data/utils/isType/isDateString';

describe('func: isDateString', () => {
Expand Down Expand Up @@ -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])',
'(?<year>(18|19|20)\\d{2})',
'(?<year>(18|19|20)\\d{2})([-_./\\s])?W(?<week>[0-4]\\d|5[0-2])(([-_./\\s])?(?<weekday>[1-7]))?',
'(?<month>0?[1-9]|1[012])([-_./\\s])?(?<day>0?[1-9]|[12]\\d|3[01])([-_./\\s])?(?<year>(18|19|20)\\d{2})',
'(?<year>(18|19|20)\\d{2})([-_./\\s])?(?<month>0?[1-9]|1[012])([-_./\\s])?(?<day>0?[1-9]|[12]\\d|3[01])',
'(?<year>(18|19|20)\\d{2})([-_./\\s])?(?<month>0?[1-9]|1[012])',
'(?<year>(18|19|20)\\d{2})([-_./\\s])?(?<yearDay>(([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))?)',
'(?<hour>(0?\\d|[012345]\\d)):?(?<minute>(0?\\d|[012345]\\d)):?(?<second>(0?\\d|[012345]\\d))([.,](?<millisecond>\\d{1,4}))?(?<offset>Z|[+-](0?\\d|1\\d|2[0-4])(:(0?\\d|[012345]\\d))?)?',
'(?<hour>(0?\\d|[012345]\\d)):?(?<minute>(0?\\d|[012345]\\d))?(?<offset>Z|[+-](0?\\d|1\\d|2[0-4])(:(0?\\d|[012345]\\d))?)',
]);
});
Original file line number Diff line number Diff line change
Expand Up @@ -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: [] };
BBSQQ marked this conversation as resolved.
Show resolved Hide resolved
}
}
24 changes: 13 additions & 11 deletions packages/ava/src/data/utils/isType/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = '(?<year>(18|19|20)\\d{2})';
export const MONTH = '(?<month>0?[1-9]|1[012])';
export const DAY = '(?<day>0?[1-9]|[12]\\d|3[01])';
export const WEEK = '(?<week>[0-4]\\d|5[0-2])';
export const WEEKDAY = '(?<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 = `(?<hour>${BASE_MINUTE})`;
export const MINUTE = `(?<minute>${BASE_MINUTE})`;
export const SECOND = `(?<second>${BASE_MINUTE})`;
export const MILLISECOND = '(?<millisecond>\\d{1,4})';
export const YEARDAY = '(?<yearDay>(([0-2]\\d|3[0-5])\\d)|36[0-6])';
export const OFFSET = `(?<offset>Z|[+-]${BASE_HOUR}(:${BASE_MINUTE})?)`;
42 changes: 42 additions & 0 deletions packages/ava/src/data/utils/isType/isDateString.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
BBSQQ marked this conversation as resolved.
Show resolved Hide resolved
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;
}