chronolizer 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +170 -167
- package/dist/ast/constructors.d.mts +3 -8
- package/dist/ast/fold.d.mts +2 -1
- package/dist/ast/fold.mjs +38 -31
- package/dist/ast/normalize.mjs +4 -2
- package/dist/ast/schemas.d.mts +18 -25
- package/dist/ast/schemas.mjs +8 -17
- package/dist/filter/expression.mjs +27 -38
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +2 -2
- package/dist/language/model.d.mts +33 -33
- package/dist/language/model.mjs +5 -8
- package/dist/language/registry.mjs +35 -33
- package/dist/locales/cs.mjs +253 -31
- package/dist/locales/de.mjs +366 -42
- package/dist/locales/en.mjs +215 -51
- package/dist/locales/es.mjs +498 -91
- package/dist/locales/fr.mjs +360 -65
- package/dist/locales/nl.mjs +302 -64
- package/dist/locales/pl.mjs +447 -135
- package/dist/locales/shared.mjs +192 -23
- package/dist/locales/tr.mjs +220 -41
- package/dist/natural/correction.d.mts +1 -1
- package/dist/natural/correction.mjs +31 -5
- package/dist/natural/format.mjs +1 -1
- package/dist/natural/parse.d.mts +1 -1
- package/dist/natural/parse.mjs +5 -4
- package/dist/natural/policy.mjs +2 -2
- package/dist/natural/suggest.mjs +19 -3
- package/dist/natural/suggestion.mjs +11 -5
- package/dist/resolve/resolve.mjs +2 -10
- package/package.json +1 -1
package/dist/locales/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, compileCountAliasNormalizer, compoundCountAliases, countAliasVocabulary, currentYearDatePeriods, datedPeriods, datedQuarterPeriods, decimalTens, decomposeShiftedPeriodRange, fixedDatePeriod, fixedMonthPeriod, fixedQuarterPeriod, fixedYearPeriod, fromNowRange, futurePeriod, futureRange, isoDate, joinedNowCandidate, joinedPeriodCandidate, monthOfRelativeYear, namedCurrentYearDatePeriod, namedDatePeriod, openBoundaryCandidate, parseTrailingCount, periodBoundaryCandidate, periodEndDay, periodRange, periodStartDay, periodToDateRange, periodsFromPhrases, quarterOfRelativeYear, relativePeriod, relativeWeekday, relativeWeekend, remainingPeriodRange, renderPeriodRange, sequentialCountAliases, shiftPeriod, 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 = [
|
|
@@ -21,6 +21,61 @@ const months = [
|
|
|
21
21
|
"kasım",
|
|
22
22
|
"aralık"
|
|
23
23
|
];
|
|
24
|
+
const weekdays = [
|
|
25
|
+
"pazartesi",
|
|
26
|
+
"salı",
|
|
27
|
+
"çarşamba",
|
|
28
|
+
"perşembe",
|
|
29
|
+
"cuma",
|
|
30
|
+
"cumartesi",
|
|
31
|
+
"pazar"
|
|
32
|
+
];
|
|
33
|
+
const nextWeekdayPhrases = weekdays.map((weekday) => `gelecek ${weekday}`);
|
|
34
|
+
const turkishCountWords = [
|
|
35
|
+
["iki"],
|
|
36
|
+
["üç", "uc"],
|
|
37
|
+
["dört", "dort"],
|
|
38
|
+
["beş", "bes"],
|
|
39
|
+
["altı", "alti"],
|
|
40
|
+
["yedi"],
|
|
41
|
+
["sekiz"],
|
|
42
|
+
["dokuz"],
|
|
43
|
+
["on"],
|
|
44
|
+
["on bir"],
|
|
45
|
+
["on iki"],
|
|
46
|
+
["on üç"],
|
|
47
|
+
["on dört"],
|
|
48
|
+
["on beş"],
|
|
49
|
+
["on altı"],
|
|
50
|
+
["on yedi"],
|
|
51
|
+
["on sekiz"],
|
|
52
|
+
["on dokuz"],
|
|
53
|
+
["yirmi"]
|
|
54
|
+
];
|
|
55
|
+
const turkishCountOnes = [
|
|
56
|
+
[1, "bir"],
|
|
57
|
+
[2, "iki"],
|
|
58
|
+
[3, "üç"],
|
|
59
|
+
[4, "dört"],
|
|
60
|
+
[5, "beş"],
|
|
61
|
+
[6, "altı"],
|
|
62
|
+
[7, "yedi"],
|
|
63
|
+
[8, "sekiz"],
|
|
64
|
+
[9, "dokuz"]
|
|
65
|
+
];
|
|
66
|
+
const turkishCountAliases = [...sequentialCountAliases(turkishCountWords, 2), ...compoundCountAliases(decimalTens([
|
|
67
|
+
"yirmi",
|
|
68
|
+
"otuz",
|
|
69
|
+
"kırk",
|
|
70
|
+
"elli",
|
|
71
|
+
"altmış",
|
|
72
|
+
"yetmiş",
|
|
73
|
+
"seksen",
|
|
74
|
+
"doksan"
|
|
75
|
+
]), turkishCountOnes, (ten, one) => [`${ten} ${one}`])];
|
|
76
|
+
const normalizeTurkishCounts = compileCountAliasNormalizer(turkishCountAliases);
|
|
77
|
+
const turkishCountVocabulary = new Set(countAliasVocabulary(turkishCountAliases));
|
|
78
|
+
const correctTurkish = (input, vocabulary) => correctWhitespaceSeparatedText(input, vocabulary, turkishCountVocabulary);
|
|
24
79
|
const monthAbbreviations = [
|
|
25
80
|
["oca"],
|
|
26
81
|
["şub", "sub"],
|
|
@@ -298,17 +353,22 @@ const monthNumber = (value) => {
|
|
|
298
353
|
return short === -1 ? void 0 : short + 1;
|
|
299
354
|
};
|
|
300
355
|
const dateLabel = (day, month, year) => `${day} ${textAt(months, month - 1)} ${year}`;
|
|
356
|
+
const currentDateLabel = (day, month) => `${day} ${textAt(months, month - 1)}`;
|
|
301
357
|
const parseNamedDate = (input) => {
|
|
302
358
|
const named = String$1.match(/^([0-3]?\d)(?:\.)? ([a-zçğıöşü]+\.?)(?:,)? (\d{4})$/u)(input);
|
|
303
359
|
if (Option.isSome(named)) return namedDatePeriod(textAt(named.value, 3), textAt(named.value, 2), textAt(named.value, 1), monthNumber, dateLabel);
|
|
304
360
|
const numeric = String$1.match(/^([0-3]?\d)[./-]([01]?\d)[./-](\d{4})$/u)(input);
|
|
305
|
-
if (Option.
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
361
|
+
if (Option.isSome(numeric)) {
|
|
362
|
+
const year = validYear(textAt(numeric.value, 3));
|
|
363
|
+
const month = Number(textAt(numeric.value, 2));
|
|
364
|
+
const day = Number(textAt(numeric.value, 1));
|
|
365
|
+
if (year !== void 0 && month >= 1 && month <= 12) {
|
|
366
|
+
const value = isoDate(year, month, day);
|
|
367
|
+
if (isIsoDate(value) && value !== "9999-12-31") return Option.some(fixedDatePeriod(value, dateLabel(day, month, year)));
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
const current = String$1.match(/^([0-3]?\d)(?:\.)? ([a-zçğıöşü]+\.?)$/u)(input);
|
|
371
|
+
return Option.isSome(current) ? namedCurrentYearDatePeriod(textAt(current.value, 2), textAt(current.value, 1), monthNumber, currentDateLabel) : Option.none();
|
|
312
372
|
};
|
|
313
373
|
const quarterNumber = (value) => {
|
|
314
374
|
if ((value.startsWith("q") || value.startsWith("ç")) && value.length === 2) return Number(value.slice(1));
|
|
@@ -335,12 +395,13 @@ const parseQuarter = (input) => {
|
|
|
335
395
|
const quarter = quarterNumber(textAt(standalone.value, 1));
|
|
336
396
|
return quarter === void 0 ? Option.none() : Option.some(quarterOfRelativeYear(quarter, 0, `Ç${quarter}`));
|
|
337
397
|
};
|
|
338
|
-
const
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
398
|
+
const parseDatedPeriod = (input) => {
|
|
399
|
+
const knownPeriod = Option.firstSomeOf([
|
|
400
|
+
absoluteDatePeriod(input, "tr"),
|
|
401
|
+
parseNamedDate(input),
|
|
402
|
+
parseQuarter(input)
|
|
403
|
+
]);
|
|
404
|
+
if (Option.isSome(knownPeriod)) return knownPeriod;
|
|
344
405
|
const yearMatch = String$1.match(/^(?:yıl |yılı )?(\d{4})$/u)(input);
|
|
345
406
|
if (Option.isSome(yearMatch)) {
|
|
346
407
|
const year = validYear(textAt(yearMatch.value, 1));
|
|
@@ -352,6 +413,20 @@ const parseBasePeriod = (input) => {
|
|
|
352
413
|
const year = validYear(textAt(monthYear.value, 2));
|
|
353
414
|
if (month !== void 0 && year !== void 0) return Option.some(fixedMonthPeriod(year, month, `${title(textAt(months, month - 1))} ${year}`));
|
|
354
415
|
}
|
|
416
|
+
return Option.none();
|
|
417
|
+
};
|
|
418
|
+
const parseBasePeriod = (input) => {
|
|
419
|
+
const datedPeriod = parseDatedPeriod(input);
|
|
420
|
+
if (Option.isSome(datedPeriod)) return datedPeriod;
|
|
421
|
+
const prefixedRelativeMonth = String$1.match(/^(geçen|bu|gelecek) ([a-zçğıöşü]+\.?)$/u)(input);
|
|
422
|
+
if (Option.isSome(prefixedRelativeMonth)) {
|
|
423
|
+
const month = monthNumber(textAt(prefixedRelativeMonth.value, 2));
|
|
424
|
+
if (month !== void 0) {
|
|
425
|
+
const modifier = textAt(prefixedRelativeMonth.value, 1);
|
|
426
|
+
const direction = relativeYearDirection(modifier);
|
|
427
|
+
return Option.some(monthOfRelativeYear(month, direction, `${modifier} ${title(textAt(months, month - 1))}`));
|
|
428
|
+
}
|
|
429
|
+
}
|
|
355
430
|
const relativeMonth = String$1.match(/^([a-zçğıöşü]+\.?) (geçen yıl|gelecek yıl|bu yıl)$/u)(input);
|
|
356
431
|
const relativeMonthYearFirst = String$1.match(/^(geçen yıl|gelecek yıl|bu yıl) ([a-zçğıöşü]+\.?)$/u)(input);
|
|
357
432
|
const relativeMatch = Option.firstSomeOf([relativeMonth, relativeMonthYearFirst]);
|
|
@@ -372,16 +447,38 @@ const parseBasePeriod = (input) => {
|
|
|
372
447
|
if (["gelecek hafta sonu", "önümüzdeki hafta sonu"].includes(input)) return Option.some(relativeWeekend(1, "gelecek hafta sonu"));
|
|
373
448
|
if (input === "geçen hafta sonundan önceki hafta sonu") return Option.some(relativeWeekend(-2, input));
|
|
374
449
|
if (input === "gelecek hafta sonundan sonraki hafta sonu") return Option.some(relativeWeekend(2, input));
|
|
375
|
-
|
|
450
|
+
const weekday = nextWeekdayPhrases.indexOf(input);
|
|
451
|
+
return weekday === -1 ? Option.none() : Option.some(relativeWeekday(weekday, 1, nextWeekdayPhrases[weekday] ?? input));
|
|
376
452
|
};
|
|
377
|
-
const
|
|
453
|
+
const parsePeriodEdge = (input) => {
|
|
378
454
|
const edge = String$1.match(/^(.+?)(?:ın|in|un|ün) (başı|başlangıcı|sonu)$/u)(input);
|
|
379
|
-
if (Option.
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
455
|
+
if (Option.isNone(edge)) return Option.none();
|
|
456
|
+
const periodText = textAt(edge.value, 1);
|
|
457
|
+
const basePeriod = parseBasePeriod(periodText);
|
|
458
|
+
const implicitUnit = unitAliases.find((entry) => entry[0] === periodText)?.[1];
|
|
459
|
+
const implicit = units.find((entry) => entry.unit === implicitUnit);
|
|
460
|
+
const period = Option.isSome(basePeriod) || implicit === void 0 ? basePeriod : Option.some(relativePeriod(implicit.unit, 0, implicit.current));
|
|
461
|
+
if (Option.isNone(period)) return Option.none();
|
|
462
|
+
const isEnd = textAt(edge.value, 2) === "sonu";
|
|
463
|
+
const canonical = `${period.value.canonical} ${isEnd ? "sonu" : "başı"}`;
|
|
464
|
+
return Option.some(isEnd ? periodEndDay(period.value, canonical) : periodStartDay(period.value, canonical));
|
|
465
|
+
};
|
|
466
|
+
const parsePeriod = (input) => {
|
|
467
|
+
const prefix = String$1.match(/^([1-9]\d*) (gün|gun|hafta|ay|çeyrek|ceyrek|yıl|yil|sene) (önce|sonra) (.+)$/u)(input);
|
|
468
|
+
const suffix = String$1.match(/^(.+) ([1-9]\d*) (gün|gun|hafta|ay|çeyrek|ceyrek|yıl|yil|sene) (önce|sonra)$/u)(input);
|
|
469
|
+
const shifted = Option.firstSomeOf([prefix, suffix]);
|
|
470
|
+
if (Option.isSome(shifted)) {
|
|
471
|
+
const prefixOrder = Option.isSome(prefix);
|
|
472
|
+
const amount = parseTrailingCount(textAt(shifted.value, prefixOrder ? 1 : 2));
|
|
473
|
+
const alias = unitAliases.find((unit) => unit[0] === textAt(shifted.value, prefixOrder ? 2 : 3));
|
|
474
|
+
const entry = alias === void 0 ? void 0 : units.find((unit) => unit.unit === alias[1]);
|
|
475
|
+
const period = parsePeriod(textAt(shifted.value, prefixOrder ? 4 : 1));
|
|
476
|
+
if (Option.isSome(amount) && entry !== void 0 && Option.isSome(period)) {
|
|
477
|
+
const past = textAt(shifted.value, prefixOrder ? 3 : 4) === "önce";
|
|
478
|
+
const direction = past ? -amount.value : amount.value;
|
|
479
|
+
const noun = amount.value === 1 ? entry.singular : entry.plural;
|
|
480
|
+
const canonical = `${amount.value} ${noun} ${past ? "önce" : "sonra"} ${period.value.canonical}`;
|
|
481
|
+
return Option.some(shiftPeriod(period.value, direction, entry.unit, canonical));
|
|
385
482
|
}
|
|
386
483
|
}
|
|
387
484
|
const wrapper = [
|
|
@@ -389,7 +486,11 @@ const parsePeriod = (input) => {
|
|
|
389
486
|
"içinde ",
|
|
390
487
|
"tüm "
|
|
391
488
|
].find((prefix) => input.startsWith(prefix));
|
|
392
|
-
return
|
|
489
|
+
return Option.firstSomeOf([
|
|
490
|
+
parsePeriodEdge(input),
|
|
491
|
+
parseBasePeriod(wrapper === void 0 ? input : input.slice(wrapper.length)),
|
|
492
|
+
parseCalendarOffset(input)
|
|
493
|
+
]);
|
|
393
494
|
};
|
|
394
495
|
const countedUnit = (value) => unitAliases.find((entry) => entry[0] === value)?.[1];
|
|
395
496
|
const countedUnitPattern = "gün|gun|hafta|ay|çeyrek|ceyrek|yıl|yil|sene";
|
|
@@ -401,7 +502,46 @@ const rollingFuturePatterns = [countedPattern("^(?:gelecek|önümüzdeki|sonraki
|
|
|
401
502
|
const rollingSincePattern = countedPattern("^([1-9]\\d*) (UNIT) boyunca$");
|
|
402
503
|
const rollingBarePattern = countedPattern("^([1-9]\\d*) (UNIT)$");
|
|
403
504
|
const firstPatternMatch = (input, patterns) => Option.firstSomeOf(patterns.map((pattern) => String$1.match(pattern)(input)));
|
|
505
|
+
const singularRollingCanonical = (entry, future) => future ? `önümüzdeki bir ${entry.singular}` : `son bir ${entry.singular}`;
|
|
506
|
+
const singularRollingPhrases = units.flatMap((entry) => [
|
|
507
|
+
{
|
|
508
|
+
phrase: `son bir ${entry.singular}`,
|
|
509
|
+
entry,
|
|
510
|
+
future: false
|
|
511
|
+
},
|
|
512
|
+
{
|
|
513
|
+
phrase: `bir ${entry.singular} boyunca`,
|
|
514
|
+
entry,
|
|
515
|
+
future: false
|
|
516
|
+
},
|
|
517
|
+
{
|
|
518
|
+
phrase: `geçtiğimiz bir ${entry.singular}`,
|
|
519
|
+
entry,
|
|
520
|
+
future: false
|
|
521
|
+
},
|
|
522
|
+
{
|
|
523
|
+
phrase: `önümüzdeki bir ${entry.singular}`,
|
|
524
|
+
entry,
|
|
525
|
+
future: true
|
|
526
|
+
},
|
|
527
|
+
{
|
|
528
|
+
phrase: `bugünden itibaren bir ${entry.singular} boyunca`,
|
|
529
|
+
entry,
|
|
530
|
+
future: true
|
|
531
|
+
}
|
|
532
|
+
]);
|
|
533
|
+
const singularCalendarOffsets = units.flatMap((entry) => [{
|
|
534
|
+
phrase: `bir ${entry.singular} önce`,
|
|
535
|
+
entry,
|
|
536
|
+
direction: -1
|
|
537
|
+
}, {
|
|
538
|
+
phrase: `bir ${entry.singular} sonra`,
|
|
539
|
+
entry,
|
|
540
|
+
direction: 1
|
|
541
|
+
}]);
|
|
404
542
|
const parseCalendarOffset = (input) => {
|
|
543
|
+
const singular = singularCalendarOffsets.find((entry) => entry.phrase === input);
|
|
544
|
+
if (singular !== void 0) return Option.some(relativePeriod(singular.entry.unit, singular.direction, singular.phrase));
|
|
405
545
|
const past = String$1.match(calendarPastPattern)(input);
|
|
406
546
|
const future = firstPatternMatch(input, calendarFuturePatterns);
|
|
407
547
|
const match = Option.firstSomeOf([past, future]);
|
|
@@ -414,9 +554,14 @@ const parseCalendarOffset = (input) => {
|
|
|
414
554
|
const direction = Option.isSome(past) ? -amount.value : amount.value;
|
|
415
555
|
const noun = amount.value === 1 ? entry.singular : entry.plural;
|
|
416
556
|
const canonical = direction < 0 ? `${amount.value} ${noun} önce` : `${amount.value} ${noun} sonra`;
|
|
417
|
-
return Option.some(
|
|
557
|
+
return Option.some(relativePeriod(unit, direction, canonical));
|
|
418
558
|
};
|
|
419
559
|
const parseRollingPeriod = (input) => {
|
|
560
|
+
const singular = singularRollingPhrases.find((entry) => entry.phrase === input);
|
|
561
|
+
if (singular !== void 0) {
|
|
562
|
+
const range = singular.future ? futureRange(1, singular.entry.unit) : trailingRange(1, singular.entry.unit);
|
|
563
|
+
return Option.some(candidate(range, singularRollingCanonical(singular.entry, singular.future)));
|
|
564
|
+
}
|
|
420
565
|
const since = String$1.match(rollingSincePattern)(input);
|
|
421
566
|
const past = firstPatternMatch(input, rollingPastPatterns);
|
|
422
567
|
const bare = String$1.match(rollingBarePattern)(input);
|
|
@@ -435,9 +580,22 @@ const parseRollingPeriod = (input) => {
|
|
|
435
580
|
if (entry === void 0) return Option.none();
|
|
436
581
|
const isFuture = Option.isSome(future);
|
|
437
582
|
const range = isFuture ? futureRange(amount.value, unit) : trailingRange(amount.value, unit);
|
|
583
|
+
if (amount.value === 1) return Option.some(candidate(range, singularRollingCanonical(entry, isFuture)));
|
|
438
584
|
const modifier = isFuture ? "gelecek" : "son";
|
|
439
|
-
|
|
440
|
-
|
|
585
|
+
return Option.some(candidate(range, `${modifier} ${amount.value} ${entry.plural}`));
|
|
586
|
+
};
|
|
587
|
+
const parseElidedDateRange = (input) => {
|
|
588
|
+
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);
|
|
589
|
+
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ı`);
|
|
590
|
+
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);
|
|
591
|
+
const dashed = String$1.match(/^([0-3]?\d)[–—-]([0-3]?\d) ([a-zçğıöşü]+\.?)$/u)(input);
|
|
592
|
+
const match = Option.firstSomeOf([elided, dashed]);
|
|
593
|
+
if (Option.isNone(match)) return Option.none();
|
|
594
|
+
const isElided = Option.isSome(elided);
|
|
595
|
+
const lowerDay = textAt(match.value, 1);
|
|
596
|
+
const upperDay = textAt(match.value, isElided ? 3 : 2);
|
|
597
|
+
const month = textAt(match.value, isElided ? 2 : 3);
|
|
598
|
+
return joinedPeriodCandidate(`${lowerDay} ${month} ile ${upperDay} ${month}`, [["", " ile "]], parsePeriod, (lower, upper) => `${lower} ile ${upper} arası`);
|
|
441
599
|
};
|
|
442
600
|
const suffixBoundary = (input) => {
|
|
443
601
|
const boundaries = [
|
|
@@ -480,14 +638,19 @@ const parseTurkish = (input) => {
|
|
|
480
638
|
"şu andan itibaren",
|
|
481
639
|
"bundan sonra"
|
|
482
640
|
].includes(input)) return Option.some(candidate(fromNowRange(), "bugünden itibaren"));
|
|
483
|
-
const offset = parseCalendarOffset(input);
|
|
484
|
-
if (Option.isSome(offset)) return offset;
|
|
485
641
|
const rolling = parseRollingPeriod(input);
|
|
486
642
|
if (Option.isSome(rolling)) return rolling;
|
|
487
643
|
const toDate = toDatePhrases.find((entry) => entry.phrase === input);
|
|
488
644
|
if (toDate !== void 0) return Option.some(candidate(periodToDateRange(toDate.entry.unit), toDate.entry.toDate));
|
|
645
|
+
const elided = parseElidedDateRange(input);
|
|
646
|
+
if (Option.isSome(elided)) return elided;
|
|
489
647
|
const joinedInput = input.endsWith(" arası") ? input.slice(0, -6) : input;
|
|
490
|
-
const nowBounded = joinedNowCandidate(joinedInput, [["", " ile bugün"], ["", " ve bugün"]], [
|
|
648
|
+
const nowBounded = joinedNowCandidate(joinedInput, [["", " ile bugün"], ["", " ve bugün"]], [
|
|
649
|
+
"bugün ile ",
|
|
650
|
+
"bugün ve ",
|
|
651
|
+
"şimdi ile ",
|
|
652
|
+
"şimdi ve "
|
|
653
|
+
], parsePeriod, (period) => `${period} ile bugün arası`, (period) => `bugün ile ${period} arası`);
|
|
491
654
|
if (Option.isSome(nowBounded)) return nowBounded;
|
|
492
655
|
const bounded = joinedPeriodCandidate(joinedInput, [
|
|
493
656
|
["", " ile "],
|
|
@@ -499,7 +662,7 @@ const parseTurkish = (input) => {
|
|
|
499
662
|
if (Option.isSome(bounded)) return bounded;
|
|
500
663
|
const boundary = boundaryCandidate(input);
|
|
501
664
|
if (Option.isSome(boundary)) return boundary;
|
|
502
|
-
return Option.map(
|
|
665
|
+
return parsePeriod(input).pipe(Option.map((period) => candidate(periodRange(period), period.canonical)));
|
|
503
666
|
};
|
|
504
667
|
const staticPeriodPhrases = [
|
|
505
668
|
...periodAliases.map((entry) => entry[0]),
|
|
@@ -526,6 +689,8 @@ const staticPeriodPhrases = [
|
|
|
526
689
|
"geçen yılın sonu",
|
|
527
690
|
"gelecek yılın başı",
|
|
528
691
|
"gelecek yılın sonu",
|
|
692
|
+
...nextWeekdayPhrases,
|
|
693
|
+
...months.flatMap((month) => [`geçen ${month}`, `gelecek ${month}`]),
|
|
529
694
|
...[
|
|
530
695
|
1,
|
|
531
696
|
2,
|
|
@@ -569,6 +734,8 @@ const countedSuggestions = (input) => {
|
|
|
569
734
|
const turkishSuggestionPhrases = [
|
|
570
735
|
...units.map((entry) => entry.toDate),
|
|
571
736
|
...units.map((entry) => entry.remaining),
|
|
737
|
+
...singularRollingPhrases.map((entry) => entry.phrase),
|
|
738
|
+
...singularCalendarOffsets.map((entry) => entry.phrase),
|
|
572
739
|
...staticPeriodPhrases,
|
|
573
740
|
...prefixNaturalPhrases(staticPeriodPhrases, boundaryPrefixes),
|
|
574
741
|
"şimdiye kadar",
|
|
@@ -584,6 +751,17 @@ const suggestTurkish = (input, limit) => {
|
|
|
584
751
|
], limit);
|
|
585
752
|
};
|
|
586
753
|
const renderTurkish = (range) => {
|
|
754
|
+
const shifted = decomposeShiftedPeriodRange(range);
|
|
755
|
+
if (Option.isSome(shifted)) {
|
|
756
|
+
const base = renderTurkish(shifted.value.baseRange);
|
|
757
|
+
const entry = units.find((unit) => unit.unit === shifted.value.unit);
|
|
758
|
+
if (Option.isSome(base) && entry !== void 0) {
|
|
759
|
+
const amount = Math.abs(shifted.value.amount);
|
|
760
|
+
const noun = amount === 1 ? entry.singular : entry.plural;
|
|
761
|
+
const direction = shifted.value.amount < 0 ? "önce" : "sonra";
|
|
762
|
+
return Option.some(`${amount} ${noun} ${direction} ${base.value}`);
|
|
763
|
+
}
|
|
764
|
+
}
|
|
587
765
|
const offset = calendarPeriodOffset(range);
|
|
588
766
|
if (Option.isSome(offset) && Math.abs(offset.value.amount) > 1) {
|
|
589
767
|
const entry = units.find((unit) => unit.unit === offset.value.unit);
|
|
@@ -596,27 +774,26 @@ const renderTurkish = (range) => {
|
|
|
596
774
|
const future = futurePeriod(range);
|
|
597
775
|
if (Option.isSome(future)) {
|
|
598
776
|
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
|
-
}
|
|
777
|
+
if (entry !== void 0) return Option.some(future.value.amount === 1 ? singularRollingCanonical(entry, true) : `gelecek ${future.value.amount} ${entry.plural}`);
|
|
603
778
|
}
|
|
604
779
|
const trailing = trailingPeriod(range);
|
|
605
780
|
if (Option.isSome(trailing)) {
|
|
606
781
|
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
|
-
}
|
|
782
|
+
if (entry !== void 0) return Option.some(trailing.value.amount === 1 ? singularRollingCanonical(entry, false) : `son ${trailing.value.amount} ${entry.plural}`);
|
|
611
783
|
}
|
|
612
|
-
const periods = [
|
|
784
|
+
const periods = [
|
|
785
|
+
...staticPeriods,
|
|
786
|
+
...currentYearDatePeriods(range, currentDateLabel),
|
|
787
|
+
...periodsFromPhrases([...datedPeriods(range, months), ...datedQuarterPeriods(range).map((phrase) => phrase.startsWith("Q") ? `Ç${phrase.slice(1)}` : phrase)], parsePeriod)
|
|
788
|
+
];
|
|
613
789
|
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
790
|
};
|
|
615
|
-
const normalizeTurkish = (input, locale) => normalizeNaturalText(input, locale).replaceAll("’", "'");
|
|
791
|
+
const normalizeTurkish = (input, locale) => normalizeTurkishCounts(normalizeNaturalText(input, locale).replaceAll("’", "'"));
|
|
616
792
|
const TurkishContribution = new BaseLanguageContribution({
|
|
617
793
|
locale: "tr",
|
|
618
794
|
vocabulary: [
|
|
619
795
|
...months,
|
|
796
|
+
...weekdays,
|
|
620
797
|
...monthAbbreviations.flatMap((aliases) => aliases),
|
|
621
798
|
...units.flatMap((entry) => [
|
|
622
799
|
entry.singular,
|
|
@@ -633,6 +810,7 @@ const TurkishContribution = new BaseLanguageContribution({
|
|
|
633
810
|
"gelecek",
|
|
634
811
|
"geçen",
|
|
635
812
|
"geri",
|
|
813
|
+
"ile",
|
|
636
814
|
"itibaren",
|
|
637
815
|
"itibarıyla",
|
|
638
816
|
"kadar",
|
|
@@ -643,10 +821,11 @@ const TurkishContribution = new BaseLanguageContribution({
|
|
|
643
821
|
"son",
|
|
644
822
|
"sonra",
|
|
645
823
|
"sonrası",
|
|
824
|
+
"şimdi",
|
|
646
825
|
"şimdiye"
|
|
647
826
|
],
|
|
648
827
|
normalize: normalizeTurkish,
|
|
649
|
-
correct:
|
|
828
|
+
correct: correctTurkish,
|
|
650
829
|
parseExact: parseTurkish,
|
|
651
830
|
suggest: suggestTurkish,
|
|
652
831
|
render: renderTurkish
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
//#region src/natural/correction.d.ts
|
|
2
|
-
declare const correctWhitespaceSeparatedText: (input: string, vocabulary: ReadonlyArray<string>) => {
|
|
2
|
+
declare const correctWhitespaceSeparatedText: (input: string, vocabulary: ReadonlyArray<string>, segmentationVocabulary?: ReadonlySet<string>) => {
|
|
3
3
|
readonly text: string;
|
|
4
4
|
readonly corrections: readonly {
|
|
5
5
|
readonly original: string;
|
|
@@ -20,22 +20,48 @@ const damerauLevenshteinDistance = (left, right) => {
|
|
|
20
20
|
}
|
|
21
21
|
return previous[right.length] ?? fallback;
|
|
22
22
|
};
|
|
23
|
-
const
|
|
23
|
+
const segmentedReplacements = (word, vocabulary) => {
|
|
24
|
+
const segmentations = Array.from({ length: word.length + 1 }, () => []);
|
|
25
|
+
const initial = segmentations[0];
|
|
26
|
+
if (initial !== void 0) initial.push([]);
|
|
27
|
+
for (let start = 0; start < word.length; start += 1) {
|
|
28
|
+
const prefixes = segmentations[start] ?? [];
|
|
29
|
+
if (prefixes.length === 0) continue;
|
|
30
|
+
for (let end = start + 1; end <= word.length; end += 1) {
|
|
31
|
+
const part = word.slice(start, end);
|
|
32
|
+
if (!vocabulary.has(part)) continue;
|
|
33
|
+
const target = segmentations[end];
|
|
34
|
+
if (target === void 0) continue;
|
|
35
|
+
for (const prefix of prefixes) {
|
|
36
|
+
if (target.length >= 4) break;
|
|
37
|
+
target.push([...prefix, part]);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return (segmentations[word.length] ?? []).filter((parts) => parts.length > 1).map((parts) => ({
|
|
42
|
+
word: parts.join(" "),
|
|
43
|
+
distance: parts.length - 1
|
|
44
|
+
}));
|
|
45
|
+
};
|
|
46
|
+
const replacementsFor = (word, vocabulary, segmentationVocabulary) => {
|
|
24
47
|
if (vocabulary.includes(word) || isProtectedValue(word)) return [{
|
|
25
48
|
word,
|
|
26
49
|
distance: 0
|
|
27
50
|
}];
|
|
28
|
-
|
|
51
|
+
const segmented = segmentedReplacements(word, segmentationVocabulary);
|
|
52
|
+
if (word.length <= 3) return segmented;
|
|
29
53
|
const maximum = word.length >= 6 ? 2 : 1;
|
|
30
|
-
const
|
|
54
|
+
const fuzzy = vocabulary.filter((candidate) => Math.abs(candidate.length - word.length) <= maximum).map((candidate) => ({
|
|
31
55
|
word: candidate,
|
|
32
56
|
distance: damerauLevenshteinDistance(word, candidate)
|
|
33
57
|
})).filter((candidate) => candidate.distance <= maximum);
|
|
58
|
+
const matches = [...segmented, ...fuzzy];
|
|
34
59
|
if (matches.length === 0) return [];
|
|
35
60
|
const minimum = Math.min(...matches.map((candidate) => candidate.distance));
|
|
36
61
|
return matches.filter((candidate) => candidate.distance === minimum).slice(0, 4);
|
|
37
62
|
};
|
|
38
|
-
const
|
|
63
|
+
const emptySegmentationVocabulary = /* @__PURE__ */ new Set();
|
|
64
|
+
const correctWhitespaceSeparatedText = (input, vocabulary, segmentationVocabulary = emptySegmentationVocabulary) => {
|
|
39
65
|
const words = naturalWords(input);
|
|
40
66
|
let partials = [{
|
|
41
67
|
words: [],
|
|
@@ -44,7 +70,7 @@ const correctWhitespaceSeparatedText = (input, vocabulary) => {
|
|
|
44
70
|
offset: 0
|
|
45
71
|
}];
|
|
46
72
|
for (const word of words) {
|
|
47
|
-
const replacements = replacementsFor(word, vocabulary);
|
|
73
|
+
const replacements = replacementsFor(word, vocabulary, segmentationVocabulary);
|
|
48
74
|
if (replacements.length === 0) return [];
|
|
49
75
|
const next = [];
|
|
50
76
|
for (const partial of partials) for (const replacement of replacements) {
|
package/dist/natural/format.mjs
CHANGED
|
@@ -7,7 +7,7 @@ const formatNatural = Effect.fn("chronolizer.formatNatural")(function* (range, o
|
|
|
7
7
|
if (Option.isSome(rendered)) return rendered.value;
|
|
8
8
|
return yield* new NaturalLanguageRenderError({
|
|
9
9
|
locale: options.locale,
|
|
10
|
-
message: "The range has no
|
|
10
|
+
message: "The range has no standard natural-language form for this language"
|
|
11
11
|
});
|
|
12
12
|
});
|
|
13
13
|
//#endregion
|
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/parse.mjs
CHANGED
|
@@ -31,13 +31,13 @@ const parseNatural = Effect.fn("chronolizer.parseNatural")(function* (input, opt
|
|
|
31
31
|
if (normalized.length === 0) return yield* new NaturalLanguageParseError({
|
|
32
32
|
input,
|
|
33
33
|
locale: options.locale,
|
|
34
|
-
message: "The complete input must contain a date
|
|
34
|
+
message: "The complete input must contain a date range"
|
|
35
35
|
});
|
|
36
36
|
const exact = language.parseExact(normalized);
|
|
37
37
|
const allowedExact = applyFuturePolicy(exact, options.allowFuture);
|
|
38
38
|
if (Array.isReadonlyArrayNonEmpty(allowedExact)) return resultFromCandidates(allowedExact, []);
|
|
39
39
|
if (options.typoMode !== "tolerant") {
|
|
40
|
-
const message = exact.length > 0 && options.allowFuture === false ? "
|
|
40
|
+
const message = exact.length > 0 && options.allowFuture === false ? "This date range is in the future, but future ranges are disabled" : "The complete input is not a supported date range";
|
|
41
41
|
return yield* new NaturalLanguageParseError({
|
|
42
42
|
input,
|
|
43
43
|
locale: options.locale,
|
|
@@ -46,7 +46,8 @@ const parseNatural = Effect.fn("chronolizer.parseNatural")(function* (input, opt
|
|
|
46
46
|
}
|
|
47
47
|
const corrected = language.correct?.(normalized, language.vocabulary) ?? [];
|
|
48
48
|
const parsedCorrections = Array.filterMap(corrected, (correction) => {
|
|
49
|
-
const
|
|
49
|
+
const correctedText = language.normalize(correction.text, language.locale);
|
|
50
|
+
const candidates = applyFuturePolicy(language.parseExact(correctedText), options.allowFuture);
|
|
50
51
|
return Array.isReadonlyArrayNonEmpty(candidates) ? Result.succeed({
|
|
51
52
|
correction,
|
|
52
53
|
candidates
|
|
@@ -56,7 +57,7 @@ const parseNatural = Effect.fn("chronolizer.parseNatural")(function* (input, opt
|
|
|
56
57
|
if (!Array.isReadonlyArrayNonEmpty(successful)) return yield* new NaturalLanguageParseError({
|
|
57
58
|
input,
|
|
58
59
|
locale: options.locale,
|
|
59
|
-
message: "No
|
|
60
|
+
message: "No supported date range was found after spelling correction"
|
|
60
61
|
});
|
|
61
62
|
const first = Array.headNonEmpty(successful);
|
|
62
63
|
const best = Array.prepend(Array.takeWhile(Array.tailNonEmpty(successful), (entry) => entry.correction.cost === first.correction.cost), first);
|
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/natural/suggest.mjs
CHANGED
|
@@ -1,24 +1,40 @@
|
|
|
1
1
|
import { rangeKey } from "../filter/codec.mjs";
|
|
2
2
|
import { NaturalSuggestion } from "../language/model.mjs";
|
|
3
|
+
import { naturalWords } from "./text.mjs";
|
|
3
4
|
import { LanguageRegistry } from "../language/registry.mjs";
|
|
4
5
|
import { applyFuturePolicy } from "./policy.mjs";
|
|
5
|
-
import { Array, Effect } from "effect";
|
|
6
|
+
import { Array as Array$1, Effect } from "effect";
|
|
6
7
|
//#region src/natural/suggest.ts
|
|
7
8
|
const suggestionLimit = (limit) => {
|
|
8
9
|
if (limit === void 0) return 10;
|
|
9
10
|
if (!Number.isSafeInteger(limit) || limit <= 0) return 0;
|
|
10
11
|
return Math.min(limit, 100);
|
|
11
12
|
};
|
|
13
|
+
const singleWordCompletions = (input, vocabulary) => {
|
|
14
|
+
const words = naturalWords(input);
|
|
15
|
+
if (words.length === 0 || words.length > 12) return [];
|
|
16
|
+
const positions = Array.from({ length: words.length + 1 }, (_, index) => index);
|
|
17
|
+
const insertions = Array$1.flatMap(positions, (index) => Array$1.flatMap(vocabulary, (word) => word.length === 0 || word.includes(" ") ? [] : [[
|
|
18
|
+
...words.slice(0, index),
|
|
19
|
+
word,
|
|
20
|
+
...words.slice(index)
|
|
21
|
+
].join(" ")]));
|
|
22
|
+
return Array$1.dedupe(insertions);
|
|
23
|
+
};
|
|
12
24
|
const suggestNatural = Effect.fn("chronolizer.suggestNatural")(function* (input, options) {
|
|
13
25
|
const language = yield* (yield* LanguageRegistry).resolve(options.locale);
|
|
14
26
|
const normalized = language.normalize(input, language.locale);
|
|
15
27
|
const limit = suggestionLimit(options.limit);
|
|
16
28
|
if (limit === 0) return [];
|
|
17
|
-
const
|
|
29
|
+
const suggestionsFrom = (texts) => Array$1.flatMap(Array$1.dedupe(texts), (text) => applyFuturePolicy(language.parseExact(language.normalize(text, language.locale)), options.allowFuture).map((candidate) => NaturalSuggestion.make({
|
|
18
30
|
text: candidate.canonical,
|
|
19
31
|
range: candidate.range
|
|
20
32
|
})));
|
|
21
|
-
|
|
33
|
+
const distinct = (suggestions) => Array$1.dedupeWith(suggestions, (left, right) => rangeKey(left.range) === rangeKey(right.range));
|
|
34
|
+
const suggested = distinct(suggestionsFrom([...language.suggest(normalized, limit * 2), normalized]));
|
|
35
|
+
if (suggested.length > 0) return Array$1.take(suggested, limit);
|
|
36
|
+
const completionVocabulary = Array$1.dedupe([...Array$1.flatMap(language.suggest("", 100), naturalWords), ...language.vocabulary]);
|
|
37
|
+
return Array$1.take(distinct(suggestionsFrom(singleWordCompletions(normalized, completionVocabulary))), limit);
|
|
22
38
|
});
|
|
23
39
|
//#endregion
|
|
24
40
|
export { suggestNatural };
|
|
@@ -23,14 +23,20 @@ const phraseScore = (input, phrase) => {
|
|
|
23
23
|
const phraseWords = phrase.split(" ");
|
|
24
24
|
if (inputWords.length > phraseWords.length) return void 0;
|
|
25
25
|
let cost = 0;
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
let addedWords = 0;
|
|
27
|
+
let phraseIndex = 0;
|
|
28
|
+
for (const inputWord of inputWords) {
|
|
29
|
+
let wordCost = completionCost(inputWord, phraseWords[phraseIndex] ?? "");
|
|
30
|
+
while (wordCost === void 0 && addedWords < 3) {
|
|
31
|
+
phraseIndex += 1;
|
|
32
|
+
addedWords += 1;
|
|
33
|
+
wordCost = completionCost(inputWord, phraseWords[phraseIndex] ?? "");
|
|
34
|
+
}
|
|
30
35
|
if (wordCost === void 0) return void 0;
|
|
31
36
|
cost += wordCost;
|
|
37
|
+
phraseIndex += 1;
|
|
32
38
|
}
|
|
33
|
-
return
|
|
39
|
+
return 2 + cost + addedWords;
|
|
34
40
|
};
|
|
35
41
|
const completeYearPrefix = (input) => {
|
|
36
42
|
const match = String.match(/(?:^| )(\d{3,4})$/u)(input);
|
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");
|
|
@@ -53,7 +45,7 @@ const resolve = Effect.fn("chronolizer.resolve")(function* (range) {
|
|
|
53
45
|
if (range.lower !== void 0 && range.upper !== void 0) {
|
|
54
46
|
const lower = yield* resolveLower(range.lower, reference, zone);
|
|
55
47
|
const upper = yield* resolveUpper(range.upper, reference, zone);
|
|
56
|
-
if (DateTime.toEpochMillis(lower.value) >= DateTime.toEpochMillis(upper.value)) return yield* new ResolutionError({ message: "The
|
|
48
|
+
if (DateTime.toEpochMillis(lower.value) >= DateTime.toEpochMillis(upper.value)) return yield* new ResolutionError({ message: "The range start must be before the range end" });
|
|
57
49
|
return ResolvedDateRange.make({
|
|
58
50
|
lower,
|
|
59
51
|
upper
|