chronolizer 0.3.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 } from "../natural/suggestion.mjs";
7
- import { absoluteDatePeriod, calendarPeriodOffset, candidate, currentYearDatePeriods, datedPeriods, datedQuarterPeriods, fixedDatePeriod, fixedMonthPeriod, fixedQuarterPeriod, fixedYearPeriod, fromNowRange, futurePeriod, futureRange, isoDate, joinedNowCandidate, joinedPeriodCandidate, monthOfRelativeYear, namedCurrentYearDatePeriod, namedDatePeriod, parseTrailingCount, periodBoundaryCandidate, periodEndDay, periodRange, periodStartDay, periodToDateRange, periodsFromPhrases, quarterOfRelativeYear, relativePeriod, relativeWeekend, remainingPeriodRange, renderPeriodRange, 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, 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/pl.ts
10
9
  const months = [
@@ -21,6 +20,95 @@ const months = [
21
20
  "listopad",
22
21
  "grudzień"
23
22
  ];
23
+ const weekdays = [
24
+ "poniedziałek",
25
+ "wtorek",
26
+ "środa",
27
+ "czwartek",
28
+ "piątek",
29
+ "sobota",
30
+ "niedziela"
31
+ ];
32
+ const weekdayGenitives = [
33
+ "poniedziałku",
34
+ "wtorku",
35
+ "środy",
36
+ "czwartku",
37
+ "piątku",
38
+ "soboty",
39
+ "niedzieli"
40
+ ];
41
+ const nextWeekdays = weekdays.flatMap((weekday, day) => {
42
+ const feminine = day === 2 || day >= 5;
43
+ const canonical = `${feminine ? "następna" : "następny"} ${weekday}`;
44
+ return [{
45
+ phrase: canonical,
46
+ day,
47
+ canonical
48
+ }, {
49
+ phrase: `${feminine ? "następnej" : "następnego"} ${textAt(weekdayGenitives, day)}`,
50
+ day,
51
+ canonical
52
+ }];
53
+ });
54
+ const polishCountWords = [
55
+ [
56
+ "dwa",
57
+ "dwie",
58
+ "dwóch"
59
+ ],
60
+ ["trzy"],
61
+ ["cztery"],
62
+ ["pięć"],
63
+ ["sześć"],
64
+ ["siedem"],
65
+ ["osiem"],
66
+ ["dziewięć"],
67
+ ["dziesięć"],
68
+ ["jedenaście"],
69
+ ["dwanaście"],
70
+ ["trzynaście"],
71
+ ["czternaście"],
72
+ ["piętnaście"],
73
+ ["szesnaście"],
74
+ ["siedemnaście"],
75
+ ["osiemnaście"],
76
+ ["dziewiętnaście"],
77
+ ["dwadzieścia"]
78
+ ];
79
+ const polishCountOnes = [
80
+ [1, "jeden"],
81
+ [2, "dwa"],
82
+ [3, "trzy"],
83
+ [4, "cztery"],
84
+ [5, "pięć"],
85
+ [6, "sześć"],
86
+ [7, "siedem"],
87
+ [8, "osiem"],
88
+ [9, "dziewięć"]
89
+ ];
90
+ const polishCountAliases = [
91
+ ...sequentialCountAliases([[
92
+ "jeden",
93
+ "jedna",
94
+ "jedno"
95
+ ]], 1),
96
+ ...sequentialCountAliases(polishCountWords, 2),
97
+ ...compoundCountAliases(decimalTens([
98
+ "dwadzieścia",
99
+ "trzydzieści",
100
+ "czterdzieści",
101
+ "pięćdziesiąt",
102
+ "sześćdziesiąt",
103
+ "siedemdziesiąt",
104
+ "osiemdziesiąt",
105
+ "dziewięćdziesiąt"
106
+ ]), polishCountOnes, (ten, one) => [`${ten} ${one}`])
107
+ ];
108
+ const normalizePolishCounts = compileCountAliasNormalizer(polishCountAliases);
109
+ const polishCountVocabulary = new Set(countAliasVocabulary(polishCountAliases));
110
+ const correctPolish = (input, vocabulary) => correctWhitespaceSeparatedText(input, vocabulary, polishCountVocabulary);
111
+ const normalizePolish = (input, locale) => normalizePolishCounts(normalizeNaturalText(input, locale));
24
112
  const monthGenitives = [
25
113
  "stycznia",
26
114
  "lutego",
@@ -447,15 +535,7 @@ const parseNamedDate = (input) => {
447
535
  const named = String$1.match(/^([0-3]?\d)\.?(?: )([a-ząćęłńóśźż]+\.?) (\d{4})$/u)(input);
448
536
  if (Option.isSome(named)) return namedDatePeriod(textAt(named.value, 3), textAt(named.value, 2), textAt(named.value, 1), monthNumber, dateLabel);
449
537
  const numeric = String$1.match(/^([0-3]?\d)(?:\. ?|[/-])([01]?\d)(?:\. ?|[/-])(\d{4})$/u)(input);
450
- if (Option.isSome(numeric)) {
451
- const year = validYear(textAt(numeric.value, 3));
452
- const month = Number(textAt(numeric.value, 2));
453
- const day = Number(textAt(numeric.value, 1));
454
- if (year !== void 0 && month >= 1 && month <= 12) {
455
- const value = isoDate(year, month, day);
456
- if (isIsoDate(value) && value !== "9999-12-31") return Option.some(fixedDatePeriod(value, dateLabel(day, month, year)));
457
- }
458
- }
538
+ if (Option.isSome(numeric)) return namedDatePeriod(textAt(numeric.value, 3), textAt(numeric.value, 2), textAt(numeric.value, 1), Number, dateLabel);
459
539
  const current = String$1.match(/^([0-3]?\d)\.? ([a-ząćęłńóśźż]+\.?)$/u)(input);
460
540
  return Option.isSome(current) ? namedCurrentYearDatePeriod(textAt(current.value, 2), textAt(current.value, 1), monthNumber, currentDateLabel) : Option.none();
461
541
  };
@@ -484,13 +564,13 @@ const parseQuarter = (input) => {
484
564
  const quarter = quarterNumber(textAt(standalone.value, 1));
485
565
  return quarter === void 0 ? Option.none() : Option.some(quarterOfRelativeYear(quarter, 0, `Q${quarter}`));
486
566
  };
487
- const parseBasePeriod = (input) => {
488
- const absoluteDate = absoluteDatePeriod(input, "pl");
489
- if (Option.isSome(absoluteDate)) return absoluteDate;
490
- const namedDate = parseNamedDate(input);
491
- if (Option.isSome(namedDate)) return namedDate;
492
- const quarter = parseQuarter(input);
493
- if (Option.isSome(quarter)) return quarter;
567
+ const parseDatedPeriod = (input) => {
568
+ const knownPeriod = Option.firstSomeOf([
569
+ absoluteDatePeriod(input, "pl"),
570
+ parseNamedDate(input),
571
+ parseQuarter(input)
572
+ ]);
573
+ if (Option.isSome(knownPeriod)) return knownPeriod;
494
574
  const yearMatch = String$1.match(/^(?:rok |roku )?(\d{4})$/u)(input);
495
575
  if (Option.isSome(yearMatch)) {
496
576
  const year = validYear(textAt(yearMatch.value, 1));
@@ -502,6 +582,20 @@ const parseBasePeriod = (input) => {
502
582
  const year = validYear(textAt(monthYear.value, 2));
503
583
  if (month !== void 0 && year !== void 0) return Option.some(fixedMonthPeriod(year, month, `${title(textAt(months, month - 1))} ${year}`));
504
584
  }
585
+ return Option.none();
586
+ };
587
+ const parseBasePeriod = (input) => {
588
+ const datedPeriod = parseDatedPeriod(input);
589
+ if (Option.isSome(datedPeriod)) return datedPeriod;
590
+ const prefixedRelativeMonth = String$1.match(/^(poprzedni|ten|następny) ([a-ząćęłńóśźż]+\.?)$/u)(input);
591
+ if (Option.isSome(prefixedRelativeMonth)) {
592
+ const month = monthNumber(textAt(prefixedRelativeMonth.value, 2));
593
+ if (month !== void 0) {
594
+ const modifier = textAt(prefixedRelativeMonth.value, 1);
595
+ const direction = relativeYearDirection(modifier);
596
+ return Option.some(monthOfRelativeYear(month, direction, `${modifier} ${textAt(months, month - 1)}`));
597
+ }
598
+ }
505
599
  const relativeMonth = String$1.match(/^([a-ząćęłńóśźż]+\.?) (poprzedniego roku|następnego roku|tego roku)$/u)(input);
506
600
  const relativeMonthYearFirst = String$1.match(/^(poprzedniego roku|następnego roku|tego roku) ([a-ząćęłńóśźż]+\.?)$/u)(input);
507
601
  const relativeMatch = Option.firstSomeOf([relativeMonth, relativeMonthYearFirst]);
@@ -522,12 +616,31 @@ const parseBasePeriod = (input) => {
522
616
  if (["następny weekend", "przyszły weekend"].includes(input)) return Option.some(relativeWeekend(1, "następny weekend"));
523
617
  if (input === "weekend przed poprzednim") return Option.some(relativeWeekend(-2, input));
524
618
  if (input === "weekend po następnym") return Option.some(relativeWeekend(2, input));
525
- return Option.none();
619
+ const weekday = nextWeekdays.find((entry) => entry.phrase === input);
620
+ return weekday === void 0 ? Option.none() : Option.some(relativeWeekday(weekday.day, 1, weekday.canonical));
526
621
  };
527
622
  const parsePeriod = (input) => {
623
+ const shifted = String$1.match(/^(.+) (?:([1-9]\d*) (dzień|dni|tydzień|tygodnie|tygodni|miesiąc|miesiące|miesięcy|kwartał|kwartały|kwartałów|rok|lata|lat) (temu)|za ([1-9]\d*) (dzień|dni|tydzień|tygodnie|tygodni|miesiąc|miesiące|miesięcy|kwartał|kwartały|kwartałów|rok|lata|lat))$/u)(input);
624
+ if (Option.isSome(shifted)) {
625
+ const past = textAt(shifted.value, 4) === "temu";
626
+ const amount = parseTrailingCount(textAt(shifted.value, past ? 2 : 5));
627
+ const alias = unitAliases.find((unit) => unit[0] === textAt(shifted.value, past ? 3 : 6));
628
+ const entry = alias === void 0 ? void 0 : units.find((unit) => unit.unit === alias[1]);
629
+ const period = parsePeriod(textAt(shifted.value, 1));
630
+ if (Option.isSome(amount) && entry !== void 0 && Option.isSome(period)) {
631
+ const direction = past ? -amount.value : amount.value;
632
+ const noun = countNoun(amount.value, entry);
633
+ const canonical = past ? `${period.value.canonical} ${amount.value} ${noun} temu` : `${period.value.canonical} za ${amount.value} ${noun}`;
634
+ return Option.some(shiftPeriod(period.value, direction, entry.unit, canonical));
635
+ }
636
+ }
528
637
  const edge = String$1.match(/^(początek|koniec) (.+)$/u)(input);
529
638
  if (Option.isSome(edge)) {
530
- const period = parseBasePeriod(textAt(edge.value, 2));
639
+ const periodText = textAt(edge.value, 2);
640
+ const basePeriod = parseBasePeriod(periodText);
641
+ const implicitUnit = declinedUnits.find((entry) => entry[2] === periodText)?.[0];
642
+ const implicit = units.find((entry) => entry.unit === implicitUnit);
643
+ const period = Option.isSome(basePeriod) || implicit === void 0 ? basePeriod : Option.some(relativePeriod(implicit.unit, 0, implicit.current));
531
644
  if (Option.isSome(period)) {
532
645
  const isEnd = textAt(edge.value, 1) === "koniec";
533
646
  const canonical = `${isEnd ? "koniec" : "początek"} ${withPeriodCase(period.value.canonical, "genitive")}`;
@@ -541,16 +654,15 @@ const parsePeriod = (input) => {
541
654
  "cały ",
542
655
  "całe "
543
656
  ].find((prefix) => input.startsWith(prefix));
544
- return parseBasePeriod(wrapper === void 0 ? input : input.slice(wrapper.length));
657
+ const base = parseBasePeriod(wrapper === void 0 ? input : input.slice(wrapper.length));
658
+ return Option.isSome(base) ? base : parseCalendarOffset(input);
545
659
  };
546
660
  const countedUnit = (value) => unitAliases.find((entry) => entry[0] === value)?.[1];
547
- const countedUnitPattern = "dzień|dni|tydzień|tygodnie|tygodni|miesiąc|miesiące|miesięcy|kwartał|kwartały|kwartałów|rok|lata|lat";
548
- const countedPattern = (source) => new RegExp(source.replace("UNIT", countedUnitPattern), "u");
549
- const calendarPastPattern = countedPattern("^(?:dokładnie )?(?:([1-9]\\d*) )?(UNIT) temu$");
550
- const calendarFuturePattern = countedPattern("^za (?:([1-9]\\d*) )?(UNIT)$");
551
- const rollingPastPatterns = [countedPattern("^(?:ostatni|ostatnie|ostatnich|miniony|minione|minionych) ([1-9]\\d*) (UNIT)$"), countedPattern("^w ciągu (?:ostatniego|ostatnich|minionego|minionych) ([1-9]\\d*) (UNIT)$")];
552
- const rollingFuturePatterns = [countedPattern("^(?:następny|następne|następnych|kolejny|kolejne|kolejnych) ([1-9]\\d*) (UNIT)$"), countedPattern("^w ciągu (?:najbliższego|najbliższych) ([1-9]\\d*) (UNIT)$")];
553
- const rollingBarePattern = countedPattern("^([1-9]\\d*) (UNIT)$");
661
+ const calendarPastPattern = /^(?:dokładnie )?(?:([1-9]\d*) )?([^ ]+) temu$/u;
662
+ const calendarFuturePattern = /^za (?:([1-9]\d*) )?([^ ]+)$/u;
663
+ const rollingPastPatterns = [/^(?:ostatni|ostatnie|ostatnich|miniony|minione|minionych) ([1-9]\d*) ([^ ]+)$/u, /^w ciągu (?:ostatniego|ostatnich|minionego|minionych) ([1-9]\d*) ([^ ]+)$/u];
664
+ const rollingFuturePatterns = [/^(?:następny|następne|następnych|kolejny|kolejne|kolejnych) ([1-9]\d*) ([^ ]+)$/u, /^w ciągu (?:najbliższego|najbliższych) ([1-9]\d*) ([^ ]+)$/u];
665
+ const rollingBarePattern = /^([1-9]\d*) ([^ ]+)$/u;
554
666
  const firstPatternMatch = (input, patterns) => Option.firstSomeOf(patterns.map((pattern) => String$1.match(pattern)(input)));
555
667
  const countNoun = (amount, entry) => {
556
668
  if (amount === 1) return entry.singular;
@@ -573,7 +685,7 @@ const parseCalendarOffset = (input) => {
573
685
  const direction = Option.isSome(past) ? -amount.value : amount.value;
574
686
  const noun = countNoun(amount.value, entry);
575
687
  const canonical = direction < 0 ? `${amount.value} ${noun} temu` : `za ${amount.value} ${noun}`;
576
- return Option.some(candidate(periodRange(relativePeriod(unit, direction, canonical)), canonical));
688
+ return Option.some(relativePeriod(unit, direction, canonical));
577
689
  };
578
690
  const rollingModifier = (amount, future) => {
579
691
  if (amount === 1) return future ? "następny" : "ostatni";
@@ -635,6 +747,7 @@ const boundaries = [
635
747
  ]
636
748
  ];
637
749
  const boundaryCandidate = (input) => {
750
+ if (input.startsWith("do koniec ")) return Option.none();
638
751
  const included = String$1.match(/^do (.+) włącznie$/u)(input);
639
752
  if (Option.isSome(included)) return parsePeriod(textAt(included.value, 1)).pipe(Option.map((value) => periodBoundaryCandidate(value, "through", `do ${withPeriodCase(value.canonical, "genitive")} włącznie`)));
640
753
  for (const [prefix, boundary, canonical] of boundaries) {
@@ -653,8 +766,6 @@ const parsePolish = (input) => {
653
766
  "do teraz"
654
767
  ].includes(input)) return Option.some(candidate(untilNowRange(), "do dziś"));
655
768
  if (["od teraz", "od dziś"].includes(input)) return Option.some(candidate(fromNowRange(), "od teraz"));
656
- const offset = parseCalendarOffset(input);
657
- if (Option.isSome(offset)) return offset;
658
769
  const rolling = parseRollingPeriod(input);
659
770
  if (Option.isSome(rolling)) return rolling;
660
771
  const toDate = toDatePhrases.find((entry) => entry.phrase === input);
@@ -677,10 +788,11 @@ const parsePolish = (input) => {
677
788
  ], [
678
789
  "od dziś do ",
679
790
  "między dziś a ",
680
- "pomiędzy dziś a "
791
+ "pomiędzy dziś a ",
792
+ "teraz do "
681
793
  ], parsePeriod, (period) => `od ${period} do dziś`, (period) => `od dziś do ${period}`);
682
794
  if (Option.isSome(nowBounded)) return nowBounded;
683
- const bounded = joinedPeriodCandidate(input, [
795
+ const bounded = joinedPeriodCandidate(input.replace(/ włącznie$/u, ""), [
684
796
  ["od ", " do "],
685
797
  ["między ", " a "],
686
798
  ["pomiędzy ", " a "],
@@ -731,9 +843,12 @@ const staticPeriodPhrases = [
731
843
  ]),
732
844
  ...months.flatMap((month) => [
733
845
  month,
846
+ `poprzedni ${month}`,
847
+ `następny ${month}`,
734
848
  `${month} poprzedniego roku`,
735
849
  `${month} następnego roku`
736
- ])
850
+ ]),
851
+ ...nextWeekdays.map((entry) => entry.phrase)
737
852
  ];
738
853
  const staticPeriods = periodsFromPhrases(staticPeriodPhrases, parsePeriod);
739
854
  const monthBoundaryPhrases = months.flatMap((_, index) => {
@@ -800,6 +915,16 @@ const suggestPolish = (input, limit) => {
800
915
  ], limit);
801
916
  };
802
917
  const renderPolish = (range) => {
918
+ const shifted = decomposeShiftedPeriodRange(range);
919
+ if (Option.isSome(shifted)) {
920
+ const base = renderPolish(shifted.value.baseRange);
921
+ const entry = units.find((unit) => unit.unit === shifted.value.unit);
922
+ if (Option.isSome(base) && entry !== void 0) {
923
+ const amount = Math.abs(shifted.value.amount);
924
+ const noun = countNoun(amount, entry);
925
+ return Option.some(shifted.value.amount < 0 ? `${base.value} ${amount} ${noun} temu` : `${base.value} za ${amount} ${noun}`);
926
+ }
927
+ }
803
928
  const offset = calendarPeriodOffset(range);
804
929
  if (Option.isSome(offset) && Math.abs(offset.value.amount) > 1) {
805
930
  const entry = units.find((unit) => unit.unit === offset.value.unit);
@@ -830,6 +955,8 @@ const PolishContribution = new BaseLanguageContribution({
830
955
  locale: "pl",
831
956
  vocabulary: [
832
957
  ...months,
958
+ ...weekdays,
959
+ ...weekdayGenitives,
833
960
  ...monthGenitives,
834
961
  ...monthInstrumentals,
835
962
  ...monthLocatives,
@@ -870,8 +997,8 @@ const PolishContribution = new BaseLanguageContribution({
870
997
  "we",
871
998
  "za"
872
999
  ],
873
- normalize: normalizeNaturalText,
874
- correct: correctWhitespaceSeparatedText,
1000
+ normalize: normalizePolish,
1001
+ correct: correctPolish,
875
1002
  parseExact: parsePolish,
876
1003
  suggest: suggestPolish,
877
1004
  render: renderPolish
@@ -6,6 +6,43 @@ import { NaturalCandidate } from "../language/model.mjs";
6
6
  import { Match, Option, RegExp as RegExp$1, Schema, String as String$1 } from "effect";
7
7
  //#region src/locales/shared.ts
8
8
  const textAt = (values, index) => values[index] ?? "";
9
+ const sequentialCountAliases = (words, start) => words.flatMap((aliases, index) => aliases.map((phrase) => [phrase, start + index]));
10
+ const decimalTens = (words) => words.map((word, index) => [20 + index * 10, word]);
11
+ const compoundCountAliases = (tens, ones, join) => tens.flatMap(([tensAmount, tensWord]) => [[tensWord, tensAmount], ...ones.flatMap(([oneAmount, oneWord]) => {
12
+ const amount = tensAmount + oneAmount;
13
+ return join(tensWord, oneWord, amount).map((phrase) => [phrase, amount]);
14
+ })]);
15
+ const countAliasVocabulary = (aliases) => [...new Set(aliases.flatMap(([phrase]) => phrase.split(" ")))];
16
+ const compileCountAliasNormalizer = (aliases) => {
17
+ const distinct = /* @__PURE__ */ new Map();
18
+ for (const [phrase, amount] of aliases) if (!distinct.has(phrase)) distinct.set(phrase, amount);
19
+ const byFirstWord = /* @__PURE__ */ new Map();
20
+ for (const [phrase, amount] of distinct) {
21
+ const parts = phrase.split(" ");
22
+ const first = parts[0] ?? "";
23
+ const entries = byFirstWord.get(first) ?? [];
24
+ byFirstWord.set(first, [...entries, {
25
+ amount,
26
+ parts
27
+ }]);
28
+ }
29
+ for (const [first, entries] of byFirstWord) byFirstWord.set(first, [...entries].sort((left, right) => right.parts.length - left.parts.length));
30
+ return (input) => {
31
+ const words = input.split(" ");
32
+ const output = [];
33
+ for (let index = 0; index < words.length;) {
34
+ const alias = (byFirstWord.get(words[index] ?? "") ?? []).find((entry) => entry.parts.every((part, offset) => words[index + offset] === part));
35
+ if (alias === void 0) {
36
+ output.push(words[index] ?? "");
37
+ index += 1;
38
+ } else {
39
+ output.push(String(alias.amount));
40
+ index += alias.parts.length;
41
+ }
42
+ }
43
+ return output.join(" ");
44
+ };
45
+ };
9
46
  const Period = Schema.Struct({
10
47
  start: InstantExpr,
11
48
  end: InstantExpr,
@@ -18,6 +55,22 @@ const isNow = Schema.is(Now);
18
55
  const isShift = Schema.is(Shift);
19
56
  const isStartOf = Schema.is(StartOf);
20
57
  const periodRange = (period) => boundedRange(greaterThanOrEqual(period.start), lessThan(period.end));
58
+ const shiftPeriod = (period, amount, unit, canonical) => Period.make({
59
+ start: shift(period.start, amount, unit),
60
+ end: shift(period.end, amount, unit),
61
+ canonical
62
+ });
63
+ const decomposeShiftedPeriodRange = (range) => {
64
+ if (!isGreaterThanOrEqual(range.lower) || !isLessThan(range.upper)) return Option.none();
65
+ const start = range.lower.value;
66
+ const end = range.upper.value;
67
+ if (!isShift(start) || !isShift(end) || start.amount === 0 || start.amount !== end.amount || start.unit !== end.unit) return Option.none();
68
+ return Option.some({
69
+ amount: start.amount,
70
+ unit: start.unit,
71
+ baseRange: boundedRange(greaterThanOrEqual(start.base), lessThan(end.base))
72
+ });
73
+ };
21
74
  const periodToDateRange = (unit) => boundedRange(greaterThanOrEqual(startOf(now(), unit)), lessThanOrEqual(now()));
22
75
  const fromNowRange = () => lowerOpenRange(greaterThanOrEqual(now()));
23
76
  const untilNowRange = () => upperOpenRange(lessThanOrEqual(now()));
@@ -40,7 +93,7 @@ const periodDay = (period, day, canonical) => {
40
93
  const start = day === 1 ? period.start : shift(period.start, day - 1, "day");
41
94
  return Option.some(Period.make({
42
95
  start,
43
- end: shift(start, 1, "day"),
96
+ end: shift(period.start, day, "day"),
44
97
  canonical
45
98
  }));
46
99
  };
@@ -49,13 +102,23 @@ const periodPreviousDay = (period, canonical) => Period.make({
49
102
  end: period.start,
50
103
  canonical
51
104
  });
105
+ const relativeWeekday = (day, direction, canonical) => {
106
+ const weekBase = direction === 0 ? now() : shift(now(), direction, "week");
107
+ const weekStart = startOf(weekBase, "week");
108
+ const start = day === 0 ? weekStart : shift(weekStart, day, "day");
109
+ return Period.make({
110
+ start,
111
+ end: shift(weekStart, day + 1, "day"),
112
+ canonical
113
+ });
114
+ };
52
115
  const relativeWeekend = (direction, canonical) => {
53
116
  const weekBase = direction === 0 ? now() : shift(now(), direction, "week");
54
117
  const weekStart = startOf(weekBase, "week");
55
118
  const start = shift(weekStart, 5, "day");
56
119
  return Period.make({
57
120
  start,
58
- end: shift(start, 2, "day"),
121
+ end: shift(weekStart, 7, "day"),
59
122
  canonical
60
123
  });
61
124
  };
@@ -191,7 +254,7 @@ const currentYearDatePeriod = (month, day, canonical) => {
191
254
  const start = day === 1 ? monthStart : shift(monthStart, day - 1, "day");
192
255
  return Period.make({
193
256
  start,
194
- end: shift(start, 1, "day"),
257
+ end: shift(monthStart, day, "day"),
195
258
  canonical
196
259
  });
197
260
  };
@@ -269,7 +332,7 @@ const monthOfRelativeYear = (month, direction, canonical) => {
269
332
  const start = month === 1 ? yearStart : shift(yearStart, month - 1, "month");
270
333
  return Period.make({
271
334
  start,
272
- end: shift(start, 1, "month"),
335
+ end: shift(yearStart, month, "month"),
273
336
  canonical
274
337
  });
275
338
  };
@@ -392,4 +455,4 @@ const renderPeriodRange = (range, toDate, periods, since, before, through, after
392
455
  return Option.none();
393
456
  };
394
457
  //#endregion
395
- export { Period, absoluteDatePeriod, calendarPeriodOffset, candidate, currentYearDatePeriods, datedPeriods, datedQuarterPeriods, fixedDatePeriod, fixedMonthPeriod, fixedQuarterPeriod, fixedYearPeriod, formatAbsoluteDate, fromNowRange, futurePeriod, futureRange, isoDate, joinedNowCandidate, joinedPeriodCandidate, monthOfRelativeYear, namedCurrentYearDatePeriod, namedDatePeriod, openBoundaryCandidate, parseTrailingCount, periodBoundaryCandidate, periodDay, periodEndDay, periodPreviousDay, periodRange, periodStartDay, periodToDateRange, periodsFromPhrases, quarterOfRelativeYear, relativePeriod, relativeWeekend, remainingPeriodRange, renderPeriodRange, textAt, trailingPeriod, trailingRange, untilNowRange, validYear };
458
+ export { Period, absoluteDatePeriod, calendarPeriodOffset, candidate, compileCountAliasNormalizer, compoundCountAliases, countAliasVocabulary, currentYearDatePeriods, datedPeriods, datedQuarterPeriods, decimalTens, decomposeShiftedPeriodRange, fixedDatePeriod, fixedMonthPeriod, fixedQuarterPeriod, fixedYearPeriod, formatAbsoluteDate, fromNowRange, futurePeriod, futureRange, isoDate, joinedNowCandidate, joinedPeriodCandidate, monthOfRelativeYear, namedCurrentYearDatePeriod, namedDatePeriod, openBoundaryCandidate, parseTrailingCount, periodBoundaryCandidate, periodDay, periodEndDay, periodPreviousDay, periodRange, periodStartDay, periodToDateRange, periodsFromPhrases, quarterOfRelativeYear, relativePeriod, relativeWeekday, relativeWeekend, remainingPeriodRange, renderPeriodRange, sequentialCountAliases, shiftPeriod, textAt, trailingPeriod, trailingRange, untilNowRange, validYear };