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.
@@ -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, 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, 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/cs.ts
10
10
  const months = [
@@ -21,6 +21,123 @@ const months = [
21
21
  "listopad",
22
22
  "prosinec"
23
23
  ];
24
+ const weekdays = [
25
+ "pondělí",
26
+ "úterý",
27
+ "středa",
28
+ "čtvrtek",
29
+ "pátek",
30
+ "sobota",
31
+ "neděle"
32
+ ];
33
+ const weekdayGenitives = [
34
+ "pondělí",
35
+ "úterý",
36
+ "středy",
37
+ "čtvrtka",
38
+ "pátku",
39
+ "soboty",
40
+ "neděle"
41
+ ];
42
+ const nextWeekdays = weekdays.flatMap((weekday, day) => [{
43
+ phrase: `příští ${weekday}`,
44
+ day,
45
+ canonical: `příští ${weekday}`
46
+ }, {
47
+ phrase: `příštího ${textAt(weekdayGenitives, day)}`,
48
+ day,
49
+ canonical: `příští ${weekday}`
50
+ }]);
51
+ const czechCountWords = [
52
+ [
53
+ "dva",
54
+ "dvě",
55
+ "dvou",
56
+ "dvěma"
57
+ ],
58
+ [
59
+ "tři",
60
+ "tří",
61
+ "třemi"
62
+ ],
63
+ [
64
+ "čtyři",
65
+ "čtyř",
66
+ "čtyřmi"
67
+ ],
68
+ ["pět", "pěti"],
69
+ ["šest", "šesti"],
70
+ ["sedm", "sedmi"],
71
+ ["osm", "osmi"],
72
+ ["devět", "devíti"],
73
+ ["deset", "deseti"],
74
+ ["jedenáct", "jedenácti"],
75
+ ["dvanáct", "dvanácti"],
76
+ ["třináct", "třinácti"],
77
+ ["čtrnáct", "čtrnácti"],
78
+ ["patnáct", "patnácti"],
79
+ ["šestnáct", "šestnácti"],
80
+ ["sedmnáct", "sedmnácti"],
81
+ ["osmnáct", "osmnácti"],
82
+ ["devatenáct", "devatenácti"],
83
+ ["dvacet", "dvaceti"]
84
+ ];
85
+ const czechCountOnes = [
86
+ [1, "jedna"],
87
+ [2, "dva"],
88
+ [3, "tři"],
89
+ [4, "čtyři"],
90
+ [5, "pět"],
91
+ [6, "šest"],
92
+ [7, "sedm"],
93
+ [8, "osm"],
94
+ [9, "devět"]
95
+ ];
96
+ const czechObliqueCountOnes = [
97
+ [1, "jedním"],
98
+ [2, "dvěma"],
99
+ [3, "třemi"],
100
+ [4, "čtyřmi"],
101
+ [5, "pěti"],
102
+ [6, "šesti"],
103
+ [7, "sedmi"],
104
+ [8, "osmi"],
105
+ [9, "devíti"]
106
+ ];
107
+ const czechCountAliases = [
108
+ ...sequentialCountAliases([[
109
+ "jeden",
110
+ "jedna",
111
+ "jedno",
112
+ "jedním",
113
+ "jednou"
114
+ ]], 1),
115
+ ...sequentialCountAliases(czechCountWords, 2),
116
+ ...compoundCountAliases(decimalTens([
117
+ "dvacet",
118
+ "třicet",
119
+ "čtyřicet",
120
+ "padesát",
121
+ "šedesát",
122
+ "sedmdesát",
123
+ "osmdesát",
124
+ "devadesát"
125
+ ]), czechCountOnes, (ten, one) => [`${ten} ${one}`]),
126
+ ...compoundCountAliases(decimalTens([
127
+ "dvaceti",
128
+ "třiceti",
129
+ "čtyřiceti",
130
+ "padesáti",
131
+ "šedesáti",
132
+ "sedmdesáti",
133
+ "osmdesáti",
134
+ "devadesáti"
135
+ ]), czechObliqueCountOnes, (ten, one) => [`${ten} ${one}`])
136
+ ];
137
+ const normalizeCzechCounts = compileCountAliasNormalizer(czechCountAliases);
138
+ const czechCountVocabulary = new Set(countAliasVocabulary(czechCountAliases));
139
+ const correctCzech = (input, vocabulary) => correctWhitespaceSeparatedText(input, vocabulary, czechCountVocabulary);
140
+ const normalizeCzech = (input, locale) => normalizeCzechCounts(normalizeNaturalText(input, locale));
24
141
  const monthGenitives = [
25
142
  "ledna",
26
143
  "února",
@@ -85,6 +202,7 @@ const units = [
85
202
  many: "dnů",
86
203
  pastSingular: "dnem",
87
204
  pastPlural: "dny",
205
+ durationGenitive: "dne",
88
206
  current: "dnes",
89
207
  previous: "včera",
90
208
  next: "zítra",
@@ -98,6 +216,7 @@ const units = [
98
216
  many: "týdnů",
99
217
  pastSingular: "týdnem",
100
218
  pastPlural: "týdny",
219
+ durationGenitive: "týdne",
101
220
  current: "tento týden",
102
221
  previous: "minulý týden",
103
222
  next: "příští týden",
@@ -111,6 +230,7 @@ const units = [
111
230
  many: "měsíců",
112
231
  pastSingular: "měsícem",
113
232
  pastPlural: "měsíci",
233
+ durationGenitive: "měsíce",
114
234
  current: "tento měsíc",
115
235
  previous: "minulý měsíc",
116
236
  next: "příští měsíc",
@@ -124,6 +244,7 @@ const units = [
124
244
  many: "čtvrtletí",
125
245
  pastSingular: "čtvrtletím",
126
246
  pastPlural: "čtvrtletími",
247
+ durationGenitive: "čtvrtletí",
127
248
  current: "toto čtvrtletí",
128
249
  previous: "minulé čtvrtletí",
129
250
  next: "příští čtvrtletí",
@@ -137,6 +258,7 @@ const units = [
137
258
  many: "let",
138
259
  pastSingular: "rokem",
139
260
  pastPlural: "lety",
261
+ durationGenitive: "roku",
140
262
  current: "tento rok",
141
263
  previous: "minulý rok",
142
264
  next: "příští rok",
@@ -144,7 +266,6 @@ const units = [
144
266
  remaining: "zbytek roku"
145
267
  }
146
268
  ];
147
- const title = (value) => `${value.slice(0, 1).toLocaleUpperCase("cs")}${value.slice(1)}`;
148
269
  const unitAliases = [
149
270
  ["den", "day"],
150
271
  ["dny", "day"],
@@ -360,17 +481,22 @@ const monthNumber = (value) => {
360
481
  return short === -1 ? void 0 : short + 1;
361
482
  };
362
483
  const dateLabel = (day, month, year) => `${day}. ${textAt(monthGenitives, month - 1)} ${year}`;
484
+ const currentDateLabel = (day, month) => `${day}. ${textAt(monthGenitives, month - 1)}`;
363
485
  const parseNamedDate = (input) => {
364
486
  const named = String$1.match(/^([0-3]?\d)\.?(?: )([a-záčďéěíňóřšťúůýž]+\.?) (\d{4})$/u)(input);
365
487
  if (Option.isSome(named)) return namedDatePeriod(textAt(named.value, 3), textAt(named.value, 2), textAt(named.value, 1), monthNumber, dateLabel);
366
488
  const numeric = String$1.match(/^([0-3]?\d)(?:\. ?|[/-])([01]?\d)(?:\. ?|[/-])(\d{4})$/u)(input);
367
- if (Option.isNone(numeric)) return Option.none();
368
- const year = validYear(textAt(numeric.value, 3));
369
- const month = Number(textAt(numeric.value, 2));
370
- const day = Number(textAt(numeric.value, 1));
371
- if (year === void 0 || month < 1 || month > 12) return Option.none();
372
- const value = isoDate(year, month, day);
373
- return isIsoDate(value) && value !== "9999-12-31" ? Option.some(fixedDatePeriod(value, dateLabel(day, month, year))) : Option.none();
489
+ if (Option.isSome(numeric)) {
490
+ const year = validYear(textAt(numeric.value, 3));
491
+ const month = Number(textAt(numeric.value, 2));
492
+ const day = Number(textAt(numeric.value, 1));
493
+ if (year !== void 0 && month >= 1 && month <= 12) {
494
+ const value = isoDate(year, month, day);
495
+ if (isIsoDate(value) && value !== "9999-12-31") return Option.some(fixedDatePeriod(value, dateLabel(day, month, year)));
496
+ }
497
+ }
498
+ const current = String$1.match(/^([0-3]?\d)\.? ([a-záčďéěíňóřšťúůýž]+\.?)$/u)(input);
499
+ return Option.isSome(current) ? namedCurrentYearDatePeriod(textAt(current.value, 2), textAt(current.value, 1), monthNumber, currentDateLabel) : Option.none();
374
500
  };
375
501
  const quarterNumber = (value) => {
376
502
  if (value.startsWith("q") && value.length === 2) return Number(value.slice(1));
@@ -397,12 +523,13 @@ const parseQuarter = (input) => {
397
523
  const quarter = quarterNumber(textAt(standalone.value, 1));
398
524
  return quarter === void 0 ? Option.none() : Option.some(quarterOfRelativeYear(quarter, 0, `Q${quarter}`));
399
525
  };
400
- const parseBasePeriod = (input) => {
401
- if (isIsoDate(input) && input !== "9999-12-31") return Option.some(fixedDatePeriod(input, input));
402
- const namedDate = parseNamedDate(input);
403
- if (Option.isSome(namedDate)) return namedDate;
404
- const quarter = parseQuarter(input);
405
- if (Option.isSome(quarter)) return quarter;
526
+ const parseDatedPeriod = (input) => {
527
+ const knownPeriod = Option.firstSomeOf([
528
+ absoluteDatePeriod(input, "cs"),
529
+ parseNamedDate(input),
530
+ parseQuarter(input)
531
+ ]);
532
+ if (Option.isSome(knownPeriod)) return knownPeriod;
406
533
  const yearMatch = String$1.match(/^(?:rok |roku )?(\d{4})$/u)(input);
407
534
  if (Option.isSome(yearMatch)) {
408
535
  const year = validYear(textAt(yearMatch.value, 1));
@@ -412,7 +539,21 @@ const parseBasePeriod = (input) => {
412
539
  if (Option.isSome(monthYear)) {
413
540
  const month = monthNumber(textAt(monthYear.value, 1));
414
541
  const year = validYear(textAt(monthYear.value, 2));
415
- if (month !== void 0 && year !== void 0) return Option.some(fixedMonthPeriod(year, month, `${title(textAt(months, month - 1))} ${year}`));
542
+ if (month !== void 0 && year !== void 0) return Option.some(fixedMonthPeriod(year, month, `${textAt(months, month - 1)} ${year}`));
543
+ }
544
+ return Option.none();
545
+ };
546
+ const parseBasePeriod = (input) => {
547
+ const datedPeriod = parseDatedPeriod(input);
548
+ if (Option.isSome(datedPeriod)) return datedPeriod;
549
+ const prefixedRelativeMonth = String$1.match(/^(minulý|tento|příští) ([a-záčďéěíňóřšťúůýž]+\.?)$/u)(input);
550
+ if (Option.isSome(prefixedRelativeMonth)) {
551
+ const month = monthNumber(textAt(prefixedRelativeMonth.value, 2));
552
+ if (month !== void 0) {
553
+ const modifier = textAt(prefixedRelativeMonth.value, 1);
554
+ const direction = relativeYearDirection(modifier);
555
+ return Option.some(monthOfRelativeYear(month, direction, `${modifier} ${textAt(months, month - 1)}`));
556
+ }
416
557
  }
417
558
  const relativeMonth = String$1.match(/^([a-záčďéěíňóřšťúůýž]+\.?) (minulého roku|příštího roku|tohoto roku)$/u)(input);
418
559
  const relativeMonthYearFirst = String$1.match(/^(minulého roku|příštího roku|tohoto roku) ([a-záčďéěíňóřšťúůýž]+\.?)$/u)(input);
@@ -423,10 +564,10 @@ const parseBasePeriod = (input) => {
423
564
  const yearText = textAt(relativeMatch.value, yearFirst ? 1 : 2);
424
565
  const month = monthNumber(monthText);
425
566
  const direction = relativeYearDirection(yearText);
426
- if (month !== void 0) return Option.some(monthOfRelativeYear(month, direction, `${title(textAt(months, month - 1))} ${relativeYearName(direction)}`));
567
+ if (month !== void 0) return Option.some(monthOfRelativeYear(month, direction, `${textAt(months, month - 1)} ${relativeYearName(direction)}`));
427
568
  }
428
569
  const standaloneMonth = monthNumber(input);
429
- if (standaloneMonth !== void 0) return Option.some(monthOfRelativeYear(standaloneMonth, 0, title(textAt(months, standaloneMonth - 1))));
570
+ if (standaloneMonth !== void 0) return Option.some(monthOfRelativeYear(standaloneMonth, 0, textAt(months, standaloneMonth - 1)));
430
571
  const alias = periodAliases.find((entry) => entry[0] === input);
431
572
  if (alias !== void 0) return Option.some(relativePeriod(alias[1], alias[2], alias[3]));
432
573
  if (["víkend", "tento víkend"].includes(input)) return Option.some(relativeWeekend(0, "tento víkend"));
@@ -434,12 +575,31 @@ const parseBasePeriod = (input) => {
434
575
  if (["příští víkend", "následující víkend"].includes(input)) return Option.some(relativeWeekend(1, "příští víkend"));
435
576
  if (input === "předminulý víkend") return Option.some(relativeWeekend(-2, input));
436
577
  if (input === "přespříští víkend") return Option.some(relativeWeekend(2, input));
437
- return Option.none();
578
+ const weekday = nextWeekdays.find((entry) => entry.phrase === input);
579
+ return weekday === void 0 ? Option.none() : Option.some(relativeWeekday(weekday.day, 1, weekday.canonical));
438
580
  };
439
581
  const parsePeriod = (input) => {
582
+ const shifted = String$1.match(/^(.+) (před|za) ([1-9]\d*) (den|dny|dnů|dnem|týden|týdny|týdnů|týdnem|měsíc|měsíce|měsíců|měsícem|měsíci|čtvrtletí|čtvrtletím|čtvrtletími|rok|roky|let|rokem|lety)$/u)(input);
583
+ if (Option.isSome(shifted)) {
584
+ const amount = parseTrailingCount(textAt(shifted.value, 3));
585
+ const alias = unitAliases.find((unit) => unit[0] === textAt(shifted.value, 4));
586
+ const entry = alias === void 0 ? void 0 : units.find((unit) => unit.unit === alias[1]);
587
+ const period = parsePeriod(textAt(shifted.value, 1));
588
+ if (Option.isSome(amount) && entry !== void 0 && Option.isSome(period)) {
589
+ const past = textAt(shifted.value, 2) === "před";
590
+ const direction = past ? -amount.value : amount.value;
591
+ const pastNoun = amount.value === 1 ? entry.pastSingular : entry.pastPlural;
592
+ const noun = past ? pastNoun : countNoun(amount.value, entry);
593
+ const canonical = `${period.value.canonical} ${past ? "před" : "za"} ${amount.value} ${noun}`;
594
+ return Option.some(shiftPeriod(period.value, direction, entry.unit, canonical));
595
+ }
596
+ }
440
597
  const edge = String$1.match(/^(začátek|počátek|konec) (.+)$/u)(input);
441
598
  if (Option.isSome(edge)) {
442
- const period = parseBasePeriod(textAt(edge.value, 2));
599
+ const periodText = textAt(edge.value, 2);
600
+ const basePeriod = parseBasePeriod(periodText);
601
+ const implicit = units.find((entry) => entry.durationGenitive === periodText);
602
+ const period = Option.isSome(basePeriod) || implicit === void 0 ? basePeriod : Option.some(relativePeriod(implicit.unit, 0, implicit.current));
443
603
  if (Option.isSome(period)) {
444
604
  const isEnd = textAt(edge.value, 1) === "konec";
445
605
  const canonical = `${isEnd ? "konec" : "začátek"} ${period.value.canonical}`;
@@ -452,7 +612,8 @@ const parsePeriod = (input) => {
452
612
  "celý ",
453
613
  "celé "
454
614
  ].find((prefix) => input.startsWith(prefix));
455
- return parseBasePeriod(wrapper === void 0 ? input : input.slice(wrapper.length));
615
+ const base = parseBasePeriod(wrapper === void 0 ? input : input.slice(wrapper.length));
616
+ return Option.isSome(base) ? base : parseCalendarOffset(input);
456
617
  };
457
618
  const countedUnit = (value) => unitAliases.find((entry) => entry[0] === value)?.[1];
458
619
  const countedUnitPattern = "den|dny|dnů|dnem|týden|týdny|týdnů|týdnem|měsíc|měsíce|měsíců|měsícem|měsíci|čtvrtletí|čtvrtletím|čtvrtletími|rok|roky|let|rokem|lety";
@@ -464,6 +625,24 @@ const rollingFuturePatterns = [countedPattern("^(?:příští|následující) ([
464
625
  const rollingSincePattern = countedPattern("^za poslední ([1-9]\\d*) (UNIT)$");
465
626
  const rollingBarePattern = countedPattern("^([1-9]\\d*) (UNIT)$");
466
627
  const firstPatternMatch = (input, patterns) => Option.firstSomeOf(patterns.map((pattern) => String$1.match(pattern)(input)));
628
+ const singularRollingPhrases = units.flatMap((entry) => [
629
+ {
630
+ phrase: `poslední ${entry.singular}`,
631
+ entry,
632
+ future: false
633
+ },
634
+ {
635
+ phrase: `během posledního ${entry.durationGenitive}`,
636
+ entry,
637
+ future: false
638
+ },
639
+ {
640
+ phrase: `během následujícího ${entry.durationGenitive}`,
641
+ entry,
642
+ future: true
643
+ }
644
+ ]);
645
+ const singularRollingCanonical = (entry, future) => `během ${future ? "následujícího" : "posledního"} ${entry.durationGenitive}`;
467
646
  const countNoun = (amount, entry) => {
468
647
  if (amount === 1) return entry.singular;
469
648
  const lastTwo = amount % 100;
@@ -484,9 +663,14 @@ const parseCalendarOffset = (input) => {
484
663
  const futureNoun = countNoun(amount.value, entry);
485
664
  const pastNoun = amount.value === 1 ? entry.pastSingular : entry.pastPlural;
486
665
  const canonical = direction < 0 ? `před ${amount.value} ${pastNoun}` : `za ${amount.value} ${futureNoun}`;
487
- return Option.some(candidate(periodRange(relativePeriod(unit, direction, canonical)), canonical));
666
+ return Option.some(relativePeriod(unit, direction, canonical));
488
667
  };
489
668
  const parseRollingPeriod = (input) => {
669
+ const singular = singularRollingPhrases.find((entry) => entry.phrase === input);
670
+ if (singular !== void 0) {
671
+ const range = singular.future ? futureRange(1, singular.entry.unit) : trailingRange(1, singular.entry.unit);
672
+ return Option.some(candidate(range, singularRollingCanonical(singular.entry, singular.future)));
673
+ }
490
674
  const since = String$1.match(rollingSincePattern)(input);
491
675
  const past = firstPatternMatch(input, rollingPastPatterns);
492
676
  const bare = String$1.match(rollingBarePattern)(input);
@@ -505,9 +689,22 @@ const parseRollingPeriod = (input) => {
505
689
  if (entry === void 0) return Option.none();
506
690
  const isFuture = Option.isSome(future);
507
691
  const range = isFuture ? futureRange(amount.value, unit) : trailingRange(amount.value, unit);
692
+ if (amount.value === 1) return Option.some(candidate(range, singularRollingCanonical(entry, isFuture)));
508
693
  const modifier = isFuture ? "příští" : "poslední";
509
694
  return Option.some(candidate(range, `${modifier} ${amount.value} ${countNoun(amount.value, entry)}`));
510
695
  };
696
+ const parseElidedDateRange = (input) => {
697
+ const joined = String$1.match(/^od ([0-3]?\d)\.?(?: do) ([0-3]?\d)\.? ([a-záčďéěíňóřšťúůýž]+\.?)(?: (\d{4}))?$/u)(input);
698
+ const dashed = String$1.match(/^([0-3]?\d)\.?[–—-]([0-3]?\d)\.? ([a-záčďéěíňóřšťúůýž]+\.?)(?: (\d{4}))?$/u)(input);
699
+ const match = Option.firstSomeOf([joined, dashed]);
700
+ if (Option.isNone(match)) return Option.none();
701
+ const lowerDay = textAt(match.value, 1);
702
+ const upperDay = textAt(match.value, 2);
703
+ const month = textAt(match.value, 3);
704
+ const year = textAt(match.value, 4);
705
+ const suffix = year.length === 0 ? "" : ` ${year}`;
706
+ return joinedPeriodCandidate(`od ${lowerDay}. ${month}${suffix} do ${upperDay}. ${month}${suffix}`, [["od ", " do "]], parsePeriod, (lower, upper) => `od ${lower} do ${upper} včetně`);
707
+ };
511
708
  const boundaryCandidate = (input) => {
512
709
  const included = String$1.match(/^do (.+) včetně$/u)(input);
513
710
  if (Option.isSome(included)) return openBoundaryCandidate(`do ${textAt(included.value, 1)}`, [["do ", "through"]], parsePeriod);
@@ -533,13 +730,17 @@ const parseCzech = (input) => {
533
730
  "ode dneška",
534
731
  "od teď"
535
732
  ].includes(input)) return Option.some(candidate(fromNowRange(), "od nynějška"));
536
- const offset = parseCalendarOffset(input);
537
- if (Option.isSome(offset)) return offset;
538
733
  const rolling = parseRollingPeriod(input);
539
734
  if (Option.isSome(rolling)) return rolling;
540
735
  const toDate = toDatePhrases.find((entry) => entry.phrase === input);
541
736
  if (toDate !== void 0) return Option.some(candidate(periodToDateRange(toDate.entry.unit), toDate.entry.toDate));
542
- const nowBounded = joinedNowCandidate(input, [["od ", " do dneška"], ["mezi ", " a dneškem"]], ["od dneška do ", "mezi dneškem a "], parsePeriod, (period) => `od ${period} do dneška`, (period) => `od dneška do ${period}`);
737
+ const elided = parseElidedDateRange(input);
738
+ if (Option.isSome(elided)) return elided;
739
+ const nowBounded = joinedNowCandidate(input, [["od ", " do dneška"], ["mezi ", " a dneškem"]], [
740
+ "od dneška do ",
741
+ "mezi dneškem a ",
742
+ "dnešek až "
743
+ ], parsePeriod, (period) => `od ${period} do dneška`, (period) => `od dneška do ${period}`);
543
744
  if (Option.isSome(nowBounded)) return nowBounded;
544
745
  const bounded = joinedPeriodCandidate(input, [
545
746
  ["od ", " do "],
@@ -551,7 +752,7 @@ const parseCzech = (input) => {
551
752
  if (Option.isSome(bounded)) return bounded;
552
753
  const boundary = boundaryCandidate(input);
553
754
  if (Option.isSome(boundary)) return boundary;
554
- return Option.map(parsePeriod(input), (period) => candidate(periodRange(period), period.canonical));
755
+ return parsePeriod(input).pipe(Option.map((period) => candidate(periodRange(period), period.canonical)));
555
756
  };
556
757
  const staticPeriodPhrases = [
557
758
  ...periodAliases.map((entry) => entry[0]),
@@ -561,6 +762,8 @@ const staticPeriodPhrases = [
561
762
  "předminulý víkend",
562
763
  "přespříští víkend",
563
764
  ...periodAliases.flatMap((entry) => [`začátek ${entry[0]}`, `konec ${entry[0]}`]),
765
+ ...nextWeekdays.map((entry) => entry.phrase),
766
+ ...months.flatMap((month) => [`minulý ${month}`, `příští ${month}`]),
564
767
  ...[
565
768
  1,
566
769
  2,
@@ -606,6 +809,7 @@ const countedSuggestions = (input) => {
606
809
  const czechSuggestionPhrases = [
607
810
  ...units.map((entry) => entry.toDate),
608
811
  ...units.map((entry) => entry.remaining),
812
+ ...singularRollingPhrases.map((entry) => entry.phrase),
609
813
  ...staticPeriodPhrases,
610
814
  ...prefixNaturalPhrases(staticPeriodPhrases, boundaryPrefixes),
611
815
  "dosud",
@@ -621,6 +825,18 @@ const suggestCzech = (input, limit) => {
621
825
  ], limit);
622
826
  };
623
827
  const renderCzech = (range) => {
828
+ const shifted = decomposeShiftedPeriodRange(range);
829
+ if (Option.isSome(shifted)) {
830
+ const base = renderCzech(shifted.value.baseRange);
831
+ const entry = units.find((unit) => unit.unit === shifted.value.unit);
832
+ if (Option.isSome(base) && entry !== void 0) {
833
+ const amount = Math.abs(shifted.value.amount);
834
+ const pastNoun = amount === 1 ? entry.pastSingular : entry.pastPlural;
835
+ const noun = shifted.value.amount < 0 ? pastNoun : countNoun(amount, entry);
836
+ const direction = shifted.value.amount < 0 ? "před" : "za";
837
+ return Option.some(`${base.value} ${direction} ${amount} ${noun}`);
838
+ }
839
+ }
624
840
  const offset = calendarPeriodOffset(range);
625
841
  if (Option.isSome(offset) && Math.abs(offset.value.amount) > 1) {
626
842
  const entry = units.find((unit) => unit.unit === offset.value.unit);
@@ -634,20 +850,26 @@ const renderCzech = (range) => {
634
850
  const future = futurePeriod(range);
635
851
  if (Option.isSome(future)) {
636
852
  const entry = units.find((unit) => unit.unit === future.value.unit);
637
- if (entry !== void 0) return Option.some(`příští ${future.value.amount} ${countNoun(future.value.amount, entry)}`);
853
+ if (entry !== void 0) return Option.some(future.value.amount === 1 ? singularRollingCanonical(entry, true) : `příští ${future.value.amount} ${countNoun(future.value.amount, entry)}`);
638
854
  }
639
855
  const trailing = trailingPeriod(range);
640
856
  if (Option.isSome(trailing)) {
641
857
  const entry = units.find((unit) => unit.unit === trailing.value.unit);
642
- if (entry !== void 0) return Option.some(`poslední ${trailing.value.amount} ${countNoun(trailing.value.amount, entry)}`);
858
+ if (entry !== void 0) return Option.some(trailing.value.amount === 1 ? singularRollingCanonical(entry, false) : `poslední ${trailing.value.amount} ${countNoun(trailing.value.amount, entry)}`);
643
859
  }
644
- const periods = [...staticPeriods, ...periodsFromPhrases([...datedPeriods(range, months), ...datedQuarterPeriods(range)], parsePeriod)];
860
+ const periods = [
861
+ ...staticPeriods,
862
+ ...currentYearDatePeriods(range, currentDateLabel),
863
+ ...periodsFromPhrases([...datedPeriods(range, months), ...datedQuarterPeriods(range)], parsePeriod)
864
+ ];
645
865
  return renderPeriodRange(range, [...units.map((entry) => candidate(periodToDateRange(entry.unit), entry.toDate)), ...units.map((entry) => candidate(remainingPeriodRange(entry.unit), entry.remaining))], periods, (period) => `od ${period}`, (period) => `před ${period}`, (period) => `do ${period} včetně`, (period) => `po ${period}`, (lower, upper) => `od ${lower} do ${upper} včetně`, (period) => `od ${period} do dneška`, (period) => `od dneška do ${period}`, () => "dosud", () => "od nynějška");
646
866
  };
647
867
  const CzechContribution = new BaseLanguageContribution({
648
868
  locale: "cs",
649
869
  vocabulary: [
650
870
  ...months,
871
+ ...weekdays,
872
+ ...weekdayGenitives,
651
873
  ...monthAbbreviations.flatMap((aliases) => aliases),
652
874
  ...units.flatMap((entry) => [
653
875
  entry.singular,
@@ -678,8 +900,8 @@ const CzechContribution = new BaseLanguageContribution({
678
900
  "začátek",
679
901
  "zbytek"
680
902
  ],
681
- normalize: normalizeNaturalText,
682
- correct: correctWhitespaceSeparatedText,
903
+ normalize: normalizeCzech,
904
+ correct: correctCzech,
683
905
  parseExact: parseCzech,
684
906
  suggest: suggestCzech,
685
907
  render: renderCzech