chronolizer 0.4.0 → 0.4.1

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.
@@ -1,10 +1,9 @@
1
- import { isIsoDate } from "../ast/schemas.mjs";
2
1
  import { BaseLanguageContribution } from "../language/model.mjs";
3
2
  import { normalizeNaturalText } from "../natural/text.mjs";
4
3
  import { defineLanguagePlugin, languagePluginsLayer } from "../language/registry.mjs";
5
4
  import { correctWhitespaceSeparatedText } from "../natural/correction.mjs";
6
5
  import { completeNaturalPhrases, fixedCalendarPeriodPhrases, naturalCount, prefixNaturalPhrases } from "../natural/suggestion.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";
6
+ import { absoluteDatePeriod, calendarPeriodOffset, candidate, compileCountAliasNormalizer, compoundCountAliases, countAliasVocabulary, currentYearDatePeriods, datedPeriods, datedQuarterPeriods, decimalTens, decomposeShiftedPeriodRange, fixedMonthPeriod, fixedQuarterPeriod, fixedYearPeriod, fromNowRange, futurePeriod, futureRange, 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
7
  import { Effect, Option, String as String$1 } from "effect";
9
8
  //#region src/locales/de.ts
10
9
  const months = [
@@ -21,6 +20,20 @@ const months = [
21
20
  "november",
22
21
  "dezember"
23
22
  ];
23
+ const minimumMonthDays = [
24
+ 31,
25
+ 28,
26
+ 31,
27
+ 30,
28
+ 31,
29
+ 30,
30
+ 31,
31
+ 31,
32
+ 30,
33
+ 31,
34
+ 30,
35
+ 31
36
+ ];
24
37
  const germanCountWords = [
25
38
  "zwei",
26
39
  "drei",
@@ -56,11 +69,14 @@ const germanCountAliases = [...sequentialCountAliases(germanCountWords.map((word
56
69
  const normalizeGermanCounts = compileCountAliasNormalizer(germanCountAliases);
57
70
  const germanCountVocabulary = new Set(countAliasVocabulary(germanCountAliases));
58
71
  const correctGerman = (input, vocabulary) => correctWhitespaceSeparatedText(input, vocabulary, germanCountVocabulary);
59
- const normalizeGerman = (input, locale) => normalizeGermanCounts(normalizeNaturalText(input, locale));
60
72
  const monthAbbreviations = [
61
73
  ["jan"],
62
74
  ["feb"],
63
- ["mär", "mrz"],
75
+ [
76
+ "mär",
77
+ "mrz",
78
+ "maerz"
79
+ ],
64
80
  ["apr"],
65
81
  ["mai"],
66
82
  ["jun"],
@@ -215,390 +231,45 @@ const currentBoundaryPhrases = unitPhrases.flatMap((entry) => [
215
231
  entry,
216
232
  phrase
217
233
  })));
218
- const declinedBoundaryPeriods = [
219
- [
220
- "diese woche",
221
- "week",
222
- 0,
223
- "dieser woche",
224
- "diese woche"
225
- ],
226
- [
227
- "letzte woche",
228
- "week",
229
- -1,
230
- "letzter woche",
231
- "letzte woche"
232
- ],
233
- [
234
- "nächste woche",
235
- "week",
236
- 1,
237
- "nächster woche",
238
- "nächste woche"
239
- ],
240
- [
241
- "dieser monat",
242
- "month",
243
- 0,
244
- "diesem monat",
245
- "diesen monat"
246
- ],
247
- [
248
- "letzter monat",
249
- "month",
250
- -1,
251
- "letztem monat",
252
- "letzten monat"
253
- ],
254
- [
255
- "nächster monat",
256
- "month",
257
- 1,
258
- "nächstem monat",
259
- "nächsten monat"
260
- ],
261
- [
262
- "dieses quartal",
263
- "quarter",
264
- 0,
265
- "diesem quartal",
266
- "dieses quartal"
267
- ],
268
- [
269
- "letztes quartal",
270
- "quarter",
271
- -1,
272
- "letztem quartal",
273
- "letztes quartal"
274
- ],
275
- [
276
- "nächstes quartal",
277
- "quarter",
278
- 1,
279
- "nächstem quartal",
280
- "nächstes quartal"
281
- ],
282
- [
283
- "dieses jahr",
284
- "year",
285
- 0,
286
- "diesem jahr",
287
- "dieses jahr"
288
- ],
289
- [
290
- "letztes jahr",
291
- "year",
292
- -1,
293
- "letztem jahr",
294
- "letztes jahr"
295
- ],
296
- [
297
- "nächstes jahr",
298
- "year",
299
- 1,
300
- "nächstem jahr",
301
- "nächstes jahr"
302
- ]
303
- ];
304
- const relativeAliases = [
305
- ...unitPhrases.flatMap((entry) => [
306
- [
307
- entry.currentGenitive,
308
- entry.unit,
309
- 0,
310
- entry.current
311
- ],
312
- [
313
- entry.previousGenitive,
314
- entry.unit,
315
- -1,
316
- entry.previous
317
- ],
318
- [
319
- entry.nextGenitive,
320
- entry.unit,
321
- 1,
322
- entry.next
323
- ]
324
- ]),
325
- ...declinedBoundaryPeriods.map(([canonical, unit, direction, dative]) => [
326
- dative,
327
- unit,
328
- direction,
329
- canonical
330
- ]),
331
- [
332
- "vorgestern",
333
- "day",
334
- -2,
335
- "vorgestern"
336
- ],
337
- [
338
- "vorletzten tag",
339
- "day",
340
- -2,
341
- "vorletzter tag"
342
- ],
343
- [
344
- "übernächsten tag",
345
- "day",
346
- 2,
347
- "übernächster tag"
348
- ],
349
- [
350
- "vorletzte woche",
351
- "week",
352
- -2,
353
- "vorletzte woche"
354
- ],
355
- [
356
- "übernächste woche",
357
- "week",
358
- 2,
359
- "übernächste woche"
360
- ],
361
- [
362
- "vorletzten monat",
363
- "month",
364
- -2,
365
- "vorletzter monat"
366
- ],
367
- [
368
- "übernächsten monat",
369
- "month",
370
- 2,
371
- "übernächster monat"
372
- ],
373
- [
374
- "vorletztes quartal",
375
- "quarter",
376
- -2,
377
- "vorletztes quartal"
378
- ],
379
- [
380
- "übernächstes quartal",
381
- "quarter",
382
- 2,
383
- "übernächstes quartal"
384
- ],
385
- [
386
- "vorletztes jahr",
387
- "year",
388
- -2,
389
- "vorletztes jahr"
390
- ],
391
- [
392
- "übernächstes jahr",
393
- "year",
394
- 2,
395
- "übernächstes jahr"
396
- ],
397
- [
398
- "letzten tag",
399
- "day",
400
- -1,
401
- "gestern"
402
- ],
403
- [
404
- "vergangenen tag",
405
- "day",
406
- -1,
407
- "gestern"
408
- ],
409
- [
410
- "vorherigen tag",
411
- "day",
412
- -1,
413
- "gestern"
414
- ],
415
- [
416
- "diesen tag",
417
- "day",
418
- 0,
419
- "heute"
420
- ],
421
- [
422
- "heutigen tag",
423
- "day",
424
- 0,
425
- "heute"
426
- ],
427
- [
428
- "nächsten tag",
429
- "day",
430
- 1,
431
- "morgen"
432
- ],
433
- [
434
- "kommenden tag",
435
- "day",
436
- 1,
437
- "morgen"
438
- ],
439
- [
440
- "vergangene woche",
441
- "week",
442
- -1,
443
- "letzte woche"
444
- ],
445
- [
446
- "vorherige woche",
447
- "week",
448
- -1,
449
- "letzte woche"
450
- ],
451
- [
452
- "aktuelle woche",
453
- "week",
454
- 0,
455
- "diese woche"
456
- ],
457
- [
458
- "laufende woche",
459
- "week",
460
- 0,
461
- "diese woche"
462
- ],
463
- [
464
- "kommende woche",
465
- "week",
466
- 1,
467
- "nächste woche"
468
- ],
469
- [
470
- "folgende woche",
471
- "week",
472
- 1,
473
- "nächste woche"
474
- ],
475
- [
476
- "letzten monat",
477
- "month",
478
- -1,
479
- "letzter monat"
480
- ],
481
- [
482
- "vergangenen monat",
483
- "month",
484
- -1,
485
- "letzter monat"
486
- ],
487
- [
488
- "vorherigen monat",
489
- "month",
490
- -1,
491
- "letzter monat"
492
- ],
493
- [
494
- "diesen monat",
495
- "month",
496
- 0,
497
- "dieser monat"
498
- ],
499
- [
500
- "aktuellen monat",
501
- "month",
502
- 0,
503
- "dieser monat"
504
- ],
505
- [
506
- "laufenden monat",
507
- "month",
508
- 0,
509
- "dieser monat"
510
- ],
511
- [
512
- "nächsten monat",
513
- "month",
514
- 1,
515
- "nächster monat"
516
- ],
517
- [
518
- "kommenden monat",
519
- "month",
520
- 1,
521
- "nächster monat"
522
- ],
523
- [
524
- "folgenden monat",
525
- "month",
526
- 1,
527
- "nächster monat"
528
- ],
529
- [
530
- "vergangenes quartal",
531
- "quarter",
532
- -1,
533
- "letztes quartal"
534
- ],
535
- [
536
- "vorheriges quartal",
537
- "quarter",
538
- -1,
539
- "letztes quartal"
540
- ],
541
- [
542
- "aktuelles quartal",
543
- "quarter",
544
- 0,
545
- "dieses quartal"
546
- ],
547
- [
548
- "laufendes quartal",
549
- "quarter",
550
- 0,
551
- "dieses quartal"
552
- ],
553
- [
554
- "kommendes quartal",
555
- "quarter",
556
- 1,
557
- "nächstes quartal"
558
- ],
559
- [
560
- "folgendes quartal",
561
- "quarter",
562
- 1,
563
- "nächstes quartal"
564
- ],
565
- [
566
- "vergangenes jahr",
567
- "year",
568
- -1,
569
- "letztes jahr"
570
- ],
571
- [
572
- "vorheriges jahr",
573
- "year",
574
- -1,
575
- "letztes jahr"
576
- ],
577
- [
578
- "aktuelles jahr",
579
- "year",
580
- 0,
581
- "dieses jahr"
582
- ],
583
- [
584
- "laufendes jahr",
585
- "year",
586
- 0,
587
- "dieses jahr"
588
- ],
589
- [
590
- "kommendes jahr",
591
- "year",
592
- 1,
593
- "nächstes jahr"
594
- ],
595
- [
596
- "folgendes jahr",
597
- "year",
598
- 1,
599
- "nächstes jahr"
600
- ]
234
+ const relativeModifierStems = [
235
+ ["vorletzt", -2],
236
+ ["übernächst", 2],
237
+ ["vergangen", -1],
238
+ ["vorherig", -1],
239
+ ["letzt", -1],
240
+ ["heutig", 0],
241
+ ["dies", 0],
242
+ ["aktuell", 0],
243
+ ["laufend", 0],
244
+ ["nächst", 1],
245
+ ["kommend", 1],
246
+ ["folgend", 1]
601
247
  ];
248
+ const relativeModifierVocabulary = relativeModifierStems.flatMap(([stem]) => [
249
+ "e",
250
+ "er",
251
+ "es",
252
+ "en",
253
+ "em"
254
+ ].map((ending) => `${stem}${ending}`));
255
+ const relativeUnitCanonical = (entry, direction) => {
256
+ if (direction === -1) return entry.previous;
257
+ if (direction === 0) return entry.current;
258
+ if (direction === 1) return entry.next;
259
+ return `${direction < 0 ? "vor" : "in"} 2 ${title(entry.dative)}`;
260
+ };
261
+ const parseModifiedUnit = (input) => {
262
+ const match = String$1.match(/^([a-zäöüß]+) ([a-zäöüß]+)$/u)(input);
263
+ if (Option.isNone(match)) return Option.none();
264
+ const modifier = relativeModifierStems.find(([stem]) => textAt(match.value, 1).startsWith(stem));
265
+ const entry = unitPhrases.find((unit) => [
266
+ unit.noun,
267
+ unit.plural,
268
+ unit.dative,
269
+ unit.compound
270
+ ].includes(textAt(match.value, 2)));
271
+ return modifier === void 0 || entry === void 0 ? Option.none() : Option.some(relativePeriod(entry.unit, modifier[1], canonicalRelative(relativeUnitCanonical(entry, modifier[1]))));
272
+ };
602
273
  const monthNumber = (value) => {
603
274
  const normalized = value.endsWith(".") ? value.slice(0, -1) : value;
604
275
  const fullIndex = months.findIndex((month) => month === normalized);
@@ -606,11 +277,26 @@ const monthNumber = (value) => {
606
277
  const shortIndex = monthAbbreviations.findIndex((aliases) => aliases.some((alias) => alias === normalized));
607
278
  return shortIndex === -1 ? void 0 : shortIndex + 1;
608
279
  };
280
+ const normalizeGerman = (input, locale) => {
281
+ const normalized = normalizeGermanCounts(normalizeNaturalText(input, locale));
282
+ if (normalized.endsWith(" auflaufend")) return `seit ${normalized.slice(0, -11)}`;
283
+ const fullMonth = String$1.match(/^(?:([a-zäöüß]+\.?) per )?(?:(0?1)\.-)?([0-3]?\d)\.([01]?\d)\.?$/u)(normalized);
284
+ if (Option.isNone(fullMonth)) return normalized;
285
+ const named = textAt(fullMonth.value, 1);
286
+ const lower = textAt(fullMonth.value, 2);
287
+ if (named.length === 0 && lower.length === 0) return normalized;
288
+ const numericMonth = Number(textAt(fullMonth.value, 4));
289
+ const month = named.length === 0 ? numericMonth : monthNumber(named);
290
+ return month !== void 0 && month === numericMonth && Number(textAt(fullMonth.value, 3)) === minimumMonthDays[month - 1] ? textAt(months, month - 1) : normalized;
291
+ };
609
292
  const currentDateLabel = (day, month) => `${day}. ${title(textAt(months, month - 1))}`;
610
293
  const withRelativeCase = (canonical, grammaticalCase) => {
611
- const period = declinedBoundaryPeriods.find((entry) => canonical === canonicalRelative(entry[0]));
612
- if (period === void 0) return canonical;
613
- return canonicalRelative(period[grammaticalCase === "dative" ? 3 : 4]);
294
+ const separator = canonical.indexOf(" ");
295
+ if (separator === -1) return canonical;
296
+ const modifier = canonical.slice(0, separator);
297
+ if (!relativeModifierStems.some(([stem]) => modifier.startsWith(stem))) return canonical;
298
+ if (grammaticalCase === "dative") return `${modifier.endsWith("e") ? `${modifier}r` : `${modifier.slice(0, -2)}em`}${canonical.slice(separator)}`;
299
+ return `${modifier.endsWith("er") ? `${modifier.slice(0, -2)}en` : modifier}${canonical.slice(separator)}`;
614
300
  };
615
301
  const withRelativeGenitive = (canonical) => {
616
302
  for (const entry of unitPhrases) {
@@ -624,15 +310,7 @@ const parseNamedDate = (input) => {
624
310
  const named = String$1.match(/^([0-3]?\d)\.? ([a-zäöüß]+\.?) (\d{4})$/u)(input);
625
311
  if (Option.isSome(named)) return namedDatePeriod(textAt(named.value, 3), textAt(named.value, 2), textAt(named.value, 1), monthNumber, (day, month, year) => `${day}. ${title(textAt(months, month - 1))} ${year}`);
626
312
  const numeric = String$1.match(/^([0-3]?\d)\.([01]?\d)\.(\d{4})$/u)(input);
627
- if (Option.isSome(numeric)) {
628
- const year = validYear(textAt(numeric.value, 3));
629
- const day = Number(textAt(numeric.value, 1));
630
- const month = Number(textAt(numeric.value, 2));
631
- if (year !== void 0 && month >= 1 && month <= 12) {
632
- const value = isoDate(year, month, day);
633
- if (isIsoDate(value) && value !== "9999-12-31") return Option.some(fixedDatePeriod(value, `${day}. ${title(textAt(months, month - 1))} ${year}`));
634
- }
635
- }
313
+ if (Option.isSome(numeric)) return namedDatePeriod(textAt(numeric.value, 3), textAt(numeric.value, 2), textAt(numeric.value, 1), Number, (day, month, year) => `${day}. ${title(textAt(months, month - 1))} ${year}`);
636
314
  const current = String$1.match(/^([0-3]?\d)\.? ([a-zäöüß]+\.?)$/u)(input);
637
315
  return Option.isSome(current) ? namedCurrentYearDatePeriod(textAt(current.value, 2), textAt(current.value, 1), monthNumber, currentDateLabel) : Option.none();
638
316
  };
@@ -699,12 +377,16 @@ const parseDatedPeriod = (input) => {
699
377
  return Option.none();
700
378
  };
701
379
  const parseRelativeMonth = (input) => {
702
- const prefixedRelativeMonth = String$1.match(/^(letzter|dieser|nächster) ([a-zäöüß]+\.?)$/u)(input);
380
+ const prefixedRelativeMonth = String$1.match(/^(letzt|dies|nächst)(?:er|en|em) ([a-zäöüß]+\.?)$/u)(input);
703
381
  if (Option.isSome(prefixedRelativeMonth)) {
704
382
  const month = monthNumber(textAt(prefixedRelativeMonth.value, 2));
705
383
  if (month !== void 0) {
706
- const modifier = textAt(prefixedRelativeMonth.value, 1);
707
- const direction = relativeDirection(modifier);
384
+ const direction = relativeDirection(textAt(prefixedRelativeMonth.value, 1));
385
+ const modifier = textAt([
386
+ "letzter",
387
+ "dieser",
388
+ "nächster"
389
+ ], direction + 1);
708
390
  return Option.some(monthOfRelativeYear(month, direction, `${modifier} ${title(textAt(months, month - 1))}`));
709
391
  }
710
392
  }
@@ -736,28 +418,58 @@ const parseBasePeriod = (input) => {
736
418
  if (input === relative.next) direction = 1;
737
419
  return Option.some(relativePeriod(relative.unit, direction, canonicalRelative(input)));
738
420
  }
739
- const alias = relativeAliases.find((entry) => entry[0] === input);
740
- if (alias !== void 0) return Option.some(relativePeriod(alias[1], alias[2], canonicalRelative(alias[3])));
741
- return Option.none();
421
+ if (input === "vorgestern") return Option.some(relativePeriod("day", -2, "vorgestern"));
422
+ return parseModifiedUnit(input);
423
+ };
424
+ const countedUnit = (value, amount, dative = false) => unitPhrases.find((entry) => {
425
+ if (amount === 1) return value === entry.noun;
426
+ return value === (dative ? entry.dative : entry.plural);
427
+ });
428
+ const parseCalendarOffsetValue = (input) => {
429
+ const singular = String$1.match(/^(vor|in) (ein[^ ]* [^ ]+)$/u)(input);
430
+ if (Option.isSome(singular)) {
431
+ const entry = unitPhrases.find((unit) => unit.indefiniteDative === textAt(singular.value, 2));
432
+ if (entry !== void 0) {
433
+ const past = textAt(singular.value, 1) === "vor";
434
+ return Option.some({
435
+ amount: past ? -1 : 1,
436
+ canonical: `${past ? "vor" : "in"} ${canonicalRelative(entry.indefiniteDative)}`,
437
+ entry
438
+ });
439
+ }
440
+ }
441
+ const prefixed = String$1.match(/^(vor|in) ([1-9]\d*) ([^ ]+)$/u)(input);
442
+ const prior = String$1.match(/^([1-9]\d*) ([^ ]+) zuvor$/u)(input);
443
+ const match = Option.firstSomeOf([prefixed, prior]);
444
+ if (Option.isNone(match)) return Option.none();
445
+ const amountText = textAt(match.value, Option.isSome(prefixed) ? 2 : 1);
446
+ const unitText = textAt(match.value, Option.isSome(prefixed) ? 3 : 2);
447
+ const amount = parseTrailingCount(amountText);
448
+ if (Option.isNone(amount)) return Option.none();
449
+ const entry = countedUnit(unitText, amount.value, Option.isSome(prefixed));
450
+ if (entry === void 0) return Option.none();
451
+ const past = Option.isSome(prior) || Option.isSome(prefixed) && textAt(match.value, 1) === "vor";
452
+ const direction = past ? "vor" : "in";
453
+ const noun = amount.value === 1 ? entry.noun : entry.dative;
454
+ return Option.some({
455
+ amount: past ? -amount.value : amount.value,
456
+ canonical: `${direction} ${amount.value} ${title(noun)}`,
457
+ entry
458
+ });
742
459
  };
743
460
  const parsePeriod = (input) => {
744
- const shifted = String$1.match(/^(.+) (vor|in) ([1-9]\d*) (tag|tagen|woche|wochen|monat|monaten|quartal|quartalen|jahr|jahren)$/u)(input);
745
- if (Option.isSome(shifted)) {
746
- const amount = parseTrailingCount(textAt(shifted.value, 3));
747
- const unitText = textAt(shifted.value, 4);
748
- const entry = unitPhrases.find((unit) => [
749
- unit.noun,
750
- unit.plural,
751
- unit.dative
752
- ].includes(unitText));
753
- const period = parsePeriod(textAt(shifted.value, 1));
754
- if (Option.isSome(amount) && entry !== void 0 && Option.isSome(period)) {
755
- const past = textAt(shifted.value, 2) === "vor";
756
- const direction = past ? -amount.value : amount.value;
757
- const noun = amount.value === 1 ? entry.noun : entry.dative;
758
- const canonical = `${period.value.canonical} ${past ? "vor" : "in"} ${amount.value} ${title(noun)}`;
759
- return Option.some(shiftPeriod(period.value, direction, entry.unit, canonical));
461
+ let separator = input.indexOf(" ");
462
+ while (separator !== -1) {
463
+ const offsetStart = separator + 1;
464
+ const firstOffsetCharacter = input[offsetStart] ?? "";
465
+ if (input.startsWith("vor ", offsetStart) || input.startsWith("in ", offsetStart) || firstOffsetCharacter >= "1" && firstOffsetCharacter <= "9" && input.endsWith(" zuvor")) {
466
+ const offset = parseCalendarOffsetValue(input.slice(offsetStart));
467
+ if (Option.isSome(offset)) {
468
+ const period = parsePeriod(input.slice(0, separator));
469
+ if (Option.isSome(period)) return Option.some(shiftPeriod(period.value, offset.value.amount, offset.value.entry.unit, `${period.value.canonical} ${offset.value.canonical}`));
470
+ }
760
471
  }
472
+ separator = input.indexOf(" ", offsetStart);
761
473
  }
762
474
  const edge = String$1.match(/^(anfang|beginn|ende) (.+)$/u)(input);
763
475
  if (Option.isSome(edge)) {
@@ -814,50 +526,19 @@ const boundaryCandidate = (input) => Option.map(openBoundaryCandidate(input, [
814
526
  ["bis ", "before"],
815
527
  ["ab ", "since"]
816
528
  ], parsePeriod), (result) => candidate(result.range, canonicalRelative(input)));
817
- const countedUnit = (value, amount, dative = false) => unitPhrases.find((entry) => {
818
- if (amount === 1) return value === entry.noun;
819
- return value === (dative ? entry.dative : entry.plural);
820
- });
821
- const parseCalendarOffset = (input) => {
822
- const singular = String$1.match(/^(vor|in) (einem tag|einer woche|einem monat|einem quartal|einem jahr)$/u)(input);
823
- if (Option.isSome(singular)) {
824
- const entry = unitPhrases.find((unit) => unit.indefiniteDative === textAt(singular.value, 2));
825
- if (entry !== void 0) {
826
- const isPast = textAt(singular.value, 1) === "vor";
827
- const direction = isPast ? -1 : 1;
828
- const canonical = `${isPast ? "vor" : "in"} ${canonicalRelative(entry.indefiniteDative)}`;
829
- return Option.some(relativePeriod(entry.unit, direction, canonical));
830
- }
831
- }
832
- const prefixed = String$1.match(/^(vor|in) ([1-9]\d*) (tag|tagen|woche|wochen|monat|monaten|quartal|quartalen|jahr|jahren)$/u)(input);
833
- const prior = String$1.match(/^([1-9]\d*) (tag|tage|woche|wochen|monat|monate|quartal|quartale|jahr|jahre) zuvor$/u)(input);
834
- const match = Option.firstSomeOf([prefixed, prior]);
835
- if (Option.isNone(match)) return Option.none();
836
- const amountText = textAt(match.value, Option.isSome(prefixed) ? 2 : 1);
837
- const unitText = textAt(match.value, Option.isSome(prefixed) ? 3 : 2);
838
- const amount = parseTrailingCount(amountText);
839
- if (Option.isNone(amount)) return Option.none();
840
- const entry = countedUnit(unitText, amount.value, Option.isSome(prefixed));
841
- if (entry === void 0) return Option.none();
842
- const isPast = Option.isSome(prior) || Option.isSome(prefixed) && textAt(match.value, 1) === "vor";
843
- const direction = isPast ? -amount.value : amount.value;
844
- const directionName = isPast ? "vor" : "in";
845
- const noun = amount.value === 1 ? entry.noun : entry.dative;
846
- const canonical = `${directionName} ${amount.value} ${title(noun)}`;
847
- return Option.some(relativePeriod(entry.unit, direction, canonical));
848
- };
529
+ const parseCalendarOffset = (input) => parseCalendarOffsetValue(input).pipe(Option.map((offset) => relativePeriod(offset.entry.unit, offset.amount, offset.canonical)));
849
530
  const parseRollingPeriod = (input) => {
850
- const past = String$1.match(/^(?:(?:die letzten|letzten|letzte|vergangene|vorherige) )?([1-9]\d*) (tag|tage|woche|wochen|monat|monate|quartal|quartale|jahr|jahre)$/u)(input);
851
- const pastDative = String$1.match(/^(?:(?:in den letzten|in den vergangenen|in den vorherigen) |seit )([1-9]\d*) (tag|tagen|woche|wochen|monat|monaten|quartal|quartalen|jahr|jahren)$/u)(input);
852
- const singularSince = String$1.match(/^seit (einem tag|einer woche|einem monat|einem quartal|einem jahr)$/u)(input);
853
- const pastGenitive = String$1.match(/^während der letzten ([1-9]\d*) (tag|tage|woche|wochen|monat|monate|quartal|quartale|jahr|jahre)$/u)(input);
854
- const future = String$1.match(/^(?:die nächsten|nächsten|nächste|kommende) ([1-9]\d*) (tag|tage|woche|wochen|monat|monate|quartal|quartale|jahr|jahre)$/u)(input);
855
- const futureDative = String$1.match(/^(?:in den nächsten|in den kommenden|innerhalb von) ([1-9]\d*) (tag|tagen|woche|wochen|monat|monaten|quartal|quartalen|jahr|jahren)$/u)(input);
856
- const singularFuture = String$1.match(/^innerhalb (eines tages|einer woche|eines monats|eines quartals|eines jahres)$/u)(input);
857
- const futureGenitive = String$1.match(/^innerhalb der nächsten ([1-9]\d*) (tag|tage|woche|wochen|monat|monate|quartal|quartale|jahr|jahre)$/u)(input);
531
+ const past = String$1.match(/^(?:(?:die letzten|letzten|letzte|vergangene|vorherige) )?([1-9]\d*) ([^ ]+)$/u)(input);
532
+ const pastDative = String$1.match(/^(?:(?:in den letzten|in den vergangenen|in den vorherigen) |seit )([1-9]\d*) ([^ ]+)$/u)(input);
533
+ const singularSince = String$1.match(/^seit (ein[^ ]* [^ ]+)$/u)(input);
534
+ const pastGenitive = String$1.match(/^während der letzten ([1-9]\d*) ([^ ]+)$/u)(input);
535
+ const future = String$1.match(/^(?:die nächsten|nächsten|nächste|kommende) ([1-9]\d*) ([^ ]+)$/u)(input);
536
+ const futureDative = String$1.match(/^(?:in den nächsten|in den kommenden|innerhalb von) ([1-9]\d*) ([^ ]+)$/u)(input);
537
+ const singularFuture = String$1.match(/^innerhalb (ein[^ ]* [^ ]+)$/u)(input);
538
+ const futureGenitive = String$1.match(/^innerhalb der nächsten ([1-9]\d*) ([^ ]+)$/u)(input);
858
539
  const match = Option.firstSomeOf([
859
- past,
860
540
  pastDative,
541
+ past,
861
542
  singularSince,
862
543
  pastGenitive,
863
544
  future,
@@ -894,6 +575,37 @@ const parseElidedDateRange = (input) => {
894
575
  const suffix = year.length === 0 ? "" : ` ${year}`;
895
576
  return joinedPeriodCandidate(`von ${lowerDay}. ${month}${suffix} bis ${upperDay}. ${month}${suffix}`, [["von ", " bis "]], parsePeriod, (lower, upper) => `von ${lower} bis ${upper}`);
896
577
  };
578
+ const boundedJoins = [
579
+ ["von ", " bis einschließlich "],
580
+ ["vom ", " bis einschließlich "],
581
+ ["von ", " bis zum "],
582
+ ["vom ", " bis zum "],
583
+ ["von ", " bis "],
584
+ ["vom ", " bis "],
585
+ ["zwischen ", " und "],
586
+ ["", " bis einschließlich "],
587
+ ["", " bis "],
588
+ ["", " - "],
589
+ ["", " – "],
590
+ ["", " — "],
591
+ ["zwischen ", "-"],
592
+ ["", "-"],
593
+ ["zwischen ", "–"],
594
+ ["", "–"],
595
+ ["zwischen ", "—"],
596
+ ["", "—"],
597
+ ["zwischen ", "~"],
598
+ ["", "~"]
599
+ ];
600
+ const sharedRelativeYearRange = (input) => {
601
+ const match = String$1.match(/^(.+) (letzten|dieses|nächsten) jahres$/u)(input);
602
+ if (Option.isNone(match)) return Option.none();
603
+ const year = `${textAt(match.value, 2)} jahres`;
604
+ return joinedPeriodCandidate(textAt(match.value, 1), boundedJoins, (periodInput) => {
605
+ const inherited = parsePeriod(`${periodInput} ${year}`);
606
+ return Option.isSome(inherited) ? inherited : parsePeriod(periodInput);
607
+ }, (lower, upper) => `von ${lower} bis ${upper}`);
608
+ };
897
609
  const parseGerman = (input) => {
898
610
  const remaining = remainingPeriodPhrases.find((entry) => entry.phrase === input);
899
611
  if (remaining !== void 0) return Option.some(candidate(remainingPeriodRange(remaining.entry.unit), `Rest ${canonicalRelative(remaining.entry.genitive)}`));
@@ -907,6 +619,8 @@ const parseGerman = (input) => {
907
619
  if (Option.isSome(currentBoundary)) return currentBoundary;
908
620
  const elided = parseElidedDateRange(input);
909
621
  if (Option.isSome(elided)) return elided;
622
+ const inheritedYear = sharedRelativeYearRange(input);
623
+ if (Option.isSome(inheritedYear)) return inheritedYear;
910
624
  const nowBounded = joinedNowCandidate(input, [
911
625
  ["von ", " bis heute"],
912
626
  ["vom ", " bis heute"],
@@ -918,28 +632,7 @@ const parseGerman = (input) => {
918
632
  "jetzt bis "
919
633
  ], parsePeriod, (period) => `von ${period} bis heute`, (period) => `von heute bis ${period}`);
920
634
  if (Option.isSome(nowBounded)) return nowBounded;
921
- const bounded = joinedPeriodCandidate(input, [
922
- ["von ", " bis einschließlich "],
923
- ["vom ", " bis einschließlich "],
924
- ["von ", " bis zum "],
925
- ["vom ", " bis zum "],
926
- ["von ", " bis "],
927
- ["vom ", " bis "],
928
- ["zwischen ", " und "],
929
- ["", " bis einschließlich "],
930
- ["", " bis "],
931
- ["", " - "],
932
- ["", " – "],
933
- ["", " — "],
934
- ["zwischen ", "-"],
935
- ["", "-"],
936
- ["zwischen ", "–"],
937
- ["", "–"],
938
- ["zwischen ", "—"],
939
- ["", "—"],
940
- ["zwischen ", "~"],
941
- ["", "~"]
942
- ], parsePeriod, (lower, upper) => `von ${lower} bis ${upper}`);
635
+ const bounded = joinedPeriodCandidate(input, boundedJoins, parsePeriod, (lower, upper) => `von ${lower} bis ${upper}`);
943
636
  if (Option.isSome(bounded)) return bounded;
944
637
  const boundary = boundaryCandidate(input);
945
638
  if (Option.isSome(boundary)) return boundary;
@@ -978,7 +671,8 @@ const staticPeriodPhrases = [
978
671
  `letzter ${month}`,
979
672
  `nächster ${month}`,
980
673
  `${month} letzten jahres`,
981
- `${month} nächsten jahres`
674
+ `${month} nächsten jahres`,
675
+ `${month} vor einem jahr`
982
676
  ]),
983
677
  ...nextWeekdays.map((entry) => entry.phrase)
984
678
  ];
@@ -1017,12 +711,20 @@ const germanSuggestionPhrases = [
1017
711
  ]),
1018
712
  ...staticPeriodPhrases,
1019
713
  ...currentBoundaryPhrases.map((entry) => entry.phrase),
1020
- ...declinedBoundaryPeriods.flatMap(([, , , dative, accusative]) => [
1021
- `seit ${dative}`,
1022
- `vor ${dative}`,
1023
- `bis einschließlich ${accusative}`,
1024
- `nach ${dative}`
1025
- ]),
714
+ ...unitPhrases.flatMap((entry) => [
715
+ entry.current,
716
+ entry.previous,
717
+ entry.next
718
+ ].flatMap((period) => {
719
+ const dative = withRelativeCase(canonicalRelative(period), "dative");
720
+ const accusative = withRelativeCase(canonicalRelative(period), "accusative");
721
+ return [
722
+ `seit ${dative}`,
723
+ `vor ${dative}`,
724
+ `bis einschließlich ${accusative}`,
725
+ `nach ${dative}`
726
+ ];
727
+ })),
1026
728
  ...prefixNaturalPhrases(months, boundaryPrefixes),
1027
729
  "bis heute",
1028
730
  "ab jetzt"
@@ -1043,9 +745,9 @@ const renderGerman = (range) => {
1043
745
  const entry = unitPhrases.find((unit) => unit.unit === shifted.value.unit);
1044
746
  if (Option.isSome(base) && entry !== void 0) {
1045
747
  const amount = Math.abs(shifted.value.amount);
1046
- const noun = amount === 1 ? entry.noun : entry.dative;
748
+ const offset = amount === 1 ? canonicalRelative(entry.indefiniteDative) : `${amount} ${title(entry.dative)}`;
1047
749
  const direction = shifted.value.amount < 0 ? "vor" : "in";
1048
- return Option.some(`${base.value} ${direction} ${amount} ${title(noun)}`);
750
+ return Option.some(`${base.value} ${direction} ${offset}`);
1049
751
  }
1050
752
  }
1051
753
  const offset = calendarPeriodOffset(range);
@@ -1069,7 +771,7 @@ const renderGerman = (range) => {
1069
771
  ...currentYearDatePeriods(range, currentDateLabel),
1070
772
  ...periodsFromPhrases([...dated, ...dated.flatMap((phrase) => [`anfang ${phrase}`, `ende ${phrase}`])], parsePeriod)
1071
773
  ];
1072
- return renderPeriodRange(range, [...unitPhrases.map((entry) => candidate(periodToDateRange(entry.unit), canonicalToDate(entry))), ...unitPhrases.map((entry) => candidate(remainingPeriodRange(entry.unit), `Rest ${canonicalRelative(entry.genitive)}`))], periods, (period) => `seit ${withRelativeCase(period, "dative")}`, (period) => `vor ${withRelativeCase(period, "dative")}`, (period) => `bis einschließlich ${withRelativeCase(period, "accusative")}`, (period) => `nach ${withRelativeCase(period, "dative")}`, (lower, upper) => `von ${lower} bis ${upper}`, (period) => `von ${period} bis heute`, (period) => `von heute bis ${period}`, () => "bis heute", () => "ab jetzt");
774
+ return renderPeriodRange(range, [...unitPhrases.map((entry) => candidate(periodToDateRange(entry.unit), canonicalToDate(entry))), ...unitPhrases.map((entry) => candidate(remainingPeriodRange(entry.unit), `Rest ${canonicalRelative(entry.genitive)}`))], periods, (period) => `seit ${withRelativeCase(period, "dative")}`, (period) => `vor ${withRelativeCase(period, "dative")}`, (period) => `bis einschließlich ${withRelativeCase(period, "accusative")}`, (period) => `nach ${withRelativeCase(period, "dative")}`, (lower, upper) => `von ${withRelativeCase(lower, "dative")} bis ${withRelativeCase(upper, "accusative")}`, (period) => `von ${period} bis heute`, (period) => `von heute bis ${period}`, () => "bis heute", () => "ab jetzt");
1073
775
  };
1074
776
  const GermanContribution = new BaseLanguageContribution({
1075
777
  locale: "de",
@@ -1085,7 +787,7 @@ const GermanContribution = new BaseLanguageContribution({
1085
787
  ...toDatePhrases.flatMap((entry) => entry.phrase.split(" ")),
1086
788
  ...remainingPeriodPhrases.flatMap((entry) => entry.phrase.split(" ")),
1087
789
  ...currentBoundaryPhrases.flatMap((entry) => entry.phrase.split(" ")),
1088
- ...relativeAliases.flatMap((entry) => entry[0].split(" ")),
790
+ ...relativeModifierVocabulary,
1089
791
  ...unitPhrases.flatMap((entry) => [
1090
792
  entry.noun,
1091
793
  entry.plural,
@@ -1123,6 +825,8 @@ const GermanContribution = new BaseLanguageContribution({
1123
825
  "letzten",
1124
826
  "nach",
1125
827
  "nächste",
828
+ "auflaufend",
829
+ "per",
1126
830
  "nächsten",
1127
831
  "seit",
1128
832
  "vor",