@malloydata/malloy-filter 0.0.237-dev250224203840 → 0.0.237-dev250225015031
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/SAMPLES.md +336 -116
- package/SERIALIZE_SAMPLES.md +268 -64
- package/dist/a_simple_parser.js +6 -0
- package/dist/a_simple_parser.js.map +1 -1
- package/dist/base_parser.js +6 -0
- package/dist/base_parser.js.map +1 -1
- package/dist/boolean_parser.js +28 -13
- package/dist/boolean_parser.js.map +1 -1
- package/dist/boolean_serializer.js +12 -6
- package/dist/boolean_serializer.js.map +1 -1
- package/dist/clause_types.d.ts +20 -15
- package/dist/clause_types.js +6 -0
- package/dist/clause_types.js.map +1 -1
- package/dist/date_parser.d.ts +1 -0
- package/dist/date_parser.js +174 -116
- package/dist/date_parser.js.map +1 -1
- package/dist/date_serializer.js +26 -20
- package/dist/date_serializer.js.map +1 -1
- package/dist/date_types.d.ts +21 -21
- package/dist/date_types.js +6 -0
- package/dist/date_types.js.map +1 -1
- package/dist/generate_samples.js +41 -26
- package/dist/generate_samples.js.map +1 -1
- package/dist/index.d.ts +10 -0
- package/dist/index.js +42 -0
- package/dist/index.js.map +1 -0
- package/dist/number_parser.js +43 -25
- package/dist/number_parser.js.map +1 -1
- package/dist/number_serializer.js +10 -4
- package/dist/number_serializer.js.map +1 -1
- package/dist/string_parser.d.ts +0 -1
- package/dist/string_parser.js +47 -79
- package/dist/string_parser.js.map +1 -1
- package/dist/string_serializer.d.ts +1 -0
- package/dist/string_serializer.js +49 -33
- package/dist/string_serializer.js.map +1 -1
- package/dist/token_types.js +6 -0
- package/dist/token_types.js.map +1 -1
- package/dist/tokenizer.js +9 -3
- package/dist/tokenizer.js.map +1 -1
- package/dist/tokenizer.spec.js +13 -7
- package/dist/tokenizer.spec.js.map +1 -1
- package/package.json +2 -2
- package/src/a_simple_parser.ts +7 -0
- package/src/base_parser.ts +7 -0
- package/src/boolean_parser.ts +30 -18
- package/src/boolean_serializer.ts +13 -6
- package/src/clause_types.ts +36 -31
- package/src/date_parser.ts +177 -115
- package/src/date_serializer.ts +27 -20
- package/src/date_types.ts +42 -34
- package/src/generate_samples.ts +42 -26
- package/src/index.ts +17 -0
- package/src/number_parser.ts +45 -26
- package/src/number_serializer.ts +11 -4
- package/src/string_parser.ts +51 -79
- package/src/string_serializer.ts +65 -39
- package/src/token_types.ts +7 -0
- package/src/tokenizer.spec.ts +14 -7
- package/src/tokenizer.ts +10 -3
package/src/date_parser.ts
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
|
|
1
8
|
import {SpecialToken, Tokenizer, TokenizerParams} from './tokenizer';
|
|
2
9
|
import {
|
|
3
10
|
DateTimeUnit,
|
|
@@ -21,9 +28,9 @@ import {
|
|
|
21
28
|
} from './date_types';
|
|
22
29
|
import {BaseParser} from './base_parser';
|
|
23
30
|
import {Token} from './token_types';
|
|
24
|
-
import {
|
|
31
|
+
import {FilterLog} from './clause_types';
|
|
25
32
|
|
|
26
|
-
type DatePrefix = '
|
|
33
|
+
type DatePrefix = 'before' | 'after';
|
|
27
34
|
|
|
28
35
|
export class DateParser extends BaseParser {
|
|
29
36
|
private static readonly yearRegex: RegExp = /[%_]/;
|
|
@@ -35,38 +42,49 @@ export class DateParser extends BaseParser {
|
|
|
35
42
|
|
|
36
43
|
private tokenize(): void {
|
|
37
44
|
const specialSubstrings: SpecialToken[] = [{type: ',', value: ','}];
|
|
45
|
+
// Do not reorder.
|
|
38
46
|
const specialWords: SpecialToken[] = [
|
|
39
47
|
{
|
|
40
|
-
type: '
|
|
48
|
+
type: 'unitoftime',
|
|
41
49
|
value: /^(second|minute|hour|day|week|month|quarter|year)s?$/i,
|
|
42
50
|
ignoreCase: true,
|
|
43
51
|
},
|
|
44
52
|
{
|
|
45
|
-
type: '
|
|
53
|
+
type: 'dayofweek',
|
|
46
54
|
value: /^(monday|tuesday|wednesday|thursday|friday|saturday|sunday)$/i,
|
|
47
55
|
ignoreCase: true,
|
|
48
56
|
},
|
|
49
|
-
{type: '
|
|
50
|
-
{type: '
|
|
51
|
-
{type: '
|
|
52
|
-
{type: '
|
|
53
|
-
{
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
{type: '
|
|
58
|
-
{type: '
|
|
59
|
-
{type: '
|
|
60
|
-
{type: '
|
|
61
|
-
{type: '
|
|
62
|
-
{type: '
|
|
63
|
-
{type: '
|
|
64
|
-
{type: '
|
|
65
|
-
{type: '
|
|
66
|
-
{type: '
|
|
67
|
-
{type: '
|
|
68
|
-
{type: '
|
|
69
|
-
{type: '
|
|
57
|
+
{type: 'date', value: /^\d{4}-\d{2}-\d{2}T\d\d$/},
|
|
58
|
+
{type: 'date', value: /^\d{4}-\d{2}-\d{2}$/},
|
|
59
|
+
{type: 'date', value: /^\d{4}-\d{2}$/},
|
|
60
|
+
{type: 'date', value: /^\d{4}-[Qq][1234]$/},
|
|
61
|
+
{
|
|
62
|
+
type: 'date',
|
|
63
|
+
value: /^\d{4}-\d{2}-\d{2}T\d\d:\d\d:\d\d\[[a-zA-Z_/]*\]$/,
|
|
64
|
+
},
|
|
65
|
+
{type: 'date', value: /^\d{4}-\d{2}-\d{2}T\d\d:\d\d:\d\d[.,]\d+$/},
|
|
66
|
+
{type: 'date', value: /^\d{4}-\d{2}-\d{2}T\d\d:\d\d:\d\d$/},
|
|
67
|
+
{type: 'date', value: /^\d{4}-\d{2}-\d{2}T\d\d:\d\d$/},
|
|
68
|
+
{type: 'time', value: /^\d\d:\d\d:\d\d\[[a-zA-Z_/]*\]$/},
|
|
69
|
+
{type: 'time', value: /^\d\d:\d\d:\d\d[.,]\d+$/},
|
|
70
|
+
{type: 'time', value: /^\d\d:\d\d:\d\d$/},
|
|
71
|
+
{type: 'time', value: /^\d\d:\d\d$/},
|
|
72
|
+
{type: 'not_null', value: '-null', ignoreCase: true},
|
|
73
|
+
{type: 'null', value: 'null', ignoreCase: true},
|
|
74
|
+
{type: 'prefix', value: /^(before|after)/i, ignoreCase: true},
|
|
75
|
+
{type: 'today', value: 'today', ignoreCase: true},
|
|
76
|
+
{type: 'yesterday', value: 'yesterday', ignoreCase: true},
|
|
77
|
+
{type: 'tomorrow', value: 'tomorrow', ignoreCase: true},
|
|
78
|
+
{type: 'now', value: 'now', ignoreCase: true},
|
|
79
|
+
{type: 'this', value: 'this', ignoreCase: true},
|
|
80
|
+
{type: 'last', value: 'last', ignoreCase: true},
|
|
81
|
+
{type: 'next', value: 'next', ignoreCase: true},
|
|
82
|
+
{type: 'ago', value: 'ago', ignoreCase: true},
|
|
83
|
+
{type: 'from', value: 'from', ignoreCase: true},
|
|
84
|
+
{type: 'for', value: 'for', ignoreCase: true},
|
|
85
|
+
{type: 'to', value: 'to', ignoreCase: true},
|
|
86
|
+
{type: 'year', value: /^\d\d\d\d$/}, // Years are ambiguous, and require special handling.
|
|
87
|
+
{type: 'number', value: /^[\d.]+/, ignoreCase: true},
|
|
70
88
|
];
|
|
71
89
|
const params: TokenizerParams = {
|
|
72
90
|
trimWordWhitespace: true,
|
|
@@ -77,37 +95,62 @@ export class DateParser extends BaseParser {
|
|
|
77
95
|
|
|
78
96
|
const tokenizer = new Tokenizer(this.inputString, params);
|
|
79
97
|
this.tokens = tokenizer.parse();
|
|
80
|
-
|
|
98
|
+
this.tokens = this.mergeDateTimeTokens();
|
|
81
99
|
this.tokens = this.mergeMomentTokens();
|
|
82
100
|
}
|
|
83
101
|
|
|
102
|
+
private mergeDateTimeTokens(): Token[] {
|
|
103
|
+
const output: Token[] = [];
|
|
104
|
+
this.index = 0;
|
|
105
|
+
let previous: Token | undefined = undefined;
|
|
106
|
+
while (this.index < this.tokens.length) {
|
|
107
|
+
const token = this.tokens[this.index];
|
|
108
|
+
if (
|
|
109
|
+
previous &&
|
|
110
|
+
previous.type === 'date' &&
|
|
111
|
+
previous.value.length >= 10 &&
|
|
112
|
+
(token.type === 'time' ||
|
|
113
|
+
(token.type === 'number' && token.value.length === 2))
|
|
114
|
+
) {
|
|
115
|
+
previous.value = previous.value + ' ' + token.value;
|
|
116
|
+
previous.endIndex = token.endIndex;
|
|
117
|
+
previous = undefined;
|
|
118
|
+
} else {
|
|
119
|
+
previous = token;
|
|
120
|
+
output.push(token);
|
|
121
|
+
}
|
|
122
|
+
this.index++;
|
|
123
|
+
}
|
|
124
|
+
return output;
|
|
125
|
+
}
|
|
126
|
+
|
|
84
127
|
private mergeMomentTokens(): Token[] {
|
|
85
128
|
const output: Token[] = [];
|
|
86
129
|
this.index = 0;
|
|
87
130
|
while (this.index < this.tokens.length) {
|
|
131
|
+
// Do not reorder.
|
|
88
132
|
if (
|
|
89
|
-
this.matchAndMerge('
|
|
90
|
-
this.matchAndMerge('
|
|
91
|
-
this.matchAndMerge('
|
|
92
|
-
this.matchAndMerge('
|
|
93
|
-
this.matchAndMerge('
|
|
94
|
-
this.matchAndMerge('
|
|
95
|
-
this.matchAndMerge('
|
|
96
|
-
this.matchAndMerge('
|
|
97
|
-
this.matchAndMerge('
|
|
98
|
-
this.matchAndMerge('
|
|
99
|
-
this.matchAndMerge('
|
|
100
|
-
this.matchAndMerge('
|
|
101
|
-
this.matchAndMerge('
|
|
102
|
-
this.matchAndMerge('
|
|
103
|
-
this.matchAndMerge('
|
|
104
|
-
this.matchAndMerge('
|
|
105
|
-
this.matchAndMerge('
|
|
106
|
-
this.matchAndMerge('
|
|
107
|
-
this.matchAndMerge('
|
|
108
|
-
this.matchAndMerge('
|
|
109
|
-
this.matchAndMerge('
|
|
110
|
-
this.matchAndMerge('NOW', output)
|
|
133
|
+
this.matchAndMerge('last|unitoftime', output) ||
|
|
134
|
+
this.matchAndMerge('last|dayofweek', output) ||
|
|
135
|
+
this.matchAndMerge('last|number|unitoftime', output) ||
|
|
136
|
+
this.matchAndMerge('last|year|unitoftime', output) ||
|
|
137
|
+
this.matchAndMerge('this|unitoftime', output) ||
|
|
138
|
+
this.matchAndMerge('next|unitoftime', output) ||
|
|
139
|
+
this.matchAndMerge('next|dayofweek', output) ||
|
|
140
|
+
this.matchAndMerge('next|number|unitoftime', output) ||
|
|
141
|
+
this.matchAndMerge('next|year|unitoftime', output) ||
|
|
142
|
+
this.matchAndMerge('number|unitoftime|ago', output) ||
|
|
143
|
+
this.matchAndMerge('year|unitoftime|ago', output) ||
|
|
144
|
+
this.matchAndMerge('number|unitoftime|from|now', output) ||
|
|
145
|
+
this.matchAndMerge('year|unitoftime|from|now', output) ||
|
|
146
|
+
this.matchAndMerge('number|unitoftime', output) ||
|
|
147
|
+
this.matchAndMerge('year|unitoftime', output) ||
|
|
148
|
+
this.matchAndMerge('today', output) ||
|
|
149
|
+
this.matchAndMerge('yesterday', output) ||
|
|
150
|
+
this.matchAndMerge('tomorrow', output) ||
|
|
151
|
+
this.matchAndMerge('date', output) ||
|
|
152
|
+
this.matchAndMerge('year', output) ||
|
|
153
|
+
this.matchAndMerge('now', output)
|
|
111
154
|
) {
|
|
112
155
|
continue;
|
|
113
156
|
} else {
|
|
@@ -139,25 +182,34 @@ export class DateParser extends BaseParser {
|
|
|
139
182
|
this.tokenize();
|
|
140
183
|
let prefix: DatePrefix | undefined = undefined;
|
|
141
184
|
const clauses: DateClause[] = [];
|
|
142
|
-
const
|
|
185
|
+
const logs: FilterLog[] = [];
|
|
143
186
|
this.index = 0;
|
|
144
187
|
while (this.index < this.tokens.length) {
|
|
145
188
|
const token = this.getNext();
|
|
146
189
|
if (token.type === ',') {
|
|
147
190
|
if (prefix) {
|
|
148
|
-
|
|
191
|
+
logs.push({
|
|
192
|
+
severity: 'error',
|
|
149
193
|
message: 'Invalid ' + prefix,
|
|
150
194
|
startIndex: token.startIndex,
|
|
151
195
|
endIndex: token.endIndex,
|
|
152
196
|
});
|
|
197
|
+
} else if (this.index > 0 && this.tokens[this.index - 1].type === ',') {
|
|
198
|
+
logs.push({
|
|
199
|
+
severity: 'warn',
|
|
200
|
+
message: 'Empty clause',
|
|
201
|
+
startIndex: token.startIndex,
|
|
202
|
+
endIndex: token.endIndex,
|
|
203
|
+
});
|
|
153
204
|
}
|
|
154
205
|
this.index++;
|
|
155
|
-
} else if (token.type === '
|
|
206
|
+
} else if (token.type === 'prefix') {
|
|
156
207
|
prefix = token.value as DatePrefix;
|
|
157
208
|
this.index++;
|
|
158
209
|
} else if (this.handleRange(clauses)) {
|
|
159
210
|
if (prefix) {
|
|
160
|
-
|
|
211
|
+
logs.push({
|
|
212
|
+
severity: 'error',
|
|
161
213
|
message: 'Invalid ' + prefix,
|
|
162
214
|
startIndex: token.startIndex,
|
|
163
215
|
endIndex: token.endIndex,
|
|
@@ -166,8 +218,13 @@ export class DateParser extends BaseParser {
|
|
|
166
218
|
}
|
|
167
219
|
} else if (this.handleMerged(prefix, clauses)) {
|
|
168
220
|
prefix = undefined;
|
|
221
|
+
} else if (token.type === 'null' || token.type === 'not_null') {
|
|
222
|
+
prefix = undefined;
|
|
223
|
+
clauses.push({operator: token.type});
|
|
224
|
+
this.index++;
|
|
169
225
|
} else {
|
|
170
|
-
|
|
226
|
+
logs.push({
|
|
227
|
+
severity: 'error',
|
|
171
228
|
message:
|
|
172
229
|
'Invalid token ' + token.value ||
|
|
173
230
|
(token.values ? token.values.join(' ') : ''),
|
|
@@ -178,7 +235,7 @@ export class DateParser extends BaseParser {
|
|
|
178
235
|
this.index++;
|
|
179
236
|
}
|
|
180
237
|
}
|
|
181
|
-
return {clauses,
|
|
238
|
+
return {clauses, logs};
|
|
182
239
|
}
|
|
183
240
|
|
|
184
241
|
private static createMomentClause(
|
|
@@ -186,11 +243,11 @@ export class DateParser extends BaseParser {
|
|
|
186
243
|
moment: DateMoment
|
|
187
244
|
): DateClause {
|
|
188
245
|
if (!prefix) {
|
|
189
|
-
return {operator: '
|
|
190
|
-
} else if (prefix === '
|
|
191
|
-
return {operator: '
|
|
246
|
+
return {operator: 'on', moment}; // DateOnClause
|
|
247
|
+
} else if (prefix === 'before') {
|
|
248
|
+
return {operator: 'before', moment}; // DateBeforeClause
|
|
192
249
|
} else {
|
|
193
|
-
return {operator: '
|
|
250
|
+
return {operator: 'after', moment}; // DateAfterClause
|
|
194
251
|
}
|
|
195
252
|
}
|
|
196
253
|
|
|
@@ -204,7 +261,7 @@ export class DateParser extends BaseParser {
|
|
|
204
261
|
const unit: DateTimeUnit | DateWeekday = tokens[1].value as
|
|
205
262
|
| DateTimeUnit
|
|
206
263
|
| DateWeekday;
|
|
207
|
-
const moment: IntervalMoment = {type: '
|
|
264
|
+
const moment: IntervalMoment = {type: 'interval', kind, unit};
|
|
208
265
|
return DateParser.createMomentClause(prefix, moment);
|
|
209
266
|
}
|
|
210
267
|
|
|
@@ -217,12 +274,12 @@ export class DateParser extends BaseParser {
|
|
|
217
274
|
const amount = Number(tokens[0].value);
|
|
218
275
|
const unit: DateTimeUnit = tokens[1].value as DateTimeUnit;
|
|
219
276
|
const direction: DateMomentOffsetFromNowDirection =
|
|
220
|
-
tokens[2].type === '
|
|
277
|
+
tokens[2].type === 'ago' ? 'ago' : 'from_now';
|
|
221
278
|
if (!DateParser.isValidNumber(amount)) {
|
|
222
279
|
return undefined;
|
|
223
280
|
}
|
|
224
281
|
const moment: OffsetMoment = {
|
|
225
|
-
type: '
|
|
282
|
+
type: 'offset_from_now',
|
|
226
283
|
direction,
|
|
227
284
|
amount,
|
|
228
285
|
unit,
|
|
@@ -238,11 +295,11 @@ export class DateParser extends BaseParser {
|
|
|
238
295
|
const amount = Number(tokens[0].value);
|
|
239
296
|
const unit: DateTimeUnit = tokens[1].value as DateTimeUnit;
|
|
240
297
|
const direction: DateMomentSpanFromNowDirection =
|
|
241
|
-
tokens[2].type === '
|
|
298
|
+
tokens[2].type === 'last' ? 'last' : 'next';
|
|
242
299
|
if (!DateParser.isValidNumber(amount)) {
|
|
243
300
|
return undefined;
|
|
244
301
|
}
|
|
245
|
-
const moment: SpanMoment = {type: '
|
|
302
|
+
const moment: SpanMoment = {type: 'span_from_now', direction, amount, unit};
|
|
246
303
|
return DateParser.createMomentClause(prefix, moment);
|
|
247
304
|
}
|
|
248
305
|
|
|
@@ -254,7 +311,7 @@ export class DateParser extends BaseParser {
|
|
|
254
311
|
if (prefix) {
|
|
255
312
|
return undefined; // before 7 hours is ambiguous, not allowed.
|
|
256
313
|
}
|
|
257
|
-
const operator = '
|
|
314
|
+
const operator = 'duration';
|
|
258
315
|
const amount = Number(tokens[0].value);
|
|
259
316
|
const unit: DateTimeUnit = tokens[1].value as DateTimeUnit;
|
|
260
317
|
if (!DateParser.isValidNumber(amount)) {
|
|
@@ -269,19 +326,27 @@ export class DateParser extends BaseParser {
|
|
|
269
326
|
prefix: DatePrefix | undefined,
|
|
270
327
|
tokens: Token[]
|
|
271
328
|
): DateClause {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
unit = '
|
|
281
|
-
} else if (
|
|
282
|
-
unit = '
|
|
329
|
+
const dateStr = tokens[0].value;
|
|
330
|
+
const matcher: RegExpExecArray | null = /^\d{4}-\d{2}-\d{2}[Tt ](.+)$/.exec(
|
|
331
|
+
dateStr
|
|
332
|
+
);
|
|
333
|
+
const timeStr = matcher ? matcher[1] : '';
|
|
334
|
+
|
|
335
|
+
let unit: DateTimeUnit = 'year';
|
|
336
|
+
if (timeStr.length > 8) {
|
|
337
|
+
unit = 'instant';
|
|
338
|
+
} else if (timeStr.length > 5) {
|
|
339
|
+
unit = 'second';
|
|
340
|
+
} else if (timeStr.length > 2) {
|
|
341
|
+
unit = 'minute';
|
|
342
|
+
} else if (timeStr.length === 2) {
|
|
343
|
+
unit = 'hour';
|
|
344
|
+
} else if (dateStr.length > 7) {
|
|
345
|
+
unit = 'day';
|
|
346
|
+
} else if (dateStr.length > 4) {
|
|
347
|
+
unit = /[qQ]/.test(dateStr) ? 'quarter' : 'month';
|
|
283
348
|
}
|
|
284
|
-
const moment: AbsoluteMoment = {type: '
|
|
349
|
+
const moment: AbsoluteMoment = {type: 'absolute', date: dateStr, unit};
|
|
285
350
|
return DateParser.createMomentClause(prefix, moment);
|
|
286
351
|
}
|
|
287
352
|
|
|
@@ -290,19 +355,19 @@ export class DateParser extends BaseParser {
|
|
|
290
355
|
prefix: DatePrefix | undefined,
|
|
291
356
|
tokens: Token[]
|
|
292
357
|
): DateClause {
|
|
293
|
-
let momentName: DateMomentName = '
|
|
358
|
+
let momentName: DateMomentName = 'now';
|
|
294
359
|
switch (tokens[0].type) {
|
|
295
|
-
case '
|
|
296
|
-
momentName = '
|
|
360
|
+
case 'today':
|
|
361
|
+
momentName = 'today';
|
|
297
362
|
break;
|
|
298
|
-
case '
|
|
299
|
-
momentName = '
|
|
363
|
+
case 'yesterday':
|
|
364
|
+
momentName = 'yesterday';
|
|
300
365
|
break;
|
|
301
|
-
case '
|
|
302
|
-
momentName = '
|
|
366
|
+
case 'tomorrow':
|
|
367
|
+
momentName = 'tomorrow';
|
|
303
368
|
break;
|
|
304
369
|
}
|
|
305
|
-
const moment: NamedMoment = {type: '
|
|
370
|
+
const moment: NamedMoment = {type: 'named', name: momentName};
|
|
306
371
|
return DateParser.createMomentClause(prefix, moment);
|
|
307
372
|
}
|
|
308
373
|
|
|
@@ -315,8 +380,8 @@ export class DateParser extends BaseParser {
|
|
|
315
380
|
return undefined;
|
|
316
381
|
}
|
|
317
382
|
if (
|
|
318
|
-
token.type === 'MERGE:
|
|
319
|
-
token.type === 'MERGE:
|
|
383
|
+
token.type === 'MERGE:number|unitoftime' ||
|
|
384
|
+
token.type === 'MERGE:year|unitoftime'
|
|
320
385
|
) {
|
|
321
386
|
const value = Number(token.values[0].value);
|
|
322
387
|
if (!DateParser.isValidNumber(value)) {
|
|
@@ -334,33 +399,32 @@ export class DateParser extends BaseParser {
|
|
|
334
399
|
): DateClause | undefined {
|
|
335
400
|
const tokens: Token[] = token.values || [];
|
|
336
401
|
switch (token.type) {
|
|
337
|
-
case 'MERGE:
|
|
338
|
-
case 'MERGE:
|
|
339
|
-
case 'MERGE:
|
|
340
|
-
case 'MERGE:
|
|
341
|
-
case 'MERGE:
|
|
402
|
+
case 'MERGE:last|unitoftime':
|
|
403
|
+
case 'MERGE:last|dayofweek':
|
|
404
|
+
case 'MERGE:this|unitoftime':
|
|
405
|
+
case 'MERGE:next|unitoftime':
|
|
406
|
+
case 'MERGE:next|dayofweek':
|
|
342
407
|
return this.createIntervalMoment(prefix, tokens);
|
|
343
|
-
case 'MERGE:
|
|
344
|
-
case 'MERGE:
|
|
345
|
-
case 'MERGE:
|
|
346
|
-
case 'MERGE:
|
|
408
|
+
case 'MERGE:last|number|unitoftime':
|
|
409
|
+
case 'MERGE:last|year|unitoftime':
|
|
410
|
+
case 'MERGE:next|number|unitoftime':
|
|
411
|
+
case 'MERGE:next|year|unitoftime':
|
|
347
412
|
return this.createSpanMoment(prefix, tokens);
|
|
348
|
-
case 'MERGE:
|
|
349
|
-
case 'MERGE:
|
|
350
|
-
case 'MERGE:
|
|
351
|
-
case 'MERGE:
|
|
413
|
+
case 'MERGE:number|unitoftime|ago':
|
|
414
|
+
case 'MERGE:year|unitoftime|ago':
|
|
415
|
+
case 'MERGE:number|unitoftime|from|now':
|
|
416
|
+
case 'MERGE:year|unitoftime|from|now':
|
|
352
417
|
return this.createOffsetMoment(prefix, tokens);
|
|
353
|
-
case 'MERGE:
|
|
354
|
-
case 'MERGE:
|
|
418
|
+
case 'MERGE:number|unitoftime':
|
|
419
|
+
case 'MERGE:year|unitoftime':
|
|
355
420
|
return this.createDateDuration(prefix, tokens);
|
|
356
|
-
case 'MERGE:
|
|
357
|
-
case 'MERGE:
|
|
358
|
-
case 'MERGE:YEAR':
|
|
421
|
+
case 'MERGE:date':
|
|
422
|
+
case 'MERGE:year':
|
|
359
423
|
return this.createAbsoluteMoment(prefix, tokens);
|
|
360
|
-
case 'MERGE:
|
|
361
|
-
case 'MERGE:
|
|
362
|
-
case 'MERGE:
|
|
363
|
-
case 'MERGE:
|
|
424
|
+
case 'MERGE:now':
|
|
425
|
+
case 'MERGE:today':
|
|
426
|
+
case 'MERGE:yesterday':
|
|
427
|
+
case 'MERGE:tomorrow':
|
|
364
428
|
return this.createNamedMoment(prefix, tokens);
|
|
365
429
|
default:
|
|
366
430
|
return undefined;
|
|
@@ -383,8 +447,8 @@ export class DateParser extends BaseParser {
|
|
|
383
447
|
private handleRange(clauses: DateClause[]): boolean {
|
|
384
448
|
if (
|
|
385
449
|
this.isMatchingToken(this.index, 'MERGE', false) &&
|
|
386
|
-
(this.isMatchingToken(this.index + 1, '
|
|
387
|
-
this.isMatchingToken(this.index + 1, '
|
|
450
|
+
(this.isMatchingToken(this.index + 1, 'to', true) ||
|
|
451
|
+
this.isMatchingToken(this.index + 1, 'for', true)) &&
|
|
388
452
|
this.isMatchingToken(this.index + 2, 'MERGE', false)
|
|
389
453
|
) {
|
|
390
454
|
const startToken = this.tokens[this.index];
|
|
@@ -398,7 +462,7 @@ export class DateParser extends BaseParser {
|
|
|
398
462
|
if (startClause === undefined || !('moment' in startClause)) {
|
|
399
463
|
return false;
|
|
400
464
|
}
|
|
401
|
-
if (operator === '
|
|
465
|
+
if (operator === 'to') {
|
|
402
466
|
const endClause = DateParser.createClauseFromMerged(
|
|
403
467
|
undefined,
|
|
404
468
|
endToken
|
|
@@ -407,7 +471,7 @@ export class DateParser extends BaseParser {
|
|
|
407
471
|
return false;
|
|
408
472
|
}
|
|
409
473
|
const clause: DateBetweenClause = {
|
|
410
|
-
operator: '
|
|
474
|
+
operator: 'to_range',
|
|
411
475
|
from: startClause.moment,
|
|
412
476
|
to: endClause.moment,
|
|
413
477
|
};
|
|
@@ -418,7 +482,7 @@ export class DateParser extends BaseParser {
|
|
|
418
482
|
return false;
|
|
419
483
|
}
|
|
420
484
|
const clause: DateForClause = {
|
|
421
|
-
operator: '
|
|
485
|
+
operator: 'for_range',
|
|
422
486
|
from: startClause.moment,
|
|
423
487
|
duration: endDuration,
|
|
424
488
|
};
|
|
@@ -442,8 +506,6 @@ export class DateParser extends BaseParser {
|
|
|
442
506
|
}
|
|
443
507
|
clauses.push(clause);
|
|
444
508
|
return true;
|
|
445
|
-
} else if (token.type === 'NULL' || token.type === 'NOTNULL') {
|
|
446
|
-
clauses.push({operator: token.type});
|
|
447
509
|
}
|
|
448
510
|
return false;
|
|
449
511
|
}
|
package/src/date_serializer.ts
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
|
|
1
8
|
import {
|
|
2
9
|
DateMoment,
|
|
3
10
|
DateBetweenClause,
|
|
@@ -16,16 +23,16 @@ export class DateSerializer {
|
|
|
16
23
|
}
|
|
17
24
|
|
|
18
25
|
private static dateMomentToString(moment: DateMoment): string {
|
|
19
|
-
if (moment.type === '
|
|
26
|
+
if (moment.type === 'absolute') {
|
|
20
27
|
return moment.date;
|
|
21
|
-
} else if (moment.type === '
|
|
28
|
+
} else if (moment.type === 'interval') {
|
|
22
29
|
return moment.kind + ' ' + moment.unit;
|
|
23
|
-
} else if (moment.type === '
|
|
30
|
+
} else if (moment.type === 'named') {
|
|
24
31
|
return moment.name;
|
|
25
|
-
} else if (moment.type === '
|
|
26
|
-
const direction = moment.direction === '
|
|
32
|
+
} else if (moment.type === 'offset_from_now') {
|
|
33
|
+
const direction = moment.direction === 'from_now' ? 'from now' : 'ago';
|
|
27
34
|
return moment.amount + ' ' + moment.unit + ' ' + direction;
|
|
28
|
-
} else if (moment.type === '
|
|
35
|
+
} else if (moment.type === 'span_from_now') {
|
|
29
36
|
return moment.direction + ' ' + moment.amount + ' ' + moment.unit;
|
|
30
37
|
} else {
|
|
31
38
|
throw new Error('moment type not recognized ' + JSON.stringify(moment));
|
|
@@ -35,7 +42,7 @@ export class DateSerializer {
|
|
|
35
42
|
private static goDateBetweenClause(clause: DateBetweenClause): string {
|
|
36
43
|
return (
|
|
37
44
|
DateSerializer.dateMomentToString(clause.from) +
|
|
38
|
-
'
|
|
45
|
+
' to ' +
|
|
39
46
|
DateSerializer.dateMomentToString(clause.to)
|
|
40
47
|
);
|
|
41
48
|
}
|
|
@@ -43,7 +50,7 @@ export class DateSerializer {
|
|
|
43
50
|
private static goDateForClause(clause: DateForClause): string {
|
|
44
51
|
return (
|
|
45
52
|
DateSerializer.dateMomentToString(clause.from) +
|
|
46
|
-
'
|
|
53
|
+
' for ' +
|
|
47
54
|
clause.duration.amount +
|
|
48
55
|
' ' +
|
|
49
56
|
clause.duration.unit
|
|
@@ -54,21 +61,21 @@ export class DateSerializer {
|
|
|
54
61
|
if (!('operator' in clause)) {
|
|
55
62
|
throw new Error('Invalid date clause ' + JSON.stringify(clause));
|
|
56
63
|
}
|
|
57
|
-
if (clause.operator === '
|
|
64
|
+
if (clause.operator === 'to_range') {
|
|
58
65
|
return DateSerializer.goDateBetweenClause(clause);
|
|
59
|
-
} else if (clause.operator === '
|
|
66
|
+
} else if (clause.operator === 'for_range') {
|
|
60
67
|
return DateSerializer.goDateForClause(clause);
|
|
61
|
-
} else if (clause.operator === '
|
|
62
|
-
return '
|
|
63
|
-
} else if (clause.operator === '
|
|
64
|
-
return '
|
|
65
|
-
} else if (clause.operator === '
|
|
68
|
+
} else if (clause.operator === 'before') {
|
|
69
|
+
return 'before ' + DateSerializer.dateMomentToString(clause.moment);
|
|
70
|
+
} else if (clause.operator === 'after') {
|
|
71
|
+
return 'after ' + DateSerializer.dateMomentToString(clause.moment);
|
|
72
|
+
} else if (clause.operator === 'on') {
|
|
66
73
|
return DateSerializer.dateMomentToString(clause.moment);
|
|
67
|
-
} else if (clause.operator === '
|
|
68
|
-
return '
|
|
69
|
-
} else if (clause.operator === '
|
|
70
|
-
return '-
|
|
71
|
-
} else if (clause.operator === '
|
|
74
|
+
} else if (clause.operator === 'null') {
|
|
75
|
+
return 'null';
|
|
76
|
+
} else if (clause.operator === 'not_null') {
|
|
77
|
+
return '-null';
|
|
78
|
+
} else if (clause.operator === 'duration') {
|
|
72
79
|
return clause.duration.amount + ' ' + clause.duration.unit;
|
|
73
80
|
} else {
|
|
74
81
|
throw new Error('Clause type not recognized ' + JSON.stringify(clause));
|