diff --git a/packages/datasource-toolkit/src/interfaces/query/condition-tree/equivalence.ts b/packages/datasource-toolkit/src/interfaces/query/condition-tree/equivalence.ts index 8aa8a6502..ede0ab846 100644 --- a/packages/datasource-toolkit/src/interfaces/query/condition-tree/equivalence.ts +++ b/packages/datasource-toolkit/src/interfaces/query/condition-tree/equivalence.ts @@ -6,7 +6,11 @@ import patternTransforms from './transforms/pattern'; import timeTransforms from './transforms/time'; import { ColumnType, PrimitiveTypes } from '../../schema'; -export type Replacer = (leaf: ConditionTreeLeaf, timezone: string) => ConditionTree; +export type Replacer = ( + leaf: ConditionTreeLeaf, + timezone: string, + isDateOnly?: boolean, +) => ConditionTree; export type Alternative = { dependsOn: Operator[]; @@ -29,7 +33,7 @@ export default class ConditionTreeEquivalent { if (!replacer) return leaf; - return replacer ? replacer(leaf, timezone) : null; + return replacer ? replacer(leaf, timezone, columnType === 'Dateonly') : null; } static hasEquivalentTree( @@ -59,9 +63,9 @@ export default class ConditionTreeEquivalent { }); if (dependsReplacers.every(r => !!r)) { - return (leaf, timezone) => - replacer(leaf, timezone).replaceLeafs(subLeaf => - dependsReplacers[dependsOn.indexOf(subLeaf.operator)](subLeaf, timezone), + return (leaf, timezone, isDateOnly) => + replacer(leaf, timezone, isDateOnly).replaceLeafs(subLeaf => + dependsReplacers[dependsOn.indexOf(subLeaf.operator)](subLeaf, timezone, isDateOnly), ); } } diff --git a/packages/datasource-toolkit/src/interfaces/query/condition-tree/transforms/time.ts b/packages/datasource-toolkit/src/interfaces/query/condition-tree/transforms/time.ts index ab98a15ac..d573b29b0 100644 --- a/packages/datasource-toolkit/src/interfaces/query/condition-tree/transforms/time.ts +++ b/packages/datasource-toolkit/src/interfaces/query/condition-tree/transforms/time.ts @@ -6,7 +6,9 @@ import { Operator } from '../nodes/operators'; type DateCallback = (now: DateTime, value: unknown) => DateTime; -function format(value: DateTime): string { +function format(value: DateTime, isDateOnly: boolean): string { + if (isDateOnly) return value.toISODate(); + return value.toUTC().toISO({ suppressMilliseconds: true }); } @@ -14,24 +16,27 @@ function compare(operator: Operator, dateFn: DateCallback): Alternative { return { dependsOn: [operator], forTypes: ['Date', 'Dateonly'], - replacer: (leaf, tz) => { + replacer: (leaf, tz, isDateOnly) => { const now = DateTime.utc().setZone(tz); - return leaf.override({ operator, value: format(dateFn(now, leaf.value)) }); + return leaf.override({ operator, value: format(dateFn(now, leaf.value), isDateOnly) }); }, }; } function interval(startFn: DateCallback, endFn: DateCallback): Alternative { return { - dependsOn: ['LessThan', 'GreaterThan'], + dependsOn: ['LessThan', 'GreaterThan', 'GreaterThanOrEqual'], forTypes: ['Date', 'Dateonly'], - replacer: (leaf, tz) => { + replacer: (leaf, tz, isDateOnly) => { const now = DateTime.utc().setZone(tz); return ConditionTreeFactory.intersect( - leaf.override({ operator: 'GreaterThan', value: format(startFn(now, leaf.value)) }), - leaf.override({ operator: 'LessThan', value: format(endFn(now, leaf.value)) }), + leaf.override({ + operator: isDateOnly ? 'GreaterThanOrEqual' : 'GreaterThan', + value: format(startFn(now, leaf.value), isDateOnly), + }), + leaf.override({ operator: 'LessThan', value: format(endFn(now, leaf.value), isDateOnly) }), ); }, }; diff --git a/packages/datasource-toolkit/test/interfaces/transforms/dateonly.test.ts b/packages/datasource-toolkit/test/interfaces/transforms/dateonly.test.ts new file mode 100644 index 000000000..c7f633d51 --- /dev/null +++ b/packages/datasource-toolkit/test/interfaces/transforms/dateonly.test.ts @@ -0,0 +1,298 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ +import ConditionTreeLeaf from '../../../src/interfaces/query/condition-tree/nodes/leaf'; +import makeAlternatives from '../../../src/interfaces/query/condition-tree/transforms/time'; + +describe('ConditionTreeOperators > Time > DateOnly', () => { + const alternatives = makeAlternatives(); + + beforeAll(() => { + // https://static.wikia.nocookie.net/bttf/images/d/d5/Time_Circuits_BTTF.png + const date = new Date('2024-12-17T23:00:00-00:00'); + jest.useFakeTimers().setSystemTime(date); + }); + + describe('Before', () => { + test('should rewrite', () => { + expect( + alternatives.Before![0].replacer( + new ConditionTreeLeaf('column', 'Before', '2010-01-01'), + 'Europe/Paris', + true, + ), + ).toEqual({ + field: 'column', + operator: 'LessThan', + value: '2010-01-01', + }); + }); + }); + + describe('After', () => { + test('should rewrite', () => { + expect( + alternatives.After![0].replacer( + new ConditionTreeLeaf('column', 'After', '2010-01-01'), + 'Europe/Paris', + true, + ), + ).toEqual({ + field: 'column', + operator: 'GreaterThan', + value: '2010-01-01', + }); + }); + }); + + describe('Past', () => { + test('should rewrite', () => { + expect( + alternatives.Past![0].replacer( + new ConditionTreeLeaf('column', 'Past'), + 'Europe/Paris', + true, + ), + ).toEqual({ + field: 'column', + operator: 'LessThan', + value: '2024-12-18', + }); + }); + }); + + describe('Future', () => { + test('should rewrite', () => { + expect( + alternatives.Future![0].replacer( + new ConditionTreeLeaf('column', 'Future'), + 'Europe/Paris', + true, + ), + ).toEqual({ + field: 'column', + operator: 'GreaterThan', + value: '2024-12-18', + }); + }); + }); + + describe('PreviousMonthToDate', () => { + test('should rewrite', () => { + expect( + alternatives.PreviousMonthToDate![0].replacer( + new ConditionTreeLeaf('column', 'PreviousMonthToDate'), + 'Europe/Paris', + true, + ), + ).toEqual({ + aggregator: 'And', + conditions: [ + { field: 'column', operator: 'GreaterThanOrEqual', value: '2024-12-01' }, + { field: 'column', operator: 'LessThan', value: '2024-12-18' }, + ], + }); + }); + }); + + describe('PreviousMonth', () => { + test('should rewrite', () => { + expect( + alternatives.PreviousMonth![0].replacer( + new ConditionTreeLeaf('column', 'PreviousMonth'), + 'Europe/Paris', + true, + ), + ).toEqual({ + aggregator: 'And', + conditions: [ + { field: 'column', operator: 'GreaterThanOrEqual', value: '2024-11-01' }, + { field: 'column', operator: 'LessThan', value: '2024-12-01' }, + ], + }); + }); + }); + + describe('PreviousQuarterToDate', () => { + test('should rewrite', () => { + expect( + alternatives.PreviousQuarterToDate![0].replacer( + new ConditionTreeLeaf('column', 'PreviousQuarterToDate'), + 'Europe/Paris', + true, + ), + ).toEqual({ + aggregator: 'And', + conditions: [ + { field: 'column', operator: 'GreaterThanOrEqual', value: '2024-10-01' }, + { field: 'column', operator: 'LessThan', value: '2024-12-18' }, + ], + }); + }); + }); + + describe('PreviousQuarter', () => { + test('should rewrite', () => { + expect( + alternatives.PreviousQuarter![0].replacer( + new ConditionTreeLeaf('column', 'PreviousQuarter'), + 'Europe/Paris', + true, + ), + ).toEqual({ + aggregator: 'And', + conditions: [ + { field: 'column', operator: 'GreaterThanOrEqual', value: '2024-07-01' }, + { field: 'column', operator: 'LessThan', value: '2024-10-01' }, + ], + }); + }); + }); + + describe('PreviousWeekToDate', () => { + test('should rewrite', () => { + expect( + alternatives.PreviousWeekToDate![0].replacer( + new ConditionTreeLeaf('column', 'PreviousWeekToDate'), + 'Europe/Paris', + true, + ), + ).toEqual({ + aggregator: 'And', + conditions: [ + { field: 'column', operator: 'GreaterThanOrEqual', value: '2024-12-16' }, + { field: 'column', operator: 'LessThan', value: '2024-12-18' }, + ], + }); + }); + }); + + describe('PreviousWeek', () => { + test('should rewrite', () => { + // Note that luxon always consider weeks to be from monday to friday + // @see https://github.com/moment/luxon/issues/373#issuecomment-441123720 + + expect( + alternatives.PreviousWeek![0].replacer( + new ConditionTreeLeaf('column', 'PreviousWeek'), + 'Europe/Paris', + true, + ), + ).toEqual({ + aggregator: 'And', + conditions: [ + { field: 'column', operator: 'GreaterThanOrEqual', value: '2024-12-09' }, + { field: 'column', operator: 'LessThan', value: '2024-12-16' }, + ], + }); + }); + }); + + describe('PreviousXDaysToDate', () => { + test('should rewrite', () => { + expect( + alternatives.PreviousXDaysToDate![0].replacer( + new ConditionTreeLeaf('column', 'PreviousXDaysToDate', 14), + 'Europe/Paris', + true, + ), + ).toEqual({ + aggregator: 'And', + conditions: [ + { field: 'column', operator: 'GreaterThanOrEqual', value: '2024-12-04' }, + { field: 'column', operator: 'LessThan', value: '2024-12-18' }, + ], + }); + }); + }); + + describe('PreviousXDays', () => { + test('should rewrite', () => { + expect( + alternatives.PreviousXDays![0].replacer( + new ConditionTreeLeaf('column', 'PreviousXDays', 14), + 'Europe/Paris', + true, + ), + ).toEqual({ + aggregator: 'And', + conditions: [ + { field: 'column', operator: 'GreaterThanOrEqual', value: '2024-12-04' }, + { field: 'column', operator: 'LessThan', value: '2024-12-18' }, + ], + }); + }); + }); + + describe('PreviousYearToDate', () => { + test('should rewrite', () => { + expect( + alternatives.PreviousYearToDate![0].replacer( + new ConditionTreeLeaf('column', 'PreviousYearToDate'), + 'Europe/Paris', + true, + ), + ).toEqual({ + aggregator: 'And', + conditions: [ + { field: 'column', operator: 'GreaterThanOrEqual', value: '2024-01-01' }, + { field: 'column', operator: 'LessThan', value: '2024-12-18' }, + ], + }); + }); + }); + + describe('PreviousYear', () => { + test('should rewrite', () => { + // Notice daylight saving time in this test, as it's january + + expect( + alternatives.PreviousYear![0].replacer( + new ConditionTreeLeaf('column', 'PreviousYear'), + 'Europe/Paris', + true, + ), + ).toEqual({ + aggregator: 'And', + conditions: [ + { field: 'column', operator: 'GreaterThanOrEqual', value: '2023-01-01' }, + { field: 'column', operator: 'LessThan', value: '2024-01-01' }, + ], + }); + }); + }); + + describe('Today', () => { + test('should rewrite', () => { + expect( + alternatives.Today![0].replacer( + new ConditionTreeLeaf('column', 'Today'), + 'Europe/Paris', + true, + ), + ).toEqual({ + aggregator: 'And', + conditions: [ + { field: 'column', operator: 'GreaterThanOrEqual', value: '2024-12-18' }, + { field: 'column', operator: 'LessThan', value: '2024-12-19' }, + ], + }); + }); + }); + + describe('Yesterday', () => { + test('should rewrite', () => { + expect( + alternatives.Yesterday![0].replacer( + new ConditionTreeLeaf('column', 'Yesterday'), + 'Europe/Paris', + true, + ), + ).toEqual({ + aggregator: 'And', + conditions: [ + { field: 'column', operator: 'GreaterThanOrEqual', value: '2024-12-17' }, + { field: 'column', operator: 'LessThan', value: '2024-12-18' }, + ], + }); + }); + }); +});