chronolizer 0.2.0 → 0.4.0
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/README.md +170 -167
- package/dist/ast/constructors.d.mts +3 -8
- package/dist/ast/fold.d.mts +2 -1
- package/dist/ast/fold.mjs +38 -31
- package/dist/ast/normalize.mjs +4 -2
- package/dist/ast/schemas.d.mts +18 -25
- package/dist/ast/schemas.mjs +8 -17
- package/dist/filter/expression.mjs +27 -38
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +2 -2
- package/dist/language/model.d.mts +33 -33
- package/dist/language/model.mjs +5 -8
- package/dist/language/registry.mjs +35 -33
- package/dist/locales/cs.mjs +253 -31
- package/dist/locales/de.mjs +366 -42
- package/dist/locales/en.mjs +215 -51
- package/dist/locales/es.mjs +498 -91
- package/dist/locales/fr.mjs +360 -65
- package/dist/locales/nl.mjs +302 -64
- package/dist/locales/pl.mjs +447 -135
- package/dist/locales/shared.mjs +192 -23
- package/dist/locales/tr.mjs +220 -41
- package/dist/natural/correction.d.mts +1 -1
- package/dist/natural/correction.mjs +31 -5
- package/dist/natural/format.mjs +1 -1
- package/dist/natural/parse.d.mts +1 -1
- package/dist/natural/parse.mjs +5 -4
- package/dist/natural/policy.mjs +2 -2
- package/dist/natural/suggest.mjs +19 -3
- package/dist/natural/suggestion.mjs +11 -5
- package/dist/resolve/resolve.mjs +2 -10
- package/package.json +1 -1
package/dist/locales/en.mjs
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import { isIsoDate } from "../ast/schemas.mjs";
|
|
2
1
|
import { BaseLanguageContribution } from "../language/model.mjs";
|
|
3
2
|
import { normalizeNaturalText } from "../natural/text.mjs";
|
|
4
3
|
import { defineLanguagePlugin, languagePluginsLayer } from "../language/registry.mjs";
|
|
5
4
|
import { correctWhitespaceSeparatedText } from "../natural/correction.mjs";
|
|
6
5
|
import { completeNaturalPhrases, fixedCalendarPeriodPhrases, naturalCount, prefixNaturalPhrases } from "../natural/suggestion.mjs";
|
|
7
|
-
import { calendarPeriodOffset, candidate, datedPeriods, datedQuarterPeriods,
|
|
6
|
+
import { absoluteDatePeriod, calendarPeriodOffset, candidate, compileCountAliasNormalizer, compoundCountAliases, countAliasVocabulary, currentYearDatePeriods, datedPeriods, datedQuarterPeriods, decimalTens, decomposeShiftedPeriodRange, fixedMonthPeriod, fixedQuarterPeriod, fixedYearPeriod, fromNowRange, futurePeriod, futureRange, joinedNowCandidate, joinedPeriodCandidate, monthOfRelativeYear, namedCurrentYearDatePeriod, namedDatePeriod, openBoundaryCandidate, parseTrailingCount, periodEndDay, periodPreviousDay, periodRange, periodStartDay, periodToDateRange, periodsFromPhrases, quarterOfRelativeYear, relativePeriod, relativeWeekday, relativeWeekend, remainingPeriodRange, renderPeriodRange, sequentialCountAliases, shiftPeriod, textAt, trailingPeriod, trailingRange, untilNowRange, validYear } from "./shared.mjs";
|
|
8
7
|
import { Effect, Option, String as String$1 } from "effect";
|
|
9
8
|
//#region src/locales/en.ts
|
|
10
9
|
const months = [
|
|
@@ -35,12 +34,59 @@ const monthAbbreviations = [
|
|
|
35
34
|
["nov"],
|
|
36
35
|
["dec"]
|
|
37
36
|
];
|
|
37
|
+
const weekdays = [
|
|
38
|
+
"monday",
|
|
39
|
+
"tuesday",
|
|
40
|
+
"wednesday",
|
|
41
|
+
"thursday",
|
|
42
|
+
"friday",
|
|
43
|
+
"saturday",
|
|
44
|
+
"sunday"
|
|
45
|
+
];
|
|
46
|
+
const nextWeekdayPhrases = weekdays.map((weekday) => `next ${weekday}`);
|
|
38
47
|
const quarterNames = [
|
|
39
48
|
"first",
|
|
40
49
|
"second",
|
|
41
50
|
"third",
|
|
42
51
|
"fourth"
|
|
43
52
|
];
|
|
53
|
+
const englishCountWords = [
|
|
54
|
+
"one",
|
|
55
|
+
"two",
|
|
56
|
+
"three",
|
|
57
|
+
"four",
|
|
58
|
+
"five",
|
|
59
|
+
"six",
|
|
60
|
+
"seven",
|
|
61
|
+
"eight",
|
|
62
|
+
"nine",
|
|
63
|
+
"ten",
|
|
64
|
+
"eleven",
|
|
65
|
+
"twelve",
|
|
66
|
+
"thirteen",
|
|
67
|
+
"fourteen",
|
|
68
|
+
"fifteen",
|
|
69
|
+
"sixteen",
|
|
70
|
+
"seventeen",
|
|
71
|
+
"eighteen",
|
|
72
|
+
"nineteen",
|
|
73
|
+
"twenty"
|
|
74
|
+
];
|
|
75
|
+
const englishCountOnes = englishCountWords.slice(0, 9).map((word, index) => [index + 1, word]);
|
|
76
|
+
const englishCountAliases = [...sequentialCountAliases(englishCountWords.map((word) => [word]), 1), ...compoundCountAliases(decimalTens([
|
|
77
|
+
"twenty",
|
|
78
|
+
"thirty",
|
|
79
|
+
"forty",
|
|
80
|
+
"fifty",
|
|
81
|
+
"sixty",
|
|
82
|
+
"seventy",
|
|
83
|
+
"eighty",
|
|
84
|
+
"ninety"
|
|
85
|
+
]), englishCountOnes, (ten, one) => [`${ten} ${one}`, `${ten}-${one}`])];
|
|
86
|
+
const normalizeEnglishCounts = compileCountAliasNormalizer(englishCountAliases);
|
|
87
|
+
const englishCountVocabulary = new Set(countAliasVocabulary(englishCountAliases));
|
|
88
|
+
const correctEnglish = (input, vocabulary) => correctWhitespaceSeparatedText(input, vocabulary, englishCountVocabulary);
|
|
89
|
+
const normalizeEnglish = (input, locale) => normalizeEnglishCounts(normalizeNaturalText(input, locale));
|
|
44
90
|
const units = [
|
|
45
91
|
[
|
|
46
92
|
"day",
|
|
@@ -159,19 +205,25 @@ const parseQuarter = (input) => {
|
|
|
159
205
|
const quarter = quarterNumber(textAt(standaloneMatch.value, 1));
|
|
160
206
|
return quarter === void 0 ? Option.none() : Option.some(quarterOfRelativeYear(quarter, 0, `Q${quarter}`));
|
|
161
207
|
};
|
|
208
|
+
const currentDateLabel = (day, month) => `${day} ${title(textAt(months, month - 1))}`;
|
|
162
209
|
const parseNamedDate = (input) => {
|
|
163
|
-
const dayFirst = String$1.match(/^([0-3]?\d)(?:st|nd|rd|th)? ([a-z]+\.?) (\d{4})$/u)(input);
|
|
210
|
+
const dayFirst = String$1.match(/^(?:the )?([0-3]?\d)(?:st|nd|rd|th)?(?: of)? ([a-z]+\.?),? (\d{4})$/u)(input);
|
|
164
211
|
if (Option.isSome(dayFirst)) return namedDatePeriod(textAt(dayFirst.value, 3), textAt(dayFirst.value, 2), textAt(dayFirst.value, 1), monthNumber, (day, month, year) => `${day} ${title(textAt(months, month - 1))} ${year}`);
|
|
165
|
-
const monthFirst = String$1.match(/^([a-z]+\.?) ([0-3]?\d)(?:st|nd|rd|th)?,? (\d{4})$/u)(input);
|
|
212
|
+
const monthFirst = String$1.match(/^([a-z]+\.?) (?:the )?([0-3]?\d)(?:st|nd|rd|th)?,? (\d{4})$/u)(input);
|
|
166
213
|
if (Option.isSome(monthFirst)) return namedDatePeriod(textAt(monthFirst.value, 3), textAt(monthFirst.value, 1), textAt(monthFirst.value, 2), monthNumber, (day, month, year) => `${day} ${title(textAt(months, month - 1))} ${year}`);
|
|
167
|
-
|
|
214
|
+
const currentDayFirst = String$1.match(/^(?:the )?([0-3]?\d)(?:st|nd|rd|th)?(?: of)? ([a-z]+\.?)$/u)(input);
|
|
215
|
+
const currentMonthFirst = String$1.match(/^([a-z]+\.?) (?:the )?([0-3]?\d)(?:st|nd|rd|th)?$/u)(input);
|
|
216
|
+
const current = Option.firstSomeOf([currentDayFirst, currentMonthFirst]);
|
|
217
|
+
if (Option.isNone(current)) return Option.none();
|
|
218
|
+
return Option.isSome(currentDayFirst) ? namedCurrentYearDatePeriod(textAt(current.value, 2), textAt(current.value, 1), monthNumber, currentDateLabel) : namedCurrentYearDatePeriod(textAt(current.value, 1), textAt(current.value, 2), monthNumber, currentDateLabel);
|
|
168
219
|
};
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
220
|
+
const parseDatedPeriod = (input) => {
|
|
221
|
+
const knownPeriod = Option.firstSomeOf([
|
|
222
|
+
absoluteDatePeriod(input, "en"),
|
|
223
|
+
parseNamedDate(input),
|
|
224
|
+
parseQuarter(input)
|
|
225
|
+
]);
|
|
226
|
+
if (Option.isSome(knownPeriod)) return knownPeriod;
|
|
175
227
|
const yearMatch = String$1.match(/^(?:(?:the )?(?:calendar )?year )?(\d{4})$/u)(input);
|
|
176
228
|
if (Option.isSome(yearMatch)) {
|
|
177
229
|
const year = validYear(textAt(yearMatch.value, 1));
|
|
@@ -183,6 +235,15 @@ const parseBasePeriod = (input) => {
|
|
|
183
235
|
const year = validYear(textAt(monthYear.value, 2));
|
|
184
236
|
if (month !== void 0 && year !== void 0) return Option.some(fixedMonthPeriod(year, month, `${title(textAt(months, month - 1))} ${year}`));
|
|
185
237
|
}
|
|
238
|
+
return Option.none();
|
|
239
|
+
};
|
|
240
|
+
const parseRelativeMonth = (input) => {
|
|
241
|
+
const prefixedRelativeMonth = String$1.match(/^(last|this|next) ([a-z]+\.?)$/u)(input);
|
|
242
|
+
if (Option.isSome(prefixedRelativeMonth)) {
|
|
243
|
+
const directionText = textAt(prefixedRelativeMonth.value, 1);
|
|
244
|
+
const month = monthNumber(textAt(prefixedRelativeMonth.value, 2));
|
|
245
|
+
if (month !== void 0) return Option.some(monthOfRelativeYear(month, relativeDirection(directionText), `${directionText} ${title(textAt(months, month - 1))}`));
|
|
246
|
+
}
|
|
186
247
|
const relativeMonth = String$1.match(/^([a-z]+\.?) (?:of )?(last|this|next) year$/u)(input);
|
|
187
248
|
if (Option.isSome(relativeMonth)) {
|
|
188
249
|
const directionText = textAt(relativeMonth.value, 2);
|
|
@@ -192,14 +253,9 @@ const parseBasePeriod = (input) => {
|
|
|
192
253
|
}
|
|
193
254
|
const standaloneMonth = monthNumber(input);
|
|
194
255
|
if (standaloneMonth !== void 0) return Option.some(monthOfRelativeYear(standaloneMonth, 0, title(textAt(months, standaloneMonth - 1))));
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
if (input === "weekend" || input === "the weekend" || input === "this weekend") return Option.some(relativeWeekend(0, "this weekend"));
|
|
199
|
-
if (input === "last weekend" || input === "previous weekend") return Option.some(relativeWeekend(-1, "last weekend"));
|
|
200
|
-
if (input === "next weekend" || input === "coming weekend") return Option.some(relativeWeekend(1, "next weekend"));
|
|
201
|
-
if (input === "the weekend before last") return Option.some(relativeWeekend(-2, "the weekend before last"));
|
|
202
|
-
if (input === "the weekend after next") return Option.some(relativeWeekend(2, "the weekend after next"));
|
|
256
|
+
return Option.none();
|
|
257
|
+
};
|
|
258
|
+
const parseRelativeUnit = (input) => {
|
|
203
259
|
const articlePeriod = String$1.match(/^the (day|week|month|quarter|year)$/u)(input);
|
|
204
260
|
if (Option.isSome(articlePeriod)) {
|
|
205
261
|
const entry = units.find((unit) => unit[0] === textAt(articlePeriod.value, 1));
|
|
@@ -213,7 +269,7 @@ const parseBasePeriod = (input) => {
|
|
|
213
269
|
return Option.some(relativePeriod(entry[1], direction, input));
|
|
214
270
|
}
|
|
215
271
|
}
|
|
216
|
-
const relative = String$1.match(/^(?:the )?(last|previous|this|current|next|coming|upcoming) ([a-z]+)$/u)(input);
|
|
272
|
+
const relative = String$1.match(/^(?:the )?(last|previous|this|current|next|coming|upcoming) (?:calendar )?([a-z]+)$/u)(input);
|
|
217
273
|
if (Option.isSome(relative)) {
|
|
218
274
|
const unitText = textAt(relative.value, 2);
|
|
219
275
|
const unit = units.find((entry) => entry[0] === unitText)?.[1];
|
|
@@ -225,17 +281,69 @@ const parseBasePeriod = (input) => {
|
|
|
225
281
|
}
|
|
226
282
|
return Option.none();
|
|
227
283
|
};
|
|
228
|
-
const
|
|
284
|
+
const parseBasePeriod = (input) => {
|
|
285
|
+
const knownPeriod = Option.firstSomeOf([parseDatedPeriod(input), parseRelativeMonth(input)]);
|
|
286
|
+
if (Option.isSome(knownPeriod)) return knownPeriod;
|
|
287
|
+
if (input === "today") return Option.some(relativePeriod("day", 0, "today"));
|
|
288
|
+
if (input === "yesterday") return Option.some(relativePeriod("day", -1, "yesterday"));
|
|
289
|
+
if (input === "tomorrow") return Option.some(relativePeriod("day", 1, "tomorrow"));
|
|
290
|
+
if (input === "weekend" || input === "the weekend" || input === "this weekend") return Option.some(relativeWeekend(0, "this weekend"));
|
|
291
|
+
if (input === "last weekend" || input === "previous weekend") return Option.some(relativeWeekend(-1, "last weekend"));
|
|
292
|
+
if (input === "next weekend" || input === "coming weekend") return Option.some(relativeWeekend(1, "next weekend"));
|
|
293
|
+
if (input === "the weekend before last") return Option.some(relativeWeekend(-2, "the weekend before last"));
|
|
294
|
+
if (input === "the weekend after next") return Option.some(relativeWeekend(2, "the weekend after next"));
|
|
295
|
+
const weekday = nextWeekdayPhrases.indexOf(input);
|
|
296
|
+
if (weekday !== -1) return Option.some(relativeWeekday(weekday, 1, `next ${title(textAt(weekdays, weekday))}`));
|
|
297
|
+
return parseRelativeUnit(input);
|
|
298
|
+
};
|
|
299
|
+
const parsePeriodEdge = (input) => {
|
|
229
300
|
const edge = String$1.match(/^(?:the )?(start|beginning|end) of (.+)$/u)(input);
|
|
230
|
-
if (Option.
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
301
|
+
if (Option.isNone(edge)) return Option.none();
|
|
302
|
+
const periodText = textAt(edge.value, 2);
|
|
303
|
+
const basePeriod = parseBasePeriod(periodText);
|
|
304
|
+
const implicit = units.find((entry) => entry[0] === periodText);
|
|
305
|
+
const period = Option.isSome(basePeriod) || implicit === void 0 ? basePeriod : Option.some(relativePeriod(implicit[1], 0, currentPeriod(implicit[0])));
|
|
306
|
+
if (Option.isNone(period)) return Option.none();
|
|
307
|
+
const edgeName = textAt(edge.value, 1);
|
|
308
|
+
const name = edgeName === "beginning" ? "start" : edgeName;
|
|
309
|
+
const canonical = `${name} of ${period.value.canonical}`;
|
|
310
|
+
return Option.some(name === "end" ? periodEndDay(period.value, canonical) : periodStartDay(period.value, canonical));
|
|
311
|
+
};
|
|
312
|
+
const parsePeriod = (input) => {
|
|
313
|
+
const shifted = String$1.match(/^(.+) ([1-9]\d*) (day|days|week|weeks|month|months|quarter|quarters|year|years) (ago|prior|from now)$/u)(input);
|
|
314
|
+
if (Option.isSome(shifted)) {
|
|
315
|
+
const amount = parseTrailingCount(textAt(shifted.value, 2));
|
|
316
|
+
const unitText = textAt(shifted.value, 3);
|
|
317
|
+
const period = parsePeriod(textAt(shifted.value, 1));
|
|
318
|
+
if (Option.isSome(amount) && Option.isSome(period)) {
|
|
319
|
+
const entry = countedUnit(unitText, amount.value);
|
|
320
|
+
if (entry !== void 0) {
|
|
321
|
+
const past = textAt(shifted.value, 4) !== "from now";
|
|
322
|
+
const direction = past ? -amount.value : amount.value;
|
|
323
|
+
const suffix = past ? "ago" : "from now";
|
|
324
|
+
const canonical = `${period.value.canonical} ${amount.value} ${unitText} ${suffix}`;
|
|
325
|
+
return Option.some(shiftPeriod(period.value, direction, entry[1], canonical));
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
const relativeShift = String$1.match(/^([1-9]\d*) (day|days|week|weeks|month|months|quarter|quarters|year|years) (before|after) (.+)$/u)(input);
|
|
330
|
+
if (Option.isSome(relativeShift)) {
|
|
331
|
+
const amount = parseTrailingCount(textAt(relativeShift.value, 1));
|
|
332
|
+
const unitText = textAt(relativeShift.value, 2);
|
|
333
|
+
const period = parsePeriod(textAt(relativeShift.value, 4));
|
|
334
|
+
if (Option.isSome(amount) && Option.isSome(period)) {
|
|
335
|
+
const entry = countedUnit(unitText, amount.value);
|
|
336
|
+
if (entry !== void 0) {
|
|
337
|
+
const direction = textAt(relativeShift.value, 3) === "before" ? -amount.value : amount.value;
|
|
338
|
+
return Option.some(shiftPeriod(period.value, direction, entry[1], input));
|
|
339
|
+
}
|
|
237
340
|
}
|
|
238
341
|
}
|
|
342
|
+
const previousDay = String$1.match(/^(?:the )?day before (.+)$/u)(input);
|
|
343
|
+
if (Option.isSome(previousDay)) {
|
|
344
|
+
const period = parseBasePeriod(textAt(previousDay.value, 1));
|
|
345
|
+
if (Option.isSome(period)) return Option.some(periodPreviousDay(period.value, `the day before ${period.value.canonical}`));
|
|
346
|
+
}
|
|
239
347
|
const wrapper = [
|
|
240
348
|
"during ",
|
|
241
349
|
"in ",
|
|
@@ -243,10 +351,16 @@ const parsePeriod = (input) => {
|
|
|
243
351
|
"all of ",
|
|
244
352
|
"the whole of "
|
|
245
353
|
].find((prefix) => input.startsWith(prefix));
|
|
246
|
-
return
|
|
354
|
+
return Option.firstSomeOf([
|
|
355
|
+
parsePeriodEdge(input),
|
|
356
|
+
parseBasePeriod(wrapper === void 0 ? input : input.slice(wrapper.length)),
|
|
357
|
+
parseCalendarOffset(input)
|
|
358
|
+
]);
|
|
247
359
|
};
|
|
248
360
|
const boundaryCandidate = (input) => openBoundaryCandidate(input, [
|
|
249
361
|
["up to and including ", "through"],
|
|
362
|
+
["on or before ", "through"],
|
|
363
|
+
["on or after ", "since"],
|
|
250
364
|
["up to including ", "through"],
|
|
251
365
|
["since the beginning of ", "since"],
|
|
252
366
|
["since the start of ", "since"],
|
|
@@ -263,6 +377,8 @@ const boundaryCandidate = (input) => openBoundaryCandidate(input, [
|
|
|
263
377
|
["before start of ", "before"],
|
|
264
378
|
["starting ", "since"],
|
|
265
379
|
["through ", "through"],
|
|
380
|
+
["earlier than ", "before"],
|
|
381
|
+
["later than ", "after"],
|
|
266
382
|
["before ", "before"],
|
|
267
383
|
["since ", "since"],
|
|
268
384
|
["after ", "after"],
|
|
@@ -282,7 +398,7 @@ const parseCalendarOffset = (input) => {
|
|
|
282
398
|
const isPast = directionText === "ago" || directionText === "prior";
|
|
283
399
|
const direction = isPast ? -1 : 1;
|
|
284
400
|
const canonical = isPast ? `1 ${entry[0]} ago` : `in 1 ${entry[0]}`;
|
|
285
|
-
return Option.some(
|
|
401
|
+
return Option.some(relativePeriod(entry[1], direction, canonical));
|
|
286
402
|
}
|
|
287
403
|
}
|
|
288
404
|
const past = String$1.match(/^([1-9]\d*) (day|days|week|weeks|month|months|quarter|quarters|year|years) (?:ago|prior)$/u)(input);
|
|
@@ -301,26 +417,51 @@ const parseCalendarOffset = (input) => {
|
|
|
301
417
|
if (entry === void 0) return Option.none();
|
|
302
418
|
const direction = Option.isSome(past) ? -amount.value : amount.value;
|
|
303
419
|
const canonical = direction < 0 ? `${amount.value} ${unitText} ago` : `in ${amount.value} ${unitText}`;
|
|
304
|
-
return Option.some(
|
|
420
|
+
return Option.some(relativePeriod(entry[1], direction, canonical));
|
|
305
421
|
};
|
|
306
422
|
const parseRollingPeriod = (input) => {
|
|
307
423
|
const past = String$1.match(/^(?:(?:last|previous|past) |(?:in|over) the (?:last|previous|past) )?([1-9]\d*) (day|days|week|weeks|month|months|quarter|quarters|year|years)$/u)(input);
|
|
424
|
+
const singularPast = String$1.match(/^(?:past|the past|in the past|over the past|this past) (day|week|month|quarter|year)$/u)(input);
|
|
308
425
|
const future = String$1.match(/^(?:(?:next|coming|within) |(?:in|over|within) the (?:next|coming) )([1-9]\d*) (day|days|week|weeks|month|months|quarter|quarters|year|years)$/u)(input);
|
|
309
|
-
const
|
|
426
|
+
const singularFuture = String$1.match(/^(?:in|over|within) the (?:next|coming) (day|week|month|quarter|year)$/u)(input);
|
|
427
|
+
const match = Option.firstSomeOf([
|
|
428
|
+
past,
|
|
429
|
+
singularPast,
|
|
430
|
+
future,
|
|
431
|
+
singularFuture
|
|
432
|
+
]);
|
|
310
433
|
if (Option.isNone(match)) return Option.none();
|
|
311
|
-
const
|
|
434
|
+
const singular = Option.isSome(singularPast) || Option.isSome(singularFuture);
|
|
435
|
+
const amount = parseTrailingCount(singular ? "1" : textAt(match.value, 1));
|
|
312
436
|
if (Option.isNone(amount)) return Option.none();
|
|
313
|
-
const
|
|
437
|
+
const unitText = textAt(match.value, singular ? 1 : 2);
|
|
438
|
+
const entry = countedUnit(unitText, amount.value);
|
|
314
439
|
if (entry === void 0) return Option.none();
|
|
315
|
-
const isFuture = Option.isSome(future);
|
|
440
|
+
const isFuture = Option.isSome(future) || Option.isSome(singularFuture);
|
|
316
441
|
const range = isFuture ? futureRange(amount.value, entry[1]) : trailingRange(amount.value, entry[1]);
|
|
442
|
+
if (amount.value === 1) {
|
|
443
|
+
const canonical = isFuture ? `within the next ${entry[0]}` : `past ${entry[0]}`;
|
|
444
|
+
return Option.some(candidate(range, canonical));
|
|
445
|
+
}
|
|
317
446
|
const direction = isFuture ? "next" : "last";
|
|
318
|
-
|
|
319
|
-
|
|
447
|
+
return Option.some(candidate(range, `${direction} ${amount.value} ${entry[2]}`));
|
|
448
|
+
};
|
|
449
|
+
const parseElidedDateRange = (input) => {
|
|
450
|
+
const monthFirst = String$1.match(/^([a-z]+\.?) ([0-3]?\d)(?:st|nd|rd|th)?(?: (?:to|through) |[–—-])([0-3]?\d)(?:st|nd|rd|th)?,?(?: (\d{4}))?$/u)(input);
|
|
451
|
+
const dayFirst = String$1.match(/^([0-3]?\d)(?:st|nd|rd|th)?(?: (?:to|through) |[–—-])([0-3]?\d)(?:st|nd|rd|th)? ([a-z]+\.?)(?: (\d{4}))?$/u)(input);
|
|
452
|
+
const match = Option.firstSomeOf([monthFirst, dayFirst]);
|
|
453
|
+
if (Option.isNone(match)) return Option.none();
|
|
454
|
+
const isMonthFirst = Option.isSome(monthFirst);
|
|
455
|
+
const month = textAt(match.value, isMonthFirst ? 1 : 3);
|
|
456
|
+
const lowerDay = textAt(match.value, isMonthFirst ? 2 : 1);
|
|
457
|
+
const upperDay = textAt(match.value, isMonthFirst ? 3 : 2);
|
|
458
|
+
const year = textAt(match.value, 4);
|
|
459
|
+
const suffix = year.length === 0 ? "" : ` ${year}`;
|
|
460
|
+
return joinedPeriodCandidate(`from ${lowerDay} ${month}${suffix} to ${upperDay} ${month}${suffix}`, [["from ", " to "]], parsePeriod, (lower, upper) => `from ${lower} to ${upper}`);
|
|
320
461
|
};
|
|
321
462
|
const parseEnglish = (input) => {
|
|
322
463
|
const remaining = remainingPeriodPhrases.find((entry) => entry.phrase === input);
|
|
323
|
-
if (remaining !== void 0) return Option.some(candidate(remainingPeriodRange(remaining.entry[1]), `rest of ${remaining.entry[0]}`));
|
|
464
|
+
if (remaining !== void 0) return Option.some(candidate(remainingPeriodRange(remaining.entry[1]), `rest of the ${remaining.entry[0]}`));
|
|
324
465
|
if ([
|
|
325
466
|
"until now",
|
|
326
467
|
"till now",
|
|
@@ -332,22 +473,24 @@ const parseEnglish = (input) => {
|
|
|
332
473
|
"from now on",
|
|
333
474
|
"starting now"
|
|
334
475
|
].includes(input)) return Option.some(candidate(fromNowRange(), "from now"));
|
|
335
|
-
const offset = parseCalendarOffset(input);
|
|
336
|
-
if (Option.isSome(offset)) return offset;
|
|
337
476
|
const rolling = parseRollingPeriod(input);
|
|
338
477
|
if (Option.isSome(rolling)) return rolling;
|
|
339
478
|
const toDate = toDatePhrases.find((entry) => entry.phrase === input);
|
|
340
479
|
if (toDate !== void 0) return Option.some(candidate(periodToDateRange(toDate.entry[1]), `${toDate.entry[0]} to date`));
|
|
480
|
+
const elided = parseElidedDateRange(input);
|
|
481
|
+
if (Option.isSome(elided)) return elided;
|
|
341
482
|
const nowBounded = joinedNowCandidate(input, [
|
|
342
483
|
["from ", " to now"],
|
|
343
484
|
["from ", " until now"],
|
|
344
485
|
["from ", " through now"],
|
|
486
|
+
["from ", " to date"],
|
|
345
487
|
["between ", " and now"]
|
|
346
488
|
], [
|
|
347
489
|
"from now to ",
|
|
348
490
|
"from now until ",
|
|
349
491
|
"from now through ",
|
|
350
|
-
"between now and "
|
|
492
|
+
"between now and ",
|
|
493
|
+
"now to "
|
|
351
494
|
], parsePeriod, (period) => `from ${period} to now`, (period) => `from now to ${period}`);
|
|
352
495
|
if (Option.isSome(nowBounded)) return nowBounded;
|
|
353
496
|
const bounded = joinedPeriodCandidate(input, [
|
|
@@ -380,18 +523,19 @@ const parseEnglish = (input) => {
|
|
|
380
523
|
if (Option.isSome(bounded)) return bounded;
|
|
381
524
|
const boundary = boundaryCandidate(input);
|
|
382
525
|
if (Option.isSome(boundary)) return boundary;
|
|
383
|
-
return Option.map(
|
|
526
|
+
return parsePeriod(input).pipe(Option.map((period) => candidate(periodRange(period), period.canonical)));
|
|
384
527
|
};
|
|
385
528
|
const staticPeriodPhrases = [
|
|
386
529
|
"today",
|
|
387
530
|
"yesterday",
|
|
531
|
+
"the day before yesterday",
|
|
388
532
|
"tomorrow",
|
|
389
533
|
"this weekend",
|
|
390
534
|
"last weekend",
|
|
391
535
|
"next weekend",
|
|
392
536
|
"the weekend before last",
|
|
393
537
|
"the weekend after next",
|
|
394
|
-
...units.flatMap((entry) => [
|
|
538
|
+
...units.filter((entry) => entry[1] !== "day").flatMap((entry) => [
|
|
395
539
|
`this ${entry[0]}`,
|
|
396
540
|
`last ${entry[0]}`,
|
|
397
541
|
`next ${entry[0]}`,
|
|
@@ -414,9 +558,12 @@ const staticPeriodPhrases = [
|
|
|
414
558
|
]),
|
|
415
559
|
...months.flatMap((month) => [
|
|
416
560
|
month,
|
|
561
|
+
`last ${month}`,
|
|
562
|
+
`next ${month}`,
|
|
417
563
|
`${month} of last year`,
|
|
418
564
|
`${month} of next year`
|
|
419
|
-
])
|
|
565
|
+
]),
|
|
566
|
+
...nextWeekdayPhrases
|
|
420
567
|
];
|
|
421
568
|
const staticPeriods = periodsFromPhrases(staticPeriodPhrases, parsePeriod);
|
|
422
569
|
const boundaryPrefixes = [
|
|
@@ -441,7 +588,8 @@ const countedSuggestions = (input) => {
|
|
|
441
588
|
};
|
|
442
589
|
const englishSuggestionPhrases = [
|
|
443
590
|
...units.map((entry) => `${entry[0]} to date`),
|
|
444
|
-
...
|
|
591
|
+
...remainingPeriodPhrases.map((entry) => entry.phrase),
|
|
592
|
+
...units.flatMap((entry) => [`past ${entry[0]}`, `within the next ${entry[0]}`]),
|
|
445
593
|
...staticPeriodPhrases,
|
|
446
594
|
...prefixNaturalPhrases(staticPeriodPhrases, boundaryPrefixes),
|
|
447
595
|
"until now",
|
|
@@ -457,6 +605,17 @@ const suggestEnglish = (input, limit) => {
|
|
|
457
605
|
], limit);
|
|
458
606
|
};
|
|
459
607
|
const renderEnglish = (range) => {
|
|
608
|
+
const shifted = decomposeShiftedPeriodRange(range);
|
|
609
|
+
if (Option.isSome(shifted)) {
|
|
610
|
+
const base = renderEnglish(shifted.value.baseRange);
|
|
611
|
+
const entry = units.find((unit) => unit[1] === shifted.value.unit);
|
|
612
|
+
if (Option.isSome(base) && entry !== void 0) {
|
|
613
|
+
const amount = Math.abs(shifted.value.amount);
|
|
614
|
+
const noun = amount === 1 ? entry[0] : entry[2];
|
|
615
|
+
const suffix = shifted.value.amount < 0 ? "ago" : "from now";
|
|
616
|
+
return Option.some(`${base.value} ${amount} ${noun} ${suffix}`);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
460
619
|
const offset = calendarPeriodOffset(range);
|
|
461
620
|
if (Option.isSome(offset) && Math.abs(offset.value.amount) > 1) {
|
|
462
621
|
const entry = units.find((unit) => unit[1] === offset.value.unit);
|
|
@@ -468,20 +627,25 @@ const renderEnglish = (range) => {
|
|
|
468
627
|
const future = futurePeriod(range);
|
|
469
628
|
if (Option.isSome(future)) {
|
|
470
629
|
const entry = units.find((unit) => unit[1] === future.value.unit);
|
|
471
|
-
if (entry !== void 0) return Option.some(
|
|
630
|
+
if (entry !== void 0) return Option.some(future.value.amount === 1 ? `within the next ${entry[0]}` : `next ${future.value.amount} ${entry[2]}`);
|
|
472
631
|
}
|
|
473
632
|
const trailing = trailingPeriod(range);
|
|
474
633
|
if (Option.isSome(trailing)) {
|
|
475
634
|
const entry = units.find((unit) => unit[1] === trailing.value.unit);
|
|
476
|
-
if (entry !== void 0) return Option.some(trailing.value.amount === 1 ? `
|
|
635
|
+
if (entry !== void 0) return Option.some(trailing.value.amount === 1 ? `past ${entry[0]}` : `last ${trailing.value.amount} ${entry[2]}`);
|
|
477
636
|
}
|
|
478
|
-
const periods = [
|
|
479
|
-
|
|
637
|
+
const periods = [
|
|
638
|
+
...staticPeriods,
|
|
639
|
+
...currentYearDatePeriods(range, currentDateLabel),
|
|
640
|
+
...periodsFromPhrases([...datedPeriods(range, months), ...datedQuarterPeriods(range)], parsePeriod)
|
|
641
|
+
];
|
|
642
|
+
return renderPeriodRange(range, [...units.map((entry) => candidate(periodToDateRange(entry[1]), `${entry[0]} to date`)), ...units.map((entry) => candidate(remainingPeriodRange(entry[1]), `rest of the ${entry[0]}`))], periods, (period) => `since ${period}`, (period) => `before ${period}`, (period) => `through ${period}`, (period) => `after ${period}`, (lower, upper) => `from ${lower} to ${upper}`, (period) => `from ${period} to now`, (period) => `from now to ${period}`, () => "until now", () => "from now");
|
|
480
643
|
};
|
|
481
644
|
const EnglishContribution = new BaseLanguageContribution({
|
|
482
645
|
locale: "en",
|
|
483
646
|
vocabulary: [
|
|
484
647
|
...months,
|
|
648
|
+
...weekdays,
|
|
485
649
|
...quarterNames,
|
|
486
650
|
"q1",
|
|
487
651
|
"q2",
|
|
@@ -535,8 +699,8 @@ const EnglishContribution = new BaseLanguageContribution({
|
|
|
535
699
|
"weekend",
|
|
536
700
|
"yesterday"
|
|
537
701
|
],
|
|
538
|
-
normalize:
|
|
539
|
-
correct:
|
|
702
|
+
normalize: normalizeEnglish,
|
|
703
|
+
correct: correctEnglish,
|
|
540
704
|
parseExact: parseEnglish,
|
|
541
705
|
suggest: suggestEnglish,
|
|
542
706
|
render: renderEnglish
|