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/shared.mjs
CHANGED
|
@@ -1,17 +1,76 @@
|
|
|
1
|
-
import { InstantExpr, Unit, daysInMonth, isIsoDate } from "../ast/schemas.mjs";
|
|
1
|
+
import { GreaterThanOrEqual, InstantExpr, LessThan, LessThanOrEqual, Now, Shift, StartOf, Unit, daysInMonth, isIsoDate } from "../ast/schemas.mjs";
|
|
2
2
|
import { boundedRange, dateLiteral, greaterThanOrEqual, lessThan, lessThanOrEqual, lowerOpenRange, now, shift, startOf, upperOpenRange } from "../ast/constructors.mjs";
|
|
3
3
|
import { formatInstantExpression } from "../filter/expression.mjs";
|
|
4
4
|
import { formatFilter, rangeKey } from "../filter/codec.mjs";
|
|
5
5
|
import { NaturalCandidate } from "../language/model.mjs";
|
|
6
|
-
import { Option, Schema, String as String$1 } from "effect";
|
|
6
|
+
import { Match, Option, RegExp as RegExp$1, Schema, String as String$1 } from "effect";
|
|
7
7
|
//#region src/locales/shared.ts
|
|
8
8
|
const textAt = (values, index) => values[index] ?? "";
|
|
9
|
+
const sequentialCountAliases = (words, start) => words.flatMap((aliases, index) => aliases.map((phrase) => [phrase, start + index]));
|
|
10
|
+
const decimalTens = (words) => words.map((word, index) => [20 + index * 10, word]);
|
|
11
|
+
const compoundCountAliases = (tens, ones, join) => tens.flatMap(([tensAmount, tensWord]) => ones.flatMap(([oneAmount, oneWord]) => {
|
|
12
|
+
const amount = tensAmount + oneAmount;
|
|
13
|
+
return join(tensWord, oneWord, amount).map((phrase) => [phrase, amount]);
|
|
14
|
+
}));
|
|
15
|
+
const countAliasVocabulary = (aliases) => [...new Set(aliases.flatMap(([phrase]) => phrase.split(" ")))];
|
|
16
|
+
const compileCountAliasNormalizer = (aliases) => {
|
|
17
|
+
const distinct = /* @__PURE__ */ new Map();
|
|
18
|
+
for (const [phrase, amount] of aliases) if (!distinct.has(phrase)) distinct.set(phrase, amount);
|
|
19
|
+
const byFirstWord = /* @__PURE__ */ new Map();
|
|
20
|
+
for (const [phrase, amount] of distinct) {
|
|
21
|
+
const parts = phrase.split(" ");
|
|
22
|
+
const first = parts[0] ?? "";
|
|
23
|
+
const entries = byFirstWord.get(first) ?? [];
|
|
24
|
+
byFirstWord.set(first, [...entries, {
|
|
25
|
+
amount,
|
|
26
|
+
parts
|
|
27
|
+
}]);
|
|
28
|
+
}
|
|
29
|
+
for (const [first, entries] of byFirstWord) byFirstWord.set(first, [...entries].sort((left, right) => right.parts.length - left.parts.length));
|
|
30
|
+
return (input) => {
|
|
31
|
+
const words = input.split(" ");
|
|
32
|
+
const output = [];
|
|
33
|
+
for (let index = 0; index < words.length;) {
|
|
34
|
+
const alias = (byFirstWord.get(words[index] ?? "") ?? []).find((entry) => entry.parts.every((part, offset) => words[index + offset] === part));
|
|
35
|
+
if (alias === void 0) {
|
|
36
|
+
output.push(words[index] ?? "");
|
|
37
|
+
index += 1;
|
|
38
|
+
} else {
|
|
39
|
+
output.push(String(alias.amount));
|
|
40
|
+
index += alias.parts.length;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return output.join(" ");
|
|
44
|
+
};
|
|
45
|
+
};
|
|
9
46
|
const Period = Schema.Struct({
|
|
10
47
|
start: InstantExpr,
|
|
11
48
|
end: InstantExpr,
|
|
12
49
|
canonical: Schema.String
|
|
13
50
|
});
|
|
51
|
+
const isGreaterThanOrEqual = Schema.is(GreaterThanOrEqual);
|
|
52
|
+
const isLessThan = Schema.is(LessThan);
|
|
53
|
+
const isLessThanOrEqual = Schema.is(LessThanOrEqual);
|
|
54
|
+
const isNow = Schema.is(Now);
|
|
55
|
+
const isShift = Schema.is(Shift);
|
|
56
|
+
const isStartOf = Schema.is(StartOf);
|
|
14
57
|
const periodRange = (period) => boundedRange(greaterThanOrEqual(period.start), lessThan(period.end));
|
|
58
|
+
const shiftPeriod = (period, amount, unit, canonical) => Period.make({
|
|
59
|
+
start: shift(period.start, amount, unit),
|
|
60
|
+
end: shift(period.end, amount, unit),
|
|
61
|
+
canonical
|
|
62
|
+
});
|
|
63
|
+
const decomposeShiftedPeriodRange = (range) => {
|
|
64
|
+
if (!isGreaterThanOrEqual(range.lower) || !isLessThan(range.upper)) return Option.none();
|
|
65
|
+
const start = range.lower.value;
|
|
66
|
+
const end = range.upper.value;
|
|
67
|
+
if (!isShift(start) || !isShift(end) || start.amount === 0 || start.amount !== end.amount || start.unit !== end.unit) return Option.none();
|
|
68
|
+
return Option.some({
|
|
69
|
+
amount: start.amount,
|
|
70
|
+
unit: start.unit,
|
|
71
|
+
baseRange: boundedRange(greaterThanOrEqual(start.base), lessThan(end.base))
|
|
72
|
+
});
|
|
73
|
+
};
|
|
15
74
|
const periodToDateRange = (unit) => boundedRange(greaterThanOrEqual(startOf(now(), unit)), lessThanOrEqual(now()));
|
|
16
75
|
const fromNowRange = () => lowerOpenRange(greaterThanOrEqual(now()));
|
|
17
76
|
const untilNowRange = () => upperOpenRange(lessThanOrEqual(now()));
|
|
@@ -29,6 +88,30 @@ const periodEndDay = (period, canonical) => Period.make({
|
|
|
29
88
|
end: period.end,
|
|
30
89
|
canonical
|
|
31
90
|
});
|
|
91
|
+
const periodDay = (period, day, canonical) => {
|
|
92
|
+
if (!Number.isInteger(day) || day < 1 || day > 28) return Option.none();
|
|
93
|
+
const start = day === 1 ? period.start : shift(period.start, day - 1, "day");
|
|
94
|
+
return Option.some(Period.make({
|
|
95
|
+
start,
|
|
96
|
+
end: shift(start, 1, "day"),
|
|
97
|
+
canonical
|
|
98
|
+
}));
|
|
99
|
+
};
|
|
100
|
+
const periodPreviousDay = (period, canonical) => Period.make({
|
|
101
|
+
start: shift(period.start, -1, "day"),
|
|
102
|
+
end: period.start,
|
|
103
|
+
canonical
|
|
104
|
+
});
|
|
105
|
+
const relativeWeekday = (day, direction, canonical) => {
|
|
106
|
+
const weekBase = direction === 0 ? now() : shift(now(), direction, "week");
|
|
107
|
+
const weekStart = startOf(weekBase, "week");
|
|
108
|
+
const start = day === 0 ? weekStart : shift(weekStart, day, "day");
|
|
109
|
+
return Period.make({
|
|
110
|
+
start,
|
|
111
|
+
end: shift(start, 1, "day"),
|
|
112
|
+
canonical
|
|
113
|
+
});
|
|
114
|
+
};
|
|
32
115
|
const relativeWeekend = (direction, canonical) => {
|
|
33
116
|
const weekBase = direction === 0 ? now() : shift(now(), direction, "week");
|
|
34
117
|
const weekStart = startOf(weekBase, "week");
|
|
@@ -43,11 +126,7 @@ const TrailingCount = Schema.Int.check(Schema.isBetween({
|
|
|
43
126
|
minimum: 1,
|
|
44
127
|
maximum: Number.MAX_SAFE_INTEGER
|
|
45
128
|
}));
|
|
46
|
-
const
|
|
47
|
-
amount: TrailingCount,
|
|
48
|
-
unit: Unit
|
|
49
|
-
});
|
|
50
|
-
const FuturePeriod = Schema.Struct({
|
|
129
|
+
const CountedPeriod = Schema.Struct({
|
|
51
130
|
amount: TrailingCount,
|
|
52
131
|
unit: Unit
|
|
53
132
|
});
|
|
@@ -66,15 +145,17 @@ const parseTrailingCount = (value) => {
|
|
|
66
145
|
const trailingRange = (amount, unit) => boundedRange(greaterThanOrEqual(shift(now(), -amount, unit)), lessThanOrEqual(now()));
|
|
67
146
|
const futureRange = (amount, unit) => boundedRange(greaterThanOrEqual(now()), lessThanOrEqual(shift(now(), amount, unit)));
|
|
68
147
|
const trailingPeriod = (range) => {
|
|
69
|
-
if (range.lower
|
|
70
|
-
return Option.
|
|
148
|
+
if (!isGreaterThanOrEqual(range.lower) || !isLessThanOrEqual(range.upper)) return Option.none();
|
|
149
|
+
if (!isShift(range.lower.value) || range.lower.value.amount >= 0 || !isNow(range.lower.value.base) || !isNow(range.upper.value)) return Option.none();
|
|
150
|
+
return Option.some(CountedPeriod.make({
|
|
71
151
|
amount: -range.lower.value.amount,
|
|
72
152
|
unit: range.lower.value.unit
|
|
73
153
|
}));
|
|
74
154
|
};
|
|
75
155
|
const futurePeriod = (range) => {
|
|
76
|
-
if (range.lower
|
|
77
|
-
return Option.
|
|
156
|
+
if (!isGreaterThanOrEqual(range.lower) || !isLessThanOrEqual(range.upper)) return Option.none();
|
|
157
|
+
if (!isNow(range.lower.value) || !isShift(range.upper.value) || range.upper.value.amount <= 0 || !isNow(range.upper.value.base)) return Option.none();
|
|
158
|
+
return Option.some(CountedPeriod.make({
|
|
78
159
|
amount: range.upper.value.amount,
|
|
79
160
|
unit: range.upper.value.unit
|
|
80
161
|
}));
|
|
@@ -100,6 +181,50 @@ const previousDay = (year, month, day) => {
|
|
|
100
181
|
if (month > 1) return isoDate(year, month - 1, daysInMonth(year, month - 1));
|
|
101
182
|
return isoDate(year - 1, 12, 31);
|
|
102
183
|
};
|
|
184
|
+
const absoluteDateProfiles = /* @__PURE__ */ new Map();
|
|
185
|
+
const absoluteDateProfile = (locale) => {
|
|
186
|
+
const cached = absoluteDateProfiles.get(locale);
|
|
187
|
+
if (cached !== void 0) return cached;
|
|
188
|
+
const formatter = new Intl.DateTimeFormat(locale, {
|
|
189
|
+
day: "numeric",
|
|
190
|
+
month: "numeric",
|
|
191
|
+
year: "numeric",
|
|
192
|
+
numberingSystem: "latn",
|
|
193
|
+
timeZone: "UTC"
|
|
194
|
+
});
|
|
195
|
+
const parts = [];
|
|
196
|
+
const pattern = formatter.formatToParts(/* @__PURE__ */ new Date("2006-11-22T00:00:00.000Z")).map((part) => {
|
|
197
|
+
if (part.type === "day" || part.type === "month" || part.type === "year") {
|
|
198
|
+
parts.push(part.type);
|
|
199
|
+
return part.type === "year" ? "([0-9]{4})" : "([0-9]{1,2})";
|
|
200
|
+
}
|
|
201
|
+
return RegExp$1.escape(part.value);
|
|
202
|
+
}).join("");
|
|
203
|
+
const profile = {
|
|
204
|
+
formatter,
|
|
205
|
+
pattern: new RegExp(`^${pattern}$`, "u"),
|
|
206
|
+
parts
|
|
207
|
+
};
|
|
208
|
+
absoluteDateProfiles.set(locale, profile);
|
|
209
|
+
return profile;
|
|
210
|
+
};
|
|
211
|
+
const formatAbsoluteDate = (value, locale) => absoluteDateProfile(locale).formatter.format(/* @__PURE__ */ new Date(`${value}T00:00:00.000Z`));
|
|
212
|
+
const parseLocalizedAbsoluteDate = (input, locale) => {
|
|
213
|
+
const profile = absoluteDateProfile(locale);
|
|
214
|
+
const match = String$1.match(profile.pattern)(input);
|
|
215
|
+
if (Option.isNone(match)) return Option.none();
|
|
216
|
+
let day = "";
|
|
217
|
+
let month = "";
|
|
218
|
+
let year = "";
|
|
219
|
+
for (const [index, part] of profile.parts.entries()) {
|
|
220
|
+
const value = textAt(match.value, index + 1);
|
|
221
|
+
if (part === "day") day = value;
|
|
222
|
+
if (part === "month") month = value;
|
|
223
|
+
if (part === "year") year = value;
|
|
224
|
+
}
|
|
225
|
+
const value = isoDate(Number(year), Number(month), Number(day));
|
|
226
|
+
return isIsoDate(value) ? Option.some(value) : Option.none();
|
|
227
|
+
};
|
|
103
228
|
const fixedDatePeriod = (value, canonical) => {
|
|
104
229
|
const year = Number(value.slice(0, 4));
|
|
105
230
|
const month = Number(value.slice(5, 7));
|
|
@@ -110,6 +235,9 @@ const fixedDatePeriod = (value, canonical) => {
|
|
|
110
235
|
canonical
|
|
111
236
|
});
|
|
112
237
|
};
|
|
238
|
+
const absoluteDatePeriod = (input, locale) => {
|
|
239
|
+
return (isIsoDate(input) ? Option.some(input) : parseLocalizedAbsoluteDate(input, locale)).pipe(Option.filter((date) => date !== "9999-12-31"), Option.map((date) => fixedDatePeriod(date, formatAbsoluteDate(date, locale))));
|
|
240
|
+
};
|
|
113
241
|
const namedDatePeriod = (yearText, monthText, dayText, monthNumber, canonical) => {
|
|
114
242
|
const year = validYear(yearText);
|
|
115
243
|
const month = monthNumber(monthText);
|
|
@@ -119,6 +247,51 @@ const namedDatePeriod = (yearText, monthText, dayText, monthNumber, canonical) =
|
|
|
119
247
|
if (!isIsoDate(value) || value === "9999-12-31") return Option.none();
|
|
120
248
|
return Option.some(fixedDatePeriod(value, canonical(day, month, year)));
|
|
121
249
|
};
|
|
250
|
+
const minimumDaysInMonth = (month) => daysInMonth(1, month);
|
|
251
|
+
const currentYearDatePeriod = (month, day, canonical) => {
|
|
252
|
+
const yearStart = startOf(now(), "year");
|
|
253
|
+
const monthStart = month === 1 ? yearStart : shift(yearStart, month - 1, "month");
|
|
254
|
+
const start = day === 1 ? monthStart : shift(monthStart, day - 1, "day");
|
|
255
|
+
return Period.make({
|
|
256
|
+
start,
|
|
257
|
+
end: shift(start, 1, "day"),
|
|
258
|
+
canonical
|
|
259
|
+
});
|
|
260
|
+
};
|
|
261
|
+
const namedCurrentYearDatePeriod = (monthText, dayText, monthNumber, canonical) => {
|
|
262
|
+
const month = monthNumber(monthText);
|
|
263
|
+
const day = Number(dayText);
|
|
264
|
+
if (month === void 0 || !Number.isInteger(day) || day < 1 || day > minimumDaysInMonth(month)) return Option.none();
|
|
265
|
+
return Option.some(currentYearDatePeriod(month, day, canonical(day, month)));
|
|
266
|
+
};
|
|
267
|
+
const currentYearDate = (expression) => {
|
|
268
|
+
const match = String$1.match(/^now\/y(?:\+([1-9]\d*)M)?(?:\+([1-9]\d*)d)?$/u)(expression);
|
|
269
|
+
if (Option.isNone(match)) return Option.none();
|
|
270
|
+
const month = Number(textAt(match.value, 1) || "0") + 1;
|
|
271
|
+
const offset = Number(textAt(match.value, 2) || "0");
|
|
272
|
+
return month >= 1 && month <= 12 ? Option.some([month, offset]) : Option.none();
|
|
273
|
+
};
|
|
274
|
+
const currentYearDatePeriods = (range, canonical) => {
|
|
275
|
+
const filter = formatFilter(range);
|
|
276
|
+
const expressions = [
|
|
277
|
+
filter.gt,
|
|
278
|
+
filter.gte,
|
|
279
|
+
filter.lt,
|
|
280
|
+
filter.lte
|
|
281
|
+
];
|
|
282
|
+
const dates = /* @__PURE__ */ new Map();
|
|
283
|
+
for (const expression of expressions) {
|
|
284
|
+
if (expression === void 0) continue;
|
|
285
|
+
const date = currentYearDate(expression);
|
|
286
|
+
if (Option.isNone(date)) continue;
|
|
287
|
+
const [month, offset] = date.value;
|
|
288
|
+
for (const day of [offset, offset + 1]) {
|
|
289
|
+
if (day < 1 || day > minimumDaysInMonth(month)) continue;
|
|
290
|
+
dates.set(`${month}-${day}`, [month, day]);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
return [...dates.values()].map(([month, day]) => currentYearDatePeriod(month, day, canonical(day, month)));
|
|
294
|
+
};
|
|
122
295
|
const fixedMonthPeriod = (year, month, canonical) => {
|
|
123
296
|
const nextYear = month === 12 ? year + 1 : year;
|
|
124
297
|
const nextMonth = month === 12 ? 1 : month + 1;
|
|
@@ -164,10 +337,13 @@ const monthOfRelativeYear = (month, direction, canonical) => {
|
|
|
164
337
|
});
|
|
165
338
|
};
|
|
166
339
|
const calendarPeriodOffset = (range) => {
|
|
167
|
-
if (range.lower
|
|
340
|
+
if (!isGreaterThanOrEqual(range.lower) || !isLessThan(range.upper)) return Option.none();
|
|
341
|
+
const start = range.lower.value;
|
|
342
|
+
const end = range.upper.value;
|
|
343
|
+
if (!isStartOf(start) || !isShift(start.base) || start.base.amount === 0 || !isNow(start.base.base) || start.unit !== start.base.unit || !isShift(end) || end.amount !== 1 || end.unit !== start.unit || formatInstantExpression(end.base) !== formatInstantExpression(start)) return Option.none();
|
|
168
344
|
return Option.some(CalendarPeriodOffset.make({
|
|
169
|
-
amount:
|
|
170
|
-
unit:
|
|
345
|
+
amount: start.base.amount,
|
|
346
|
+
unit: start.unit
|
|
171
347
|
}));
|
|
172
348
|
};
|
|
173
349
|
const candidate = (range, canonical) => NaturalCandidate.make({
|
|
@@ -201,14 +377,7 @@ const joinedPeriodCandidate = (input, joins, parsePeriod, canonical) => {
|
|
|
201
377
|
}
|
|
202
378
|
return Option.none();
|
|
203
379
|
};
|
|
204
|
-
const periodBoundaryCandidate = (period, boundary, canonical) =>
|
|
205
|
-
switch (boundary) {
|
|
206
|
-
case "since": return candidate(lowerOpenRange(greaterThanOrEqual(period.start)), canonical);
|
|
207
|
-
case "before": return candidate(upperOpenRange(lessThan(period.start)), canonical);
|
|
208
|
-
case "through": return candidate(upperOpenRange(lessThan(period.end)), canonical);
|
|
209
|
-
case "after": return candidate(lowerOpenRange(greaterThanOrEqual(period.end)), canonical);
|
|
210
|
-
}
|
|
211
|
-
};
|
|
380
|
+
const periodBoundaryCandidate = (period, boundary, canonical) => Match.value(boundary).pipe(Match.when("since", () => candidate(lowerOpenRange(greaterThanOrEqual(period.start)), canonical)), Match.when("before", () => candidate(upperOpenRange(lessThan(period.start)), canonical)), Match.when("through", () => candidate(upperOpenRange(lessThan(period.end)), canonical)), Match.when("after", () => candidate(lowerOpenRange(greaterThanOrEqual(period.end)), canonical)), Match.exhaustive);
|
|
212
381
|
const openBoundaryCandidate = (input, boundaries, parsePeriod) => {
|
|
213
382
|
for (const boundary of boundaries) {
|
|
214
383
|
if (!input.startsWith(boundary[0])) continue;
|
|
@@ -286,4 +455,4 @@ const renderPeriodRange = (range, toDate, periods, since, before, through, after
|
|
|
286
455
|
return Option.none();
|
|
287
456
|
};
|
|
288
457
|
//#endregion
|
|
289
|
-
export { Period, calendarPeriodOffset, candidate, datedPeriods, datedQuarterPeriods, fixedDatePeriod, fixedMonthPeriod, fixedQuarterPeriod, fixedYearPeriod, fromNowRange, futurePeriod, futureRange, isoDate, joinedNowCandidate, joinedPeriodCandidate, monthOfRelativeYear, namedDatePeriod, openBoundaryCandidate, parseTrailingCount, periodBoundaryCandidate, periodEndDay, periodRange, periodStartDay, periodToDateRange, periodsFromPhrases, quarterOfRelativeYear, relativePeriod, relativeWeekend, remainingPeriodRange, renderPeriodRange, textAt, trailingPeriod, trailingRange, untilNowRange, validYear };
|
|
458
|
+
export { Period, absoluteDatePeriod, calendarPeriodOffset, candidate, compileCountAliasNormalizer, compoundCountAliases, countAliasVocabulary, currentYearDatePeriods, datedPeriods, datedQuarterPeriods, decimalTens, decomposeShiftedPeriodRange, fixedDatePeriod, fixedMonthPeriod, fixedQuarterPeriod, fixedYearPeriod, formatAbsoluteDate, fromNowRange, futurePeriod, futureRange, isoDate, joinedNowCandidate, joinedPeriodCandidate, monthOfRelativeYear, namedCurrentYearDatePeriod, namedDatePeriod, openBoundaryCandidate, parseTrailingCount, periodBoundaryCandidate, periodDay, periodEndDay, periodPreviousDay, periodRange, periodStartDay, periodToDateRange, periodsFromPhrases, quarterOfRelativeYear, relativePeriod, relativeWeekday, relativeWeekend, remainingPeriodRange, renderPeriodRange, sequentialCountAliases, shiftPeriod, textAt, trailingPeriod, trailingRange, untilNowRange, validYear };
|