chronolizer 0.1.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/LICENSE +21 -0
- package/README.md +196 -0
- package/dist/index.d.mts +969 -0
- package/dist/index.mjs +2305 -0
- package/package.json +55 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,2305 @@
|
|
|
1
|
+
import { Array as Array$1, Context, Data, DateTime, Effect, Layer, Match, Option, Order, Ref, Result, Schema, SchemaIssue, SchemaTransformation, String as String$1 } from "effect";
|
|
2
|
+
//#region src/ast/schemas.ts
|
|
3
|
+
const Unit = Schema.Literals([
|
|
4
|
+
"day",
|
|
5
|
+
"week",
|
|
6
|
+
"month",
|
|
7
|
+
"quarter",
|
|
8
|
+
"year"
|
|
9
|
+
]);
|
|
10
|
+
const isLeapYear = (year) => year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
|
|
11
|
+
const daysInMonth = (year, month) => {
|
|
12
|
+
switch (month) {
|
|
13
|
+
case 2: return isLeapYear(year) ? 29 : 28;
|
|
14
|
+
case 4:
|
|
15
|
+
case 6:
|
|
16
|
+
case 9:
|
|
17
|
+
case 11: return 30;
|
|
18
|
+
default: return 31;
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
const isIsoDate = (value) => {
|
|
22
|
+
if (Option.isNone(String$1.match(/^\d{4}-\d{2}-\d{2}$/)(value))) return false;
|
|
23
|
+
const year = Number(value.slice(0, 4));
|
|
24
|
+
const month = Number(value.slice(5, 7));
|
|
25
|
+
const day = Number(value.slice(8, 10));
|
|
26
|
+
return month >= 1 && month <= 12 && day >= 1 && day <= daysInMonth(year, month);
|
|
27
|
+
};
|
|
28
|
+
const IsoDate = Schema.String.check(Schema.makeFilter(isIsoDate, { expected: "an ISO calendar date (YYYY-MM-DD)" })).annotate({ identifier: "IsoDate" });
|
|
29
|
+
const Now = Schema.TaggedStruct("Now", {});
|
|
30
|
+
const DateLiteral = Schema.TaggedStruct("DateLiteral", { value: IsoDate });
|
|
31
|
+
const ShiftAmount = Schema.Int.check(Schema.isBetween({
|
|
32
|
+
minimum: Number.MIN_SAFE_INTEGER,
|
|
33
|
+
maximum: Number.MAX_SAFE_INTEGER
|
|
34
|
+
}));
|
|
35
|
+
const Shift = Schema.TaggedStruct("Shift", {
|
|
36
|
+
base: Schema.suspend(() => InstantExpr),
|
|
37
|
+
amount: ShiftAmount,
|
|
38
|
+
unit: Unit
|
|
39
|
+
});
|
|
40
|
+
const StartOf = Schema.TaggedStruct("StartOf", {
|
|
41
|
+
base: Schema.suspend(() => InstantExpr),
|
|
42
|
+
unit: Unit
|
|
43
|
+
});
|
|
44
|
+
const InstantExpr = Schema.suspend(() => Schema.Union([
|
|
45
|
+
Now,
|
|
46
|
+
DateLiteral,
|
|
47
|
+
Shift,
|
|
48
|
+
StartOf
|
|
49
|
+
])).annotate({ identifier: "InstantExpr" });
|
|
50
|
+
const GreaterThan = Schema.TaggedStruct("GreaterThan", { value: InstantExpr });
|
|
51
|
+
const GreaterThanOrEqual = Schema.TaggedStruct("GreaterThanOrEqual", { value: InstantExpr });
|
|
52
|
+
const LessThan = Schema.TaggedStruct("LessThan", { value: InstantExpr });
|
|
53
|
+
const LessThanOrEqual = Schema.TaggedStruct("LessThanOrEqual", { value: InstantExpr });
|
|
54
|
+
const LowerBound = Schema.Union([GreaterThan, GreaterThanOrEqual]);
|
|
55
|
+
const UpperBound = Schema.Union([LessThan, LessThanOrEqual]);
|
|
56
|
+
const BoundedDateRange = Schema.TaggedStruct("DateRange", {
|
|
57
|
+
lower: LowerBound,
|
|
58
|
+
upper: UpperBound
|
|
59
|
+
});
|
|
60
|
+
const LowerOpenDateRange = Schema.TaggedStruct("DateRange", {
|
|
61
|
+
lower: LowerBound,
|
|
62
|
+
upper: Schema.optionalKey(Schema.Never)
|
|
63
|
+
});
|
|
64
|
+
const UpperOpenDateRange = Schema.TaggedStruct("DateRange", {
|
|
65
|
+
lower: Schema.optionalKey(Schema.Never),
|
|
66
|
+
upper: UpperBound
|
|
67
|
+
});
|
|
68
|
+
const DateRangeExpr = Schema.Union([
|
|
69
|
+
BoundedDateRange,
|
|
70
|
+
LowerOpenDateRange,
|
|
71
|
+
UpperOpenDateRange
|
|
72
|
+
]).annotate({ identifier: "DateRangeExpr" });
|
|
73
|
+
//#endregion
|
|
74
|
+
//#region src/ast/constructors.ts
|
|
75
|
+
const now = () => Now.make({});
|
|
76
|
+
const dateLiteral = (value) => DateLiteral.make({ value });
|
|
77
|
+
const shift = (base, amount, unit) => Shift.make({
|
|
78
|
+
base,
|
|
79
|
+
amount,
|
|
80
|
+
unit
|
|
81
|
+
});
|
|
82
|
+
const startOf = (base, unit) => StartOf.make({
|
|
83
|
+
base,
|
|
84
|
+
unit
|
|
85
|
+
});
|
|
86
|
+
const greaterThan = (value) => GreaterThan.make({ value });
|
|
87
|
+
const greaterThanOrEqual = (value) => GreaterThanOrEqual.make({ value });
|
|
88
|
+
const lessThan = (value) => LessThan.make({ value });
|
|
89
|
+
const lessThanOrEqual = (value) => LessThanOrEqual.make({ value });
|
|
90
|
+
const boundedRange = (lower, upper) => DateRangeExpr.make({
|
|
91
|
+
lower,
|
|
92
|
+
upper
|
|
93
|
+
});
|
|
94
|
+
const lowerOpenRange = (lower) => DateRangeExpr.make({ lower });
|
|
95
|
+
const upperOpenRange = (upper) => DateRangeExpr.make({ upper });
|
|
96
|
+
//#endregion
|
|
97
|
+
//#region src/ast/fold.ts
|
|
98
|
+
const foldInstant = (expression, algebra) => {
|
|
99
|
+
switch (expression._tag) {
|
|
100
|
+
case "Now": return algebra.now();
|
|
101
|
+
case "DateLiteral": return algebra.dateLiteral(expression.value);
|
|
102
|
+
case "Shift": return algebra.shift(foldInstant(expression.base, algebra), expression.amount, expression.unit);
|
|
103
|
+
case "StartOf": return algebra.startOf(foldInstant(expression.base, algebra), expression.unit);
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
const shiftOffset = (base, amount, unit) => {
|
|
107
|
+
switch (unit) {
|
|
108
|
+
case "year": return {
|
|
109
|
+
...base,
|
|
110
|
+
months: base.months + amount * 12
|
|
111
|
+
};
|
|
112
|
+
case "quarter": return {
|
|
113
|
+
...base,
|
|
114
|
+
months: base.months + amount * 3
|
|
115
|
+
};
|
|
116
|
+
case "month": return {
|
|
117
|
+
...base,
|
|
118
|
+
months: base.months + amount
|
|
119
|
+
};
|
|
120
|
+
case "week": return {
|
|
121
|
+
...base,
|
|
122
|
+
days: base.days + amount * 7
|
|
123
|
+
};
|
|
124
|
+
case "day": return {
|
|
125
|
+
...base,
|
|
126
|
+
days: base.days + amount
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
const containsPositiveShiftInstant = (expression) => {
|
|
131
|
+
const offset = foldInstant(expression, {
|
|
132
|
+
now: () => ({
|
|
133
|
+
months: 0,
|
|
134
|
+
days: 0
|
|
135
|
+
}),
|
|
136
|
+
dateLiteral: () => ({
|
|
137
|
+
months: 0,
|
|
138
|
+
days: 0
|
|
139
|
+
}),
|
|
140
|
+
shift: shiftOffset,
|
|
141
|
+
startOf: (base) => base
|
|
142
|
+
});
|
|
143
|
+
return offset.months > 0 || offset.days > 0;
|
|
144
|
+
};
|
|
145
|
+
const containsPositiveShift = (range) => range.lower !== void 0 && containsPositiveShiftInstant(range.lower.value) || range.upper !== void 0 && containsPositiveShiftInstant(range.upper.value);
|
|
146
|
+
//#endregion
|
|
147
|
+
//#region src/ast/normalize.ts
|
|
148
|
+
const normalizeInstant = (expression) => Match.valueTags(expression, {
|
|
149
|
+
Now: () => now(),
|
|
150
|
+
DateLiteral: (literal) => dateLiteral(literal.value),
|
|
151
|
+
StartOf: (operation) => startOf(normalizeInstant(operation.base), operation.unit),
|
|
152
|
+
Shift: (operation) => {
|
|
153
|
+
const base = normalizeInstant(operation.base);
|
|
154
|
+
if (operation.amount === 0) return base;
|
|
155
|
+
if (base._tag === "Shift" && base.unit === operation.unit) {
|
|
156
|
+
const amount = base.amount + operation.amount;
|
|
157
|
+
if (Number.isSafeInteger(amount)) return normalizeInstant(shift(base.base, amount, operation.unit));
|
|
158
|
+
}
|
|
159
|
+
return shift(base, operation.amount, operation.unit);
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
const normalizeLower = (bound) => Match.valueTags(bound, {
|
|
163
|
+
GreaterThan: (value) => greaterThan(normalizeInstant(value.value)),
|
|
164
|
+
GreaterThanOrEqual: (value) => greaterThanOrEqual(normalizeInstant(value.value))
|
|
165
|
+
});
|
|
166
|
+
const normalizeUpper = (bound) => Match.valueTags(bound, {
|
|
167
|
+
LessThan: (value) => lessThan(normalizeInstant(value.value)),
|
|
168
|
+
LessThanOrEqual: (value) => lessThanOrEqual(normalizeInstant(value.value))
|
|
169
|
+
});
|
|
170
|
+
const normalizeRange = (range) => {
|
|
171
|
+
if (range.lower !== void 0 && range.upper !== void 0) return boundedRange(normalizeLower(range.lower), normalizeUpper(range.upper));
|
|
172
|
+
if (range.lower !== void 0) return lowerOpenRange(normalizeLower(range.lower));
|
|
173
|
+
return upperOpenRange(normalizeUpper(range.upper));
|
|
174
|
+
};
|
|
175
|
+
//#endregion
|
|
176
|
+
//#region src/filter/errors.ts
|
|
177
|
+
var FilterExpressionParseError = class extends Schema.TaggedError()("FilterExpressionParseError", {
|
|
178
|
+
input: Schema.String,
|
|
179
|
+
offset: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
|
|
180
|
+
expected: Schema.String
|
|
181
|
+
}) {};
|
|
182
|
+
var InvalidDateFilterError = class extends Schema.TaggedError()("InvalidDateFilterError", { message: Schema.String }) {};
|
|
183
|
+
//#endregion
|
|
184
|
+
//#region src/filter/expression.ts
|
|
185
|
+
const unitFromSymbol = (symbol) => {
|
|
186
|
+
switch (symbol) {
|
|
187
|
+
case "d": return "day";
|
|
188
|
+
case "w": return "week";
|
|
189
|
+
case "M": return "month";
|
|
190
|
+
case "q": return "quarter";
|
|
191
|
+
case "y": return "year";
|
|
192
|
+
default: return;
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
const symbolFromUnit = (unit) => {
|
|
196
|
+
switch (unit) {
|
|
197
|
+
case "day": return "d";
|
|
198
|
+
case "week": return "w";
|
|
199
|
+
case "month": return "M";
|
|
200
|
+
case "quarter": return "q";
|
|
201
|
+
case "year": return "y";
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
const failAt = (input, offset, expected) => Effect.fail(new FilterExpressionParseError({
|
|
205
|
+
input,
|
|
206
|
+
offset,
|
|
207
|
+
expected
|
|
208
|
+
}));
|
|
209
|
+
const isDigit = (value) => value >= "0" && value <= "9";
|
|
210
|
+
const isFirstPositiveDigit = (value) => value >= "1" && value <= "9";
|
|
211
|
+
const parseInstantExpression = Effect.fn(function* (input) {
|
|
212
|
+
let cursor = 0;
|
|
213
|
+
let expression;
|
|
214
|
+
let fixedAnchor = false;
|
|
215
|
+
if (input.startsWith("now")) {
|
|
216
|
+
expression = now();
|
|
217
|
+
cursor = 3;
|
|
218
|
+
} else {
|
|
219
|
+
const candidate = input.slice(0, 10);
|
|
220
|
+
if (!isIsoDate(candidate)) return yield* failAt(input, 0, "\"now\" or an ISO date (YYYY-MM-DD)");
|
|
221
|
+
expression = dateLiteral(candidate);
|
|
222
|
+
cursor = 10;
|
|
223
|
+
fixedAnchor = true;
|
|
224
|
+
}
|
|
225
|
+
if (fixedAnchor && cursor < input.length) {
|
|
226
|
+
if (input.slice(cursor, cursor + 2) !== "||") return yield* failAt(input, cursor, "\"||\" before date operations");
|
|
227
|
+
cursor += 2;
|
|
228
|
+
if (cursor === input.length) return yield* failAt(input, cursor, "an operation beginning with \"+\", \"-\", or \"/\"");
|
|
229
|
+
}
|
|
230
|
+
while (cursor < input.length) {
|
|
231
|
+
const operator = input[cursor];
|
|
232
|
+
if (operator === "/") {
|
|
233
|
+
const unit = unitFromSymbol(input[cursor + 1] ?? "");
|
|
234
|
+
if (unit === void 0) return yield* failAt(input, cursor + 1, "a date unit: d, w, M, q, or y");
|
|
235
|
+
expression = startOf(expression, unit);
|
|
236
|
+
cursor += 2;
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
if (operator !== "+" && operator !== "-") return yield* failAt(input, cursor, "an operation beginning with \"+\", \"-\", or \"/\"");
|
|
240
|
+
const amountStart = cursor + 1;
|
|
241
|
+
if (!isFirstPositiveDigit(input[amountStart] ?? "")) return yield* failAt(input, amountStart, "a positive integer without a leading zero");
|
|
242
|
+
cursor = amountStart + 1;
|
|
243
|
+
while (cursor < input.length && isDigit(input[cursor] ?? "")) cursor += 1;
|
|
244
|
+
const amount = Number(input.slice(amountStart, cursor));
|
|
245
|
+
if (!Number.isSafeInteger(amount)) return yield* failAt(input, amountStart, "a safe positive integer");
|
|
246
|
+
const unit = unitFromSymbol(input[cursor] ?? "");
|
|
247
|
+
if (unit === void 0) return yield* failAt(input, cursor, "a date unit: d, w, M, q, or y");
|
|
248
|
+
expression = shift(expression, operator === "+" ? amount : -amount, unit);
|
|
249
|
+
cursor += 1;
|
|
250
|
+
}
|
|
251
|
+
return expression;
|
|
252
|
+
});
|
|
253
|
+
const appendOperation = (base, operation) => ({
|
|
254
|
+
text: `${base.text}${base.fixedAnchor ? "||" : ""}${operation}`,
|
|
255
|
+
fixedAnchor: false
|
|
256
|
+
});
|
|
257
|
+
const formatInstantExpression = (expression) => foldInstant(normalizeInstant(expression), {
|
|
258
|
+
now: () => ({
|
|
259
|
+
text: "now",
|
|
260
|
+
fixedAnchor: false
|
|
261
|
+
}),
|
|
262
|
+
dateLiteral: (value) => ({
|
|
263
|
+
text: value,
|
|
264
|
+
fixedAnchor: true
|
|
265
|
+
}),
|
|
266
|
+
shift: (base, amount, unit) => appendOperation(base, `${amount < 0 ? "-" : "+"}${Math.abs(amount)}${symbolFromUnit(unit)}`),
|
|
267
|
+
startOf: (base, unit) => appendOperation(base, `/${symbolFromUnit(unit)}`)
|
|
268
|
+
}).text;
|
|
269
|
+
//#endregion
|
|
270
|
+
//#region src/filter/schema.ts
|
|
271
|
+
const DateExpressionString = Schema.String.check(Schema.isMinLength(1)).annotate({ identifier: "DateExpressionString" });
|
|
272
|
+
const DateFilter = Schema.Struct({
|
|
273
|
+
gt: Schema.optionalKey(DateExpressionString),
|
|
274
|
+
gte: Schema.optionalKey(DateExpressionString),
|
|
275
|
+
lt: Schema.optionalKey(DateExpressionString),
|
|
276
|
+
lte: Schema.optionalKey(DateExpressionString)
|
|
277
|
+
}).check(Schema.makeFilter((filter) => !(filter.gt !== void 0 && filter.gte !== void 0), { expected: "at most one lower date bound" }), Schema.makeFilter((filter) => !(filter.lt !== void 0 && filter.lte !== void 0), { expected: "at most one upper date bound" }), Schema.makeFilter((filter) => filter.gt !== void 0 || filter.gte !== void 0 || filter.lt !== void 0 || filter.lte !== void 0, { expected: "at least one date bound" })).annotate({ identifier: "DateFilter" });
|
|
278
|
+
//#endregion
|
|
279
|
+
//#region src/filter/codec.ts
|
|
280
|
+
const parseLower = (filter) => {
|
|
281
|
+
if (filter.gt !== void 0) return Effect.map(parseInstantExpression(filter.gt), greaterThan);
|
|
282
|
+
if (filter.gte !== void 0) return Effect.map(parseInstantExpression(filter.gte), greaterThanOrEqual);
|
|
283
|
+
return Effect.void;
|
|
284
|
+
};
|
|
285
|
+
const parseUpper = (filter) => {
|
|
286
|
+
if (filter.lt !== void 0) return Effect.map(parseInstantExpression(filter.lt), lessThan);
|
|
287
|
+
if (filter.lte !== void 0) return Effect.map(parseInstantExpression(filter.lte), lessThanOrEqual);
|
|
288
|
+
return Effect.void;
|
|
289
|
+
};
|
|
290
|
+
const parseFilter = Effect.fn("chronolizer.parseFilter")(function* (filter) {
|
|
291
|
+
const lower = yield* parseLower(filter);
|
|
292
|
+
const upper = yield* parseUpper(filter);
|
|
293
|
+
if (lower !== void 0 && upper !== void 0) return normalizeRange(boundedRange(lower, upper));
|
|
294
|
+
if (lower !== void 0) return normalizeRange(lowerOpenRange(lower));
|
|
295
|
+
if (upper !== void 0) return normalizeRange(upperOpenRange(upper));
|
|
296
|
+
return yield* new InvalidDateFilterError({ message: "A date filter must contain at least one bound" });
|
|
297
|
+
});
|
|
298
|
+
const formatLower = Match.typeTags()({
|
|
299
|
+
GreaterThan: (bound) => DateFilter.make({ gt: formatInstantExpression(bound.value) }),
|
|
300
|
+
GreaterThanOrEqual: (bound) => DateFilter.make({ gte: formatInstantExpression(bound.value) })
|
|
301
|
+
});
|
|
302
|
+
const formatUpper = Match.typeTags()({
|
|
303
|
+
LessThan: (bound) => DateFilter.make({ lt: formatInstantExpression(bound.value) }),
|
|
304
|
+
LessThanOrEqual: (bound) => DateFilter.make({ lte: formatInstantExpression(bound.value) })
|
|
305
|
+
});
|
|
306
|
+
const formatFilter = (range) => {
|
|
307
|
+
const normalized = normalizeRange(range);
|
|
308
|
+
if (normalized.lower !== void 0 && normalized.upper !== void 0) return DateFilter.make({
|
|
309
|
+
...formatLower(normalized.lower),
|
|
310
|
+
...formatUpper(normalized.upper)
|
|
311
|
+
});
|
|
312
|
+
if (normalized.lower !== void 0) return formatLower(normalized.lower);
|
|
313
|
+
return formatUpper(normalized.upper);
|
|
314
|
+
};
|
|
315
|
+
const rangeKey = (range) => {
|
|
316
|
+
const filter = formatFilter(range);
|
|
317
|
+
return `${filter.gt ?? ""}|${filter.gte ?? ""}|${filter.lt ?? ""}|${filter.lte ?? ""}`;
|
|
318
|
+
};
|
|
319
|
+
const completePeriod = (start, end) => boundedRange(greaterThanOrEqual(start), lessThan(end));
|
|
320
|
+
//#endregion
|
|
321
|
+
//#region src/filter/transformation.ts
|
|
322
|
+
const expressionIssue = (input, offset, expected) => new SchemaIssue.Forbidden({ message: `Invalid date expression at offset ${offset}: expected ${expected}; input: ${input}` });
|
|
323
|
+
const InstantExpressionFromString = Schema.String.pipe(Schema.decodeTo(InstantExpr, SchemaTransformation.transformOrFail({
|
|
324
|
+
decode: (input) => Effect.mapError(parseInstantExpression(input), (error) => expressionIssue(error.input, error.offset, error.expected)),
|
|
325
|
+
encode: (expression) => Effect.succeed(formatInstantExpression(expression))
|
|
326
|
+
})));
|
|
327
|
+
const DateRangeFromFilter = DateFilter.pipe(Schema.decodeTo(DateRangeExpr, SchemaTransformation.transformOrFail({
|
|
328
|
+
decode: (filter) => Effect.mapError(parseFilter(filter), (error) => new SchemaIssue.Forbidden({ message: error._tag === "FilterExpressionParseError" ? `Invalid filter expression at offset ${error.offset}: expected ${error.expected}` : error.message })),
|
|
329
|
+
encode: (range) => Effect.succeed(formatFilter(range))
|
|
330
|
+
})));
|
|
331
|
+
//#endregion
|
|
332
|
+
//#region src/language/errors.ts
|
|
333
|
+
var UnsupportedLocaleError = class extends Schema.TaggedError()("UnsupportedLocaleError", { locale: Schema.String }) {};
|
|
334
|
+
var LanguageConflictError = class extends Schema.TaggedError()("LanguageConflictError", {
|
|
335
|
+
locale: Schema.String,
|
|
336
|
+
firstPluginId: Schema.String,
|
|
337
|
+
secondPluginId: Schema.String,
|
|
338
|
+
message: Schema.String
|
|
339
|
+
}) {};
|
|
340
|
+
var LanguageRegistrationError = class extends Schema.TaggedError()("LanguageRegistrationError", {
|
|
341
|
+
pluginId: Schema.String,
|
|
342
|
+
locale: Schema.String,
|
|
343
|
+
message: Schema.String
|
|
344
|
+
}) {};
|
|
345
|
+
var NaturalLanguageParseError = class extends Schema.TaggedError()("NaturalLanguageParseError", {
|
|
346
|
+
input: Schema.String,
|
|
347
|
+
locale: Schema.String,
|
|
348
|
+
message: Schema.String
|
|
349
|
+
}) {};
|
|
350
|
+
var AmbiguousNaturalLanguageError = class extends Schema.TaggedError()("AmbiguousNaturalLanguageError", {
|
|
351
|
+
input: Schema.String,
|
|
352
|
+
locale: Schema.String,
|
|
353
|
+
alternatives: Schema.Array(Schema.String)
|
|
354
|
+
}) {};
|
|
355
|
+
var NaturalLanguageRenderError = class extends Schema.TaggedError()("NaturalLanguageRenderError", {
|
|
356
|
+
locale: Schema.String,
|
|
357
|
+
message: Schema.String
|
|
358
|
+
}) {};
|
|
359
|
+
//#endregion
|
|
360
|
+
//#region src/language/model.ts
|
|
361
|
+
const ParseQuality = Schema.Literals([
|
|
362
|
+
"exact",
|
|
363
|
+
"corrected",
|
|
364
|
+
"ambiguous"
|
|
365
|
+
]);
|
|
366
|
+
const Correction = Schema.Struct({
|
|
367
|
+
original: Schema.String,
|
|
368
|
+
replacement: Schema.String,
|
|
369
|
+
distance: Schema.Int.check(Schema.isGreaterThanOrEqualTo(1)),
|
|
370
|
+
offset: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0))
|
|
371
|
+
});
|
|
372
|
+
const NaturalCorrectionCandidate = Schema.Struct({
|
|
373
|
+
text: Schema.String,
|
|
374
|
+
corrections: Schema.Array(Correction),
|
|
375
|
+
cost: Schema.Int.check(Schema.isGreaterThanOrEqualTo(1))
|
|
376
|
+
});
|
|
377
|
+
const NaturalAlternative = Schema.Struct({
|
|
378
|
+
canonical: Schema.String,
|
|
379
|
+
range: DateRangeExpr
|
|
380
|
+
});
|
|
381
|
+
const NaturalParseResult = Schema.Struct({
|
|
382
|
+
range: DateRangeExpr,
|
|
383
|
+
quality: ParseQuality,
|
|
384
|
+
corrections: Schema.Array(Correction),
|
|
385
|
+
alternatives: Schema.Array(NaturalAlternative)
|
|
386
|
+
});
|
|
387
|
+
const NaturalCandidate = Schema.Struct({
|
|
388
|
+
range: DateRangeExpr,
|
|
389
|
+
canonical: Schema.String
|
|
390
|
+
});
|
|
391
|
+
var BaseLanguageContribution = class extends Data.TaggedClass("BaseLanguage") {};
|
|
392
|
+
var LanguageExtensionContribution = class extends Data.TaggedClass("LanguageExtension") {};
|
|
393
|
+
const canonicalBaseLocale = (input) => Result.getSuccess(Result.try(() => new Intl.Locale(input).baseName));
|
|
394
|
+
const Locale = Schema.String.check(Schema.makeFilter((value) => Option.contains(canonicalBaseLocale(value), value), { expected: "a canonical BCP 47 base locale identifier" })).annotate({ identifier: "Locale" });
|
|
395
|
+
const BaseLanguageMetadata = Schema.TaggedStruct("BaseLanguage", {
|
|
396
|
+
locale: Locale,
|
|
397
|
+
vocabulary: Schema.Array(Schema.String)
|
|
398
|
+
});
|
|
399
|
+
const LanguageExtensionMetadata = Schema.TaggedStruct("LanguageExtension", {
|
|
400
|
+
locale: Locale,
|
|
401
|
+
priority: Schema.Int,
|
|
402
|
+
vocabulary: Schema.Array(Schema.String)
|
|
403
|
+
});
|
|
404
|
+
const LanguageContributionMetadata = Schema.Union([BaseLanguageMetadata, LanguageExtensionMetadata]);
|
|
405
|
+
//#endregion
|
|
406
|
+
//#region src/natural/text.ts
|
|
407
|
+
const normalizeNaturalText = (input, locale) => input.normalize("NFKC").toLocaleLowerCase(locale).trim().replace(/\s+/gu, " ");
|
|
408
|
+
const naturalWords = (input) => input.length === 0 ? [] : input.split(" ");
|
|
409
|
+
//#endregion
|
|
410
|
+
//#region src/language/registry.ts
|
|
411
|
+
var LanguageRegistry = class extends Context.Service()("chronolizer/LanguageRegistry") {};
|
|
412
|
+
const metadataOf = (contribution) => Match.valueTags(contribution, {
|
|
413
|
+
BaseLanguage: (base) => BaseLanguageMetadata.make({
|
|
414
|
+
locale: base.locale,
|
|
415
|
+
vocabulary: base.vocabulary
|
|
416
|
+
}),
|
|
417
|
+
LanguageExtension: (extension) => LanguageExtensionMetadata.make({
|
|
418
|
+
locale: extension.locale,
|
|
419
|
+
priority: extension.priority,
|
|
420
|
+
vocabulary: extension.vocabulary
|
|
421
|
+
})
|
|
422
|
+
});
|
|
423
|
+
const localeCandidates = (locale) => {
|
|
424
|
+
const candidates = [locale];
|
|
425
|
+
let parent = locale;
|
|
426
|
+
while (parent.includes("-")) {
|
|
427
|
+
parent = parent.slice(0, parent.lastIndexOf("-"));
|
|
428
|
+
candidates.push(parent);
|
|
429
|
+
}
|
|
430
|
+
return candidates;
|
|
431
|
+
};
|
|
432
|
+
const compileLanguage = (locale, registered) => {
|
|
433
|
+
const locales = localeCandidates(locale);
|
|
434
|
+
const base = Array$1.sort(Array$1.filterMap(registered, (entry) => {
|
|
435
|
+
const contribution = entry.contribution;
|
|
436
|
+
return contribution._tag === "BaseLanguage" && locales.includes(contribution.locale) ? Result.succeed({
|
|
437
|
+
pluginId: entry.pluginId,
|
|
438
|
+
contribution
|
|
439
|
+
}) : Result.failVoid;
|
|
440
|
+
}), Order.mapInput(Order.Number, (entry) => locales.indexOf(entry.contribution.locale)))[0];
|
|
441
|
+
if (base === void 0) return Option.none();
|
|
442
|
+
const extensions = Array$1.sort(Array$1.filterMap(registered, (entry) => {
|
|
443
|
+
const contribution = entry.contribution;
|
|
444
|
+
return contribution._tag === "LanguageExtension" && locales.includes(contribution.locale) ? Result.succeed({
|
|
445
|
+
pluginId: entry.pluginId,
|
|
446
|
+
contribution
|
|
447
|
+
}) : Result.failVoid;
|
|
448
|
+
}), Order.combine(Order.mapInput(Order.flip(Order.Number), (entry) => entry.contribution.priority), Order.mapInput(Order.String, (entry) => entry.pluginId)));
|
|
449
|
+
const vocabulary = Object.freeze(Array$1.dedupe([...base.contribution.vocabulary, ...Array$1.flatMap(extensions, (entry) => entry.contribution.vocabulary)]));
|
|
450
|
+
const parsers = Object.freeze([...extensions.map((entry) => entry.contribution.parseExact), base.contribution.parseExact]);
|
|
451
|
+
const parseExact = (input) => {
|
|
452
|
+
const candidates = [];
|
|
453
|
+
for (const parser of parsers) {
|
|
454
|
+
const candidate = parser(input);
|
|
455
|
+
if (Option.isSome(candidate)) candidates.push(candidate.value);
|
|
456
|
+
}
|
|
457
|
+
return candidates;
|
|
458
|
+
};
|
|
459
|
+
return Option.some(Object.freeze({
|
|
460
|
+
locale: base.contribution.locale,
|
|
461
|
+
vocabulary,
|
|
462
|
+
normalize: base.contribution.normalize ?? normalizeNaturalText,
|
|
463
|
+
correct: base.contribution.correct,
|
|
464
|
+
parseExact,
|
|
465
|
+
render: base.contribution.render
|
|
466
|
+
}));
|
|
467
|
+
};
|
|
468
|
+
const createRegistry = Effect.fn(function* () {
|
|
469
|
+
const entries = yield* Ref.make([]);
|
|
470
|
+
const register = Effect.fn(function* (pluginId, contribution) {
|
|
471
|
+
if (pluginId.length === 0 || !Schema.is(LanguageContributionMetadata)(metadataOf(contribution))) return yield* new LanguageRegistrationError({
|
|
472
|
+
pluginId,
|
|
473
|
+
locale: contribution.locale,
|
|
474
|
+
message: "Invalid plugin identifier or contribution metadata"
|
|
475
|
+
});
|
|
476
|
+
const token = Symbol(pluginId);
|
|
477
|
+
yield* Effect.acquireRelease(Ref.modify(entries, (current) => {
|
|
478
|
+
const conflictingBase = Array$1.findFirst(current, (entry) => contribution._tag === "BaseLanguage" && entry.contribution._tag === "BaseLanguage" && entry.contribution.locale === contribution.locale);
|
|
479
|
+
return Option.match(conflictingBase, {
|
|
480
|
+
onNone: () => [Result.succeed(token), Array$1.append(current, {
|
|
481
|
+
token,
|
|
482
|
+
pluginId,
|
|
483
|
+
contribution
|
|
484
|
+
})],
|
|
485
|
+
onSome: (conflict) => [Result.fail(new LanguageConflictError({
|
|
486
|
+
locale: contribution.locale,
|
|
487
|
+
firstPluginId: conflict.pluginId,
|
|
488
|
+
secondPluginId: pluginId,
|
|
489
|
+
message: "Only one base language can be registered for a locale"
|
|
490
|
+
})), current]
|
|
491
|
+
});
|
|
492
|
+
}).pipe(Effect.flatMap((result) => Effect.fromResult(result))), (registeredToken) => Ref.update(entries, (items) => Array$1.filter(items, (entry) => entry.token !== registeredToken)));
|
|
493
|
+
});
|
|
494
|
+
const resolve = Effect.fn(function* (locale) {
|
|
495
|
+
const canonical = canonicalBaseLocale(locale);
|
|
496
|
+
if (Option.isSome(canonical)) {
|
|
497
|
+
const compiled = compileLanguage(canonical.value, yield* Ref.get(entries));
|
|
498
|
+
if (Option.isSome(compiled)) return compiled.value;
|
|
499
|
+
}
|
|
500
|
+
return yield* new UnsupportedLocaleError({ locale });
|
|
501
|
+
});
|
|
502
|
+
return LanguageRegistry.of({
|
|
503
|
+
register,
|
|
504
|
+
resolve
|
|
505
|
+
});
|
|
506
|
+
});
|
|
507
|
+
const LanguageRegistryLayer = Layer.effect(LanguageRegistry, createRegistry());
|
|
508
|
+
const duplicatePluginId = (plugins) => {
|
|
509
|
+
const ids = /* @__PURE__ */ new Set();
|
|
510
|
+
for (const plugin of plugins) {
|
|
511
|
+
if (ids.has(plugin.id)) return plugin.id;
|
|
512
|
+
ids.add(plugin.id);
|
|
513
|
+
}
|
|
514
|
+
};
|
|
515
|
+
const createPluginRegistry = Effect.fn(function* (plugins) {
|
|
516
|
+
const duplicate = duplicatePluginId(plugins);
|
|
517
|
+
if (duplicate !== void 0) return yield* new LanguageRegistrationError({
|
|
518
|
+
pluginId: duplicate,
|
|
519
|
+
locale: "*",
|
|
520
|
+
message: "Plugin identifiers must be unique"
|
|
521
|
+
});
|
|
522
|
+
const registry = yield* createRegistry();
|
|
523
|
+
const context = { register: registry.register };
|
|
524
|
+
const ordered = Array$1.sortWith(plugins, (plugin) => plugin.id, Order.String);
|
|
525
|
+
for (const plugin of ordered) yield* plugin.effect(context);
|
|
526
|
+
return registry;
|
|
527
|
+
});
|
|
528
|
+
const languagePluginsLayer = (plugins) => Layer.effect(LanguageRegistry, createPluginRegistry(plugins));
|
|
529
|
+
const defineLanguagePlugin = (plugin) => plugin;
|
|
530
|
+
//#endregion
|
|
531
|
+
//#region src/natural/correction.ts
|
|
532
|
+
const isProtectedToken = (word) => Option.isSome(String$1.match(/^\d+$/u)(word)) || Option.isSome(String$1.match(/^\d{4}-\d{2}-\d{2}$/u)(word)) || word.length <= 3;
|
|
533
|
+
const damerauLevenshtein = (left, right) => {
|
|
534
|
+
const rows = left.length + 1;
|
|
535
|
+
const columns = right.length + 1;
|
|
536
|
+
const matrix = Array.from({ length: rows }, () => Array(columns).fill(0));
|
|
537
|
+
for (let row = 0; row < rows; row += 1) matrix[row][0] = row;
|
|
538
|
+
for (let column = 0; column < columns; column += 1) matrix[0][column] = column;
|
|
539
|
+
for (let row = 1; row < rows; row += 1) for (let column = 1; column < columns; column += 1) {
|
|
540
|
+
const substitution = left[row - 1] === right[column - 1] ? 0 : 1;
|
|
541
|
+
matrix[row][column] = Math.min(matrix[row - 1][column] + 1, matrix[row][column - 1] + 1, matrix[row - 1][column - 1] + substitution);
|
|
542
|
+
if (row > 1 && column > 1 && left[row - 1] === right[column - 2] && left[row - 2] === right[column - 1]) matrix[row][column] = Math.min(matrix[row][column], matrix[row - 2][column - 2] + 1);
|
|
543
|
+
}
|
|
544
|
+
return matrix[left.length][right.length];
|
|
545
|
+
};
|
|
546
|
+
const replacementsFor = (word, vocabulary) => {
|
|
547
|
+
if (vocabulary.includes(word)) return [{
|
|
548
|
+
word,
|
|
549
|
+
distance: 0
|
|
550
|
+
}];
|
|
551
|
+
if (isProtectedToken(word)) return [];
|
|
552
|
+
const maximum = word.length >= 6 ? 2 : 1;
|
|
553
|
+
const matches = vocabulary.filter((candidate) => Math.abs(candidate.length - word.length) <= maximum).map((candidate) => ({
|
|
554
|
+
word: candidate,
|
|
555
|
+
distance: damerauLevenshtein(word, candidate)
|
|
556
|
+
})).filter((candidate) => candidate.distance <= maximum);
|
|
557
|
+
if (matches.length === 0) return [];
|
|
558
|
+
const minimum = Math.min(...matches.map((candidate) => candidate.distance));
|
|
559
|
+
return matches.filter((candidate) => candidate.distance === minimum).slice(0, 4);
|
|
560
|
+
};
|
|
561
|
+
const correctWhitespaceSeparatedText = (input, vocabulary) => {
|
|
562
|
+
const words = naturalWords(input);
|
|
563
|
+
let partials = [{
|
|
564
|
+
words: [],
|
|
565
|
+
corrections: [],
|
|
566
|
+
cost: 0,
|
|
567
|
+
offset: 0
|
|
568
|
+
}];
|
|
569
|
+
for (const word of words) {
|
|
570
|
+
const replacements = replacementsFor(word, vocabulary);
|
|
571
|
+
if (replacements.length === 0) return [];
|
|
572
|
+
const next = [];
|
|
573
|
+
for (const partial of partials) for (const replacement of replacements) {
|
|
574
|
+
const correction = replacement.distance === 0 ? partial.corrections : [...partial.corrections, Correction.make({
|
|
575
|
+
original: word,
|
|
576
|
+
replacement: replacement.word,
|
|
577
|
+
distance: replacement.distance,
|
|
578
|
+
offset: partial.offset
|
|
579
|
+
})];
|
|
580
|
+
next.push({
|
|
581
|
+
words: [...partial.words, replacement.word],
|
|
582
|
+
corrections: correction,
|
|
583
|
+
cost: partial.cost + replacement.distance,
|
|
584
|
+
offset: partial.offset + word.length + 1
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
partials = next.slice(0, 32);
|
|
588
|
+
}
|
|
589
|
+
return partials.filter((partial) => partial.corrections.length > 0).map((partial) => NaturalCorrectionCandidate.make({
|
|
590
|
+
text: partial.words.join(" "),
|
|
591
|
+
corrections: partial.corrections,
|
|
592
|
+
cost: partial.cost
|
|
593
|
+
}));
|
|
594
|
+
};
|
|
595
|
+
//#endregion
|
|
596
|
+
//#region src/locales/shared.ts
|
|
597
|
+
const Period = Schema.Struct({
|
|
598
|
+
start: InstantExpr,
|
|
599
|
+
end: InstantExpr,
|
|
600
|
+
canonical: Schema.String
|
|
601
|
+
});
|
|
602
|
+
const periodRange = (period) => boundedRange(greaterThanOrEqual(period.start), lessThan(period.end));
|
|
603
|
+
const periodToDateRange = (unit) => boundedRange(greaterThanOrEqual(startOf(now(), unit)), lessThanOrEqual(now()));
|
|
604
|
+
const fromNowRange = () => lowerOpenRange(greaterThanOrEqual(now()));
|
|
605
|
+
const untilNowRange = () => upperOpenRange(lessThanOrEqual(now()));
|
|
606
|
+
const remainingPeriodRange = (unit) => {
|
|
607
|
+
const start = startOf(now(), unit);
|
|
608
|
+
return boundedRange(greaterThanOrEqual(now()), lessThan(shift(start, 1, unit)));
|
|
609
|
+
};
|
|
610
|
+
const periodStartDay = (period, canonical) => Period.make({
|
|
611
|
+
start: period.start,
|
|
612
|
+
end: shift(period.start, 1, "day"),
|
|
613
|
+
canonical
|
|
614
|
+
});
|
|
615
|
+
const periodEndDay = (period, canonical) => Period.make({
|
|
616
|
+
start: shift(period.end, -1, "day"),
|
|
617
|
+
end: period.end,
|
|
618
|
+
canonical
|
|
619
|
+
});
|
|
620
|
+
const relativeWeekend = (direction, canonical) => {
|
|
621
|
+
const weekBase = direction === 0 ? now() : shift(now(), direction, "week");
|
|
622
|
+
const weekStart = startOf(weekBase, "week");
|
|
623
|
+
const start = shift(weekStart, 5, "day");
|
|
624
|
+
return Period.make({
|
|
625
|
+
start,
|
|
626
|
+
end: shift(start, 2, "day"),
|
|
627
|
+
canonical
|
|
628
|
+
});
|
|
629
|
+
};
|
|
630
|
+
const TrailingCount = Schema.Int.check(Schema.isBetween({
|
|
631
|
+
minimum: 1,
|
|
632
|
+
maximum: Number.MAX_SAFE_INTEGER
|
|
633
|
+
}));
|
|
634
|
+
const TrailingPeriod = Schema.Struct({
|
|
635
|
+
amount: TrailingCount,
|
|
636
|
+
unit: Unit
|
|
637
|
+
});
|
|
638
|
+
const FuturePeriod = Schema.Struct({
|
|
639
|
+
amount: TrailingCount,
|
|
640
|
+
unit: Unit
|
|
641
|
+
});
|
|
642
|
+
const CalendarPeriodOffset = Schema.Struct({
|
|
643
|
+
amount: Schema.Int,
|
|
644
|
+
unit: Unit
|
|
645
|
+
});
|
|
646
|
+
const validYear = (value) => {
|
|
647
|
+
const year = Number(value);
|
|
648
|
+
return Number.isInteger(year) && year >= 1 && year <= 9998 ? year : void 0;
|
|
649
|
+
};
|
|
650
|
+
const parseTrailingCount = (value) => {
|
|
651
|
+
const amount = Number(value);
|
|
652
|
+
return Schema.is(TrailingCount)(amount) ? Option.some(amount) : Option.none();
|
|
653
|
+
};
|
|
654
|
+
const trailingRange = (amount, unit) => boundedRange(greaterThanOrEqual(shift(now(), -amount, unit)), lessThanOrEqual(now()));
|
|
655
|
+
const futureRange = (amount, unit) => boundedRange(greaterThanOrEqual(now()), lessThanOrEqual(shift(now(), amount, unit)));
|
|
656
|
+
const trailingPeriod = (range) => {
|
|
657
|
+
if (range.lower?._tag !== "GreaterThanOrEqual" || range.upper?._tag !== "LessThanOrEqual" || range.lower.value._tag !== "Shift" || range.lower.value.amount >= 0 || range.lower.value.base._tag !== "Now" || range.upper.value._tag !== "Now") return Option.none();
|
|
658
|
+
return Option.some(TrailingPeriod.make({
|
|
659
|
+
amount: -range.lower.value.amount,
|
|
660
|
+
unit: range.lower.value.unit
|
|
661
|
+
}));
|
|
662
|
+
};
|
|
663
|
+
const futurePeriod = (range) => {
|
|
664
|
+
if (range.lower?._tag !== "GreaterThanOrEqual" || range.upper?._tag !== "LessThanOrEqual" || range.lower.value._tag !== "Now" || range.upper.value._tag !== "Shift" || range.upper.value.amount <= 0 || range.upper.value.base._tag !== "Now") return Option.none();
|
|
665
|
+
return Option.some(FuturePeriod.make({
|
|
666
|
+
amount: range.upper.value.amount,
|
|
667
|
+
unit: range.upper.value.unit
|
|
668
|
+
}));
|
|
669
|
+
};
|
|
670
|
+
const relativePeriod = (unit, direction, canonical) => {
|
|
671
|
+
const base = direction === 0 ? now() : shift(now(), direction, unit);
|
|
672
|
+
const start = startOf(base, unit);
|
|
673
|
+
return Period.make({
|
|
674
|
+
start,
|
|
675
|
+
end: shift(start, 1, unit),
|
|
676
|
+
canonical
|
|
677
|
+
});
|
|
678
|
+
};
|
|
679
|
+
const pad = (value) => String(value).padStart(2, "0");
|
|
680
|
+
const isoDate = (year, month, day) => `${String(year).padStart(4, "0")}-${pad(month)}-${pad(day)}`;
|
|
681
|
+
const nextDay = (year, month, day) => {
|
|
682
|
+
if (day < daysInMonth(year, month)) return isoDate(year, month, day + 1);
|
|
683
|
+
if (month < 12) return isoDate(year, month + 1, 1);
|
|
684
|
+
return isoDate(year + 1, 1, 1);
|
|
685
|
+
};
|
|
686
|
+
const previousDay = (year, month, day) => {
|
|
687
|
+
if (day > 1) return isoDate(year, month, day - 1);
|
|
688
|
+
if (month > 1) return isoDate(year, month - 1, daysInMonth(year, month - 1));
|
|
689
|
+
return isoDate(year - 1, 12, 31);
|
|
690
|
+
};
|
|
691
|
+
const fixedDatePeriod = (value, canonical) => {
|
|
692
|
+
const year = Number(value.slice(0, 4));
|
|
693
|
+
const month = Number(value.slice(5, 7));
|
|
694
|
+
const day = Number(value.slice(8, 10));
|
|
695
|
+
return Period.make({
|
|
696
|
+
start: dateLiteral(value),
|
|
697
|
+
end: dateLiteral(nextDay(year, month, day)),
|
|
698
|
+
canonical
|
|
699
|
+
});
|
|
700
|
+
};
|
|
701
|
+
const namedDatePeriod = (yearText, monthText, dayText, monthNumber, canonical) => {
|
|
702
|
+
const year = validYear(yearText);
|
|
703
|
+
const month = monthNumber(monthText);
|
|
704
|
+
const day = Number(dayText);
|
|
705
|
+
if (year === void 0 || month === void 0) return Option.none();
|
|
706
|
+
const value = isoDate(year, month, day);
|
|
707
|
+
if (!isIsoDate(value) || value === "9999-12-31") return Option.none();
|
|
708
|
+
return Option.some(fixedDatePeriod(value, canonical(day, month, year)));
|
|
709
|
+
};
|
|
710
|
+
const fixedMonthPeriod = (year, month, canonical) => {
|
|
711
|
+
const nextYear = month === 12 ? year + 1 : year;
|
|
712
|
+
const nextMonth = month === 12 ? 1 : month + 1;
|
|
713
|
+
return Period.make({
|
|
714
|
+
start: dateLiteral(isoDate(year, month, 1)),
|
|
715
|
+
end: dateLiteral(isoDate(nextYear, nextMonth, 1)),
|
|
716
|
+
canonical
|
|
717
|
+
});
|
|
718
|
+
};
|
|
719
|
+
const fixedYearPeriod = (year, canonical) => Period.make({
|
|
720
|
+
start: dateLiteral(isoDate(year, 1, 1)),
|
|
721
|
+
end: dateLiteral(isoDate(year + 1, 1, 1)),
|
|
722
|
+
canonical
|
|
723
|
+
});
|
|
724
|
+
const fixedQuarterPeriod = (year, quarter, canonical) => {
|
|
725
|
+
const month = (quarter - 1) * 3 + 1;
|
|
726
|
+
const nextYear = quarter === 4 ? year + 1 : year;
|
|
727
|
+
const nextMonth = quarter === 4 ? 1 : month + 3;
|
|
728
|
+
return Period.make({
|
|
729
|
+
start: dateLiteral(isoDate(year, month, 1)),
|
|
730
|
+
end: dateLiteral(isoDate(nextYear, nextMonth, 1)),
|
|
731
|
+
canonical
|
|
732
|
+
});
|
|
733
|
+
};
|
|
734
|
+
const quarterOfRelativeYear = (quarter, direction, canonical) => {
|
|
735
|
+
const yearBase = direction === 0 ? now() : shift(now(), direction, "year");
|
|
736
|
+
const yearStart = startOf(yearBase, "year");
|
|
737
|
+
const start = quarter === 1 ? yearStart : shift(yearStart, (quarter - 1) * 3, "month");
|
|
738
|
+
return Period.make({
|
|
739
|
+
start,
|
|
740
|
+
end: shift(start, 1, "quarter"),
|
|
741
|
+
canonical
|
|
742
|
+
});
|
|
743
|
+
};
|
|
744
|
+
const monthOfRelativeYear = (month, direction, canonical) => {
|
|
745
|
+
const yearBase = direction === 0 ? now() : shift(now(), direction, "year");
|
|
746
|
+
const yearStart = startOf(yearBase, "year");
|
|
747
|
+
const start = month === 1 ? yearStart : shift(yearStart, month - 1, "month");
|
|
748
|
+
return Period.make({
|
|
749
|
+
start,
|
|
750
|
+
end: shift(start, 1, "month"),
|
|
751
|
+
canonical
|
|
752
|
+
});
|
|
753
|
+
};
|
|
754
|
+
const calendarPeriodOffset = (range) => {
|
|
755
|
+
if (range.lower?._tag !== "GreaterThanOrEqual" || range.upper?._tag !== "LessThan" || range.lower.value._tag !== "StartOf" || range.lower.value.base._tag !== "Shift" || range.lower.value.base.amount === 0 || range.lower.value.base.base._tag !== "Now" || range.lower.value.unit !== range.lower.value.base.unit || range.upper.value._tag !== "Shift" || range.upper.value.amount !== 1 || range.upper.value.unit !== range.lower.value.unit || formatInstantExpression(range.upper.value.base) !== formatInstantExpression(range.lower.value)) return Option.none();
|
|
756
|
+
return Option.some(CalendarPeriodOffset.make({
|
|
757
|
+
amount: range.lower.value.base.amount,
|
|
758
|
+
unit: range.lower.value.unit
|
|
759
|
+
}));
|
|
760
|
+
};
|
|
761
|
+
const candidate = (range, canonical) => NaturalCandidate.make({
|
|
762
|
+
range,
|
|
763
|
+
canonical
|
|
764
|
+
});
|
|
765
|
+
const joinedNowCandidate = (input, periodToNow, nowToPeriod, parsePeriod, canonicalToNow, canonicalFromNow) => {
|
|
766
|
+
for (const [prefix, suffix] of periodToNow) {
|
|
767
|
+
if (!input.startsWith(prefix) || !input.endsWith(suffix)) continue;
|
|
768
|
+
const period = parsePeriod(input.slice(prefix.length, -suffix.length));
|
|
769
|
+
if (Option.isNone(period)) continue;
|
|
770
|
+
return Option.some(candidate(boundedRange(greaterThanOrEqual(period.value.start), lessThanOrEqual(now())), canonicalToNow(period.value.canonical)));
|
|
771
|
+
}
|
|
772
|
+
for (const prefix of nowToPeriod) {
|
|
773
|
+
if (!input.startsWith(prefix)) continue;
|
|
774
|
+
const period = parsePeriod(input.slice(prefix.length));
|
|
775
|
+
if (Option.isNone(period)) continue;
|
|
776
|
+
return Option.some(candidate(boundedRange(greaterThanOrEqual(now()), lessThan(period.value.end)), canonicalFromNow(period.value.canonical)));
|
|
777
|
+
}
|
|
778
|
+
return Option.none();
|
|
779
|
+
};
|
|
780
|
+
const joinedPeriodCandidate = (input, joins, parsePeriod, canonical) => {
|
|
781
|
+
for (const [prefix, separator] of joins) {
|
|
782
|
+
if (!input.startsWith(prefix)) continue;
|
|
783
|
+
const separatorIndex = input.indexOf(separator, prefix.length);
|
|
784
|
+
if (separatorIndex === -1) continue;
|
|
785
|
+
const lower = parsePeriod(input.slice(prefix.length, separatorIndex));
|
|
786
|
+
const upper = parsePeriod(input.slice(separatorIndex + separator.length));
|
|
787
|
+
if (Option.isNone(lower) || Option.isNone(upper)) continue;
|
|
788
|
+
return Option.some(candidate(boundedRange(greaterThanOrEqual(lower.value.start), lessThan(upper.value.end)), canonical(lower.value.canonical, upper.value.canonical)));
|
|
789
|
+
}
|
|
790
|
+
return Option.none();
|
|
791
|
+
};
|
|
792
|
+
const periodBoundaryCandidate = (period, boundary, canonical) => {
|
|
793
|
+
switch (boundary) {
|
|
794
|
+
case "since": return candidate(lowerOpenRange(greaterThanOrEqual(period.start)), canonical);
|
|
795
|
+
case "before": return candidate(upperOpenRange(lessThan(period.start)), canonical);
|
|
796
|
+
case "through": return candidate(upperOpenRange(lessThan(period.end)), canonical);
|
|
797
|
+
case "after": return candidate(lowerOpenRange(greaterThanOrEqual(period.end)), canonical);
|
|
798
|
+
}
|
|
799
|
+
};
|
|
800
|
+
const openBoundaryCandidate = (input, boundaries, parsePeriod) => {
|
|
801
|
+
for (const boundary of boundaries) {
|
|
802
|
+
if (!input.startsWith(boundary[0])) continue;
|
|
803
|
+
const period = parsePeriod(input.slice(boundary[0].length));
|
|
804
|
+
if (Option.isNone(period)) continue;
|
|
805
|
+
const canonical = `${boundary[0]}${period.value.canonical}`;
|
|
806
|
+
return Option.some(periodBoundaryCandidate(period.value, boundary[1], canonical));
|
|
807
|
+
}
|
|
808
|
+
return Option.none();
|
|
809
|
+
};
|
|
810
|
+
const expressionDates = (range) => {
|
|
811
|
+
const filter = formatFilter(range);
|
|
812
|
+
const expressions = [
|
|
813
|
+
filter.gt,
|
|
814
|
+
filter.gte,
|
|
815
|
+
filter.lt,
|
|
816
|
+
filter.lte
|
|
817
|
+
];
|
|
818
|
+
const dates = /* @__PURE__ */ new Set();
|
|
819
|
+
for (const expression of expressions) {
|
|
820
|
+
if (expression === void 0) continue;
|
|
821
|
+
const match = String$1.match(/^(\d{4})-(\d{2})-(\d{2})/u)(expression);
|
|
822
|
+
if (Option.isSome(match)) dates.add(match.value[0]);
|
|
823
|
+
}
|
|
824
|
+
return [...dates];
|
|
825
|
+
};
|
|
826
|
+
const datedQuarterPeriods = (range) => {
|
|
827
|
+
return [...new Set(expressionDates(range).map((date) => date.slice(0, 4)))].flatMap((year) => [
|
|
828
|
+
1,
|
|
829
|
+
2,
|
|
830
|
+
3,
|
|
831
|
+
4
|
|
832
|
+
].map((quarter) => `q${quarter} ${year}`));
|
|
833
|
+
};
|
|
834
|
+
const datedPeriods = (range, months) => {
|
|
835
|
+
const dates = expressionDates(range);
|
|
836
|
+
return [...[...new Set(dates.map((date) => date.slice(0, 4)))].flatMap((year) => [...months.map((month) => `${month} ${year}`), year]), ...dates.flatMap((date) => date === "0000-01-01" ? [date] : [date, previousDay(Number(date.slice(0, 4)), Number(date.slice(5, 7)), Number(date.slice(8, 10)))])];
|
|
837
|
+
};
|
|
838
|
+
const periodsFromPhrases = (phrases, parsePeriod) => phrases.flatMap((phrase) => Option.match(parsePeriod(phrase), {
|
|
839
|
+
onNone: () => [],
|
|
840
|
+
onSome: (period) => [period]
|
|
841
|
+
}));
|
|
842
|
+
const renderPeriodRange = (range, toDate, periods, since, before, through, after, between, toNow, fromNow, untilNow, afterNow) => {
|
|
843
|
+
const expected = rangeKey(range);
|
|
844
|
+
for (const entry of toDate) if (rangeKey(entry.range) === expected) return Option.some(entry.canonical);
|
|
845
|
+
const filter = formatFilter(range);
|
|
846
|
+
if (filter.lte === "now" && filter.gt === void 0 && filter.gte === void 0) return Option.some(untilNow());
|
|
847
|
+
if (filter.gte === "now" && filter.lt === void 0 && filter.lte === void 0) return Option.some(afterNow());
|
|
848
|
+
if (filter.gte !== void 0 && filter.lte === "now") {
|
|
849
|
+
const period = periods.find((candidate) => formatInstantExpression(candidate.start) === filter.gte);
|
|
850
|
+
if (period !== void 0) return Option.some(toNow(period.canonical));
|
|
851
|
+
}
|
|
852
|
+
if (filter.gte === "now" && filter.lt !== void 0) {
|
|
853
|
+
const period = periods.find((candidate) => formatInstantExpression(candidate.end) === filter.lt);
|
|
854
|
+
if (period !== void 0) return Option.some(fromNow(period.canonical));
|
|
855
|
+
}
|
|
856
|
+
if (filter.gte !== void 0 && filter.lt !== void 0) {
|
|
857
|
+
const exact = periods.find((period) => formatInstantExpression(period.start) === filter.gte && formatInstantExpression(period.end) === filter.lt);
|
|
858
|
+
if (exact !== void 0) return Option.some(exact.canonical);
|
|
859
|
+
const lower = periods.find((period) => formatInstantExpression(period.start) === filter.gte)?.canonical;
|
|
860
|
+
const upper = periods.find((period) => formatInstantExpression(period.end) === filter.lt)?.canonical;
|
|
861
|
+
return lower === void 0 || upper === void 0 ? Option.none() : Option.some(between(lower, upper));
|
|
862
|
+
}
|
|
863
|
+
if (filter.gte !== void 0 && filter.lt === void 0 && filter.lte === void 0) for (const period of periods) {
|
|
864
|
+
if (formatInstantExpression(period.start) === filter.gte) return Option.some(since(period.canonical));
|
|
865
|
+
if (formatInstantExpression(period.end) === filter.gte) return Option.some(after(period.canonical));
|
|
866
|
+
}
|
|
867
|
+
if (filter.lt !== void 0 && filter.gt === void 0 && filter.gte === void 0) for (const period of periods) {
|
|
868
|
+
if (formatInstantExpression(period.start) === filter.lt) return Option.some(before(period.canonical));
|
|
869
|
+
if (formatInstantExpression(period.end) === filter.lt) return Option.some(through(period.canonical));
|
|
870
|
+
}
|
|
871
|
+
return Option.none();
|
|
872
|
+
};
|
|
873
|
+
//#endregion
|
|
874
|
+
//#region src/locales/de.ts
|
|
875
|
+
const months$1 = [
|
|
876
|
+
"januar",
|
|
877
|
+
"februar",
|
|
878
|
+
"märz",
|
|
879
|
+
"april",
|
|
880
|
+
"mai",
|
|
881
|
+
"juni",
|
|
882
|
+
"juli",
|
|
883
|
+
"august",
|
|
884
|
+
"september",
|
|
885
|
+
"oktober",
|
|
886
|
+
"november",
|
|
887
|
+
"dezember"
|
|
888
|
+
];
|
|
889
|
+
const monthAbbreviations$1 = [
|
|
890
|
+
["jan"],
|
|
891
|
+
["feb"],
|
|
892
|
+
["mär", "mrz"],
|
|
893
|
+
["apr"],
|
|
894
|
+
["mai"],
|
|
895
|
+
["jun"],
|
|
896
|
+
["jul"],
|
|
897
|
+
["aug"],
|
|
898
|
+
["sep", "sept"],
|
|
899
|
+
["okt"],
|
|
900
|
+
["nov"],
|
|
901
|
+
["dez"]
|
|
902
|
+
];
|
|
903
|
+
const unitPhrases = [
|
|
904
|
+
{
|
|
905
|
+
unit: "day",
|
|
906
|
+
noun: "tag",
|
|
907
|
+
plural: "tage",
|
|
908
|
+
current: "heute",
|
|
909
|
+
previous: "gestern",
|
|
910
|
+
next: "morgen",
|
|
911
|
+
compound: "tages",
|
|
912
|
+
currentGenitive: "dieses tages",
|
|
913
|
+
dative: "tagen",
|
|
914
|
+
genitive: "des tages"
|
|
915
|
+
},
|
|
916
|
+
{
|
|
917
|
+
unit: "week",
|
|
918
|
+
noun: "woche",
|
|
919
|
+
plural: "wochen",
|
|
920
|
+
current: "diese woche",
|
|
921
|
+
previous: "letzte woche",
|
|
922
|
+
next: "nächste woche",
|
|
923
|
+
compound: "wochen",
|
|
924
|
+
currentGenitive: "dieser woche",
|
|
925
|
+
dative: "wochen",
|
|
926
|
+
genitive: "der woche"
|
|
927
|
+
},
|
|
928
|
+
{
|
|
929
|
+
unit: "month",
|
|
930
|
+
noun: "monat",
|
|
931
|
+
plural: "monate",
|
|
932
|
+
current: "dieser monat",
|
|
933
|
+
previous: "letzter monat",
|
|
934
|
+
next: "nächster monat",
|
|
935
|
+
compound: "monats",
|
|
936
|
+
currentGenitive: "dieses monats",
|
|
937
|
+
dative: "monaten",
|
|
938
|
+
genitive: "des monats"
|
|
939
|
+
},
|
|
940
|
+
{
|
|
941
|
+
unit: "quarter",
|
|
942
|
+
noun: "quartal",
|
|
943
|
+
plural: "quartale",
|
|
944
|
+
current: "dieses quartal",
|
|
945
|
+
previous: "letztes quartal",
|
|
946
|
+
next: "nächstes quartal",
|
|
947
|
+
compound: "quartals",
|
|
948
|
+
currentGenitive: "dieses quartals",
|
|
949
|
+
dative: "quartalen",
|
|
950
|
+
genitive: "des quartals"
|
|
951
|
+
},
|
|
952
|
+
{
|
|
953
|
+
unit: "year",
|
|
954
|
+
noun: "jahr",
|
|
955
|
+
plural: "jahre",
|
|
956
|
+
current: "dieses jahr",
|
|
957
|
+
previous: "letztes jahr",
|
|
958
|
+
next: "nächstes jahr",
|
|
959
|
+
compound: "jahres",
|
|
960
|
+
currentGenitive: "dieses jahres",
|
|
961
|
+
dative: "jahren",
|
|
962
|
+
genitive: "des jahres"
|
|
963
|
+
}
|
|
964
|
+
];
|
|
965
|
+
const title$1 = (value) => `${value.slice(0, 1).toLocaleUpperCase("de")}${value.slice(1)}`;
|
|
966
|
+
const canonicalToDate = (entry) => `seit ${title$1(entry.compound)}beginn`;
|
|
967
|
+
const canonicalRelative = (value) => {
|
|
968
|
+
const separator = value.lastIndexOf(" ");
|
|
969
|
+
if (separator === -1) return value;
|
|
970
|
+
return `${value.slice(0, separator + 1)}${title$1(value.slice(separator + 1))}`;
|
|
971
|
+
};
|
|
972
|
+
const remainingPeriodPhrases$1 = unitPhrases.flatMap((entry) => [`rest ${entry.genitive}`, `rest ${entry.currentGenitive}`].map((phrase) => ({
|
|
973
|
+
entry,
|
|
974
|
+
phrase
|
|
975
|
+
})));
|
|
976
|
+
const toDatePhrases$1 = unitPhrases.flatMap((entry) => [
|
|
977
|
+
`seit ${entry.compound}beginn`,
|
|
978
|
+
`seit ${entry.compound}anfang`,
|
|
979
|
+
`seit beginn ${entry.genitive}`,
|
|
980
|
+
`seit anfang ${entry.genitive}`,
|
|
981
|
+
`seit beginn ${entry.currentGenitive}`,
|
|
982
|
+
`seit anfang ${entry.currentGenitive}`,
|
|
983
|
+
`vom ${entry.compound}beginn bis heute`,
|
|
984
|
+
`vom ${entry.compound}anfang bis heute`,
|
|
985
|
+
`vom beginn ${entry.genitive} bis heute`,
|
|
986
|
+
`vom anfang ${entry.genitive} bis heute`,
|
|
987
|
+
`vom beginn ${entry.currentGenitive} bis heute`,
|
|
988
|
+
`vom anfang ${entry.currentGenitive} bis heute`,
|
|
989
|
+
`${entry.noun} bis heute`,
|
|
990
|
+
`${entry.current} bisher`,
|
|
991
|
+
`bisher ${entry.current}`
|
|
992
|
+
].map((phrase) => ({
|
|
993
|
+
entry,
|
|
994
|
+
phrase
|
|
995
|
+
})));
|
|
996
|
+
const currentBoundaryPhrases = unitPhrases.flatMap((entry) => [
|
|
997
|
+
[`vor ${entry.compound}beginn`, "before"],
|
|
998
|
+
[`vor ${entry.compound}anfang`, "before"],
|
|
999
|
+
[`bis ${entry.compound}beginn`, "before"],
|
|
1000
|
+
[`bis ${entry.compound}anfang`, "before"],
|
|
1001
|
+
[`bis ${entry.compound}ende`, "through"],
|
|
1002
|
+
[`nach ${entry.compound}ende`, "after"],
|
|
1003
|
+
[`ab ${entry.compound}ende`, "after"]
|
|
1004
|
+
].map(([phrase, boundary]) => ({
|
|
1005
|
+
boundary,
|
|
1006
|
+
entry,
|
|
1007
|
+
phrase
|
|
1008
|
+
})));
|
|
1009
|
+
const relativeAliases = [
|
|
1010
|
+
[
|
|
1011
|
+
"vorletzten tag",
|
|
1012
|
+
"day",
|
|
1013
|
+
-2,
|
|
1014
|
+
"vorletzter tag"
|
|
1015
|
+
],
|
|
1016
|
+
[
|
|
1017
|
+
"übernächsten tag",
|
|
1018
|
+
"day",
|
|
1019
|
+
2,
|
|
1020
|
+
"übernächster tag"
|
|
1021
|
+
],
|
|
1022
|
+
[
|
|
1023
|
+
"vorletzte woche",
|
|
1024
|
+
"week",
|
|
1025
|
+
-2,
|
|
1026
|
+
"vorletzte woche"
|
|
1027
|
+
],
|
|
1028
|
+
[
|
|
1029
|
+
"übernächste woche",
|
|
1030
|
+
"week",
|
|
1031
|
+
2,
|
|
1032
|
+
"übernächste woche"
|
|
1033
|
+
],
|
|
1034
|
+
[
|
|
1035
|
+
"vorletzten monat",
|
|
1036
|
+
"month",
|
|
1037
|
+
-2,
|
|
1038
|
+
"vorletzter monat"
|
|
1039
|
+
],
|
|
1040
|
+
[
|
|
1041
|
+
"übernächsten monat",
|
|
1042
|
+
"month",
|
|
1043
|
+
2,
|
|
1044
|
+
"übernächster monat"
|
|
1045
|
+
],
|
|
1046
|
+
[
|
|
1047
|
+
"vorletztes quartal",
|
|
1048
|
+
"quarter",
|
|
1049
|
+
-2,
|
|
1050
|
+
"vorletztes quartal"
|
|
1051
|
+
],
|
|
1052
|
+
[
|
|
1053
|
+
"übernächstes quartal",
|
|
1054
|
+
"quarter",
|
|
1055
|
+
2,
|
|
1056
|
+
"übernächstes quartal"
|
|
1057
|
+
],
|
|
1058
|
+
[
|
|
1059
|
+
"vorletztes jahr",
|
|
1060
|
+
"year",
|
|
1061
|
+
-2,
|
|
1062
|
+
"vorletztes jahr"
|
|
1063
|
+
],
|
|
1064
|
+
[
|
|
1065
|
+
"übernächstes jahr",
|
|
1066
|
+
"year",
|
|
1067
|
+
2,
|
|
1068
|
+
"übernächstes jahr"
|
|
1069
|
+
],
|
|
1070
|
+
[
|
|
1071
|
+
"letzten tag",
|
|
1072
|
+
"day",
|
|
1073
|
+
-1,
|
|
1074
|
+
"gestern"
|
|
1075
|
+
],
|
|
1076
|
+
[
|
|
1077
|
+
"vergangenen tag",
|
|
1078
|
+
"day",
|
|
1079
|
+
-1,
|
|
1080
|
+
"gestern"
|
|
1081
|
+
],
|
|
1082
|
+
[
|
|
1083
|
+
"vorherigen tag",
|
|
1084
|
+
"day",
|
|
1085
|
+
-1,
|
|
1086
|
+
"gestern"
|
|
1087
|
+
],
|
|
1088
|
+
[
|
|
1089
|
+
"diesen tag",
|
|
1090
|
+
"day",
|
|
1091
|
+
0,
|
|
1092
|
+
"heute"
|
|
1093
|
+
],
|
|
1094
|
+
[
|
|
1095
|
+
"heutigen tag",
|
|
1096
|
+
"day",
|
|
1097
|
+
0,
|
|
1098
|
+
"heute"
|
|
1099
|
+
],
|
|
1100
|
+
[
|
|
1101
|
+
"nächsten tag",
|
|
1102
|
+
"day",
|
|
1103
|
+
1,
|
|
1104
|
+
"morgen"
|
|
1105
|
+
],
|
|
1106
|
+
[
|
|
1107
|
+
"kommenden tag",
|
|
1108
|
+
"day",
|
|
1109
|
+
1,
|
|
1110
|
+
"morgen"
|
|
1111
|
+
],
|
|
1112
|
+
[
|
|
1113
|
+
"vergangene woche",
|
|
1114
|
+
"week",
|
|
1115
|
+
-1,
|
|
1116
|
+
"letzte woche"
|
|
1117
|
+
],
|
|
1118
|
+
[
|
|
1119
|
+
"vorherige woche",
|
|
1120
|
+
"week",
|
|
1121
|
+
-1,
|
|
1122
|
+
"letzte woche"
|
|
1123
|
+
],
|
|
1124
|
+
[
|
|
1125
|
+
"aktuelle woche",
|
|
1126
|
+
"week",
|
|
1127
|
+
0,
|
|
1128
|
+
"diese woche"
|
|
1129
|
+
],
|
|
1130
|
+
[
|
|
1131
|
+
"laufende woche",
|
|
1132
|
+
"week",
|
|
1133
|
+
0,
|
|
1134
|
+
"diese woche"
|
|
1135
|
+
],
|
|
1136
|
+
[
|
|
1137
|
+
"kommende woche",
|
|
1138
|
+
"week",
|
|
1139
|
+
1,
|
|
1140
|
+
"nächste woche"
|
|
1141
|
+
],
|
|
1142
|
+
[
|
|
1143
|
+
"folgende woche",
|
|
1144
|
+
"week",
|
|
1145
|
+
1,
|
|
1146
|
+
"nächste woche"
|
|
1147
|
+
],
|
|
1148
|
+
[
|
|
1149
|
+
"letzten monat",
|
|
1150
|
+
"month",
|
|
1151
|
+
-1,
|
|
1152
|
+
"letzter monat"
|
|
1153
|
+
],
|
|
1154
|
+
[
|
|
1155
|
+
"vergangenen monat",
|
|
1156
|
+
"month",
|
|
1157
|
+
-1,
|
|
1158
|
+
"letzter monat"
|
|
1159
|
+
],
|
|
1160
|
+
[
|
|
1161
|
+
"vorherigen monat",
|
|
1162
|
+
"month",
|
|
1163
|
+
-1,
|
|
1164
|
+
"letzter monat"
|
|
1165
|
+
],
|
|
1166
|
+
[
|
|
1167
|
+
"diesen monat",
|
|
1168
|
+
"month",
|
|
1169
|
+
0,
|
|
1170
|
+
"dieser monat"
|
|
1171
|
+
],
|
|
1172
|
+
[
|
|
1173
|
+
"aktuellen monat",
|
|
1174
|
+
"month",
|
|
1175
|
+
0,
|
|
1176
|
+
"dieser monat"
|
|
1177
|
+
],
|
|
1178
|
+
[
|
|
1179
|
+
"laufenden monat",
|
|
1180
|
+
"month",
|
|
1181
|
+
0,
|
|
1182
|
+
"dieser monat"
|
|
1183
|
+
],
|
|
1184
|
+
[
|
|
1185
|
+
"nächsten monat",
|
|
1186
|
+
"month",
|
|
1187
|
+
1,
|
|
1188
|
+
"nächster monat"
|
|
1189
|
+
],
|
|
1190
|
+
[
|
|
1191
|
+
"kommenden monat",
|
|
1192
|
+
"month",
|
|
1193
|
+
1,
|
|
1194
|
+
"nächster monat"
|
|
1195
|
+
],
|
|
1196
|
+
[
|
|
1197
|
+
"folgenden monat",
|
|
1198
|
+
"month",
|
|
1199
|
+
1,
|
|
1200
|
+
"nächster monat"
|
|
1201
|
+
],
|
|
1202
|
+
[
|
|
1203
|
+
"vergangenes quartal",
|
|
1204
|
+
"quarter",
|
|
1205
|
+
-1,
|
|
1206
|
+
"letztes quartal"
|
|
1207
|
+
],
|
|
1208
|
+
[
|
|
1209
|
+
"vorheriges quartal",
|
|
1210
|
+
"quarter",
|
|
1211
|
+
-1,
|
|
1212
|
+
"letztes quartal"
|
|
1213
|
+
],
|
|
1214
|
+
[
|
|
1215
|
+
"aktuelles quartal",
|
|
1216
|
+
"quarter",
|
|
1217
|
+
0,
|
|
1218
|
+
"dieses quartal"
|
|
1219
|
+
],
|
|
1220
|
+
[
|
|
1221
|
+
"laufendes quartal",
|
|
1222
|
+
"quarter",
|
|
1223
|
+
0,
|
|
1224
|
+
"dieses quartal"
|
|
1225
|
+
],
|
|
1226
|
+
[
|
|
1227
|
+
"kommendes quartal",
|
|
1228
|
+
"quarter",
|
|
1229
|
+
1,
|
|
1230
|
+
"nächstes quartal"
|
|
1231
|
+
],
|
|
1232
|
+
[
|
|
1233
|
+
"folgendes quartal",
|
|
1234
|
+
"quarter",
|
|
1235
|
+
1,
|
|
1236
|
+
"nächstes quartal"
|
|
1237
|
+
],
|
|
1238
|
+
[
|
|
1239
|
+
"vergangenes jahr",
|
|
1240
|
+
"year",
|
|
1241
|
+
-1,
|
|
1242
|
+
"letztes jahr"
|
|
1243
|
+
],
|
|
1244
|
+
[
|
|
1245
|
+
"vorheriges jahr",
|
|
1246
|
+
"year",
|
|
1247
|
+
-1,
|
|
1248
|
+
"letztes jahr"
|
|
1249
|
+
],
|
|
1250
|
+
[
|
|
1251
|
+
"aktuelles jahr",
|
|
1252
|
+
"year",
|
|
1253
|
+
0,
|
|
1254
|
+
"dieses jahr"
|
|
1255
|
+
],
|
|
1256
|
+
[
|
|
1257
|
+
"laufendes jahr",
|
|
1258
|
+
"year",
|
|
1259
|
+
0,
|
|
1260
|
+
"dieses jahr"
|
|
1261
|
+
],
|
|
1262
|
+
[
|
|
1263
|
+
"kommendes jahr",
|
|
1264
|
+
"year",
|
|
1265
|
+
1,
|
|
1266
|
+
"nächstes jahr"
|
|
1267
|
+
],
|
|
1268
|
+
[
|
|
1269
|
+
"folgendes jahr",
|
|
1270
|
+
"year",
|
|
1271
|
+
1,
|
|
1272
|
+
"nächstes jahr"
|
|
1273
|
+
]
|
|
1274
|
+
];
|
|
1275
|
+
const monthNumber$1 = (value) => {
|
|
1276
|
+
const normalized = value.endsWith(".") ? value.slice(0, -1) : value;
|
|
1277
|
+
const fullIndex = months$1.findIndex((month) => month === normalized);
|
|
1278
|
+
if (fullIndex !== -1) return fullIndex + 1;
|
|
1279
|
+
const shortIndex = monthAbbreviations$1.findIndex((aliases) => aliases.some((alias) => alias === normalized));
|
|
1280
|
+
return shortIndex === -1 ? void 0 : shortIndex + 1;
|
|
1281
|
+
};
|
|
1282
|
+
const parseNamedDate$1 = (input) => {
|
|
1283
|
+
const named = String$1.match(/^([0-3]?\d)\.? ([a-zäöüß]+\.?) (\d{4})$/u)(input);
|
|
1284
|
+
if (Option.isSome(named)) return namedDatePeriod(named.value[3], named.value[2], named.value[1], monthNumber$1, (day, month, year) => `${day}. ${title$1(months$1[month - 1])} ${year}`);
|
|
1285
|
+
const numeric = String$1.match(/^([0-3]?\d)\.([01]?\d)\.(\d{4})$/u)(input);
|
|
1286
|
+
if (Option.isSome(numeric)) {
|
|
1287
|
+
const year = validYear(numeric.value[3]);
|
|
1288
|
+
const day = Number(numeric.value[1]);
|
|
1289
|
+
const month = Number(numeric.value[2]);
|
|
1290
|
+
if (year !== void 0 && month >= 1 && month <= 12) {
|
|
1291
|
+
const value = isoDate(year, month, day);
|
|
1292
|
+
if (isIsoDate(value) && value !== "9999-12-31") return Option.some(fixedDatePeriod(value, `${day}. ${title$1(months$1[month - 1])} ${year}`));
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
return Option.none();
|
|
1296
|
+
};
|
|
1297
|
+
const quarterNames$1 = [
|
|
1298
|
+
"erstes",
|
|
1299
|
+
"zweites",
|
|
1300
|
+
"drittes",
|
|
1301
|
+
"viertes"
|
|
1302
|
+
];
|
|
1303
|
+
const quarterNumber$1 = (value) => {
|
|
1304
|
+
if (value.startsWith("q")) return Number(value.slice(1));
|
|
1305
|
+
if (value.endsWith(".")) return Number(value.slice(0, -1));
|
|
1306
|
+
const index = quarterNames$1.findIndex((name) => name === value);
|
|
1307
|
+
return index === -1 ? void 0 : index + 1;
|
|
1308
|
+
};
|
|
1309
|
+
const relativeDirection$1 = (value) => {
|
|
1310
|
+
if (value === "letzten") return -1;
|
|
1311
|
+
if (value === "nächsten") return 1;
|
|
1312
|
+
return 0;
|
|
1313
|
+
};
|
|
1314
|
+
const parseQuarter$1 = (input) => {
|
|
1315
|
+
const fixed = String$1.match(/^(q[1-4]|[1-4]\.|erstes|zweites|drittes|viertes)(?: quartal)? (\d{4})$/u)(input);
|
|
1316
|
+
const reversed = String$1.match(/^(\d{4}) (q[1-4])$/u)(input);
|
|
1317
|
+
const relative = String$1.match(/^(q[1-4]|[1-4]\.|erstes|zweites|drittes|viertes)(?: quartal)? (letzten|dieses|nächsten) jahres$/u)(input);
|
|
1318
|
+
const standalone = String$1.match(/^(q[1-4]|[1-4]\.|erstes|zweites|drittes|viertes)(?: quartal)?$/u)(input);
|
|
1319
|
+
if (Option.isSome(fixed) || Option.isSome(reversed)) {
|
|
1320
|
+
const match = [fixed, reversed].find(Option.isSome)?.value;
|
|
1321
|
+
if (match === void 0) return Option.none();
|
|
1322
|
+
const quarterText = Option.isSome(reversed) ? match[2] : match[1];
|
|
1323
|
+
const yearText = Option.isSome(reversed) ? match[1] : match[2];
|
|
1324
|
+
const quarter = quarterNumber$1(quarterText);
|
|
1325
|
+
const year = validYear(yearText);
|
|
1326
|
+
if (quarter !== void 0 && year !== void 0) return Option.some(fixedQuarterPeriod(year, quarter, `Q${quarter} ${year}`));
|
|
1327
|
+
}
|
|
1328
|
+
if (Option.isSome(relative)) {
|
|
1329
|
+
const quarter = quarterNumber$1(relative.value[1]);
|
|
1330
|
+
if (quarter !== void 0) {
|
|
1331
|
+
const direction = relativeDirection$1(relative.value[2]);
|
|
1332
|
+
return Option.some(quarterOfRelativeYear(quarter, direction, `Q${quarter} ${relative.value[2]} Jahres`));
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
if (Option.isNone(standalone)) return Option.none();
|
|
1336
|
+
const quarter = quarterNumber$1(standalone.value[1]);
|
|
1337
|
+
return quarter === void 0 ? Option.none() : Option.some(quarterOfRelativeYear(quarter, 0, `Q${quarter}`));
|
|
1338
|
+
};
|
|
1339
|
+
const parseBasePeriod$1 = (input) => {
|
|
1340
|
+
if (isIsoDate(input) && input !== "9999-12-31") return Option.some(fixedDatePeriod(input, input));
|
|
1341
|
+
const namedDate = parseNamedDate$1(input);
|
|
1342
|
+
if (Option.isSome(namedDate)) return namedDate;
|
|
1343
|
+
const quarter = parseQuarter$1(input);
|
|
1344
|
+
if (Option.isSome(quarter)) return quarter;
|
|
1345
|
+
const yearMatch = String$1.match(/^(?:(?:das )?(?:kalender)?jahr )?(\d{4})$/u)(input);
|
|
1346
|
+
if (Option.isSome(yearMatch)) {
|
|
1347
|
+
const year = validYear(yearMatch.value[1]);
|
|
1348
|
+
if (year !== void 0) return Option.some(fixedYearPeriod(year, String(year)));
|
|
1349
|
+
}
|
|
1350
|
+
const monthYear = String$1.match(/^([a-zäöüß]+\.?) (\d{4})$/u)(input);
|
|
1351
|
+
if (Option.isSome(monthYear)) {
|
|
1352
|
+
const month = monthNumber$1(monthYear.value[1]);
|
|
1353
|
+
const year = validYear(monthYear.value[2]);
|
|
1354
|
+
if (month !== void 0 && year !== void 0) return Option.some(fixedMonthPeriod(year, month, `${title$1(months$1[month - 1])} ${year}`));
|
|
1355
|
+
}
|
|
1356
|
+
const relativeMonth = String$1.match(/^([a-zäöüß]+\.?) (letzten|dieses|nächsten) jahres$/u)(input);
|
|
1357
|
+
if (Option.isSome(relativeMonth)) {
|
|
1358
|
+
const month = monthNumber$1(relativeMonth.value[1]);
|
|
1359
|
+
const direction = relativeDirection$1(relativeMonth.value[2]);
|
|
1360
|
+
if (month !== void 0) return Option.some(monthOfRelativeYear(month, direction, `${title$1(months$1[month - 1])} ${relativeMonth.value[2]} Jahres`));
|
|
1361
|
+
}
|
|
1362
|
+
const standaloneMonth = monthNumber$1(input);
|
|
1363
|
+
if (standaloneMonth !== void 0) return Option.some(monthOfRelativeYear(standaloneMonth, 0, title$1(months$1[standaloneMonth - 1])));
|
|
1364
|
+
if (input === "wochenende" || input === "dieses wochenende" || input === "am wochenende") return Option.some(relativeWeekend(0, "dieses Wochenende"));
|
|
1365
|
+
if (input === "letztes wochenende" || input === "vergangenes wochenende") return Option.some(relativeWeekend(-1, "letztes Wochenende"));
|
|
1366
|
+
if (input === "nächstes wochenende" || input === "kommendes wochenende") return Option.some(relativeWeekend(1, "nächstes Wochenende"));
|
|
1367
|
+
if (input === "vorletztes wochenende") return Option.some(relativeWeekend(-2, "vorletztes Wochenende"));
|
|
1368
|
+
if (input === "übernächstes wochenende") return Option.some(relativeWeekend(2, "übernächstes Wochenende"));
|
|
1369
|
+
const relative = unitPhrases.find((entry) => entry.current === input || entry.previous === input || entry.next === input);
|
|
1370
|
+
if (relative !== void 0) {
|
|
1371
|
+
let direction = 0;
|
|
1372
|
+
if (input === relative.previous) direction = -1;
|
|
1373
|
+
if (input === relative.next) direction = 1;
|
|
1374
|
+
return Option.some(relativePeriod(relative.unit, direction, canonicalRelative(input)));
|
|
1375
|
+
}
|
|
1376
|
+
const alias = relativeAliases.find((entry) => entry[0] === input);
|
|
1377
|
+
if (alias !== void 0) return Option.some(relativePeriod(alias[1], alias[2], canonicalRelative(alias[3])));
|
|
1378
|
+
return Option.none();
|
|
1379
|
+
};
|
|
1380
|
+
const parsePeriod$1 = (input) => {
|
|
1381
|
+
const edge = String$1.match(/^(anfang|beginn|ende) (.+)$/u)(input);
|
|
1382
|
+
if (Option.isSome(edge)) {
|
|
1383
|
+
const period = parseBasePeriod$1(edge.value[2]);
|
|
1384
|
+
if (Option.isSome(period)) {
|
|
1385
|
+
const canonical = `${edge.value[1] === "ende" ? "Ende" : "Anfang"} ${period.value.canonical}`;
|
|
1386
|
+
return Option.some(edge.value[1] === "ende" ? periodEndDay(period.value, canonical) : periodStartDay(period.value, canonical));
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
const wrapper = [
|
|
1390
|
+
"im ",
|
|
1391
|
+
"für ",
|
|
1392
|
+
"während "
|
|
1393
|
+
].find((prefix) => input.startsWith(prefix));
|
|
1394
|
+
return parseBasePeriod$1(wrapper === void 0 ? input : input.slice(wrapper.length));
|
|
1395
|
+
};
|
|
1396
|
+
const currentBoundaryCandidate = (input) => {
|
|
1397
|
+
const match = currentBoundaryPhrases.find((entry) => entry.phrase === input);
|
|
1398
|
+
if (match === void 0) return Option.none();
|
|
1399
|
+
const period = relativePeriod(match.entry.unit, 0, match.entry.current);
|
|
1400
|
+
return Option.some(periodBoundaryCandidate(period, match.boundary, match.phrase));
|
|
1401
|
+
};
|
|
1402
|
+
const boundaryCandidate$1 = (input) => openBoundaryCandidate(input, [
|
|
1403
|
+
["seit dem beginn von ", "since"],
|
|
1404
|
+
["seit dem anfang von ", "since"],
|
|
1405
|
+
["seit dem jahr ", "since"],
|
|
1406
|
+
["ab dem jahr ", "since"],
|
|
1407
|
+
["bis zum beginn von ", "before"],
|
|
1408
|
+
["bis zum anfang von ", "before"],
|
|
1409
|
+
["bis zum jahr ", "before"],
|
|
1410
|
+
["bis zum ende von ", "through"],
|
|
1411
|
+
["nach dem ende von ", "after"],
|
|
1412
|
+
["bis einschließlich ", "through"],
|
|
1413
|
+
["seit beginn ", "since"],
|
|
1414
|
+
["seit anfang ", "since"],
|
|
1415
|
+
["ab beginn ", "since"],
|
|
1416
|
+
["ab anfang ", "since"],
|
|
1417
|
+
["vor beginn ", "before"],
|
|
1418
|
+
["vor anfang ", "before"],
|
|
1419
|
+
["bis beginn ", "before"],
|
|
1420
|
+
["bis anfang ", "before"],
|
|
1421
|
+
["bis ende ", "through"],
|
|
1422
|
+
["nach ende ", "after"],
|
|
1423
|
+
["ab ende ", "after"],
|
|
1424
|
+
["seit ", "since"],
|
|
1425
|
+
["vor ", "before"],
|
|
1426
|
+
["nach ", "after"],
|
|
1427
|
+
["bis ", "before"],
|
|
1428
|
+
["ab ", "since"]
|
|
1429
|
+
], parsePeriod$1);
|
|
1430
|
+
const countedUnit$1 = (value, amount, dative = false) => unitPhrases.find((entry) => {
|
|
1431
|
+
if (amount === 1) return value === entry.noun;
|
|
1432
|
+
return value === (dative ? entry.dative : entry.plural);
|
|
1433
|
+
});
|
|
1434
|
+
const parseCalendarOffset$1 = (input) => {
|
|
1435
|
+
const prefixed = String$1.match(/^(vor|in) ([1-9]\d*) (tag|tagen|woche|wochen|monat|monaten|quartal|quartalen|jahr|jahren)$/u)(input);
|
|
1436
|
+
const prior = String$1.match(/^([1-9]\d*) (tag|tage|woche|wochen|monat|monate|quartal|quartale|jahr|jahre) zuvor$/u)(input);
|
|
1437
|
+
const match = [prefixed, prior].find(Option.isSome)?.value;
|
|
1438
|
+
if (match === void 0) return Option.none();
|
|
1439
|
+
const amountText = Option.isSome(prefixed) ? match[2] : match[1];
|
|
1440
|
+
const unitText = Option.isSome(prefixed) ? match[3] : match[2];
|
|
1441
|
+
const amount = parseTrailingCount(amountText);
|
|
1442
|
+
if (Option.isNone(amount)) return Option.none();
|
|
1443
|
+
const entry = countedUnit$1(unitText, amount.value, Option.isSome(prefixed));
|
|
1444
|
+
if (entry === void 0) return Option.none();
|
|
1445
|
+
const isPast = Option.isSome(prior) || Option.isSome(prefixed) && match[1] === "vor";
|
|
1446
|
+
const direction = isPast ? -amount.value : amount.value;
|
|
1447
|
+
const directionName = isPast ? "vor" : "in";
|
|
1448
|
+
const noun = amount.value === 1 ? entry.noun : entry.dative;
|
|
1449
|
+
const canonical = `${directionName} ${amount.value} ${title$1(noun)}`;
|
|
1450
|
+
return Option.some(candidate(periodRange(relativePeriod(entry.unit, direction, canonical)), canonical));
|
|
1451
|
+
};
|
|
1452
|
+
const parseRollingPeriod$1 = (input) => {
|
|
1453
|
+
const past = String$1.match(/^(?:(?:die letzten|letzten|letzte|vergangene|vorherige) )?([1-9]\d*) (tag|tage|woche|wochen|monat|monate|quartal|quartale|jahr|jahre)$/u)(input);
|
|
1454
|
+
const pastDative = String$1.match(/^(?:in den letzten|in den vergangenen|in den vorherigen) ([1-9]\d*) (tag|tagen|woche|wochen|monat|monaten|quartal|quartalen|jahr|jahren)$/u)(input);
|
|
1455
|
+
const pastGenitive = String$1.match(/^während der letzten ([1-9]\d*) (tag|tage|woche|wochen|monat|monate|quartal|quartale|jahr|jahre)$/u)(input);
|
|
1456
|
+
const future = String$1.match(/^(?:die nächsten|nächsten|nächste|kommende) ([1-9]\d*) (tag|tage|woche|wochen|monat|monate|quartal|quartale|jahr|jahre)$/u)(input);
|
|
1457
|
+
const futureDative = String$1.match(/^(?:in den nächsten|in den kommenden|innerhalb von) ([1-9]\d*) (tag|tagen|woche|wochen|monat|monaten|quartal|quartalen|jahr|jahren)$/u)(input);
|
|
1458
|
+
const futureGenitive = String$1.match(/^innerhalb der nächsten ([1-9]\d*) (tag|tage|woche|wochen|monat|monate|quartal|quartale|jahr|jahre)$/u)(input);
|
|
1459
|
+
const match = [
|
|
1460
|
+
past,
|
|
1461
|
+
pastDative,
|
|
1462
|
+
pastGenitive,
|
|
1463
|
+
future,
|
|
1464
|
+
futureDative,
|
|
1465
|
+
futureGenitive
|
|
1466
|
+
].find(Option.isSome)?.value;
|
|
1467
|
+
if (match === void 0) return Option.none();
|
|
1468
|
+
const amount = parseTrailingCount(match[1]);
|
|
1469
|
+
if (Option.isNone(amount)) return Option.none();
|
|
1470
|
+
const usesDative = Option.isSome(pastDative) || Option.isSome(futureDative);
|
|
1471
|
+
const entry = countedUnit$1(match[2], amount.value, usesDative);
|
|
1472
|
+
if (entry === void 0) return Option.none();
|
|
1473
|
+
const isFuture = Option.isSome(future) || Option.isSome(futureDative) || Option.isSome(futureGenitive);
|
|
1474
|
+
const range = isFuture ? futureRange(amount.value, entry.unit) : trailingRange(amount.value, entry.unit);
|
|
1475
|
+
const direction = isFuture ? "nächste" : "letzte";
|
|
1476
|
+
const noun = amount.value === 1 ? entry.noun : entry.plural;
|
|
1477
|
+
return Option.some(candidate(range, `${direction} ${amount.value} ${title$1(noun)}`));
|
|
1478
|
+
};
|
|
1479
|
+
const parseGerman = (input) => {
|
|
1480
|
+
const remaining = remainingPeriodPhrases$1.find((entry) => entry.phrase === input);
|
|
1481
|
+
if (remaining !== void 0) return Option.some(candidate(remainingPeriodRange(remaining.entry.unit), `Rest ${canonicalRelative(remaining.entry.genitive)}`));
|
|
1482
|
+
if (["bis heute", "bis jetzt"].includes(input)) return Option.some(candidate(untilNowRange(), "bis heute"));
|
|
1483
|
+
if (["ab jetzt", "von jetzt an"].includes(input)) return Option.some(candidate(fromNowRange(), "ab jetzt"));
|
|
1484
|
+
const offset = parseCalendarOffset$1(input);
|
|
1485
|
+
if (Option.isSome(offset)) return offset;
|
|
1486
|
+
const rolling = parseRollingPeriod$1(input);
|
|
1487
|
+
if (Option.isSome(rolling)) return rolling;
|
|
1488
|
+
const toDate = toDatePhrases$1.find((entry) => entry.phrase === input);
|
|
1489
|
+
if (toDate !== void 0) return Option.some(candidate(periodToDateRange(toDate.entry.unit), canonicalToDate(toDate.entry)));
|
|
1490
|
+
const currentBoundary = currentBoundaryCandidate(input);
|
|
1491
|
+
if (Option.isSome(currentBoundary)) return currentBoundary;
|
|
1492
|
+
const nowBounded = joinedNowCandidate(input, [
|
|
1493
|
+
["von ", " bis heute"],
|
|
1494
|
+
["vom ", " bis heute"],
|
|
1495
|
+
["zwischen ", " und heute"]
|
|
1496
|
+
], ["von heute bis ", "zwischen heute und "], parsePeriod$1, (period) => `von ${period} bis heute`, (period) => `von heute bis ${period}`);
|
|
1497
|
+
if (Option.isSome(nowBounded)) return nowBounded;
|
|
1498
|
+
const bounded = joinedPeriodCandidate(input, [
|
|
1499
|
+
["von ", " bis einschließlich "],
|
|
1500
|
+
["vom ", " bis einschließlich "],
|
|
1501
|
+
["von ", " bis zum "],
|
|
1502
|
+
["vom ", " bis zum "],
|
|
1503
|
+
["von ", " bis "],
|
|
1504
|
+
["vom ", " bis "],
|
|
1505
|
+
["zwischen ", " und "],
|
|
1506
|
+
["", " bis einschließlich "],
|
|
1507
|
+
["", " bis "],
|
|
1508
|
+
["", " - "],
|
|
1509
|
+
["", " – "],
|
|
1510
|
+
["", " — "],
|
|
1511
|
+
["zwischen ", "-"],
|
|
1512
|
+
["", "-"],
|
|
1513
|
+
["zwischen ", "–"],
|
|
1514
|
+
["", "–"],
|
|
1515
|
+
["zwischen ", "—"],
|
|
1516
|
+
["", "—"],
|
|
1517
|
+
["zwischen ", "~"],
|
|
1518
|
+
["", "~"]
|
|
1519
|
+
], parsePeriod$1, (lower, upper) => `von ${lower} bis ${upper}`);
|
|
1520
|
+
if (Option.isSome(bounded)) return bounded;
|
|
1521
|
+
const boundary = boundaryCandidate$1(input);
|
|
1522
|
+
if (Option.isSome(boundary)) return boundary;
|
|
1523
|
+
return Option.map(parsePeriod$1(input), (period) => candidate(periodRange(period), period.canonical));
|
|
1524
|
+
};
|
|
1525
|
+
const staticPeriods$1 = periodsFromPhrases([
|
|
1526
|
+
"dieses wochenende",
|
|
1527
|
+
"letztes wochenende",
|
|
1528
|
+
"nächstes wochenende",
|
|
1529
|
+
"vorletztes wochenende",
|
|
1530
|
+
"übernächstes wochenende",
|
|
1531
|
+
...unitPhrases.flatMap((entry) => [
|
|
1532
|
+
entry.current,
|
|
1533
|
+
entry.previous,
|
|
1534
|
+
entry.next,
|
|
1535
|
+
`anfang ${entry.current}`,
|
|
1536
|
+
`ende ${entry.current}`,
|
|
1537
|
+
`anfang ${entry.previous}`,
|
|
1538
|
+
`ende ${entry.previous}`,
|
|
1539
|
+
`anfang ${entry.next}`,
|
|
1540
|
+
`ende ${entry.next}`
|
|
1541
|
+
]),
|
|
1542
|
+
...[
|
|
1543
|
+
1,
|
|
1544
|
+
2,
|
|
1545
|
+
3,
|
|
1546
|
+
4
|
|
1547
|
+
].flatMap((quarter) => [
|
|
1548
|
+
`q${quarter}`,
|
|
1549
|
+
`q${quarter} letzten jahres`,
|
|
1550
|
+
`q${quarter} nächsten jahres`
|
|
1551
|
+
]),
|
|
1552
|
+
...months$1.flatMap((month) => [
|
|
1553
|
+
month,
|
|
1554
|
+
`${month} letzten jahres`,
|
|
1555
|
+
`${month} nächsten jahres`
|
|
1556
|
+
])
|
|
1557
|
+
], parsePeriod$1);
|
|
1558
|
+
const renderGerman = (range) => {
|
|
1559
|
+
const offset = calendarPeriodOffset(range);
|
|
1560
|
+
if (Option.isSome(offset) && Math.abs(offset.value.amount) > 1) {
|
|
1561
|
+
const entry = unitPhrases.find((unit) => unit.unit === offset.value.unit);
|
|
1562
|
+
if (entry !== void 0) return Option.some(`${offset.value.amount < 0 ? "vor" : "in"} ${Math.abs(offset.value.amount)} ${title$1(Math.abs(offset.value.amount) === 1 ? entry.noun : entry.dative)}`);
|
|
1563
|
+
}
|
|
1564
|
+
const future = futurePeriod(range);
|
|
1565
|
+
if (Option.isSome(future)) {
|
|
1566
|
+
const entry = unitPhrases.find((unit) => unit.unit === future.value.unit);
|
|
1567
|
+
if (entry !== void 0) return Option.some(`nächste ${future.value.amount} ${title$1(future.value.amount === 1 ? entry.noun : entry.plural)}`);
|
|
1568
|
+
}
|
|
1569
|
+
const trailing = trailingPeriod(range);
|
|
1570
|
+
if (Option.isSome(trailing)) {
|
|
1571
|
+
const entry = unitPhrases.find((unit) => unit.unit === trailing.value.unit);
|
|
1572
|
+
if (entry !== void 0) return Option.some(trailing.value.amount === 1 ? `1 ${title$1(entry.noun)}` : `letzte ${trailing.value.amount} ${title$1(entry.plural)}`);
|
|
1573
|
+
}
|
|
1574
|
+
const periods = [...staticPeriods$1, ...periodsFromPhrases([...datedPeriods(range, months$1), ...datedQuarterPeriods(range)], parsePeriod$1)];
|
|
1575
|
+
return renderPeriodRange(range, [...unitPhrases.map((entry) => candidate(periodToDateRange(entry.unit), canonicalToDate(entry))), ...unitPhrases.map((entry) => candidate(remainingPeriodRange(entry.unit), `Rest ${canonicalRelative(entry.genitive)}`))], periods, (period) => `seit ${period}`, (period) => `vor ${period}`, (period) => `bis einschließlich ${period}`, (period) => `nach ${period}`, (lower, upper) => `von ${lower} bis ${upper}`, (period) => `von ${period} bis heute`, (period) => `von heute bis ${period}`, () => "bis heute", () => "ab jetzt");
|
|
1576
|
+
};
|
|
1577
|
+
const GermanContribution = new BaseLanguageContribution({
|
|
1578
|
+
locale: "de",
|
|
1579
|
+
vocabulary: [
|
|
1580
|
+
...months$1,
|
|
1581
|
+
...quarterNames$1,
|
|
1582
|
+
"q1",
|
|
1583
|
+
"q2",
|
|
1584
|
+
"q3",
|
|
1585
|
+
"q4",
|
|
1586
|
+
...monthAbbreviations$1.flatMap((aliases) => aliases),
|
|
1587
|
+
...toDatePhrases$1.flatMap((entry) => entry.phrase.split(" ")),
|
|
1588
|
+
...remainingPeriodPhrases$1.flatMap((entry) => entry.phrase.split(" ")),
|
|
1589
|
+
...currentBoundaryPhrases.flatMap((entry) => entry.phrase.split(" ")),
|
|
1590
|
+
...relativeAliases.flatMap((entry) => entry[0].split(" ")),
|
|
1591
|
+
...unitPhrases.flatMap((entry) => [
|
|
1592
|
+
entry.noun,
|
|
1593
|
+
entry.plural,
|
|
1594
|
+
entry.dative,
|
|
1595
|
+
...entry.currentGenitive.split(" "),
|
|
1596
|
+
...entry.current.split(" "),
|
|
1597
|
+
...entry.previous.split(" "),
|
|
1598
|
+
...entry.next.split(" ")
|
|
1599
|
+
]),
|
|
1600
|
+
"ab",
|
|
1601
|
+
"an",
|
|
1602
|
+
"anfang",
|
|
1603
|
+
"beginn",
|
|
1604
|
+
"bisher",
|
|
1605
|
+
"bis",
|
|
1606
|
+
"dem",
|
|
1607
|
+
"den",
|
|
1608
|
+
"die",
|
|
1609
|
+
"dieses",
|
|
1610
|
+
"einschließlich",
|
|
1611
|
+
"ende",
|
|
1612
|
+
"für",
|
|
1613
|
+
"im",
|
|
1614
|
+
"innerhalb",
|
|
1615
|
+
"jetzt",
|
|
1616
|
+
"kalenderjahr",
|
|
1617
|
+
"jahres",
|
|
1618
|
+
"kommende",
|
|
1619
|
+
"kommenden",
|
|
1620
|
+
"letzte",
|
|
1621
|
+
"letzten",
|
|
1622
|
+
"nach",
|
|
1623
|
+
"nächste",
|
|
1624
|
+
"nächsten",
|
|
1625
|
+
"seit",
|
|
1626
|
+
"vor",
|
|
1627
|
+
"vorherige",
|
|
1628
|
+
"vorherigen",
|
|
1629
|
+
"vergangene",
|
|
1630
|
+
"vergangenen",
|
|
1631
|
+
"vom",
|
|
1632
|
+
"von",
|
|
1633
|
+
"während",
|
|
1634
|
+
"wochenende",
|
|
1635
|
+
"zwischen",
|
|
1636
|
+
"zuvor"
|
|
1637
|
+
],
|
|
1638
|
+
normalize: normalizeNaturalText,
|
|
1639
|
+
correct: correctWhitespaceSeparatedText,
|
|
1640
|
+
parseExact: parseGerman,
|
|
1641
|
+
render: renderGerman
|
|
1642
|
+
});
|
|
1643
|
+
const GermanLanguage = defineLanguagePlugin({
|
|
1644
|
+
id: "chronolizer/language-de",
|
|
1645
|
+
effect: (context) => Effect.asVoid(context.register("chronolizer/language-de", GermanContribution))
|
|
1646
|
+
});
|
|
1647
|
+
const GermanLanguageLayer = languagePluginsLayer([GermanLanguage]);
|
|
1648
|
+
//#endregion
|
|
1649
|
+
//#region src/locales/en.ts
|
|
1650
|
+
const months = [
|
|
1651
|
+
"january",
|
|
1652
|
+
"february",
|
|
1653
|
+
"march",
|
|
1654
|
+
"april",
|
|
1655
|
+
"may",
|
|
1656
|
+
"june",
|
|
1657
|
+
"july",
|
|
1658
|
+
"august",
|
|
1659
|
+
"september",
|
|
1660
|
+
"october",
|
|
1661
|
+
"november",
|
|
1662
|
+
"december"
|
|
1663
|
+
];
|
|
1664
|
+
const monthAbbreviations = [
|
|
1665
|
+
["jan"],
|
|
1666
|
+
["feb"],
|
|
1667
|
+
["mar"],
|
|
1668
|
+
["apr"],
|
|
1669
|
+
["may"],
|
|
1670
|
+
["jun"],
|
|
1671
|
+
["jul"],
|
|
1672
|
+
["aug"],
|
|
1673
|
+
["sep", "sept"],
|
|
1674
|
+
["oct"],
|
|
1675
|
+
["nov"],
|
|
1676
|
+
["dec"]
|
|
1677
|
+
];
|
|
1678
|
+
const quarterNames = [
|
|
1679
|
+
"first",
|
|
1680
|
+
"second",
|
|
1681
|
+
"third",
|
|
1682
|
+
"fourth"
|
|
1683
|
+
];
|
|
1684
|
+
const units = [
|
|
1685
|
+
[
|
|
1686
|
+
"day",
|
|
1687
|
+
"day",
|
|
1688
|
+
"days"
|
|
1689
|
+
],
|
|
1690
|
+
[
|
|
1691
|
+
"week",
|
|
1692
|
+
"week",
|
|
1693
|
+
"weeks"
|
|
1694
|
+
],
|
|
1695
|
+
[
|
|
1696
|
+
"month",
|
|
1697
|
+
"month",
|
|
1698
|
+
"months"
|
|
1699
|
+
],
|
|
1700
|
+
[
|
|
1701
|
+
"quarter",
|
|
1702
|
+
"quarter",
|
|
1703
|
+
"quarters"
|
|
1704
|
+
],
|
|
1705
|
+
[
|
|
1706
|
+
"year",
|
|
1707
|
+
"year",
|
|
1708
|
+
"years"
|
|
1709
|
+
]
|
|
1710
|
+
];
|
|
1711
|
+
const title = (value) => `${value.slice(0, 1).toLocaleUpperCase("en")}${value.slice(1)}`;
|
|
1712
|
+
const currentPeriod = (unit) => unit === "day" ? "today" : `this ${unit}`;
|
|
1713
|
+
const remainingPeriodPhrases = units.flatMap((entry) => [
|
|
1714
|
+
`rest of the ${entry[0]}`,
|
|
1715
|
+
`rest of ${entry[0]}`,
|
|
1716
|
+
`rest of ${currentPeriod(entry[0])}`,
|
|
1717
|
+
`remainder of the ${entry[0]}`,
|
|
1718
|
+
`remaining ${entry[0]}`
|
|
1719
|
+
].map((phrase) => ({
|
|
1720
|
+
entry,
|
|
1721
|
+
phrase
|
|
1722
|
+
})));
|
|
1723
|
+
const toDateAbbreviations = [
|
|
1724
|
+
"dtd",
|
|
1725
|
+
"wtd",
|
|
1726
|
+
"mtd",
|
|
1727
|
+
"qtd",
|
|
1728
|
+
"ytd"
|
|
1729
|
+
];
|
|
1730
|
+
const toDatePhrases = units.flatMap((entry, index) => [
|
|
1731
|
+
`${entry[0]} to date`,
|
|
1732
|
+
`${entry[0]}-to-date`,
|
|
1733
|
+
toDateAbbreviations[index],
|
|
1734
|
+
`since the start of the ${entry[0]}`,
|
|
1735
|
+
`since the beginning of the ${entry[0]}`,
|
|
1736
|
+
`from the start of the ${entry[0]} to now`,
|
|
1737
|
+
`from the beginning of the ${entry[0]} to now`,
|
|
1738
|
+
`${currentPeriod(entry[0])} so far`,
|
|
1739
|
+
`so far ${currentPeriod(entry[0])}`,
|
|
1740
|
+
`${currentPeriod(entry[0])} until now`,
|
|
1741
|
+
`${currentPeriod(entry[0])} up to now`
|
|
1742
|
+
].map((phrase) => ({
|
|
1743
|
+
entry,
|
|
1744
|
+
phrase
|
|
1745
|
+
})));
|
|
1746
|
+
const monthNumber = (value) => {
|
|
1747
|
+
const normalized = value.endsWith(".") ? value.slice(0, -1) : value;
|
|
1748
|
+
const fullIndex = months.findIndex((month) => month === normalized);
|
|
1749
|
+
if (fullIndex !== -1) return fullIndex + 1;
|
|
1750
|
+
const shortIndex = monthAbbreviations.findIndex((aliases) => aliases.some((alias) => alias === normalized));
|
|
1751
|
+
return shortIndex === -1 ? void 0 : shortIndex + 1;
|
|
1752
|
+
};
|
|
1753
|
+
const relativeDirection = (value) => {
|
|
1754
|
+
if (value === "last" || value === "previous") return -1;
|
|
1755
|
+
if (value === "next" || value === "coming" || value === "upcoming") return 1;
|
|
1756
|
+
return 0;
|
|
1757
|
+
};
|
|
1758
|
+
const relativeDirectionName = (direction) => {
|
|
1759
|
+
if (direction < 0) return "last";
|
|
1760
|
+
if (direction > 0) return "next";
|
|
1761
|
+
return "this";
|
|
1762
|
+
};
|
|
1763
|
+
const quarterNumber = (value) => {
|
|
1764
|
+
if (value.startsWith("q")) return Number(value.slice(1));
|
|
1765
|
+
const index = quarterNames.findIndex((name) => name === value);
|
|
1766
|
+
return index === -1 ? void 0 : index + 1;
|
|
1767
|
+
};
|
|
1768
|
+
const parseQuarter = (input) => {
|
|
1769
|
+
const fixed = String$1.match(/^(q[1-4])(?: of)? (\d{4})$/u)(input);
|
|
1770
|
+
const reversed = String$1.match(/^(\d{4}) (q[1-4])$/u)(input);
|
|
1771
|
+
const namedFixed = String$1.match(/^(first|second|third|fourth) quarter(?: of)? (\d{4})$/u)(input);
|
|
1772
|
+
const relative = String$1.match(/^(q[1-4])(?: of)? (last|this|next) year$/u)(input);
|
|
1773
|
+
const namedRelative = String$1.match(/^(first|second|third|fourth) quarter(?: of)? (last|this|next) year$/u)(input);
|
|
1774
|
+
const standalone = String$1.match(/^(q[1-4])$/u)(input);
|
|
1775
|
+
const namedStandalone = String$1.match(/^(first|second|third|fourth) quarter$/u)(input);
|
|
1776
|
+
if (Option.isSome(fixed) || Option.isSome(reversed) || Option.isSome(namedFixed)) {
|
|
1777
|
+
const match = [
|
|
1778
|
+
fixed,
|
|
1779
|
+
reversed,
|
|
1780
|
+
namedFixed
|
|
1781
|
+
].find(Option.isSome)?.value;
|
|
1782
|
+
if (match === void 0) return Option.none();
|
|
1783
|
+
const quarterText = Option.isSome(reversed) ? match[2] : match[1];
|
|
1784
|
+
const yearText = Option.isSome(reversed) ? match[1] : match[2];
|
|
1785
|
+
const quarter = quarterNumber(quarterText);
|
|
1786
|
+
const year = validYear(yearText);
|
|
1787
|
+
if (quarter !== void 0 && year !== void 0) return Option.some(fixedQuarterPeriod(year, quarter, `Q${quarter} ${year}`));
|
|
1788
|
+
}
|
|
1789
|
+
if (Option.isSome(relative) || Option.isSome(namedRelative)) {
|
|
1790
|
+
const match = [relative, namedRelative].find(Option.isSome)?.value;
|
|
1791
|
+
if (match === void 0) return Option.none();
|
|
1792
|
+
const quarter = quarterNumber(match[1]);
|
|
1793
|
+
if (quarter !== void 0) {
|
|
1794
|
+
const direction = relativeDirection(match[2]);
|
|
1795
|
+
return Option.some(quarterOfRelativeYear(quarter, direction, `Q${quarter} of ${match[2]} year`));
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
const match = [standalone, namedStandalone].find(Option.isSome)?.value;
|
|
1799
|
+
if (match === void 0) return Option.none();
|
|
1800
|
+
const quarter = quarterNumber(match[1]);
|
|
1801
|
+
return quarter === void 0 ? Option.none() : Option.some(quarterOfRelativeYear(quarter, 0, `Q${quarter}`));
|
|
1802
|
+
};
|
|
1803
|
+
const parseNamedDate = (input) => {
|
|
1804
|
+
const dayFirst = String$1.match(/^([0-3]?\d)(?:st|nd|rd|th)? ([a-z]+\.?) (\d{4})$/u)(input);
|
|
1805
|
+
if (Option.isSome(dayFirst)) return namedDatePeriod(dayFirst.value[3], dayFirst.value[2], dayFirst.value[1], monthNumber, (day, month, year) => `${day} ${title(months[month - 1])} ${year}`);
|
|
1806
|
+
const monthFirst = String$1.match(/^([a-z]+\.?) ([0-3]?\d)(?:st|nd|rd|th)?,? (\d{4})$/u)(input);
|
|
1807
|
+
if (Option.isSome(monthFirst)) return namedDatePeriod(monthFirst.value[3], monthFirst.value[1], monthFirst.value[2], monthNumber, (day, month, year) => `${day} ${title(months[month - 1])} ${year}`);
|
|
1808
|
+
return Option.none();
|
|
1809
|
+
};
|
|
1810
|
+
const parseBasePeriod = (input) => {
|
|
1811
|
+
if (isIsoDate(input) && input !== "9999-12-31") return Option.some(fixedDatePeriod(input, input));
|
|
1812
|
+
const namedDate = parseNamedDate(input);
|
|
1813
|
+
if (Option.isSome(namedDate)) return namedDate;
|
|
1814
|
+
const quarter = parseQuarter(input);
|
|
1815
|
+
if (Option.isSome(quarter)) return quarter;
|
|
1816
|
+
const yearMatch = String$1.match(/^(?:(?:the )?(?:calendar )?year )?(\d{4})$/u)(input);
|
|
1817
|
+
if (Option.isSome(yearMatch)) {
|
|
1818
|
+
const year = validYear(yearMatch.value[1]);
|
|
1819
|
+
if (year !== void 0) return Option.some(fixedYearPeriod(year, String(year)));
|
|
1820
|
+
}
|
|
1821
|
+
const monthYear = String$1.match(/^([a-z]+\.?) (\d{4})$/u)(input);
|
|
1822
|
+
if (Option.isSome(monthYear)) {
|
|
1823
|
+
const month = monthNumber(monthYear.value[1]);
|
|
1824
|
+
const year = validYear(monthYear.value[2]);
|
|
1825
|
+
if (month !== void 0 && year !== void 0) return Option.some(fixedMonthPeriod(year, month, `${title(months[month - 1])} ${year}`));
|
|
1826
|
+
}
|
|
1827
|
+
const relativeMonth = String$1.match(/^([a-z]+\.?) (?:of )?(last|this|next) year$/u)(input);
|
|
1828
|
+
if (Option.isSome(relativeMonth)) {
|
|
1829
|
+
const month = monthNumber(relativeMonth.value[1]);
|
|
1830
|
+
const direction = relativeDirection(relativeMonth.value[2]);
|
|
1831
|
+
if (month !== void 0) return Option.some(monthOfRelativeYear(month, direction, `${title(months[month - 1])} of ${relativeMonth.value[2]} year`));
|
|
1832
|
+
}
|
|
1833
|
+
const standaloneMonth = monthNumber(input);
|
|
1834
|
+
if (standaloneMonth !== void 0) return Option.some(monthOfRelativeYear(standaloneMonth, 0, title(months[standaloneMonth - 1])));
|
|
1835
|
+
if (input === "today") return Option.some(relativePeriod("day", 0, "today"));
|
|
1836
|
+
if (input === "yesterday") return Option.some(relativePeriod("day", -1, "yesterday"));
|
|
1837
|
+
if (input === "tomorrow") return Option.some(relativePeriod("day", 1, "tomorrow"));
|
|
1838
|
+
if (input === "weekend" || input === "the weekend" || input === "this weekend") return Option.some(relativeWeekend(0, "this weekend"));
|
|
1839
|
+
if (input === "last weekend" || input === "previous weekend") return Option.some(relativeWeekend(-1, "last weekend"));
|
|
1840
|
+
if (input === "next weekend" || input === "coming weekend") return Option.some(relativeWeekend(1, "next weekend"));
|
|
1841
|
+
if (input === "the weekend before last") return Option.some(relativeWeekend(-2, "the weekend before last"));
|
|
1842
|
+
if (input === "the weekend after next") return Option.some(relativeWeekend(2, "the weekend after next"));
|
|
1843
|
+
const articlePeriod = String$1.match(/^the (day|week|month|quarter|year)$/u)(input);
|
|
1844
|
+
if (Option.isSome(articlePeriod)) {
|
|
1845
|
+
const entry = units.find((unit) => unit[0] === articlePeriod.value[1]);
|
|
1846
|
+
if (entry !== void 0) return Option.some(relativePeriod(entry[1], 0, currentPeriod(entry[0])));
|
|
1847
|
+
}
|
|
1848
|
+
const outerRelative = String$1.match(/^(?:the )?(day|week|month|quarter|year) (before last|after next)$/u)(input);
|
|
1849
|
+
if (Option.isSome(outerRelative)) {
|
|
1850
|
+
const entry = units.find((unit) => unit[0] === outerRelative.value[1]);
|
|
1851
|
+
if (entry !== void 0) {
|
|
1852
|
+
const direction = outerRelative.value[2] === "before last" ? -2 : 2;
|
|
1853
|
+
return Option.some(relativePeriod(entry[1], direction, input));
|
|
1854
|
+
}
|
|
1855
|
+
}
|
|
1856
|
+
const relative = String$1.match(/^(?:the )?(last|previous|this|current|next|coming|upcoming) ([a-z]+)$/u)(input);
|
|
1857
|
+
if (Option.isSome(relative)) {
|
|
1858
|
+
const unit = units.find((entry) => entry[0] === relative.value[2])?.[1];
|
|
1859
|
+
if (unit !== void 0) {
|
|
1860
|
+
const direction = relativeDirection(relative.value[1]);
|
|
1861
|
+
const canonicalDirection = relativeDirectionName(direction);
|
|
1862
|
+
return Option.some(relativePeriod(unit, direction, `${canonicalDirection} ${relative.value[2]}`));
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1865
|
+
return Option.none();
|
|
1866
|
+
};
|
|
1867
|
+
const parsePeriod = (input) => {
|
|
1868
|
+
const edge = String$1.match(/^(?:the )?(start|beginning|end) of (.+)$/u)(input);
|
|
1869
|
+
if (Option.isSome(edge)) {
|
|
1870
|
+
const period = parseBasePeriod(edge.value[2]);
|
|
1871
|
+
if (Option.isSome(period)) {
|
|
1872
|
+
const name = edge.value[1] === "beginning" ? "start" : edge.value[1];
|
|
1873
|
+
const canonical = `${name} of ${period.value.canonical}`;
|
|
1874
|
+
return Option.some(name === "end" ? periodEndDay(period.value, canonical) : periodStartDay(period.value, canonical));
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1877
|
+
const wrapper = [
|
|
1878
|
+
"during ",
|
|
1879
|
+
"in ",
|
|
1880
|
+
"for ",
|
|
1881
|
+
"all of ",
|
|
1882
|
+
"the whole of "
|
|
1883
|
+
].find((prefix) => input.startsWith(prefix));
|
|
1884
|
+
return parseBasePeriod(wrapper === void 0 ? input : input.slice(wrapper.length));
|
|
1885
|
+
};
|
|
1886
|
+
const boundaryCandidate = (input) => openBoundaryCandidate(input, [
|
|
1887
|
+
["up to and including ", "through"],
|
|
1888
|
+
["up to including ", "through"],
|
|
1889
|
+
["since the beginning of ", "since"],
|
|
1890
|
+
["since the start of ", "since"],
|
|
1891
|
+
["from the beginning of ", "since"],
|
|
1892
|
+
["from the start of ", "since"],
|
|
1893
|
+
["starting from ", "since"],
|
|
1894
|
+
["through the end of ", "through"],
|
|
1895
|
+
["until the end of ", "through"],
|
|
1896
|
+
["after the end of ", "after"],
|
|
1897
|
+
["until the start of ", "before"],
|
|
1898
|
+
["before the beginning of ", "before"],
|
|
1899
|
+
["before the start of ", "before"],
|
|
1900
|
+
["before beginning of ", "before"],
|
|
1901
|
+
["before start of ", "before"],
|
|
1902
|
+
["starting ", "since"],
|
|
1903
|
+
["through ", "through"],
|
|
1904
|
+
["before ", "before"],
|
|
1905
|
+
["since ", "since"],
|
|
1906
|
+
["after ", "after"],
|
|
1907
|
+
["until ", "before"],
|
|
1908
|
+
["till ", "before"],
|
|
1909
|
+
["up to ", "before"],
|
|
1910
|
+
["from ", "since"]
|
|
1911
|
+
], parsePeriod);
|
|
1912
|
+
const countedUnit = (value, amount) => units.find((entry) => value === (amount === 1 ? entry[0] : entry[2]));
|
|
1913
|
+
const parseCalendarOffset = (input) => {
|
|
1914
|
+
const singular = String$1.match(/^(?:(?:a|one) (day|week|month|quarter|year) (ago|prior|from now)|in (?:a|one) (day|week|month|quarter|year))$/u)(input);
|
|
1915
|
+
if (Option.isSome(singular)) {
|
|
1916
|
+
const unitText = singular.value[1] ?? singular.value[3];
|
|
1917
|
+
const entry = units.find((unit) => unit[0] === unitText);
|
|
1918
|
+
if (entry !== void 0) {
|
|
1919
|
+
const isPast = singular.value[2] === "ago" || singular.value[2] === "prior";
|
|
1920
|
+
const direction = isPast ? -1 : 1;
|
|
1921
|
+
const canonical = isPast ? `1 ${entry[0]} ago` : `in 1 ${entry[0]}`;
|
|
1922
|
+
return Option.some(candidate(periodRange(relativePeriod(entry[1], direction, canonical)), canonical));
|
|
1923
|
+
}
|
|
1924
|
+
}
|
|
1925
|
+
const past = String$1.match(/^([1-9]\d*) (day|days|week|weeks|month|months|quarter|quarters|year|years) (?:ago|prior)$/u)(input);
|
|
1926
|
+
const match = [
|
|
1927
|
+
past,
|
|
1928
|
+
String$1.match(/^in ([1-9]\d*) (day|days|week|weeks|month|months|quarter|quarters|year|years)$/u)(input),
|
|
1929
|
+
String$1.match(/^([1-9]\d*) (day|days|week|weeks|month|months|quarter|quarters|year|years) from now$/u)(input)
|
|
1930
|
+
].find(Option.isSome)?.value;
|
|
1931
|
+
if (match === void 0) return Option.none();
|
|
1932
|
+
const amount = parseTrailingCount(match[1]);
|
|
1933
|
+
if (Option.isNone(amount)) return Option.none();
|
|
1934
|
+
const entry = countedUnit(match[2], amount.value);
|
|
1935
|
+
if (entry === void 0) return Option.none();
|
|
1936
|
+
const unitText = match[2];
|
|
1937
|
+
const direction = Option.isSome(past) ? -amount.value : amount.value;
|
|
1938
|
+
const canonical = direction < 0 ? `${amount.value} ${unitText} ago` : `in ${amount.value} ${unitText}`;
|
|
1939
|
+
return Option.some(candidate(periodRange(relativePeriod(entry[1], direction, canonical)), canonical));
|
|
1940
|
+
};
|
|
1941
|
+
const parseRollingPeriod = (input) => {
|
|
1942
|
+
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);
|
|
1943
|
+
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);
|
|
1944
|
+
const match = [past, future].find(Option.isSome)?.value;
|
|
1945
|
+
if (match === void 0) return Option.none();
|
|
1946
|
+
const amount = parseTrailingCount(match[1]);
|
|
1947
|
+
if (Option.isNone(amount)) return Option.none();
|
|
1948
|
+
const entry = countedUnit(match[2], amount.value);
|
|
1949
|
+
if (entry === void 0) return Option.none();
|
|
1950
|
+
const isFuture = Option.isSome(future);
|
|
1951
|
+
const range = isFuture ? futureRange(amount.value, entry[1]) : trailingRange(amount.value, entry[1]);
|
|
1952
|
+
const direction = isFuture ? "next" : "last";
|
|
1953
|
+
const noun = amount.value === 1 ? entry[0] : entry[2];
|
|
1954
|
+
return Option.some(candidate(range, `${direction} ${amount.value} ${noun}`));
|
|
1955
|
+
};
|
|
1956
|
+
const parseEnglish = (input) => {
|
|
1957
|
+
const remaining = remainingPeriodPhrases.find((entry) => entry.phrase === input);
|
|
1958
|
+
if (remaining !== void 0) return Option.some(candidate(remainingPeriodRange(remaining.entry[1]), `rest of ${remaining.entry[0]}`));
|
|
1959
|
+
if ([
|
|
1960
|
+
"until now",
|
|
1961
|
+
"till now",
|
|
1962
|
+
"up to now",
|
|
1963
|
+
"through now"
|
|
1964
|
+
].includes(input)) return Option.some(candidate(untilNowRange(), "until now"));
|
|
1965
|
+
if ([
|
|
1966
|
+
"from now",
|
|
1967
|
+
"from now on",
|
|
1968
|
+
"starting now"
|
|
1969
|
+
].includes(input)) return Option.some(candidate(fromNowRange(), "from now"));
|
|
1970
|
+
const offset = parseCalendarOffset(input);
|
|
1971
|
+
if (Option.isSome(offset)) return offset;
|
|
1972
|
+
const rolling = parseRollingPeriod(input);
|
|
1973
|
+
if (Option.isSome(rolling)) return rolling;
|
|
1974
|
+
const toDate = toDatePhrases.find((entry) => entry.phrase === input);
|
|
1975
|
+
if (toDate !== void 0) return Option.some(candidate(periodToDateRange(toDate.entry[1]), `${toDate.entry[0]} to date`));
|
|
1976
|
+
const nowBounded = joinedNowCandidate(input, [
|
|
1977
|
+
["from ", " to now"],
|
|
1978
|
+
["from ", " until now"],
|
|
1979
|
+
["from ", " through now"],
|
|
1980
|
+
["between ", " and now"]
|
|
1981
|
+
], [
|
|
1982
|
+
"from now to ",
|
|
1983
|
+
"from now until ",
|
|
1984
|
+
"from now through ",
|
|
1985
|
+
"between now and "
|
|
1986
|
+
], parsePeriod, (period) => `from ${period} to now`, (period) => `from now to ${period}`);
|
|
1987
|
+
if (Option.isSome(nowBounded)) return nowBounded;
|
|
1988
|
+
const bounded = joinedPeriodCandidate(input, [
|
|
1989
|
+
["from ", " to and including "],
|
|
1990
|
+
["from ", " through and including "],
|
|
1991
|
+
["from ", " to "],
|
|
1992
|
+
["from ", " until "],
|
|
1993
|
+
["from ", " through "],
|
|
1994
|
+
["from ", " till "],
|
|
1995
|
+
["between ", " and "],
|
|
1996
|
+
["between ", " through "],
|
|
1997
|
+
["", " to and including "],
|
|
1998
|
+
["", " through and including "],
|
|
1999
|
+
["", " to "],
|
|
2000
|
+
["", " until "],
|
|
2001
|
+
["", " through "],
|
|
2002
|
+
["", " till "],
|
|
2003
|
+
["", " - "],
|
|
2004
|
+
["", " – "],
|
|
2005
|
+
["", " — "],
|
|
2006
|
+
["between ", "-"],
|
|
2007
|
+
["", "-"],
|
|
2008
|
+
["between ", "–"],
|
|
2009
|
+
["", "–"],
|
|
2010
|
+
["between ", "—"],
|
|
2011
|
+
["", "—"],
|
|
2012
|
+
["between ", "~"],
|
|
2013
|
+
["", "~"]
|
|
2014
|
+
], parsePeriod, (lower, upper) => `from ${lower} to ${upper}`);
|
|
2015
|
+
if (Option.isSome(bounded)) return bounded;
|
|
2016
|
+
const boundary = boundaryCandidate(input);
|
|
2017
|
+
if (Option.isSome(boundary)) return boundary;
|
|
2018
|
+
return Option.map(parsePeriod(input), (period) => candidate(periodRange(period), period.canonical));
|
|
2019
|
+
};
|
|
2020
|
+
const staticPeriods = periodsFromPhrases([
|
|
2021
|
+
"today",
|
|
2022
|
+
"yesterday",
|
|
2023
|
+
"tomorrow",
|
|
2024
|
+
"this weekend",
|
|
2025
|
+
"last weekend",
|
|
2026
|
+
"next weekend",
|
|
2027
|
+
"the weekend before last",
|
|
2028
|
+
"the weekend after next",
|
|
2029
|
+
...units.flatMap((entry) => [
|
|
2030
|
+
`this ${entry[0]}`,
|
|
2031
|
+
`last ${entry[0]}`,
|
|
2032
|
+
`next ${entry[0]}`,
|
|
2033
|
+
`start of this ${entry[0]}`,
|
|
2034
|
+
`end of this ${entry[0]}`,
|
|
2035
|
+
`start of last ${entry[0]}`,
|
|
2036
|
+
`end of last ${entry[0]}`,
|
|
2037
|
+
`start of next ${entry[0]}`,
|
|
2038
|
+
`end of next ${entry[0]}`
|
|
2039
|
+
]),
|
|
2040
|
+
...[
|
|
2041
|
+
1,
|
|
2042
|
+
2,
|
|
2043
|
+
3,
|
|
2044
|
+
4
|
|
2045
|
+
].flatMap((quarter) => [
|
|
2046
|
+
`q${quarter}`,
|
|
2047
|
+
`q${quarter} of last year`,
|
|
2048
|
+
`q${quarter} of next year`
|
|
2049
|
+
]),
|
|
2050
|
+
...months.flatMap((month) => [
|
|
2051
|
+
month,
|
|
2052
|
+
`${month} of last year`,
|
|
2053
|
+
`${month} of next year`
|
|
2054
|
+
])
|
|
2055
|
+
], parsePeriod);
|
|
2056
|
+
const renderEnglish = (range) => {
|
|
2057
|
+
const offset = calendarPeriodOffset(range);
|
|
2058
|
+
if (Option.isSome(offset) && Math.abs(offset.value.amount) > 1) {
|
|
2059
|
+
const entry = units.find((unit) => unit[1] === offset.value.unit);
|
|
2060
|
+
if (entry !== void 0) {
|
|
2061
|
+
const noun = offset.value.amount === -1 || offset.value.amount === 1 ? entry[0] : entry[2];
|
|
2062
|
+
return Option.some(offset.value.amount < 0 ? `${-offset.value.amount} ${noun} ago` : `in ${offset.value.amount} ${noun}`);
|
|
2063
|
+
}
|
|
2064
|
+
}
|
|
2065
|
+
const future = futurePeriod(range);
|
|
2066
|
+
if (Option.isSome(future)) {
|
|
2067
|
+
const entry = units.find((unit) => unit[1] === future.value.unit);
|
|
2068
|
+
if (entry !== void 0) return Option.some(`next ${future.value.amount} ${future.value.amount === 1 ? entry[0] : entry[2]}`);
|
|
2069
|
+
}
|
|
2070
|
+
const trailing = trailingPeriod(range);
|
|
2071
|
+
if (Option.isSome(trailing)) {
|
|
2072
|
+
const entry = units.find((unit) => unit[1] === trailing.value.unit);
|
|
2073
|
+
if (entry !== void 0) return Option.some(trailing.value.amount === 1 ? `1 ${entry[0]}` : `last ${trailing.value.amount} ${entry[2]}`);
|
|
2074
|
+
}
|
|
2075
|
+
const periods = [...staticPeriods, ...periodsFromPhrases([...datedPeriods(range, months), ...datedQuarterPeriods(range)], parsePeriod)];
|
|
2076
|
+
return renderPeriodRange(range, [...units.map((entry) => candidate(periodToDateRange(entry[1]), `${entry[0]} to date`)), ...units.map((entry) => candidate(remainingPeriodRange(entry[1]), `rest of ${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");
|
|
2077
|
+
};
|
|
2078
|
+
const EnglishContribution = new BaseLanguageContribution({
|
|
2079
|
+
locale: "en",
|
|
2080
|
+
vocabulary: [
|
|
2081
|
+
...months,
|
|
2082
|
+
...quarterNames,
|
|
2083
|
+
"q1",
|
|
2084
|
+
"q2",
|
|
2085
|
+
"q3",
|
|
2086
|
+
"q4",
|
|
2087
|
+
...monthAbbreviations.flatMap((aliases) => aliases),
|
|
2088
|
+
...toDatePhrases.flatMap((entry) => entry.phrase.split(" ")),
|
|
2089
|
+
...remainingPeriodPhrases.flatMap((entry) => entry.phrase.split(" ")),
|
|
2090
|
+
...units.flatMap((entry) => [entry[0], entry[2]]),
|
|
2091
|
+
"after",
|
|
2092
|
+
"ago",
|
|
2093
|
+
"all",
|
|
2094
|
+
"one",
|
|
2095
|
+
"and",
|
|
2096
|
+
"beginning",
|
|
2097
|
+
"before",
|
|
2098
|
+
"calendar",
|
|
2099
|
+
"coming",
|
|
2100
|
+
"current",
|
|
2101
|
+
"date",
|
|
2102
|
+
"during",
|
|
2103
|
+
"end",
|
|
2104
|
+
"far",
|
|
2105
|
+
"for",
|
|
2106
|
+
"from",
|
|
2107
|
+
"in",
|
|
2108
|
+
"including",
|
|
2109
|
+
"last",
|
|
2110
|
+
"next",
|
|
2111
|
+
"now",
|
|
2112
|
+
"on",
|
|
2113
|
+
"over",
|
|
2114
|
+
"of",
|
|
2115
|
+
"past",
|
|
2116
|
+
"previous",
|
|
2117
|
+
"prior",
|
|
2118
|
+
"since",
|
|
2119
|
+
"so",
|
|
2120
|
+
"start",
|
|
2121
|
+
"starting",
|
|
2122
|
+
"this",
|
|
2123
|
+
"through",
|
|
2124
|
+
"till",
|
|
2125
|
+
"whole",
|
|
2126
|
+
"to",
|
|
2127
|
+
"until",
|
|
2128
|
+
"upcoming",
|
|
2129
|
+
"within",
|
|
2130
|
+
"today",
|
|
2131
|
+
"tomorrow",
|
|
2132
|
+
"weekend",
|
|
2133
|
+
"yesterday"
|
|
2134
|
+
],
|
|
2135
|
+
normalize: normalizeNaturalText,
|
|
2136
|
+
correct: correctWhitespaceSeparatedText,
|
|
2137
|
+
parseExact: parseEnglish,
|
|
2138
|
+
render: renderEnglish
|
|
2139
|
+
});
|
|
2140
|
+
const EnglishLanguage = defineLanguagePlugin({
|
|
2141
|
+
id: "chronolizer/language-en",
|
|
2142
|
+
effect: (context) => Effect.asVoid(context.register("chronolizer/language-en", EnglishContribution))
|
|
2143
|
+
});
|
|
2144
|
+
const EnglishLanguageLayer = languagePluginsLayer([EnglishLanguage]);
|
|
2145
|
+
//#endregion
|
|
2146
|
+
//#region src/locales/default.ts
|
|
2147
|
+
const DefaultLanguageLayer = languagePluginsLayer([EnglishLanguage, GermanLanguage]);
|
|
2148
|
+
//#endregion
|
|
2149
|
+
//#region src/natural/api.ts
|
|
2150
|
+
const distinctCandidates = (candidates) => Array$1.dedupeWith(candidates, (left, right) => rangeKey(left.range) === rangeKey(right.range));
|
|
2151
|
+
const applyFuturePolicy = (candidates, allowFuture) => allowFuture === false ? Array$1.filter(candidates, (candidate) => !containsPositiveShift(candidate.range)) : candidates;
|
|
2152
|
+
const parseQuality = (hasAlternatives, hasCorrections) => {
|
|
2153
|
+
if (hasAlternatives) return "ambiguous";
|
|
2154
|
+
if (hasCorrections) return "corrected";
|
|
2155
|
+
return "exact";
|
|
2156
|
+
};
|
|
2157
|
+
const resultFromCandidates = (candidates, corrections) => {
|
|
2158
|
+
const distinct = Array$1.sortWith(distinctCandidates(candidates), (candidate) => candidate.canonical, Order.String);
|
|
2159
|
+
const selected = Array$1.headNonEmpty(distinct);
|
|
2160
|
+
const alternatives = Array$1.tailNonEmpty(distinct).map((candidate) => NaturalAlternative.make({
|
|
2161
|
+
canonical: candidate.canonical,
|
|
2162
|
+
range: candidate.range
|
|
2163
|
+
}));
|
|
2164
|
+
return NaturalParseResult.make({
|
|
2165
|
+
range: selected.range,
|
|
2166
|
+
quality: parseQuality(alternatives.length > 0, corrections.length > 0),
|
|
2167
|
+
corrections,
|
|
2168
|
+
alternatives
|
|
2169
|
+
});
|
|
2170
|
+
};
|
|
2171
|
+
const parseNatural = Effect.fn("chronolizer.parseNatural")(function* (input, options) {
|
|
2172
|
+
const language = yield* (yield* LanguageRegistry).resolve(options.locale);
|
|
2173
|
+
const normalized = language.normalize(input, language.locale);
|
|
2174
|
+
if (normalized.length === 0) return yield* new NaturalLanguageParseError({
|
|
2175
|
+
input,
|
|
2176
|
+
locale: options.locale,
|
|
2177
|
+
message: "The complete input must contain a date-range expression"
|
|
2178
|
+
});
|
|
2179
|
+
const exact = language.parseExact(normalized);
|
|
2180
|
+
const allowedExact = applyFuturePolicy(exact, options.allowFuture);
|
|
2181
|
+
if (Array$1.isReadonlyArrayNonEmpty(allowedExact)) return resultFromCandidates(allowedExact, []);
|
|
2182
|
+
if (options.typoMode !== "tolerant") {
|
|
2183
|
+
const message = exact.length > 0 && options.allowFuture === false ? "The expression contains a positive relative shift, but future ranges are disabled" : "The complete input is not a supported date-range expression";
|
|
2184
|
+
return yield* new NaturalLanguageParseError({
|
|
2185
|
+
input,
|
|
2186
|
+
locale: options.locale,
|
|
2187
|
+
message
|
|
2188
|
+
});
|
|
2189
|
+
}
|
|
2190
|
+
const corrected = language.correct?.(normalized, language.vocabulary) ?? [];
|
|
2191
|
+
const parsedCorrections = Array$1.filterMap(corrected, (correction) => {
|
|
2192
|
+
const candidates = applyFuturePolicy(language.parseExact(correction.text), options.allowFuture);
|
|
2193
|
+
return Array$1.isReadonlyArrayNonEmpty(candidates) ? Result.succeed({
|
|
2194
|
+
correction,
|
|
2195
|
+
candidates
|
|
2196
|
+
}) : Result.failVoid;
|
|
2197
|
+
});
|
|
2198
|
+
const successful = Array$1.sortWith(parsedCorrections, (entry) => entry.correction.cost, Order.Number);
|
|
2199
|
+
if (!Array$1.isReadonlyArrayNonEmpty(successful)) return yield* new NaturalLanguageParseError({
|
|
2200
|
+
input,
|
|
2201
|
+
locale: options.locale,
|
|
2202
|
+
message: "No conservative typo correction produced a complete expression"
|
|
2203
|
+
});
|
|
2204
|
+
const first = Array$1.headNonEmpty(successful);
|
|
2205
|
+
const best = Array$1.prepend(Array$1.filter(Array$1.tailNonEmpty(successful), (entry) => entry.correction.cost === first.correction.cost), first);
|
|
2206
|
+
return resultFromCandidates(Array$1.flatMap(best, (entry) => entry.candidates), first.correction.corrections);
|
|
2207
|
+
});
|
|
2208
|
+
const formatNatural = Effect.fn("chronolizer.formatNatural")(function* (range, options) {
|
|
2209
|
+
const rendered = (yield* (yield* LanguageRegistry).resolve(options.locale)).render(range);
|
|
2210
|
+
if (Option.isSome(rendered)) return rendered.value;
|
|
2211
|
+
return yield* new NaturalLanguageRenderError({
|
|
2212
|
+
locale: options.locale,
|
|
2213
|
+
message: "The range has no canonical natural-language form in this locale"
|
|
2214
|
+
});
|
|
2215
|
+
});
|
|
2216
|
+
//#endregion
|
|
2217
|
+
//#region src/resolve/schema.ts
|
|
2218
|
+
const ResolvedGreaterThan = Schema.TaggedStruct("GreaterThan", { value: Schema.DateTimeZoned });
|
|
2219
|
+
const ResolvedGreaterThanOrEqual = Schema.TaggedStruct("GreaterThanOrEqual", { value: Schema.DateTimeZoned });
|
|
2220
|
+
const ResolvedLessThan = Schema.TaggedStruct("LessThan", { value: Schema.DateTimeZoned });
|
|
2221
|
+
const ResolvedLessThanOrEqual = Schema.TaggedStruct("LessThanOrEqual", { value: Schema.DateTimeZoned });
|
|
2222
|
+
const ResolvedLowerBound = Schema.Union([ResolvedGreaterThan, ResolvedGreaterThanOrEqual]);
|
|
2223
|
+
const ResolvedUpperBound = Schema.Union([ResolvedLessThan, ResolvedLessThanOrEqual]);
|
|
2224
|
+
const ResolvedBoundedDateRange = Schema.TaggedStruct("ResolvedDateRange", {
|
|
2225
|
+
lower: ResolvedLowerBound,
|
|
2226
|
+
upper: ResolvedUpperBound
|
|
2227
|
+
});
|
|
2228
|
+
const ResolvedLowerOpenDateRange = Schema.TaggedStruct("ResolvedDateRange", {
|
|
2229
|
+
lower: ResolvedLowerBound,
|
|
2230
|
+
upper: Schema.optionalKey(Schema.Never)
|
|
2231
|
+
});
|
|
2232
|
+
const ResolvedUpperOpenDateRange = Schema.TaggedStruct("ResolvedDateRange", {
|
|
2233
|
+
lower: Schema.optionalKey(Schema.Never),
|
|
2234
|
+
upper: ResolvedUpperBound
|
|
2235
|
+
});
|
|
2236
|
+
const ResolvedDateRange = Schema.Union([
|
|
2237
|
+
ResolvedBoundedDateRange,
|
|
2238
|
+
ResolvedLowerOpenDateRange,
|
|
2239
|
+
ResolvedUpperOpenDateRange
|
|
2240
|
+
]).annotate({ identifier: "ResolvedDateRange" });
|
|
2241
|
+
var ResolutionError = class extends Schema.TaggedError()("ResolutionError", { message: Schema.String }) {};
|
|
2242
|
+
//#endregion
|
|
2243
|
+
//#region src/resolve/resolve.ts
|
|
2244
|
+
const shiftDateTime = (value, amount, unit) => {
|
|
2245
|
+
switch (unit) {
|
|
2246
|
+
case "day": return DateTime.add(value, { days: amount });
|
|
2247
|
+
case "week": return DateTime.add(value, { weeks: amount });
|
|
2248
|
+
case "month": return DateTime.add(value, { months: amount });
|
|
2249
|
+
case "quarter": return DateTime.add(value, { months: amount * 3 });
|
|
2250
|
+
case "year": return DateTime.add(value, { years: amount });
|
|
2251
|
+
}
|
|
2252
|
+
};
|
|
2253
|
+
const startOfDateTime = (value, unit) => {
|
|
2254
|
+
if (unit === "quarter") {
|
|
2255
|
+
const month = DateTime.getPart(value, "month");
|
|
2256
|
+
const quarterMonth = Math.floor((month - 1) / 3) * 3 + 1;
|
|
2257
|
+
return DateTime.startOf(DateTime.setParts(value, { month: quarterMonth }), "month");
|
|
2258
|
+
}
|
|
2259
|
+
return DateTime.startOf(value, unit, { weekStartsOn: 1 });
|
|
2260
|
+
};
|
|
2261
|
+
const literalInZone = (value, zone) => {
|
|
2262
|
+
const zoned = DateTime.makeZoned({
|
|
2263
|
+
year: Number(value.slice(0, 4)),
|
|
2264
|
+
month: Number(value.slice(5, 7)),
|
|
2265
|
+
day: Number(value.slice(8, 10))
|
|
2266
|
+
}, {
|
|
2267
|
+
timeZone: zone,
|
|
2268
|
+
adjustForTimeZone: true
|
|
2269
|
+
});
|
|
2270
|
+
return Option.match(zoned, {
|
|
2271
|
+
onNone: () => Effect.fail(new ResolutionError({ message: `Cannot resolve the date ${value}` })),
|
|
2272
|
+
onSome: Effect.succeed
|
|
2273
|
+
});
|
|
2274
|
+
};
|
|
2275
|
+
const evaluateInstant = (expression, reference, zone) => foldInstant(expression, {
|
|
2276
|
+
now: () => Effect.succeed(reference),
|
|
2277
|
+
dateLiteral: (value) => literalInZone(value, zone),
|
|
2278
|
+
shift: (base, amount, unit) => Effect.map(base, (value) => shiftDateTime(value, amount, unit)),
|
|
2279
|
+
startOf: (base, unit) => Effect.map(base, (value) => startOfDateTime(value, unit))
|
|
2280
|
+
});
|
|
2281
|
+
const resolveLower = (bound, reference, zone) => Match.valueTags(bound, {
|
|
2282
|
+
GreaterThan: (value) => Effect.map(evaluateInstant(value.value, reference, zone), (resolved) => ResolvedGreaterThan.make({ value: resolved })),
|
|
2283
|
+
GreaterThanOrEqual: (value) => Effect.map(evaluateInstant(value.value, reference, zone), (resolved) => ResolvedGreaterThanOrEqual.make({ value: resolved }))
|
|
2284
|
+
});
|
|
2285
|
+
const resolveUpper = (bound, reference, zone) => Match.valueTags(bound, {
|
|
2286
|
+
LessThan: (value) => Effect.map(evaluateInstant(value.value, reference, zone), (resolved) => ResolvedLessThan.make({ value: resolved })),
|
|
2287
|
+
LessThanOrEqual: (value) => Effect.map(evaluateInstant(value.value, reference, zone), (resolved) => ResolvedLessThanOrEqual.make({ value: resolved }))
|
|
2288
|
+
});
|
|
2289
|
+
const resolve = Effect.fn("chronolizer.resolve")(function* (range) {
|
|
2290
|
+
const zone = yield* DateTime.CurrentTimeZone;
|
|
2291
|
+
const reference = yield* DateTime.nowInCurrentZone;
|
|
2292
|
+
if (range.lower !== void 0 && range.upper !== void 0) {
|
|
2293
|
+
const lower = yield* resolveLower(range.lower, reference, zone);
|
|
2294
|
+
const upper = yield* resolveUpper(range.upper, reference, zone);
|
|
2295
|
+
if (DateTime.toEpochMillis(lower.value) >= DateTime.toEpochMillis(upper.value)) return yield* new ResolutionError({ message: "The lower range endpoint must be before the upper endpoint" });
|
|
2296
|
+
return ResolvedDateRange.make({
|
|
2297
|
+
lower,
|
|
2298
|
+
upper
|
|
2299
|
+
});
|
|
2300
|
+
}
|
|
2301
|
+
if (range.lower !== void 0) return ResolvedDateRange.make({ lower: yield* resolveLower(range.lower, reference, zone) });
|
|
2302
|
+
return ResolvedDateRange.make({ upper: yield* resolveUpper(range.upper, reference, zone) });
|
|
2303
|
+
});
|
|
2304
|
+
//#endregion
|
|
2305
|
+
export { AmbiguousNaturalLanguageError, BaseLanguageContribution, BaseLanguageMetadata, Correction, DateExpressionString, DateFilter, DateLiteral, DateRangeExpr, DateRangeFromFilter, DefaultLanguageLayer, EnglishContribution, EnglishLanguage, EnglishLanguageLayer, FilterExpressionParseError, GermanContribution, GermanLanguage, GermanLanguageLayer, GreaterThan, GreaterThanOrEqual, InstantExpr, InstantExpressionFromString, InvalidDateFilterError, IsoDate, LanguageConflictError, LanguageContributionMetadata, LanguageExtensionContribution, LanguageExtensionMetadata, LanguageRegistrationError, LanguageRegistry, LanguageRegistryLayer, LessThan, LessThanOrEqual, Locale, LowerBound, NaturalAlternative, NaturalCandidate, NaturalCorrectionCandidate, NaturalLanguageParseError, NaturalLanguageRenderError, NaturalParseResult, Now, ParseQuality, ResolutionError, ResolvedDateRange, ResolvedGreaterThan, ResolvedGreaterThanOrEqual, ResolvedLessThan, ResolvedLessThanOrEqual, ResolvedLowerBound, ResolvedUpperBound, Shift, StartOf, Unit, UnsupportedLocaleError, UpperBound, boundedRange, canonicalBaseLocale, completePeriod, containsPositiveShift, correctWhitespaceSeparatedText, dateLiteral, daysInMonth, defineLanguagePlugin, foldInstant, formatFilter, formatInstantExpression, formatNatural, greaterThan, greaterThanOrEqual, isIsoDate, languagePluginsLayer, lessThan, lessThanOrEqual, lowerOpenRange, naturalWords, normalizeInstant, normalizeNaturalText, normalizeRange, now, parseFilter, parseInstantExpression, parseNatural, rangeKey, resolve, shift, startOf, upperOpenRange };
|