diff --git a/packages/jsapi-utils/src/DateUtils.test.ts b/packages/jsapi-utils/src/DateUtils.test.ts index aabdaf7564..7349844bbf 100644 --- a/packages/jsapi-utils/src/DateUtils.test.ts +++ b/packages/jsapi-utils/src/DateUtils.test.ts @@ -109,6 +109,37 @@ describe('dateTimeString parsing tests', () => { ); }); + it('handles YYYY-xxx', () => { + testDateTimeString('2012-mar', { + year: '2012', + month: 'mar', + }); + + testDateTimeString('2012-march', { + year: '2012', + month: 'march', + }); + }); + + it('handles YYYY-m-d', () => { + testDateTimeString('2012-4-6', { + year: '2012', + month: '4', + date: '6', + }); + + testDateTimeString( + '2012-04-20 overflow', + { + year: '2012', + month: '04', + date: '20', + overflow: ' overflow', + }, + true + ); + }); + it('handles YYYY-mm-dd', () => { testDateTimeString('2012-04-20', { year: '2012', @@ -304,6 +335,7 @@ describe('dateTimeString parsing tests', () => { it('throws an error for invalid dates', () => { testDateTimeStringThrows('not a date'); + testDateTimeStringThrows('2013-231-04'); testDateTimeStringThrows('20133-23-04'); testDateTimeStringThrows('2013-23-043'); testDateTimeStringThrows('2013-23-34zzz'); @@ -449,7 +481,7 @@ describe('getJsDate', () => { }); }); -describe('trimDateTimeStringOverflow', () => { +describe('trimDateTimeStringTimeZone', () => { const dateTimeTexts = [ '2024', '2012-04', @@ -463,7 +495,7 @@ describe('trimDateTimeStringOverflow', () => { it.each(dateTimeTexts)( 'should return given string if no overflow: %s', given => { - const actual = DateUtils.trimDateTimeStringOverflow(given); + const actual = DateUtils.trimDateTimeStringTimeZone(given); expect(actual).toEqual(given); } ); @@ -472,8 +504,29 @@ describe('trimDateTimeStringOverflow', () => { 'should trim date time string overflow: %s', expected => { const given = `${expected} overflow`; - const actual = DateUtils.trimDateTimeStringOverflow(given); + const actual = DateUtils.trimDateTimeStringTimeZone(given); expect(actual).toEqual(expected); } ); + + it.each([ + '2024overflow', + '2012-04overflow', + '2012-04-20overflow', + '2012-04-20T12overflow', + '2012-04-20T12:13overflow', + '2012-04-20T12:13:14overflow', + '2012-04-20T12:13:14.321overflow', + '2024overflow', + '2012-04 overflow', + '2012-04-20 overflow', + '2012-04-20T12 overflow', + '2012-04-20T12:13 overflow', + '2012-04-20T12:13:14 overflow', + '2012-04-20T12:13:14.321 overflow', + ])('should throw for invalid timezone overflow: %s', invalidOverflow => { + expect(() => DateUtils.trimDateTimeStringTimeZone(invalidOverflow)).toThrow( + `Unexpected timezone format in overflow: '${invalidOverflow}'` + ); + }); }); diff --git a/packages/jsapi-utils/src/DateUtils.ts b/packages/jsapi-utils/src/DateUtils.ts index 95311ab9de..59090ba317 100644 --- a/packages/jsapi-utils/src/DateUtils.ts +++ b/packages/jsapi-utils/src/DateUtils.ts @@ -1,7 +1,7 @@ import type { dh as DhType } from '@deephaven/jsapi-types'; const DATE_TIME_REGEX = - /\s*(\d{4})([-./]([\da-z]+))?([-./](\d{1,2}))?([tT\s](\d{2})([:](\d{2}))?([:](\d{2}))?([.](\d{1,9}))?)?(.*)/; + /\s*(\d{4})([-./](\d{1,2}|[a-z]+))?([-./](\d{1,2}))?([tT\s](\d{2})([:](\d{2}))?([:](\d{2}))?([.](\d{1,9}))?)?(.*)/; interface DateParts { year: T; @@ -247,6 +247,7 @@ export class DateUtils { nanos, overflow, ] = result; + if (!allowOverflow && overflow != null && overflow.length > 0) { throw new Error( `Unexpected characters after date string '${dateTimeString}': ${overflow}` @@ -388,7 +389,7 @@ export class DateUtils { * @param dateTimeString The date time string to trim * @returns The date time string without overflow */ - static trimDateTimeStringOverflow(dateTimeString: string): string { + static trimDateTimeStringTimeZone(dateTimeString: string): string { const { overflow = '' } = DateUtils.parseDateTimeString( dateTimeString, true @@ -398,6 +399,14 @@ export class DateUtils { return dateTimeString; } + // Expecting timezone overflow to be a single space followed by some + // combination of letters + if (!/^\s[A-Za-z]+/.test(overflow)) { + throw new Error( + `Unexpected timezone format in overflow: '${dateTimeString}'` + ); + } + return dateTimeString.slice(0, -overflow.length); } } diff --git a/packages/jsapi-utils/src/TableUtils.test.ts b/packages/jsapi-utils/src/TableUtils.test.ts index 73f0927ff8..694ce574e9 100644 --- a/packages/jsapi-utils/src/TableUtils.test.ts +++ b/packages/jsapi-utils/src/TableUtils.test.ts @@ -659,7 +659,7 @@ describe('makeFilterValue', () => { const value = '2023-12-21'; mockTableUtils.makeFilterValue(columnType, value); expect(mockDh.FilterValue.ofNumber).toHaveBeenCalledWith( - DateUtils.trimDateTimeStringOverflow(value) + DateUtils.trimDateTimeStringTimeZone(value) ); }); @@ -773,7 +773,7 @@ describe('makeSearchTextFilter', () => { mockTableUtils.makeQuickDateFilterWithOperation ).toHaveBeenCalledWith( dateColumn, - DateUtils.trimDateTimeStringOverflow(searchText), + DateUtils.trimDateTimeStringTimeZone(searchText), 'eq', mockTimeZone ); diff --git a/packages/jsapi-utils/src/TableUtils.ts b/packages/jsapi-utils/src/TableUtils.ts index fffeb1d729..4312be8c46 100644 --- a/packages/jsapi-utils/src/TableUtils.ts +++ b/packages/jsapi-utils/src/TableUtils.ts @@ -1719,7 +1719,7 @@ export class TableUtils { if (valueType === this.dh.ValueType.DATETIME) { return this.makeQuickDateFilterWithOperation( column, - DateUtils.trimDateTimeStringOverflow(searchText), + DateUtils.trimDateTimeStringTimeZone(searchText), 'eq', timeZone ); @@ -1889,7 +1889,7 @@ export class TableUtils { } if (TableUtils.isDateType(type)) { return dh.FilterValue.ofNumber( - DateUtils.trimDateTimeStringOverflow(value) as unknown as number + DateUtils.trimDateTimeStringTimeZone(value) as unknown as number ); }