chronolizer 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +169 -167
- package/dist/ast/constructors.d.mts +3 -8
- package/dist/ast/fold.d.mts +2 -1
- package/dist/ast/fold.mjs +38 -31
- package/dist/ast/normalize.mjs +4 -2
- package/dist/ast/schemas.d.mts +18 -25
- package/dist/ast/schemas.mjs +8 -17
- package/dist/filter/expression.mjs +3 -20
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +2 -2
- package/dist/language/model.d.mts +33 -33
- package/dist/language/model.mjs +4 -7
- package/dist/language/registry.mjs +3 -21
- package/dist/locales/cs.mjs +70 -17
- package/dist/locales/de.mjs +237 -26
- package/dist/locales/en.mjs +69 -21
- package/dist/locales/es.mjs +325 -77
- package/dist/locales/fr.mjs +215 -51
- package/dist/locales/nl.mjs +189 -50
- package/dist/locales/pl.mjs +295 -121
- package/dist/locales/shared.mjs +129 -23
- package/dist/locales/tr.mjs +86 -21
- package/dist/natural/parse.d.mts +1 -1
- package/dist/natural/policy.mjs +2 -2
- package/dist/resolve/resolve.mjs +1 -9
- package/package.json +1 -1
package/dist/locales/shared.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { InstantExpr, Unit, daysInMonth, isIsoDate } from "../ast/schemas.mjs";
|
|
1
|
+
import { GreaterThanOrEqual, InstantExpr, LessThan, LessThanOrEqual, Now, Shift, StartOf, Unit, daysInMonth, isIsoDate } from "../ast/schemas.mjs";
|
|
2
2
|
import { boundedRange, dateLiteral, greaterThanOrEqual, lessThan, lessThanOrEqual, lowerOpenRange, now, shift, startOf, upperOpenRange } from "../ast/constructors.mjs";
|
|
3
3
|
import { formatInstantExpression } from "../filter/expression.mjs";
|
|
4
4
|
import { formatFilter, rangeKey } from "../filter/codec.mjs";
|
|
5
5
|
import { NaturalCandidate } from "../language/model.mjs";
|
|
6
|
-
import { Option, Schema, String as String$1 } from "effect";
|
|
6
|
+
import { Match, Option, RegExp as RegExp$1, Schema, String as String$1 } from "effect";
|
|
7
7
|
//#region src/locales/shared.ts
|
|
8
8
|
const textAt = (values, index) => values[index] ?? "";
|
|
9
9
|
const Period = Schema.Struct({
|
|
@@ -11,6 +11,12 @@ const Period = Schema.Struct({
|
|
|
11
11
|
end: InstantExpr,
|
|
12
12
|
canonical: Schema.String
|
|
13
13
|
});
|
|
14
|
+
const isGreaterThanOrEqual = Schema.is(GreaterThanOrEqual);
|
|
15
|
+
const isLessThan = Schema.is(LessThan);
|
|
16
|
+
const isLessThanOrEqual = Schema.is(LessThanOrEqual);
|
|
17
|
+
const isNow = Schema.is(Now);
|
|
18
|
+
const isShift = Schema.is(Shift);
|
|
19
|
+
const isStartOf = Schema.is(StartOf);
|
|
14
20
|
const periodRange = (period) => boundedRange(greaterThanOrEqual(period.start), lessThan(period.end));
|
|
15
21
|
const periodToDateRange = (unit) => boundedRange(greaterThanOrEqual(startOf(now(), unit)), lessThanOrEqual(now()));
|
|
16
22
|
const fromNowRange = () => lowerOpenRange(greaterThanOrEqual(now()));
|
|
@@ -29,6 +35,20 @@ const periodEndDay = (period, canonical) => Period.make({
|
|
|
29
35
|
end: period.end,
|
|
30
36
|
canonical
|
|
31
37
|
});
|
|
38
|
+
const periodDay = (period, day, canonical) => {
|
|
39
|
+
if (!Number.isInteger(day) || day < 1 || day > 28) return Option.none();
|
|
40
|
+
const start = day === 1 ? period.start : shift(period.start, day - 1, "day");
|
|
41
|
+
return Option.some(Period.make({
|
|
42
|
+
start,
|
|
43
|
+
end: shift(start, 1, "day"),
|
|
44
|
+
canonical
|
|
45
|
+
}));
|
|
46
|
+
};
|
|
47
|
+
const periodPreviousDay = (period, canonical) => Period.make({
|
|
48
|
+
start: shift(period.start, -1, "day"),
|
|
49
|
+
end: period.start,
|
|
50
|
+
canonical
|
|
51
|
+
});
|
|
32
52
|
const relativeWeekend = (direction, canonical) => {
|
|
33
53
|
const weekBase = direction === 0 ? now() : shift(now(), direction, "week");
|
|
34
54
|
const weekStart = startOf(weekBase, "week");
|
|
@@ -43,11 +63,7 @@ const TrailingCount = Schema.Int.check(Schema.isBetween({
|
|
|
43
63
|
minimum: 1,
|
|
44
64
|
maximum: Number.MAX_SAFE_INTEGER
|
|
45
65
|
}));
|
|
46
|
-
const
|
|
47
|
-
amount: TrailingCount,
|
|
48
|
-
unit: Unit
|
|
49
|
-
});
|
|
50
|
-
const FuturePeriod = Schema.Struct({
|
|
66
|
+
const CountedPeriod = Schema.Struct({
|
|
51
67
|
amount: TrailingCount,
|
|
52
68
|
unit: Unit
|
|
53
69
|
});
|
|
@@ -66,15 +82,17 @@ const parseTrailingCount = (value) => {
|
|
|
66
82
|
const trailingRange = (amount, unit) => boundedRange(greaterThanOrEqual(shift(now(), -amount, unit)), lessThanOrEqual(now()));
|
|
67
83
|
const futureRange = (amount, unit) => boundedRange(greaterThanOrEqual(now()), lessThanOrEqual(shift(now(), amount, unit)));
|
|
68
84
|
const trailingPeriod = (range) => {
|
|
69
|
-
if (range.lower
|
|
70
|
-
return Option.
|
|
85
|
+
if (!isGreaterThanOrEqual(range.lower) || !isLessThanOrEqual(range.upper)) return Option.none();
|
|
86
|
+
if (!isShift(range.lower.value) || range.lower.value.amount >= 0 || !isNow(range.lower.value.base) || !isNow(range.upper.value)) return Option.none();
|
|
87
|
+
return Option.some(CountedPeriod.make({
|
|
71
88
|
amount: -range.lower.value.amount,
|
|
72
89
|
unit: range.lower.value.unit
|
|
73
90
|
}));
|
|
74
91
|
};
|
|
75
92
|
const futurePeriod = (range) => {
|
|
76
|
-
if (range.lower
|
|
77
|
-
return Option.
|
|
93
|
+
if (!isGreaterThanOrEqual(range.lower) || !isLessThanOrEqual(range.upper)) return Option.none();
|
|
94
|
+
if (!isNow(range.lower.value) || !isShift(range.upper.value) || range.upper.value.amount <= 0 || !isNow(range.upper.value.base)) return Option.none();
|
|
95
|
+
return Option.some(CountedPeriod.make({
|
|
78
96
|
amount: range.upper.value.amount,
|
|
79
97
|
unit: range.upper.value.unit
|
|
80
98
|
}));
|
|
@@ -100,6 +118,50 @@ const previousDay = (year, month, day) => {
|
|
|
100
118
|
if (month > 1) return isoDate(year, month - 1, daysInMonth(year, month - 1));
|
|
101
119
|
return isoDate(year - 1, 12, 31);
|
|
102
120
|
};
|
|
121
|
+
const absoluteDateProfiles = /* @__PURE__ */ new Map();
|
|
122
|
+
const absoluteDateProfile = (locale) => {
|
|
123
|
+
const cached = absoluteDateProfiles.get(locale);
|
|
124
|
+
if (cached !== void 0) return cached;
|
|
125
|
+
const formatter = new Intl.DateTimeFormat(locale, {
|
|
126
|
+
day: "numeric",
|
|
127
|
+
month: "numeric",
|
|
128
|
+
year: "numeric",
|
|
129
|
+
numberingSystem: "latn",
|
|
130
|
+
timeZone: "UTC"
|
|
131
|
+
});
|
|
132
|
+
const parts = [];
|
|
133
|
+
const pattern = formatter.formatToParts(/* @__PURE__ */ new Date("2006-11-22T00:00:00.000Z")).map((part) => {
|
|
134
|
+
if (part.type === "day" || part.type === "month" || part.type === "year") {
|
|
135
|
+
parts.push(part.type);
|
|
136
|
+
return part.type === "year" ? "([0-9]{4})" : "([0-9]{1,2})";
|
|
137
|
+
}
|
|
138
|
+
return RegExp$1.escape(part.value);
|
|
139
|
+
}).join("");
|
|
140
|
+
const profile = {
|
|
141
|
+
formatter,
|
|
142
|
+
pattern: new RegExp(`^${pattern}$`, "u"),
|
|
143
|
+
parts
|
|
144
|
+
};
|
|
145
|
+
absoluteDateProfiles.set(locale, profile);
|
|
146
|
+
return profile;
|
|
147
|
+
};
|
|
148
|
+
const formatAbsoluteDate = (value, locale) => absoluteDateProfile(locale).formatter.format(/* @__PURE__ */ new Date(`${value}T00:00:00.000Z`));
|
|
149
|
+
const parseLocalizedAbsoluteDate = (input, locale) => {
|
|
150
|
+
const profile = absoluteDateProfile(locale);
|
|
151
|
+
const match = String$1.match(profile.pattern)(input);
|
|
152
|
+
if (Option.isNone(match)) return Option.none();
|
|
153
|
+
let day = "";
|
|
154
|
+
let month = "";
|
|
155
|
+
let year = "";
|
|
156
|
+
for (const [index, part] of profile.parts.entries()) {
|
|
157
|
+
const value = textAt(match.value, index + 1);
|
|
158
|
+
if (part === "day") day = value;
|
|
159
|
+
if (part === "month") month = value;
|
|
160
|
+
if (part === "year") year = value;
|
|
161
|
+
}
|
|
162
|
+
const value = isoDate(Number(year), Number(month), Number(day));
|
|
163
|
+
return isIsoDate(value) ? Option.some(value) : Option.none();
|
|
164
|
+
};
|
|
103
165
|
const fixedDatePeriod = (value, canonical) => {
|
|
104
166
|
const year = Number(value.slice(0, 4));
|
|
105
167
|
const month = Number(value.slice(5, 7));
|
|
@@ -110,6 +172,9 @@ const fixedDatePeriod = (value, canonical) => {
|
|
|
110
172
|
canonical
|
|
111
173
|
});
|
|
112
174
|
};
|
|
175
|
+
const absoluteDatePeriod = (input, locale) => {
|
|
176
|
+
return (isIsoDate(input) ? Option.some(input) : parseLocalizedAbsoluteDate(input, locale)).pipe(Option.filter((date) => date !== "9999-12-31"), Option.map((date) => fixedDatePeriod(date, formatAbsoluteDate(date, locale))));
|
|
177
|
+
};
|
|
113
178
|
const namedDatePeriod = (yearText, monthText, dayText, monthNumber, canonical) => {
|
|
114
179
|
const year = validYear(yearText);
|
|
115
180
|
const month = monthNumber(monthText);
|
|
@@ -119,6 +184,51 @@ const namedDatePeriod = (yearText, monthText, dayText, monthNumber, canonical) =
|
|
|
119
184
|
if (!isIsoDate(value) || value === "9999-12-31") return Option.none();
|
|
120
185
|
return Option.some(fixedDatePeriod(value, canonical(day, month, year)));
|
|
121
186
|
};
|
|
187
|
+
const minimumDaysInMonth = (month) => daysInMonth(1, month);
|
|
188
|
+
const currentYearDatePeriod = (month, day, canonical) => {
|
|
189
|
+
const yearStart = startOf(now(), "year");
|
|
190
|
+
const monthStart = month === 1 ? yearStart : shift(yearStart, month - 1, "month");
|
|
191
|
+
const start = day === 1 ? monthStart : shift(monthStart, day - 1, "day");
|
|
192
|
+
return Period.make({
|
|
193
|
+
start,
|
|
194
|
+
end: shift(start, 1, "day"),
|
|
195
|
+
canonical
|
|
196
|
+
});
|
|
197
|
+
};
|
|
198
|
+
const namedCurrentYearDatePeriod = (monthText, dayText, monthNumber, canonical) => {
|
|
199
|
+
const month = monthNumber(monthText);
|
|
200
|
+
const day = Number(dayText);
|
|
201
|
+
if (month === void 0 || !Number.isInteger(day) || day < 1 || day > minimumDaysInMonth(month)) return Option.none();
|
|
202
|
+
return Option.some(currentYearDatePeriod(month, day, canonical(day, month)));
|
|
203
|
+
};
|
|
204
|
+
const currentYearDate = (expression) => {
|
|
205
|
+
const match = String$1.match(/^now\/y(?:\+([1-9]\d*)M)?(?:\+([1-9]\d*)d)?$/u)(expression);
|
|
206
|
+
if (Option.isNone(match)) return Option.none();
|
|
207
|
+
const month = Number(textAt(match.value, 1) || "0") + 1;
|
|
208
|
+
const offset = Number(textAt(match.value, 2) || "0");
|
|
209
|
+
return month >= 1 && month <= 12 ? Option.some([month, offset]) : Option.none();
|
|
210
|
+
};
|
|
211
|
+
const currentYearDatePeriods = (range, canonical) => {
|
|
212
|
+
const filter = formatFilter(range);
|
|
213
|
+
const expressions = [
|
|
214
|
+
filter.gt,
|
|
215
|
+
filter.gte,
|
|
216
|
+
filter.lt,
|
|
217
|
+
filter.lte
|
|
218
|
+
];
|
|
219
|
+
const dates = /* @__PURE__ */ new Map();
|
|
220
|
+
for (const expression of expressions) {
|
|
221
|
+
if (expression === void 0) continue;
|
|
222
|
+
const date = currentYearDate(expression);
|
|
223
|
+
if (Option.isNone(date)) continue;
|
|
224
|
+
const [month, offset] = date.value;
|
|
225
|
+
for (const day of [offset, offset + 1]) {
|
|
226
|
+
if (day < 1 || day > minimumDaysInMonth(month)) continue;
|
|
227
|
+
dates.set(`${month}-${day}`, [month, day]);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return [...dates.values()].map(([month, day]) => currentYearDatePeriod(month, day, canonical(day, month)));
|
|
231
|
+
};
|
|
122
232
|
const fixedMonthPeriod = (year, month, canonical) => {
|
|
123
233
|
const nextYear = month === 12 ? year + 1 : year;
|
|
124
234
|
const nextMonth = month === 12 ? 1 : month + 1;
|
|
@@ -164,10 +274,13 @@ const monthOfRelativeYear = (month, direction, canonical) => {
|
|
|
164
274
|
});
|
|
165
275
|
};
|
|
166
276
|
const calendarPeriodOffset = (range) => {
|
|
167
|
-
if (range.lower
|
|
277
|
+
if (!isGreaterThanOrEqual(range.lower) || !isLessThan(range.upper)) return Option.none();
|
|
278
|
+
const start = range.lower.value;
|
|
279
|
+
const end = range.upper.value;
|
|
280
|
+
if (!isStartOf(start) || !isShift(start.base) || start.base.amount === 0 || !isNow(start.base.base) || start.unit !== start.base.unit || !isShift(end) || end.amount !== 1 || end.unit !== start.unit || formatInstantExpression(end.base) !== formatInstantExpression(start)) return Option.none();
|
|
168
281
|
return Option.some(CalendarPeriodOffset.make({
|
|
169
|
-
amount:
|
|
170
|
-
unit:
|
|
282
|
+
amount: start.base.amount,
|
|
283
|
+
unit: start.unit
|
|
171
284
|
}));
|
|
172
285
|
};
|
|
173
286
|
const candidate = (range, canonical) => NaturalCandidate.make({
|
|
@@ -201,14 +314,7 @@ const joinedPeriodCandidate = (input, joins, parsePeriod, canonical) => {
|
|
|
201
314
|
}
|
|
202
315
|
return Option.none();
|
|
203
316
|
};
|
|
204
|
-
const periodBoundaryCandidate = (period, boundary, canonical) =>
|
|
205
|
-
switch (boundary) {
|
|
206
|
-
case "since": return candidate(lowerOpenRange(greaterThanOrEqual(period.start)), canonical);
|
|
207
|
-
case "before": return candidate(upperOpenRange(lessThan(period.start)), canonical);
|
|
208
|
-
case "through": return candidate(upperOpenRange(lessThan(period.end)), canonical);
|
|
209
|
-
case "after": return candidate(lowerOpenRange(greaterThanOrEqual(period.end)), canonical);
|
|
210
|
-
}
|
|
211
|
-
};
|
|
317
|
+
const periodBoundaryCandidate = (period, boundary, canonical) => Match.value(boundary).pipe(Match.when("since", () => candidate(lowerOpenRange(greaterThanOrEqual(period.start)), canonical)), Match.when("before", () => candidate(upperOpenRange(lessThan(period.start)), canonical)), Match.when("through", () => candidate(upperOpenRange(lessThan(period.end)), canonical)), Match.when("after", () => candidate(lowerOpenRange(greaterThanOrEqual(period.end)), canonical)), Match.exhaustive);
|
|
212
318
|
const openBoundaryCandidate = (input, boundaries, parsePeriod) => {
|
|
213
319
|
for (const boundary of boundaries) {
|
|
214
320
|
if (!input.startsWith(boundary[0])) continue;
|
|
@@ -286,4 +392,4 @@ const renderPeriodRange = (range, toDate, periods, since, before, through, after
|
|
|
286
392
|
return Option.none();
|
|
287
393
|
};
|
|
288
394
|
//#endregion
|
|
289
|
-
export { Period, calendarPeriodOffset, candidate, datedPeriods, datedQuarterPeriods, fixedDatePeriod, fixedMonthPeriod, fixedQuarterPeriod, fixedYearPeriod, fromNowRange, futurePeriod, futureRange, isoDate, joinedNowCandidate, joinedPeriodCandidate, monthOfRelativeYear, namedDatePeriod, openBoundaryCandidate, parseTrailingCount, periodBoundaryCandidate, periodEndDay, periodRange, periodStartDay, periodToDateRange, periodsFromPhrases, quarterOfRelativeYear, relativePeriod, relativeWeekend, remainingPeriodRange, renderPeriodRange, textAt, trailingPeriod, trailingRange, untilNowRange, validYear };
|
|
395
|
+
export { Period, absoluteDatePeriod, calendarPeriodOffset, candidate, currentYearDatePeriods, datedPeriods, datedQuarterPeriods, fixedDatePeriod, fixedMonthPeriod, fixedQuarterPeriod, fixedYearPeriod, formatAbsoluteDate, fromNowRange, futurePeriod, futureRange, isoDate, joinedNowCandidate, joinedPeriodCandidate, monthOfRelativeYear, namedCurrentYearDatePeriod, namedDatePeriod, openBoundaryCandidate, parseTrailingCount, periodBoundaryCandidate, periodDay, periodEndDay, periodPreviousDay, periodRange, periodStartDay, periodToDateRange, periodsFromPhrases, quarterOfRelativeYear, relativePeriod, relativeWeekend, remainingPeriodRange, renderPeriodRange, textAt, trailingPeriod, trailingRange, untilNowRange, validYear };
|
package/dist/locales/tr.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import { normalizeNaturalText } from "../natural/text.mjs";
|
|
|
4
4
|
import { defineLanguagePlugin, languagePluginsLayer } from "../language/registry.mjs";
|
|
5
5
|
import { correctWhitespaceSeparatedText } from "../natural/correction.mjs";
|
|
6
6
|
import { completeNaturalPhrases, fixedCalendarPeriodPhrases, naturalCount, prefixNaturalPhrases } from "../natural/suggestion.mjs";
|
|
7
|
-
import { calendarPeriodOffset, candidate, datedPeriods, datedQuarterPeriods, fixedDatePeriod, fixedMonthPeriod, fixedQuarterPeriod, fixedYearPeriod, fromNowRange, futurePeriod, futureRange, isoDate, joinedNowCandidate, joinedPeriodCandidate, monthOfRelativeYear, namedDatePeriod, openBoundaryCandidate, parseTrailingCount, periodBoundaryCandidate, periodEndDay, periodRange, periodStartDay, periodToDateRange, periodsFromPhrases, quarterOfRelativeYear, relativePeriod, relativeWeekend, remainingPeriodRange, renderPeriodRange, textAt, trailingPeriod, trailingRange, untilNowRange, validYear } from "./shared.mjs";
|
|
7
|
+
import { absoluteDatePeriod, calendarPeriodOffset, candidate, currentYearDatePeriods, datedPeriods, datedQuarterPeriods, fixedDatePeriod, fixedMonthPeriod, fixedQuarterPeriod, fixedYearPeriod, fromNowRange, futurePeriod, futureRange, isoDate, joinedNowCandidate, joinedPeriodCandidate, monthOfRelativeYear, namedCurrentYearDatePeriod, namedDatePeriod, openBoundaryCandidate, parseTrailingCount, periodBoundaryCandidate, periodEndDay, periodRange, periodStartDay, periodToDateRange, periodsFromPhrases, quarterOfRelativeYear, relativePeriod, relativeWeekend, remainingPeriodRange, renderPeriodRange, textAt, trailingPeriod, trailingRange, untilNowRange, validYear } from "./shared.mjs";
|
|
8
8
|
import { Effect, Option, String as String$1 } from "effect";
|
|
9
9
|
//#region src/locales/tr.ts
|
|
10
10
|
const months = [
|
|
@@ -298,17 +298,22 @@ const monthNumber = (value) => {
|
|
|
298
298
|
return short === -1 ? void 0 : short + 1;
|
|
299
299
|
};
|
|
300
300
|
const dateLabel = (day, month, year) => `${day} ${textAt(months, month - 1)} ${year}`;
|
|
301
|
+
const currentDateLabel = (day, month) => `${day} ${textAt(months, month - 1)}`;
|
|
301
302
|
const parseNamedDate = (input) => {
|
|
302
303
|
const named = String$1.match(/^([0-3]?\d)(?:\.)? ([a-zçğıöşü]+\.?)(?:,)? (\d{4})$/u)(input);
|
|
303
304
|
if (Option.isSome(named)) return namedDatePeriod(textAt(named.value, 3), textAt(named.value, 2), textAt(named.value, 1), monthNumber, dateLabel);
|
|
304
305
|
const numeric = String$1.match(/^([0-3]?\d)[./-]([01]?\d)[./-](\d{4})$/u)(input);
|
|
305
|
-
if (Option.
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
306
|
+
if (Option.isSome(numeric)) {
|
|
307
|
+
const year = validYear(textAt(numeric.value, 3));
|
|
308
|
+
const month = Number(textAt(numeric.value, 2));
|
|
309
|
+
const day = Number(textAt(numeric.value, 1));
|
|
310
|
+
if (year !== void 0 && month >= 1 && month <= 12) {
|
|
311
|
+
const value = isoDate(year, month, day);
|
|
312
|
+
if (isIsoDate(value) && value !== "9999-12-31") return Option.some(fixedDatePeriod(value, dateLabel(day, month, year)));
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
const current = String$1.match(/^([0-3]?\d)(?:\.)? ([a-zçğıöşü]+\.?)$/u)(input);
|
|
316
|
+
return Option.isSome(current) ? namedCurrentYearDatePeriod(textAt(current.value, 2), textAt(current.value, 1), monthNumber, currentDateLabel) : Option.none();
|
|
312
317
|
};
|
|
313
318
|
const quarterNumber = (value) => {
|
|
314
319
|
if ((value.startsWith("q") || value.startsWith("ç")) && value.length === 2) return Number(value.slice(1));
|
|
@@ -336,7 +341,8 @@ const parseQuarter = (input) => {
|
|
|
336
341
|
return quarter === void 0 ? Option.none() : Option.some(quarterOfRelativeYear(quarter, 0, `Ç${quarter}`));
|
|
337
342
|
};
|
|
338
343
|
const parseBasePeriod = (input) => {
|
|
339
|
-
|
|
344
|
+
const absoluteDate = absoluteDatePeriod(input, "tr");
|
|
345
|
+
if (Option.isSome(absoluteDate)) return absoluteDate;
|
|
340
346
|
const namedDate = parseNamedDate(input);
|
|
341
347
|
if (Option.isSome(namedDate)) return namedDate;
|
|
342
348
|
const quarter = parseQuarter(input);
|
|
@@ -401,7 +407,46 @@ const rollingFuturePatterns = [countedPattern("^(?:gelecek|önümüzdeki|sonraki
|
|
|
401
407
|
const rollingSincePattern = countedPattern("^([1-9]\\d*) (UNIT) boyunca$");
|
|
402
408
|
const rollingBarePattern = countedPattern("^([1-9]\\d*) (UNIT)$");
|
|
403
409
|
const firstPatternMatch = (input, patterns) => Option.firstSomeOf(patterns.map((pattern) => String$1.match(pattern)(input)));
|
|
410
|
+
const singularRollingCanonical = (entry, future) => future ? `önümüzdeki bir ${entry.singular}` : `son bir ${entry.singular}`;
|
|
411
|
+
const singularRollingPhrases = units.flatMap((entry) => [
|
|
412
|
+
{
|
|
413
|
+
phrase: `son bir ${entry.singular}`,
|
|
414
|
+
entry,
|
|
415
|
+
future: false
|
|
416
|
+
},
|
|
417
|
+
{
|
|
418
|
+
phrase: `bir ${entry.singular} boyunca`,
|
|
419
|
+
entry,
|
|
420
|
+
future: false
|
|
421
|
+
},
|
|
422
|
+
{
|
|
423
|
+
phrase: `geçtiğimiz bir ${entry.singular}`,
|
|
424
|
+
entry,
|
|
425
|
+
future: false
|
|
426
|
+
},
|
|
427
|
+
{
|
|
428
|
+
phrase: `önümüzdeki bir ${entry.singular}`,
|
|
429
|
+
entry,
|
|
430
|
+
future: true
|
|
431
|
+
},
|
|
432
|
+
{
|
|
433
|
+
phrase: `bugünden itibaren bir ${entry.singular} boyunca`,
|
|
434
|
+
entry,
|
|
435
|
+
future: true
|
|
436
|
+
}
|
|
437
|
+
]);
|
|
438
|
+
const singularCalendarOffsets = units.flatMap((entry) => [{
|
|
439
|
+
phrase: `bir ${entry.singular} önce`,
|
|
440
|
+
entry,
|
|
441
|
+
direction: -1
|
|
442
|
+
}, {
|
|
443
|
+
phrase: `bir ${entry.singular} sonra`,
|
|
444
|
+
entry,
|
|
445
|
+
direction: 1
|
|
446
|
+
}]);
|
|
404
447
|
const parseCalendarOffset = (input) => {
|
|
448
|
+
const singular = singularCalendarOffsets.find((entry) => entry.phrase === input);
|
|
449
|
+
if (singular !== void 0) return Option.some(candidate(periodRange(relativePeriod(singular.entry.unit, singular.direction, singular.phrase)), singular.phrase));
|
|
405
450
|
const past = String$1.match(calendarPastPattern)(input);
|
|
406
451
|
const future = firstPatternMatch(input, calendarFuturePatterns);
|
|
407
452
|
const match = Option.firstSomeOf([past, future]);
|
|
@@ -417,6 +462,11 @@ const parseCalendarOffset = (input) => {
|
|
|
417
462
|
return Option.some(candidate(periodRange(relativePeriod(unit, direction, canonical)), canonical));
|
|
418
463
|
};
|
|
419
464
|
const parseRollingPeriod = (input) => {
|
|
465
|
+
const singular = singularRollingPhrases.find((entry) => entry.phrase === input);
|
|
466
|
+
if (singular !== void 0) {
|
|
467
|
+
const range = singular.future ? futureRange(1, singular.entry.unit) : trailingRange(1, singular.entry.unit);
|
|
468
|
+
return Option.some(candidate(range, singularRollingCanonical(singular.entry, singular.future)));
|
|
469
|
+
}
|
|
420
470
|
const since = String$1.match(rollingSincePattern)(input);
|
|
421
471
|
const past = firstPatternMatch(input, rollingPastPatterns);
|
|
422
472
|
const bare = String$1.match(rollingBarePattern)(input);
|
|
@@ -435,9 +485,22 @@ const parseRollingPeriod = (input) => {
|
|
|
435
485
|
if (entry === void 0) return Option.none();
|
|
436
486
|
const isFuture = Option.isSome(future);
|
|
437
487
|
const range = isFuture ? futureRange(amount.value, unit) : trailingRange(amount.value, unit);
|
|
488
|
+
if (amount.value === 1) return Option.some(candidate(range, singularRollingCanonical(entry, isFuture)));
|
|
438
489
|
const modifier = isFuture ? "gelecek" : "son";
|
|
439
|
-
|
|
440
|
-
|
|
490
|
+
return Option.some(candidate(range, `${modifier} ${amount.value} ${entry.plural}`));
|
|
491
|
+
};
|
|
492
|
+
const parseElidedDateRange = (input) => {
|
|
493
|
+
const full = String$1.match(/^([0-3]?\d)(?:\.)? ([a-zçğıöşü]+\.?)'?(?:den|dan|ten|tan) ([0-3]?\d)(?:\.)? ([a-zçğıöşü]+\.?)'?(?:e|a|ye|ya) kadar$/u)(input);
|
|
494
|
+
if (Option.isSome(full)) return joinedPeriodCandidate(`${textAt(full.value, 1)} ${textAt(full.value, 2)} ile ${textAt(full.value, 3)} ${textAt(full.value, 4)}`, [["", " ile "]], parsePeriod, (lower, upper) => `${lower} ile ${upper} arası`);
|
|
495
|
+
const elided = String$1.match(/^([0-3]?\d)(?:\.)? ([a-zçğıöşü]+\.?)'?(?:den|dan|ten|tan) ([0-3]?\d)'?(?:sine|sına|üne|una|ine|ına) kadar$/u)(input);
|
|
496
|
+
const dashed = String$1.match(/^([0-3]?\d)[–—-]([0-3]?\d) ([a-zçğıöşü]+\.?)$/u)(input);
|
|
497
|
+
const match = Option.firstSomeOf([elided, dashed]);
|
|
498
|
+
if (Option.isNone(match)) return Option.none();
|
|
499
|
+
const isElided = Option.isSome(elided);
|
|
500
|
+
const lowerDay = textAt(match.value, 1);
|
|
501
|
+
const upperDay = textAt(match.value, isElided ? 3 : 2);
|
|
502
|
+
const month = textAt(match.value, isElided ? 2 : 3);
|
|
503
|
+
return joinedPeriodCandidate(`${lowerDay} ${month} ile ${upperDay} ${month}`, [["", " ile "]], parsePeriod, (lower, upper) => `${lower} ile ${upper} arası`);
|
|
441
504
|
};
|
|
442
505
|
const suffixBoundary = (input) => {
|
|
443
506
|
const boundaries = [
|
|
@@ -486,6 +549,8 @@ const parseTurkish = (input) => {
|
|
|
486
549
|
if (Option.isSome(rolling)) return rolling;
|
|
487
550
|
const toDate = toDatePhrases.find((entry) => entry.phrase === input);
|
|
488
551
|
if (toDate !== void 0) return Option.some(candidate(periodToDateRange(toDate.entry.unit), toDate.entry.toDate));
|
|
552
|
+
const elided = parseElidedDateRange(input);
|
|
553
|
+
if (Option.isSome(elided)) return elided;
|
|
489
554
|
const joinedInput = input.endsWith(" arası") ? input.slice(0, -6) : input;
|
|
490
555
|
const nowBounded = joinedNowCandidate(joinedInput, [["", " ile bugün"], ["", " ve bugün"]], ["bugün ile ", "bugün ve "], parsePeriod, (period) => `${period} ile bugün arası`, (period) => `bugün ile ${period} arası`);
|
|
491
556
|
if (Option.isSome(nowBounded)) return nowBounded;
|
|
@@ -499,7 +564,7 @@ const parseTurkish = (input) => {
|
|
|
499
564
|
if (Option.isSome(bounded)) return bounded;
|
|
500
565
|
const boundary = boundaryCandidate(input);
|
|
501
566
|
if (Option.isSome(boundary)) return boundary;
|
|
502
|
-
return Option.map(
|
|
567
|
+
return parsePeriod(input).pipe(Option.map((period) => candidate(periodRange(period), period.canonical)));
|
|
503
568
|
};
|
|
504
569
|
const staticPeriodPhrases = [
|
|
505
570
|
...periodAliases.map((entry) => entry[0]),
|
|
@@ -569,6 +634,8 @@ const countedSuggestions = (input) => {
|
|
|
569
634
|
const turkishSuggestionPhrases = [
|
|
570
635
|
...units.map((entry) => entry.toDate),
|
|
571
636
|
...units.map((entry) => entry.remaining),
|
|
637
|
+
...singularRollingPhrases.map((entry) => entry.phrase),
|
|
638
|
+
...singularCalendarOffsets.map((entry) => entry.phrase),
|
|
572
639
|
...staticPeriodPhrases,
|
|
573
640
|
...prefixNaturalPhrases(staticPeriodPhrases, boundaryPrefixes),
|
|
574
641
|
"şimdiye kadar",
|
|
@@ -596,20 +663,18 @@ const renderTurkish = (range) => {
|
|
|
596
663
|
const future = futurePeriod(range);
|
|
597
664
|
if (Option.isSome(future)) {
|
|
598
665
|
const entry = units.find((unit) => unit.unit === future.value.unit);
|
|
599
|
-
if (entry !== void 0) {
|
|
600
|
-
const noun = future.value.amount === 1 ? entry.singular : entry.plural;
|
|
601
|
-
return Option.some(`gelecek ${future.value.amount} ${noun}`);
|
|
602
|
-
}
|
|
666
|
+
if (entry !== void 0) return Option.some(future.value.amount === 1 ? singularRollingCanonical(entry, true) : `gelecek ${future.value.amount} ${entry.plural}`);
|
|
603
667
|
}
|
|
604
668
|
const trailing = trailingPeriod(range);
|
|
605
669
|
if (Option.isSome(trailing)) {
|
|
606
670
|
const entry = units.find((unit) => unit.unit === trailing.value.unit);
|
|
607
|
-
if (entry !== void 0) {
|
|
608
|
-
const noun = trailing.value.amount === 1 ? entry.singular : entry.plural;
|
|
609
|
-
return Option.some(`son ${trailing.value.amount} ${noun}`);
|
|
610
|
-
}
|
|
671
|
+
if (entry !== void 0) return Option.some(trailing.value.amount === 1 ? singularRollingCanonical(entry, false) : `son ${trailing.value.amount} ${entry.plural}`);
|
|
611
672
|
}
|
|
612
|
-
const periods = [
|
|
673
|
+
const periods = [
|
|
674
|
+
...staticPeriods,
|
|
675
|
+
...currentYearDatePeriods(range, currentDateLabel),
|
|
676
|
+
...periodsFromPhrases([...datedPeriods(range, months), ...datedQuarterPeriods(range).map((phrase) => phrase.startsWith("Q") ? `Ç${phrase.slice(1)}` : phrase)], parsePeriod)
|
|
677
|
+
];
|
|
613
678
|
return renderPeriodRange(range, [...units.map((entry) => candidate(periodToDateRange(entry.unit), entry.toDate)), ...units.map((entry) => candidate(remainingPeriodRange(entry.unit), entry.remaining))], periods, (period) => `${period} itibarıyla`, (period) => `${period} öncesi`, (period) => `${period} sonuna kadar`, (period) => `${period} sonrası`, (lower, upper) => `${lower} ile ${upper} arası`, (period) => `${period} ile bugün arası`, (period) => `bugün ile ${period} arası`, () => "şimdiye kadar", () => "bugünden itibaren");
|
|
614
679
|
};
|
|
615
680
|
const normalizeTurkish = (input, locale) => normalizeNaturalText(input, locale).replaceAll("’", "'");
|
package/dist/natural/parse.d.mts
CHANGED
|
@@ -55,7 +55,6 @@ declare const parseNatural: (input: string, options: ParseNaturalOptions) => Eff
|
|
|
55
55
|
readonly offset: number;
|
|
56
56
|
}[];
|
|
57
57
|
readonly alternatives: readonly {
|
|
58
|
-
readonly canonical: string;
|
|
59
58
|
readonly range: {
|
|
60
59
|
readonly _tag: "DateRange";
|
|
61
60
|
readonly lower: {
|
|
@@ -93,6 +92,7 @@ declare const parseNatural: (input: string, options: ParseNaturalOptions) => Eff
|
|
|
93
92
|
readonly value: InstantExpr;
|
|
94
93
|
};
|
|
95
94
|
};
|
|
95
|
+
readonly canonical: string;
|
|
96
96
|
}[];
|
|
97
97
|
}, NaturalLanguageParseError | UnsupportedLocaleError, LanguageRegistry>;
|
|
98
98
|
//#endregion
|
package/dist/natural/policy.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { containsPositiveShift } from "../ast/fold.mjs";
|
|
1
|
+
import { containsPositiveShift, isCurrentPeriod } from "../ast/fold.mjs";
|
|
2
2
|
import { Array } from "effect";
|
|
3
3
|
//#region src/natural/policy.ts
|
|
4
|
-
const applyFuturePolicy = (candidates, allowFuture) => allowFuture === false ? Array.filter(candidates, (candidate) => !containsPositiveShift(candidate.range)) : candidates;
|
|
4
|
+
const applyFuturePolicy = (candidates, allowFuture) => allowFuture === false ? Array.filter(candidates, (candidate) => isCurrentPeriod(candidate.range) || !containsPositiveShift(candidate.range)) : candidates;
|
|
5
5
|
//#endregion
|
|
6
6
|
export { applyFuturePolicy };
|
package/dist/resolve/resolve.mjs
CHANGED
|
@@ -2,15 +2,7 @@ import { foldInstant } from "../ast/fold.mjs";
|
|
|
2
2
|
import { ResolutionError, ResolvedDateRange, ResolvedGreaterThan, ResolvedGreaterThanOrEqual, ResolvedLessThan, ResolvedLessThanOrEqual } from "./schema.mjs";
|
|
3
3
|
import { DateTime, Effect, Match, Option } from "effect";
|
|
4
4
|
//#region src/resolve/resolve.ts
|
|
5
|
-
const shiftDateTime = (value, amount, unit) => {
|
|
6
|
-
switch (unit) {
|
|
7
|
-
case "day": return DateTime.add(value, { days: amount });
|
|
8
|
-
case "week": return DateTime.add(value, { weeks: amount });
|
|
9
|
-
case "month": return DateTime.add(value, { months: amount });
|
|
10
|
-
case "quarter": return DateTime.add(value, { months: amount * 3 });
|
|
11
|
-
case "year": return DateTime.add(value, { years: amount });
|
|
12
|
-
}
|
|
13
|
-
};
|
|
5
|
+
const shiftDateTime = (value, amount, unit) => Match.value(unit).pipe(Match.when("day", () => DateTime.add(value, { days: amount })), Match.when("week", () => DateTime.add(value, { weeks: amount })), Match.when("month", () => DateTime.add(value, { months: amount })), Match.when("quarter", () => DateTime.add(value, { months: amount * 3 })), Match.when("year", () => DateTime.add(value, { years: amount })), Match.exhaustive);
|
|
14
6
|
const startOfDateTime = (value, unit) => {
|
|
15
7
|
if (unit === "quarter") {
|
|
16
8
|
const month = DateTime.getPart(value, "month");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chronolizer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Bidirectional natural-language date ranges for Effect.",
|
|
5
5
|
"homepage": "https://github.com/tobimori/chronolizer#readme",
|
|
6
6
|
"bugs": "https://github.com/tobimori/chronolizer/issues",
|