@malloydata/malloy-filter 0.0.237-dev250224215546 → 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 -114
- 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.js +135 -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 +32 -25
- package/dist/generate_samples.js.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- 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 +1 -1
- 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 +136 -118
- package/src/date_serializer.ts +27 -20
- package/src/date_types.ts +42 -34
- package/src/generate_samples.ts +33 -25
- package/src/index.ts +7 -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 = /[%_]/;
|
|
@@ -38,47 +45,46 @@ export class DateParser extends BaseParser {
|
|
|
38
45
|
// Do not reorder.
|
|
39
46
|
const specialWords: SpecialToken[] = [
|
|
40
47
|
{
|
|
41
|
-
type: '
|
|
48
|
+
type: 'unitoftime',
|
|
42
49
|
value: /^(second|minute|hour|day|week|month|quarter|year)s?$/i,
|
|
43
50
|
ignoreCase: true,
|
|
44
51
|
},
|
|
45
52
|
{
|
|
46
|
-
type: '
|
|
53
|
+
type: 'dayofweek',
|
|
47
54
|
value: /^(monday|tuesday|wednesday|thursday|friday|saturday|sunday)$/i,
|
|
48
55
|
ignoreCase: true,
|
|
49
56
|
},
|
|
50
|
-
{type: '
|
|
51
|
-
{type: '
|
|
52
|
-
{type: '
|
|
53
|
-
{type: '
|
|
54
|
-
{type: 'DATE', value: /^\d{4}-\d{2}-\d{2}-[Ww][Kk]$/},
|
|
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]$/},
|
|
55
61
|
{
|
|
56
|
-
type: '
|
|
62
|
+
type: 'date',
|
|
57
63
|
value: /^\d{4}-\d{2}-\d{2}T\d\d:\d\d:\d\d\[[a-zA-Z_/]*\]$/,
|
|
58
64
|
},
|
|
59
|
-
{type: '
|
|
60
|
-
{type: '
|
|
61
|
-
{type: '
|
|
62
|
-
{type: '
|
|
63
|
-
{type: '
|
|
64
|
-
{type: '
|
|
65
|
-
{type: '
|
|
66
|
-
{type: '
|
|
67
|
-
{type: '
|
|
68
|
-
{type: '
|
|
69
|
-
{type: '
|
|
70
|
-
{type: '
|
|
71
|
-
{type: '
|
|
72
|
-
{type: '
|
|
73
|
-
{type: '
|
|
74
|
-
{type: '
|
|
75
|
-
{type: '
|
|
76
|
-
{type: '
|
|
77
|
-
{type: '
|
|
78
|
-
{type: '
|
|
79
|
-
{type: '
|
|
80
|
-
{type: '
|
|
81
|
-
{type: '
|
|
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},
|
|
82
88
|
];
|
|
83
89
|
const params: TokenizerParams = {
|
|
84
90
|
trimWordWhitespace: true,
|
|
@@ -101,10 +107,10 @@ export class DateParser extends BaseParser {
|
|
|
101
107
|
const token = this.tokens[this.index];
|
|
102
108
|
if (
|
|
103
109
|
previous &&
|
|
104
|
-
previous.type === '
|
|
110
|
+
previous.type === 'date' &&
|
|
105
111
|
previous.value.length >= 10 &&
|
|
106
|
-
(token.type === '
|
|
107
|
-
(token.type === '
|
|
112
|
+
(token.type === 'time' ||
|
|
113
|
+
(token.type === 'number' && token.value.length === 2))
|
|
108
114
|
) {
|
|
109
115
|
previous.value = previous.value + ' ' + token.value;
|
|
110
116
|
previous.endIndex = token.endIndex;
|
|
@@ -124,27 +130,27 @@ export class DateParser extends BaseParser {
|
|
|
124
130
|
while (this.index < this.tokens.length) {
|
|
125
131
|
// Do not reorder.
|
|
126
132
|
if (
|
|
127
|
-
this.matchAndMerge('
|
|
128
|
-
this.matchAndMerge('
|
|
129
|
-
this.matchAndMerge('
|
|
130
|
-
this.matchAndMerge('
|
|
131
|
-
this.matchAndMerge('
|
|
132
|
-
this.matchAndMerge('
|
|
133
|
-
this.matchAndMerge('
|
|
134
|
-
this.matchAndMerge('
|
|
135
|
-
this.matchAndMerge('
|
|
136
|
-
this.matchAndMerge('
|
|
137
|
-
this.matchAndMerge('
|
|
138
|
-
this.matchAndMerge('
|
|
139
|
-
this.matchAndMerge('
|
|
140
|
-
this.matchAndMerge('
|
|
141
|
-
this.matchAndMerge('
|
|
142
|
-
this.matchAndMerge('
|
|
143
|
-
this.matchAndMerge('
|
|
144
|
-
this.matchAndMerge('
|
|
145
|
-
this.matchAndMerge('
|
|
146
|
-
this.matchAndMerge('
|
|
147
|
-
this.matchAndMerge('
|
|
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)
|
|
148
154
|
) {
|
|
149
155
|
continue;
|
|
150
156
|
} else {
|
|
@@ -176,25 +182,34 @@ export class DateParser extends BaseParser {
|
|
|
176
182
|
this.tokenize();
|
|
177
183
|
let prefix: DatePrefix | undefined = undefined;
|
|
178
184
|
const clauses: DateClause[] = [];
|
|
179
|
-
const
|
|
185
|
+
const logs: FilterLog[] = [];
|
|
180
186
|
this.index = 0;
|
|
181
187
|
while (this.index < this.tokens.length) {
|
|
182
188
|
const token = this.getNext();
|
|
183
189
|
if (token.type === ',') {
|
|
184
190
|
if (prefix) {
|
|
185
|
-
|
|
191
|
+
logs.push({
|
|
192
|
+
severity: 'error',
|
|
186
193
|
message: 'Invalid ' + prefix,
|
|
187
194
|
startIndex: token.startIndex,
|
|
188
195
|
endIndex: token.endIndex,
|
|
189
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
|
+
});
|
|
190
204
|
}
|
|
191
205
|
this.index++;
|
|
192
|
-
} else if (token.type === '
|
|
206
|
+
} else if (token.type === 'prefix') {
|
|
193
207
|
prefix = token.value as DatePrefix;
|
|
194
208
|
this.index++;
|
|
195
209
|
} else if (this.handleRange(clauses)) {
|
|
196
210
|
if (prefix) {
|
|
197
|
-
|
|
211
|
+
logs.push({
|
|
212
|
+
severity: 'error',
|
|
198
213
|
message: 'Invalid ' + prefix,
|
|
199
214
|
startIndex: token.startIndex,
|
|
200
215
|
endIndex: token.endIndex,
|
|
@@ -203,12 +218,13 @@ export class DateParser extends BaseParser {
|
|
|
203
218
|
}
|
|
204
219
|
} else if (this.handleMerged(prefix, clauses)) {
|
|
205
220
|
prefix = undefined;
|
|
206
|
-
} else if (token.type === '
|
|
221
|
+
} else if (token.type === 'null' || token.type === 'not_null') {
|
|
207
222
|
prefix = undefined;
|
|
208
223
|
clauses.push({operator: token.type});
|
|
209
224
|
this.index++;
|
|
210
225
|
} else {
|
|
211
|
-
|
|
226
|
+
logs.push({
|
|
227
|
+
severity: 'error',
|
|
212
228
|
message:
|
|
213
229
|
'Invalid token ' + token.value ||
|
|
214
230
|
(token.values ? token.values.join(' ') : ''),
|
|
@@ -219,7 +235,7 @@ export class DateParser extends BaseParser {
|
|
|
219
235
|
this.index++;
|
|
220
236
|
}
|
|
221
237
|
}
|
|
222
|
-
return {clauses,
|
|
238
|
+
return {clauses, logs};
|
|
223
239
|
}
|
|
224
240
|
|
|
225
241
|
private static createMomentClause(
|
|
@@ -227,11 +243,11 @@ export class DateParser extends BaseParser {
|
|
|
227
243
|
moment: DateMoment
|
|
228
244
|
): DateClause {
|
|
229
245
|
if (!prefix) {
|
|
230
|
-
return {operator: '
|
|
231
|
-
} else if (prefix === '
|
|
232
|
-
return {operator: '
|
|
246
|
+
return {operator: 'on', moment}; // DateOnClause
|
|
247
|
+
} else if (prefix === 'before') {
|
|
248
|
+
return {operator: 'before', moment}; // DateBeforeClause
|
|
233
249
|
} else {
|
|
234
|
-
return {operator: '
|
|
250
|
+
return {operator: 'after', moment}; // DateAfterClause
|
|
235
251
|
}
|
|
236
252
|
}
|
|
237
253
|
|
|
@@ -245,7 +261,7 @@ export class DateParser extends BaseParser {
|
|
|
245
261
|
const unit: DateTimeUnit | DateWeekday = tokens[1].value as
|
|
246
262
|
| DateTimeUnit
|
|
247
263
|
| DateWeekday;
|
|
248
|
-
const moment: IntervalMoment = {type: '
|
|
264
|
+
const moment: IntervalMoment = {type: 'interval', kind, unit};
|
|
249
265
|
return DateParser.createMomentClause(prefix, moment);
|
|
250
266
|
}
|
|
251
267
|
|
|
@@ -258,12 +274,12 @@ export class DateParser extends BaseParser {
|
|
|
258
274
|
const amount = Number(tokens[0].value);
|
|
259
275
|
const unit: DateTimeUnit = tokens[1].value as DateTimeUnit;
|
|
260
276
|
const direction: DateMomentOffsetFromNowDirection =
|
|
261
|
-
tokens[2].type === '
|
|
277
|
+
tokens[2].type === 'ago' ? 'ago' : 'from_now';
|
|
262
278
|
if (!DateParser.isValidNumber(amount)) {
|
|
263
279
|
return undefined;
|
|
264
280
|
}
|
|
265
281
|
const moment: OffsetMoment = {
|
|
266
|
-
type: '
|
|
282
|
+
type: 'offset_from_now',
|
|
267
283
|
direction,
|
|
268
284
|
amount,
|
|
269
285
|
unit,
|
|
@@ -279,11 +295,11 @@ export class DateParser extends BaseParser {
|
|
|
279
295
|
const amount = Number(tokens[0].value);
|
|
280
296
|
const unit: DateTimeUnit = tokens[1].value as DateTimeUnit;
|
|
281
297
|
const direction: DateMomentSpanFromNowDirection =
|
|
282
|
-
tokens[2].type === '
|
|
298
|
+
tokens[2].type === 'last' ? 'last' : 'next';
|
|
283
299
|
if (!DateParser.isValidNumber(amount)) {
|
|
284
300
|
return undefined;
|
|
285
301
|
}
|
|
286
|
-
const moment: SpanMoment = {type: '
|
|
302
|
+
const moment: SpanMoment = {type: 'span_from_now', direction, amount, unit};
|
|
287
303
|
return DateParser.createMomentClause(prefix, moment);
|
|
288
304
|
}
|
|
289
305
|
|
|
@@ -295,7 +311,7 @@ export class DateParser extends BaseParser {
|
|
|
295
311
|
if (prefix) {
|
|
296
312
|
return undefined; // before 7 hours is ambiguous, not allowed.
|
|
297
313
|
}
|
|
298
|
-
const operator = '
|
|
314
|
+
const operator = 'duration';
|
|
299
315
|
const amount = Number(tokens[0].value);
|
|
300
316
|
const unit: DateTimeUnit = tokens[1].value as DateTimeUnit;
|
|
301
317
|
if (!DateParser.isValidNumber(amount)) {
|
|
@@ -316,19 +332,21 @@ export class DateParser extends BaseParser {
|
|
|
316
332
|
);
|
|
317
333
|
const timeStr = matcher ? matcher[1] : '';
|
|
318
334
|
|
|
319
|
-
let unit: DateTimeUnit = '
|
|
320
|
-
if (timeStr.length >
|
|
321
|
-
unit = '
|
|
335
|
+
let unit: DateTimeUnit = 'year';
|
|
336
|
+
if (timeStr.length > 8) {
|
|
337
|
+
unit = 'instant';
|
|
338
|
+
} else if (timeStr.length > 5) {
|
|
339
|
+
unit = 'second';
|
|
322
340
|
} else if (timeStr.length > 2) {
|
|
323
|
-
unit = '
|
|
341
|
+
unit = 'minute';
|
|
324
342
|
} else if (timeStr.length === 2) {
|
|
325
|
-
unit = '
|
|
343
|
+
unit = 'hour';
|
|
326
344
|
} else if (dateStr.length > 7) {
|
|
327
|
-
unit = '
|
|
345
|
+
unit = 'day';
|
|
328
346
|
} else if (dateStr.length > 4) {
|
|
329
|
-
unit = /[qQ]/.test(dateStr) ? '
|
|
347
|
+
unit = /[qQ]/.test(dateStr) ? 'quarter' : 'month';
|
|
330
348
|
}
|
|
331
|
-
const moment: AbsoluteMoment = {type: '
|
|
349
|
+
const moment: AbsoluteMoment = {type: 'absolute', date: dateStr, unit};
|
|
332
350
|
return DateParser.createMomentClause(prefix, moment);
|
|
333
351
|
}
|
|
334
352
|
|
|
@@ -337,19 +355,19 @@ export class DateParser extends BaseParser {
|
|
|
337
355
|
prefix: DatePrefix | undefined,
|
|
338
356
|
tokens: Token[]
|
|
339
357
|
): DateClause {
|
|
340
|
-
let momentName: DateMomentName = '
|
|
358
|
+
let momentName: DateMomentName = 'now';
|
|
341
359
|
switch (tokens[0].type) {
|
|
342
|
-
case '
|
|
343
|
-
momentName = '
|
|
360
|
+
case 'today':
|
|
361
|
+
momentName = 'today';
|
|
344
362
|
break;
|
|
345
|
-
case '
|
|
346
|
-
momentName = '
|
|
363
|
+
case 'yesterday':
|
|
364
|
+
momentName = 'yesterday';
|
|
347
365
|
break;
|
|
348
|
-
case '
|
|
349
|
-
momentName = '
|
|
366
|
+
case 'tomorrow':
|
|
367
|
+
momentName = 'tomorrow';
|
|
350
368
|
break;
|
|
351
369
|
}
|
|
352
|
-
const moment: NamedMoment = {type: '
|
|
370
|
+
const moment: NamedMoment = {type: 'named', name: momentName};
|
|
353
371
|
return DateParser.createMomentClause(prefix, moment);
|
|
354
372
|
}
|
|
355
373
|
|
|
@@ -362,8 +380,8 @@ export class DateParser extends BaseParser {
|
|
|
362
380
|
return undefined;
|
|
363
381
|
}
|
|
364
382
|
if (
|
|
365
|
-
token.type === 'MERGE:
|
|
366
|
-
token.type === 'MERGE:
|
|
383
|
+
token.type === 'MERGE:number|unitoftime' ||
|
|
384
|
+
token.type === 'MERGE:year|unitoftime'
|
|
367
385
|
) {
|
|
368
386
|
const value = Number(token.values[0].value);
|
|
369
387
|
if (!DateParser.isValidNumber(value)) {
|
|
@@ -381,32 +399,32 @@ export class DateParser extends BaseParser {
|
|
|
381
399
|
): DateClause | undefined {
|
|
382
400
|
const tokens: Token[] = token.values || [];
|
|
383
401
|
switch (token.type) {
|
|
384
|
-
case 'MERGE:
|
|
385
|
-
case 'MERGE:
|
|
386
|
-
case 'MERGE:
|
|
387
|
-
case 'MERGE:
|
|
388
|
-
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':
|
|
389
407
|
return this.createIntervalMoment(prefix, tokens);
|
|
390
|
-
case 'MERGE:
|
|
391
|
-
case 'MERGE:
|
|
392
|
-
case 'MERGE:
|
|
393
|
-
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':
|
|
394
412
|
return this.createSpanMoment(prefix, tokens);
|
|
395
|
-
case 'MERGE:
|
|
396
|
-
case 'MERGE:
|
|
397
|
-
case 'MERGE:
|
|
398
|
-
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':
|
|
399
417
|
return this.createOffsetMoment(prefix, tokens);
|
|
400
|
-
case 'MERGE:
|
|
401
|
-
case 'MERGE:
|
|
418
|
+
case 'MERGE:number|unitoftime':
|
|
419
|
+
case 'MERGE:year|unitoftime':
|
|
402
420
|
return this.createDateDuration(prefix, tokens);
|
|
403
|
-
case 'MERGE:
|
|
404
|
-
case 'MERGE:
|
|
421
|
+
case 'MERGE:date':
|
|
422
|
+
case 'MERGE:year':
|
|
405
423
|
return this.createAbsoluteMoment(prefix, tokens);
|
|
406
|
-
case 'MERGE:
|
|
407
|
-
case 'MERGE:
|
|
408
|
-
case 'MERGE:
|
|
409
|
-
case 'MERGE:
|
|
424
|
+
case 'MERGE:now':
|
|
425
|
+
case 'MERGE:today':
|
|
426
|
+
case 'MERGE:yesterday':
|
|
427
|
+
case 'MERGE:tomorrow':
|
|
410
428
|
return this.createNamedMoment(prefix, tokens);
|
|
411
429
|
default:
|
|
412
430
|
return undefined;
|
|
@@ -429,8 +447,8 @@ export class DateParser extends BaseParser {
|
|
|
429
447
|
private handleRange(clauses: DateClause[]): boolean {
|
|
430
448
|
if (
|
|
431
449
|
this.isMatchingToken(this.index, 'MERGE', false) &&
|
|
432
|
-
(this.isMatchingToken(this.index + 1, '
|
|
433
|
-
this.isMatchingToken(this.index + 1, '
|
|
450
|
+
(this.isMatchingToken(this.index + 1, 'to', true) ||
|
|
451
|
+
this.isMatchingToken(this.index + 1, 'for', true)) &&
|
|
434
452
|
this.isMatchingToken(this.index + 2, 'MERGE', false)
|
|
435
453
|
) {
|
|
436
454
|
const startToken = this.tokens[this.index];
|
|
@@ -444,7 +462,7 @@ export class DateParser extends BaseParser {
|
|
|
444
462
|
if (startClause === undefined || !('moment' in startClause)) {
|
|
445
463
|
return false;
|
|
446
464
|
}
|
|
447
|
-
if (operator === '
|
|
465
|
+
if (operator === 'to') {
|
|
448
466
|
const endClause = DateParser.createClauseFromMerged(
|
|
449
467
|
undefined,
|
|
450
468
|
endToken
|
|
@@ -453,7 +471,7 @@ export class DateParser extends BaseParser {
|
|
|
453
471
|
return false;
|
|
454
472
|
}
|
|
455
473
|
const clause: DateBetweenClause = {
|
|
456
|
-
operator: '
|
|
474
|
+
operator: 'to_range',
|
|
457
475
|
from: startClause.moment,
|
|
458
476
|
to: endClause.moment,
|
|
459
477
|
};
|
|
@@ -464,7 +482,7 @@ export class DateParser extends BaseParser {
|
|
|
464
482
|
return false;
|
|
465
483
|
}
|
|
466
484
|
const clause: DateForClause = {
|
|
467
|
-
operator: '
|
|
485
|
+
operator: 'for_range',
|
|
468
486
|
from: startClause.moment,
|
|
469
487
|
duration: endDuration,
|
|
470
488
|
};
|
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));
|