timelang 0.1.0 → 0.2.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) Kamran Ahmed
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -14,8 +14,7 @@ It takes natural language inputs and converts them into structured date, duratio
14
14
  - **Forgiving parser** — Extra spaces, mixed case, missing separators all work
15
15
  - **Edge case handling** — Year rollovers, leap years, fiscal quarters
16
16
  - **Configurable** — Reference date, fiscal year start, week start day, date format
17
- - **Zero dependencies** — Lightweight and fast
18
- - **TypeScript** — Full type definitions included
17
+ - **TypeScript** — Typesafe with full type definitions
19
18
 
20
19
  ## Installation
21
20
 
package/dist/index.cjs CHANGED
@@ -270,8 +270,23 @@ var lexer = import_moo.default.compile({
270
270
  "sat",
271
271
  "sun"
272
272
  ],
273
+ // Ago keyword for relative dates
274
+ kw_ago: ["ago"],
275
+ // Weekend and night keywords
276
+ kw_weekend: ["weekend"],
277
+ kw_tonight: ["tonight"],
278
+ kw_night: ["night"],
279
+ kw_fortnight: ["fortnight", "fortnights"],
273
280
  // Other keywords
274
- otherKeyword: ["fiscal", "fy", "daily", "weekly", "monthly", "yearly", "annually", "ago", "hence", "later"]
281
+ otherKeyword: ["fiscal", "fy", "daily", "weekly", "monthly", "yearly", "annually", "hence", "later", "earlier"],
282
+ // Business keywords
283
+ kw_business: ["business"],
284
+ // EOD/COB keywords
285
+ kw_eod: ["eod"],
286
+ kw_cob: ["cob"],
287
+ kw_close: ["close"],
288
+ // Week keyword (for "week 1" patterns)
289
+ kw_week: ["week"]
275
290
  })
276
291
  // Keywords are matched against lowercase input
277
292
  },
@@ -324,6 +339,13 @@ var makeRelative = (direction, duration) => ({
324
339
  direction,
325
340
  duration
326
341
  });
342
+ var makeRelativeDate = (direction, duration, baseDate, time2) => ({
343
+ nodeType: "relativeDate",
344
+ direction,
345
+ duration,
346
+ baseDate,
347
+ time: time2
348
+ });
327
349
  var makeTitled = (title, expression, titleStart, titleEnd) => ({
328
350
  nodeType: "titled",
329
351
  title: title.trim(),
@@ -559,10 +581,14 @@ var grammar = {
559
581
  { "name": "expression", "symbols": ["range"], "postprocess": first },
560
582
  { "name": "expression", "symbols": ["span"], "postprocess": first },
561
583
  { "name": "expression", "symbols": ["relative"], "postprocess": first },
584
+ { "name": "expression", "symbols": ["relativeDate"], "postprocess": first },
585
+ { "name": "expression", "symbols": ["lastDayOfMonthExpr"], "postprocess": first },
586
+ { "name": "expression", "symbols": ["date"], "postprocess": first },
562
587
  { "name": "expression", "symbols": ["fuzzy"], "postprocess": first },
563
588
  { "name": "expression", "symbols": ["forDuration"], "postprocess": first },
564
589
  { "name": "expression", "symbols": ["duration"], "postprocess": first },
565
- { "name": "expression", "symbols": ["date"], "postprocess": first },
590
+ { "name": "lastDayOfMonthExpr", "symbols": ["lastRelative", "_", "dayUnit", "_", "ofConnector", "_", "month"], "postprocess": (d) => makeDate({ month: d[6], lastDayOfMonth: true }) },
591
+ { "name": "lastDayOfMonthExpr", "symbols": ["lastRelative", "_", "dayUnit", "_", "ofConnector", "_", "theConnector", "_", "monthUnit"], "postprocess": (d) => makeDate({ lastDayOfMonth: true, monthFromRef: true }) },
566
592
  { "name": "forDuration", "symbols": ["forConnector", "_", "duration"], "postprocess": (d) => d[2] },
567
593
  { "name": "range", "symbols": ["date", "_", "toConnector", "_", "date"], "postprocess": (d) => makeRange(d[0], d[4]) },
568
594
  { "name": "range", "symbols": ["fromConnector", "_", "date", "_", "toConnector", "_", "date"], "postprocess": (d) => makeRange(d[2], d[6]) },
@@ -644,6 +670,30 @@ var grammar = {
644
670
  { "name": "relative", "symbols": ["inConnector", "_", "theConnector", "_", "pastRelative", "_", "unit"], "postprocess": (d) => makeRelative("past", makeDuration(1, d[6])) },
645
671
  { "name": "relative", "symbols": ["inConnector", "_", "theConnector", "_", "comingRelative", "_", "unit"], "postprocess": (d) => makeRelative("future", makeDuration(1, d[6])) },
646
672
  { "name": "relative", "symbols": ["inConnector", "_", "theConnector", "_", "upcomingRelative", "_", "unit"], "postprocess": (d) => makeRelative("future", makeDuration(1, d[6])) },
673
+ { "name": "relativeDate", "symbols": ["duration", "_", "agoConnector"], "postprocess": (d) => makeRelativeDate("past", d[0]) },
674
+ { "name": "relativeDate", "symbols": ["duration", "_", "agoConnector", "_", "atConnector", "_", "time"], "postprocess": (d) => makeRelativeDate("past", d[0], void 0, d[6]) },
675
+ { "name": "relativeDate", "symbols": ["duration", "_", "agoConnector", "_", "atConnector", "_", "timeWord"], "postprocess": (d) => makeRelativeDate("past", d[0], void 0, { special: d[6] }) },
676
+ { "name": "relativeDate", "symbols": ["inConnector", "_", "duration"], "postprocess": (d) => makeRelativeDate("future", d[2]) },
677
+ { "name": "relativeDate", "symbols": ["inConnector", "_", "duration", "_", "atConnector", "_", "time"], "postprocess": (d) => makeRelativeDate("future", d[2], void 0, d[6]) },
678
+ { "name": "relativeDate", "symbols": ["inConnector", "_", "duration", "_", "atConnector", "_", "timeWord"], "postprocess": (d) => makeRelativeDate("future", d[2], void 0, { special: d[6] }) },
679
+ { "name": "relativeDate", "symbols": ["duration", "_", "fromConnector", "_", "now"], "postprocess": (d) => makeRelativeDate("future", d[0]) },
680
+ { "name": "relativeDate", "symbols": ["duration", "_", "fromConnector", "_", "now", "_", "atConnector", "_", "time"], "postprocess": (d) => makeRelativeDate("future", d[0], void 0, d[8]) },
681
+ { "name": "relativeDate", "symbols": ["duration", "_", "fromConnector", "_", "now", "_", "atConnector", "_", "timeWord"], "postprocess": (d) => makeRelativeDate("future", d[0], void 0, { special: d[8] }) },
682
+ { "name": "relativeDate", "symbols": ["duration", "_", "fromConnector", "_", "today"], "postprocess": (d) => makeRelativeDate("future", d[0], makeDate({ special: "today" })) },
683
+ { "name": "relativeDate", "symbols": ["duration", "_", "fromConnector", "_", "today", "_", "atConnector", "_", "time"], "postprocess": (d) => makeRelativeDate("future", d[0], makeDate({ special: "today" }), d[8]) },
684
+ { "name": "relativeDate", "symbols": ["duration", "_", "fromConnector", "_", "tomorrow"], "postprocess": (d) => makeRelativeDate("future", d[0], makeDate({ special: "tomorrow" })) },
685
+ { "name": "relativeDate", "symbols": ["duration", "_", "fromConnector", "_", "tomorrow", "_", "atConnector", "_", "time"], "postprocess": (d) => makeRelativeDate("future", d[0], makeDate({ special: "tomorrow" }), d[8]) },
686
+ { "name": "relativeDate", "symbols": ["duration", "_", "fromConnector", "_", "yesterday"], "postprocess": (d) => makeRelativeDate("future", d[0], makeDate({ special: "yesterday" })) },
687
+ { "name": "relativeDate", "symbols": ["duration", "_", "henceConnector"], "postprocess": (d) => makeRelativeDate("future", d[0]) },
688
+ { "name": "relativeDate", "symbols": ["duration", "_", "laterConnector"], "postprocess": (d) => makeRelativeDate("future", d[0]) },
689
+ { "name": "relativeDate", "symbols": ["duration", "_", "beforeConnector", "_", "date"], "postprocess": (d) => makeRelativeDate("past", d[0], d[4]) },
690
+ { "name": "relativeDate", "symbols": ["duration", "_", "afterConnector", "_", "date"], "postprocess": (d) => makeRelativeDate("future", d[0], d[4]) },
691
+ { "name": "relativeDate", "symbols": ["theConnector", "_", "dayUnit", "_", "beforeConnector", "_", "date"], "postprocess": (d) => makeRelativeDate("past", makeDuration(1, "day"), d[6]) },
692
+ { "name": "relativeDate", "symbols": ["theConnector", "_", "dayUnit", "_", "afterConnector", "_", "date"], "postprocess": (d) => makeRelativeDate("future", makeDuration(1, "day"), d[6]) },
693
+ { "name": "relativeDate", "symbols": ["dayUnit", "_", "beforeConnector", "_", "date"], "postprocess": (d) => makeRelativeDate("past", makeDuration(1, "day"), d[4]) },
694
+ { "name": "relativeDate", "symbols": ["dayUnit", "_", "afterConnector", "_", "date"], "postprocess": (d) => makeRelativeDate("future", makeDuration(1, "day"), d[4]) },
695
+ { "name": "relativeDate", "symbols": ["wordNumber", "_", "unit", "_", "beforeConnector", "_", "date"], "postprocess": (d) => makeRelativeDate("past", makeDuration(parseWordNumber(d[0]), d[2]), d[6]) },
696
+ { "name": "relativeDate", "symbols": ["wordNumber", "_", "unit", "_", "afterConnector", "_", "date"], "postprocess": (d) => makeRelativeDate("future", makeDuration(parseWordNumber(d[0]), d[2]), d[6]) },
647
697
  { "name": "fuzzy", "symbols": ["quarter"], "postprocess": (d) => makeFuzzy({ period: "quarter", quarter: parseQuarter(d[0].value) }) },
648
698
  { "name": "fuzzy", "symbols": ["quarter", "_", "year"], "postprocess": (d) => makeFuzzy({ period: "quarter", quarter: parseQuarter(d[0].value), year: d[2] }) },
649
699
  { "name": "fuzzy", "symbols": ["half"], "postprocess": (d) => makeFuzzy({ period: "half", half: parseHalf(d[0].value) }) },
@@ -726,8 +776,29 @@ var grammar = {
726
776
  { "name": "fuzzy", "symbols": ["lateModifier", "_", "thisRelative", "_", "unit"], "postprocess": (d) => makeFuzzy({ period: d[4], modifier: "late", relative: "this" }) },
727
777
  { "name": "fuzzy", "symbols": ["beginningConnector", "_", "ofConnector", "_", "theConnector", "_", "unit"], "postprocess": (d) => makeFuzzy({ period: d[6], modifier: "beginning" }) },
728
778
  { "name": "fuzzy", "symbols": ["middleConnector", "_", "ofConnector", "_", "theConnector", "_", "unit"], "postprocess": (d) => makeFuzzy({ period: d[6], modifier: "middle" }) },
729
- { "name": "fuzzy", "symbols": ["lastRelative", "_", "unit", "_", "ofConnector", "_", "month"], "postprocess": (d) => makeFuzzy({ period: "month", month: d[6], modifier: "late" }) },
730
- { "name": "fuzzy", "symbols": ["ordinalWord", "_", "unit", "_", "ofConnector", "_", "month"], "postprocess": (d) => makeFuzzy({ period: "month", month: d[6], modifier: parseOrdinalWord(d[0]) <= 2 ? "early" : parseOrdinalWord(d[0]) >= 4 ? "late" : "mid" }) },
779
+ { "name": "fuzzy", "symbols": ["lastRelative", "_", "unit", "_", "ofConnector", "_", "month"], "postprocess": (d, _, reject) => {
780
+ if (d[2].toLowerCase() === "day" || d[2].toLowerCase() === "days") return reject;
781
+ return makeFuzzy({ period: "month", month: d[6], modifier: "late" });
782
+ } },
783
+ { "name": "fuzzy", "symbols": ["ordinalWord", "_", "unit", "_", "ofConnector", "_", "month"], "postprocess": (d, _, reject) => {
784
+ if (d[2].toLowerCase() === "day" || d[2].toLowerCase() === "days") return reject;
785
+ return makeFuzzy({ period: "month", month: d[6], modifier: parseOrdinalWord(d[0]) <= 2 ? "early" : parseOrdinalWord(d[0]) >= 4 ? "late" : "mid" });
786
+ } },
787
+ { "name": "fuzzy", "symbols": ["weekendKeyword"], "postprocess": (d) => makeFuzzy({ period: "weekend", relative: "this" }) },
788
+ { "name": "fuzzy", "symbols": ["theConnector", "_", "weekendKeyword"], "postprocess": (d) => makeFuzzy({ period: "weekend", relative: "this" }) },
789
+ { "name": "fuzzy", "symbols": ["thisRelative", "_", "weekendKeyword"], "postprocess": (d) => makeFuzzy({ period: "weekend", relative: "this" }) },
790
+ { "name": "fuzzy", "symbols": ["nextRelative", "_", "weekendKeyword"], "postprocess": (d) => makeFuzzy({ period: "weekend", relative: "next" }) },
791
+ { "name": "fuzzy", "symbols": ["lastRelative", "_", "weekendKeyword"], "postprocess": (d) => makeFuzzy({ period: "weekend", relative: "last" }) },
792
+ { "name": "fuzzy", "symbols": ["tonightKeyword"], "postprocess": (d) => makeFuzzy({ period: "tonight" }) },
793
+ { "name": "fuzzy", "symbols": ["nightKeyword"], "postprocess": (d) => makeFuzzy({ period: "night" }) },
794
+ { "name": "fuzzy", "symbols": ["lastRelative", "_", "nightKeyword"], "postprocess": (d) => makeFuzzy({ period: "night", relative: "last" }) },
795
+ { "name": "fuzzy", "symbols": ["tomorrow", "_", "nightKeyword"], "postprocess": (d) => makeFuzzy({ period: "night", relative: "tomorrow" }) },
796
+ { "name": "fuzzy", "symbols": ["weekday", "_", "nightKeyword"], "postprocess": (d) => makeFuzzy({ period: "night", weekday: d[0] }) },
797
+ { "name": "fuzzy", "symbols": ["fortnightKeyword"], "postprocess": (d) => makeFuzzy({ period: "fortnight" }) },
798
+ { "name": "fuzzy", "symbols": ["nextRelative", "_", "fortnightKeyword"], "postprocess": (d) => makeFuzzy({ period: "fortnight", relative: "next" }) },
799
+ { "name": "fuzzy", "symbols": ["lastRelative", "_", "fortnightKeyword"], "postprocess": (d) => makeFuzzy({ period: "fortnight", relative: "last" }) },
800
+ { "name": "fuzzy", "symbols": ["inConnector", "_", "wordNumber", "_", "fortnightKeyword"], "postprocess": (d) => makeFuzzy({ period: "fortnight", count: parseWordNumber(d[2]) }) },
801
+ { "name": "fuzzy", "symbols": ["inConnector", "_", "number", "_", "fortnightKeyword"], "postprocess": (d) => makeFuzzy({ period: "fortnight", count: d[2] }) },
731
802
  { "name": "duration", "symbols": ["number", "_", "unit"], "postprocess": (d) => makeDuration(d[0], d[2]) },
732
803
  { "name": "duration", "symbols": ["wordNumber", "_", "unit"], "postprocess": (d) => makeDuration(parseWordNumber(d[0]), d[2]) },
733
804
  { "name": "duration", "symbols": ["abbreviatedDuration"], "postprocess": (d) => d[0] },
@@ -755,6 +826,7 @@ var grammar = {
755
826
  return makeDuration(value, unitMap[match[2]]);
756
827
  } },
757
828
  { "name": "date", "symbols": ["specialDay"], "postprocess": (d) => makeDate({ special: d[0] }) },
829
+ { "name": "date", "symbols": ["ordinalWeekdayOfMonth"], "postprocess": first },
758
830
  { "name": "date", "symbols": ["relativeWeekday"], "postprocess": first },
759
831
  { "name": "date", "symbols": ["weekday"], "postprocess": (d) => makeDate({ weekday: d[0] }) },
760
832
  { "name": "date", "symbols": ["monthDayYear"], "postprocess": first },
@@ -769,6 +841,7 @@ var grammar = {
769
841
  { "name": "date", "symbols": ["monthOnly"], "postprocess": first },
770
842
  { "name": "date", "symbols": ["yearOnly"], "postprocess": first },
771
843
  { "name": "date", "symbols": ["timeOnly"], "postprocess": first },
844
+ { "name": "date", "symbols": ["dayOnly"], "postprocess": first },
772
845
  { "name": "monthDayCompact", "symbols": [lexer.has("monthDayCompact") ? { type: "monthDayCompact" } : monthDayCompact], "postprocess": (d) => {
773
846
  const parsed = parseMonthDayCompact(d[0].value);
774
847
  return makeDate({ month: parsed.month, day: parsed.day });
@@ -785,6 +858,8 @@ var grammar = {
785
858
  } },
786
859
  { "name": "timeOnly", "symbols": ["time"], "postprocess": (d) => makeDate({ time: d[0], timeOnly: true }) },
787
860
  { "name": "timeOnly", "symbols": ["timeWord"], "postprocess": (d) => makeDate({ time: { special: d[0] }, timeOnly: true }) },
861
+ { "name": "dayOnly", "symbols": ["theConnector", "_", "dayNumber"], "postprocess": (d) => makeDate({ day: d[2], dayOnly: true }) },
862
+ { "name": "dayOnly", "symbols": ["onConnector", "_", "theConnector", "_", "dayNumber"], "postprocess": (d) => makeDate({ day: d[4], dayOnly: true }) },
788
863
  { "name": "specialDay", "symbols": ["today"], "postprocess": (d) => "today" },
789
864
  { "name": "specialDay", "symbols": ["tomorrow"], "postprocess": (d) => "tomorrow" },
790
865
  { "name": "specialDay", "symbols": ["yesterday"], "postprocess": (d) => "yesterday" },
@@ -793,7 +868,7 @@ var grammar = {
793
868
  { "name": "specialDay", "symbols": ["dayUnit", "_", "afterConnector", "_", "tomorrow"], "postprocess": (d) => "dayAfterTomorrow" },
794
869
  { "name": "specialDay", "symbols": ["theConnector", "_", "dayUnit", "_", "beforeConnector", "_", "yesterday"], "postprocess": (d) => "dayBeforeYesterday" },
795
870
  { "name": "specialDay", "symbols": ["dayUnit", "_", "beforeConnector", "_", "yesterday"], "postprocess": (d) => "dayBeforeYesterday" },
796
- { "name": "dayUnit", "symbols": [lexer.has("unit") ? { type: "unit" } : unit], "postprocess": (d) => d[0].value === "day" ? d[0] : null },
871
+ { "name": "dayUnit", "symbols": [lexer.has("unit") ? { type: "unit" } : unit], "postprocess": (d, _, reject) => d[0].value === "day" ? d[0] : reject },
797
872
  { "name": "relativeWeekday", "symbols": ["nextRelative", "_", "weekday"], "postprocess": (d) => makeDate({ weekday: d[2], relative: "next" }) },
798
873
  { "name": "relativeWeekday", "symbols": ["lastRelative", "_", "weekday"], "postprocess": (d) => makeDate({ weekday: d[2], relative: "last" }) },
799
874
  { "name": "relativeWeekday", "symbols": ["thisRelative", "_", "weekday"], "postprocess": (d) => makeDate({ weekday: d[2], relative: "this" }) },
@@ -808,6 +883,14 @@ var grammar = {
808
883
  { "name": "monthDay", "symbols": ["theConnector", "_", "dayNumber", "_", "ofConnector", "_", "month"], "postprocess": (d) => makeDate({ month: d[6], day: d[2] }) },
809
884
  { "name": "monthDay", "symbols": ["month", "_", "ordinalWord"], "postprocess": (d) => makeDate({ month: d[0], day: parseOrdinalWord(d[2]) }) },
810
885
  { "name": "monthDay", "symbols": ["month", "_", "theConnector", "_", "dayNumber"], "postprocess": (d) => makeDate({ month: d[0], day: d[4] }) },
886
+ { "name": "ordinalWeekdayOfMonth", "symbols": ["ordinalWord", "_", "weekday", "_", "ofConnector", "_", "month"], "postprocess": (d) => makeDate({ ordinalWeekday: parseOrdinalWord(d[0]), weekday: d[2], month: d[6] }) },
887
+ { "name": "ordinalWeekdayOfMonth", "symbols": ["lastRelative", "_", "weekday", "_", "ofConnector", "_", "month"], "postprocess": (d) => makeDate({ ordinalWeekday: -1, weekday: d[2], month: d[6] }) },
888
+ { "name": "ordinalWeekdayOfMonth", "symbols": ["ordinalWord", "_", "weekday", "_", "ofConnector", "_", "theConnector", "_", "monthUnit"], "postprocess": (d) => makeDate({ ordinalWeekday: parseOrdinalWord(d[0]), weekday: d[2], monthFromRef: true }) },
889
+ { "name": "ordinalWeekdayOfMonth", "symbols": ["lastRelative", "_", "weekday", "_", "ofConnector", "_", "theConnector", "_", "monthUnit"], "postprocess": (d) => makeDate({ ordinalWeekday: -1, weekday: d[2], monthFromRef: true }) },
890
+ { "name": "ordinalWeekdayOfMonth", "symbols": ["ordinalWord", "_", "dayUnit", "_", "ofConnector", "_", "month"], "postprocess": (d) => makeDate({ month: d[6], day: parseOrdinalWord(d[0]) }) },
891
+ { "name": "ordinalWeekdayOfMonth", "symbols": ["lastRelative", "_", "dayUnit", "_", "ofConnector", "_", "month"], "postprocess": (d) => makeDate({ month: d[6], day: -1, lastDayOfMonth: true }) },
892
+ { "name": "ordinalWeekdayOfMonth", "symbols": ["lastRelative", "_", "dayUnit", "_", "ofConnector", "_", "theConnector", "_", "monthUnit"], "postprocess": (d) => makeDate({ lastDayOfMonth: true, monthFromRef: true }) },
893
+ { "name": "ordinalWeekdayOfMonth", "symbols": ["ordinalWord", "_", "dayUnit", "_", "ofConnector", "_", "nextRelative", "_", "monthUnit"], "postprocess": (d) => makeDate({ ordinalWeekday: parseOrdinalWord(d[0]), dayOfMonth: true, nextMonth: true }) },
811
894
  { "name": "monthDayYear", "symbols": ["monthDay", "_", "year"], "postprocess": (d) => ({ ...d[0], year: d[2] }) },
812
895
  { "name": "monthDayYear", "symbols": ["monthDay", lexer.has("comma") ? { type: "comma" } : comma, "_", "year"], "postprocess": (d) => ({ ...d[0], year: d[3] }) },
813
896
  { "name": "dateFormat", "symbols": [lexer.has("integer") ? { type: "integer" } : integer, lexer.has("dash") ? { type: "dash" } : dash, lexer.has("integer") ? { type: "integer" } : integer, lexer.has("dash") ? { type: "dash" } : dash, lexer.has("integer") ? { type: "integer" } : integer], "postprocess": (d) => {
@@ -901,6 +984,11 @@ var grammar = {
901
984
  if (val === "year" || val === "years" || val === "yr" || val === "yrs") return val;
902
985
  return reject;
903
986
  } },
987
+ { "name": "monthUnit", "symbols": [lexer.has("unit") ? { type: "unit" } : unit], "postprocess": (d, _, reject) => {
988
+ const val = d[0].value.toLowerCase();
989
+ if (val === "month" || val === "months" || val === "mo" || val === "mos") return val;
990
+ return reject;
991
+ } },
904
992
  { "name": "wordNumber", "symbols": [lexer.has("wordNumber") ? { type: "wordNumber" } : wordNumber], "postprocess": (d) => d[0].value },
905
993
  { "name": "ordinalWord", "symbols": [lexer.has("ordinalWord") ? { type: "ordinalWord" } : ordinalWord], "postprocess": (d) => d[0].value },
906
994
  { "name": "ordinalWord", "symbols": [lexer.has("unit") ? { type: "unit" } : unit], "postprocess": (d, _, reject) => {
@@ -937,6 +1025,9 @@ var grammar = {
937
1025
  { "name": "beginningConnector", "symbols": [lexer.has("kw_beginning") ? { type: "kw_beginning" } : kw_beginning], "postprocess": first },
938
1026
  { "name": "startConnector", "symbols": [lexer.has("kw_start") ? { type: "kw_start" } : kw_start], "postprocess": first },
939
1027
  { "name": "middleConnector", "symbols": [lexer.has("kw_middle") ? { type: "kw_middle" } : kw_middle], "postprocess": first },
1028
+ { "name": "agoConnector", "symbols": [lexer.has("kw_ago") ? { type: "kw_ago" } : kw_ago], "postprocess": first },
1029
+ { "name": "henceConnector", "symbols": [lexer.has("otherKeyword") ? { type: "otherKeyword" } : otherKeyword], "postprocess": (d, _, reject) => d[0].value === "hence" ? d[0] : reject },
1030
+ { "name": "laterConnector", "symbols": [lexer.has("otherKeyword") ? { type: "otherKeyword" } : otherKeyword], "postprocess": (d, _, reject) => d[0].value === "later" ? d[0] : reject },
940
1031
  { "name": "earlyModifier", "symbols": [lexer.has("kw_early") ? { type: "kw_early" } : kw_early], "postprocess": first },
941
1032
  { "name": "midModifier", "symbols": [lexer.has("kw_mid") ? { type: "kw_mid" } : kw_mid], "postprocess": first },
942
1033
  { "name": "lateModifier", "symbols": [lexer.has("kw_late") ? { type: "kw_late" } : kw_late], "postprocess": first },
@@ -953,6 +1044,10 @@ var grammar = {
953
1044
  { "name": "now", "symbols": [lexer.has("kw_now") ? { type: "kw_now" } : kw_now], "postprocess": first },
954
1045
  { "name": "noon", "symbols": [lexer.has("kw_noon") ? { type: "kw_noon" } : kw_noon], "postprocess": first },
955
1046
  { "name": "midnight", "symbols": [lexer.has("kw_midnight") ? { type: "kw_midnight" } : kw_midnight], "postprocess": first },
1047
+ { "name": "weekendKeyword", "symbols": [lexer.has("kw_weekend") ? { type: "kw_weekend" } : kw_weekend], "postprocess": first },
1048
+ { "name": "tonightKeyword", "symbols": [lexer.has("kw_tonight") ? { type: "kw_tonight" } : kw_tonight], "postprocess": first },
1049
+ { "name": "nightKeyword", "symbols": [lexer.has("kw_night") ? { type: "kw_night" } : kw_night], "postprocess": first },
1050
+ { "name": "fortnightKeyword", "symbols": [lexer.has("kw_fortnight") ? { type: "kw_fortnight" } : kw_fortnight], "postprocess": first },
956
1051
  { "name": "_$ebnf$1", "symbols": [] },
957
1052
  { "name": "_$ebnf$1", "symbols": ["_$ebnf$1", lexer.has("ws") ? { type: "ws" } : ws], "postprocess": (d) => d[0].concat([d[1]]) },
958
1053
  { "name": "_", "symbols": ["_$ebnf$1"], "postprocess": nuller }
@@ -1210,6 +1305,114 @@ function convertMonthDay(node, opts) {
1210
1305
  }
1211
1306
  return date;
1212
1307
  }
1308
+ function convertOrdinalWeekdayOfMonth(node, opts) {
1309
+ const ref = opts.referenceDate;
1310
+ const ordinal2 = node.ordinalWeekday;
1311
+ const weekdayName = node.weekday.toLowerCase();
1312
+ const weekdayNum = WEEKDAY_MAP[weekdayName] ?? 0;
1313
+ let month2;
1314
+ let year;
1315
+ if (node.monthFromRef) {
1316
+ month2 = ref.getUTCMonth();
1317
+ year = ref.getUTCFullYear();
1318
+ } else if (node.nextMonth) {
1319
+ month2 = ref.getUTCMonth() + 1;
1320
+ year = ref.getUTCFullYear();
1321
+ if (month2 > 11) {
1322
+ month2 = 0;
1323
+ year++;
1324
+ }
1325
+ } else {
1326
+ month2 = typeof node.month === "number" ? node.month - 1 : ref.getUTCMonth();
1327
+ year = ref.getUTCFullYear();
1328
+ if (month2 < ref.getUTCMonth()) {
1329
+ year++;
1330
+ }
1331
+ }
1332
+ if (ordinal2 === -1) {
1333
+ const lastDay2 = new Date(Date.UTC(year, month2 + 1, 0));
1334
+ let day2 = lastDay2.getUTCDate();
1335
+ let date2 = new Date(Date.UTC(year, month2, day2));
1336
+ while (date2.getUTCDay() !== weekdayNum) {
1337
+ day2--;
1338
+ date2 = new Date(Date.UTC(year, month2, day2));
1339
+ }
1340
+ return date2;
1341
+ }
1342
+ let count = 0;
1343
+ for (let day2 = 1; day2 <= 31; day2++) {
1344
+ const date2 = new Date(Date.UTC(year, month2, day2));
1345
+ if (date2.getUTCMonth() !== month2) {
1346
+ break;
1347
+ }
1348
+ if (date2.getUTCDay() === weekdayNum) {
1349
+ count++;
1350
+ if (count === ordinal2) {
1351
+ return date2;
1352
+ }
1353
+ }
1354
+ }
1355
+ const lastDay = new Date(Date.UTC(year, month2 + 1, 0));
1356
+ let day = lastDay.getUTCDate();
1357
+ let date = new Date(Date.UTC(year, month2, day));
1358
+ while (date.getUTCDay() !== weekdayNum) {
1359
+ day--;
1360
+ date = new Date(Date.UTC(year, month2, day));
1361
+ }
1362
+ return date;
1363
+ }
1364
+ function convertLastDayOfMonth(node, opts) {
1365
+ const ref = opts.referenceDate;
1366
+ let month2;
1367
+ let year;
1368
+ if (node.monthFromRef) {
1369
+ month2 = ref.getUTCMonth();
1370
+ year = ref.getUTCFullYear();
1371
+ } else if (node.nextMonth) {
1372
+ month2 = ref.getUTCMonth() + 1;
1373
+ year = ref.getUTCFullYear();
1374
+ if (month2 > 11) {
1375
+ month2 = 0;
1376
+ year++;
1377
+ }
1378
+ } else {
1379
+ month2 = typeof node.month === "number" ? node.month - 1 : ref.getUTCMonth();
1380
+ year = ref.getUTCFullYear();
1381
+ if (month2 < ref.getUTCMonth()) {
1382
+ year++;
1383
+ }
1384
+ }
1385
+ return new Date(Date.UTC(year, month2 + 1, 0));
1386
+ }
1387
+ function convertDayOnly(node, opts) {
1388
+ const ref = opts.referenceDate;
1389
+ const day = node.day;
1390
+ const refDay = ref.getUTCDate();
1391
+ const refMonth = ref.getUTCMonth();
1392
+ const refYear = ref.getUTCFullYear();
1393
+ let month2 = refMonth;
1394
+ let year = refYear;
1395
+ if (day < refDay) {
1396
+ month2 = refMonth + 1;
1397
+ if (month2 > 11) {
1398
+ month2 = 0;
1399
+ year++;
1400
+ }
1401
+ }
1402
+ let date = new Date(Date.UTC(year, month2, day));
1403
+ while (date.getUTCDate() !== day) {
1404
+ month2++;
1405
+ if (month2 > 11) {
1406
+ month2 = 0;
1407
+ year++;
1408
+ }
1409
+ date = new Date(Date.UTC(year, month2, day));
1410
+ }
1411
+ if (node.time) {
1412
+ applyTime(date, node.time);
1413
+ }
1414
+ return date;
1415
+ }
1213
1416
  function convertRelativePeriod(node, opts) {
1214
1417
  const ref = opts.referenceDate;
1215
1418
  const period = node.period.toLowerCase();
@@ -1269,6 +1472,9 @@ function convertDateNode(node, opts) {
1269
1472
  if (node.timeOnly && node.time) {
1270
1473
  return convertTimeOnly(node, opts);
1271
1474
  }
1475
+ if (node.ordinalWeekday !== void 0 && node.weekday !== void 0) {
1476
+ return convertOrdinalWeekdayOfMonth(node, opts);
1477
+ }
1272
1478
  if (node.weekday) {
1273
1479
  return convertWeekday(node, opts);
1274
1480
  }
@@ -1278,6 +1484,23 @@ function convertDateNode(node, opts) {
1278
1484
  if (node.month !== void 0 && node.day !== void 0) {
1279
1485
  return convertMonthDay(node, opts);
1280
1486
  }
1487
+ if (node.dayOnly && node.day !== void 0) {
1488
+ return convertDayOnly(node, opts);
1489
+ }
1490
+ if (node.lastDayOfMonth) {
1491
+ return convertLastDayOfMonth(node, opts);
1492
+ }
1493
+ if (node.dayOfMonth && node.nextMonth) {
1494
+ const ref = opts.referenceDate;
1495
+ let month2 = ref.getUTCMonth() + 1;
1496
+ let year = ref.getUTCFullYear();
1497
+ if (month2 > 11) {
1498
+ month2 = 0;
1499
+ year++;
1500
+ }
1501
+ const day = node.ordinalWeekday || 1;
1502
+ return new Date(Date.UTC(year, month2, day));
1503
+ }
1281
1504
  if (node.relative && node.period) {
1282
1505
  return convertRelativePeriod(node, opts);
1283
1506
  }
@@ -1349,7 +1572,7 @@ function getHalfDates(half2, year, fiscalYearStart) {
1349
1572
  const start = new Date(Date.UTC(startYear, halfStartMonth, 1));
1350
1573
  const endMonth = (halfStartMonth + 6) % 12;
1351
1574
  let endYear = startYear;
1352
- if (endMonth < halfStartMonth || endMonth === halfStartMonth) {
1575
+ if (endMonth <= halfStartMonth) {
1353
1576
  endYear++;
1354
1577
  }
1355
1578
  const end = new Date(Date.UTC(endYear, endMonth, 0));
@@ -1422,8 +1645,115 @@ function getModifiedPeriod(start, end, modifier) {
1422
1645
  }
1423
1646
 
1424
1647
  // src/converters/fuzzy.ts
1648
+ function getWeekendDates(ref, relative, opts) {
1649
+ const currentDay = ref.getUTCDay();
1650
+ const saturdayOffset = (6 - currentDay + 7) % 7;
1651
+ let saturday = new Date(
1652
+ Date.UTC(
1653
+ ref.getUTCFullYear(),
1654
+ ref.getUTCMonth(),
1655
+ ref.getUTCDate() + saturdayOffset
1656
+ )
1657
+ );
1658
+ if (relative === "next") {
1659
+ saturday = new Date(saturday.getTime() + MS_PER_WEEK);
1660
+ } else if (relative === "last") {
1661
+ if (currentDay === 6 || currentDay === 0) {
1662
+ saturday = new Date(saturday.getTime() - MS_PER_WEEK);
1663
+ }
1664
+ saturday = new Date(saturday.getTime() - MS_PER_WEEK);
1665
+ }
1666
+ const sunday = new Date(saturday.getTime() + MS_PER_DAY);
1667
+ const sundayEnd = new Date(
1668
+ Date.UTC(
1669
+ sunday.getUTCFullYear(),
1670
+ sunday.getUTCMonth(),
1671
+ sunday.getUTCDate(),
1672
+ 23,
1673
+ 59
1674
+ )
1675
+ );
1676
+ return { start: saturday, end: sundayEnd };
1677
+ }
1678
+ function getNightDates(ref, relative, weekday2) {
1679
+ let baseDate = new Date(ref);
1680
+ if (relative === "last") {
1681
+ baseDate = new Date(
1682
+ Date.UTC(
1683
+ ref.getUTCFullYear(),
1684
+ ref.getUTCMonth(),
1685
+ ref.getUTCDate() - 1
1686
+ )
1687
+ );
1688
+ } else if (relative === "tomorrow") {
1689
+ baseDate = new Date(
1690
+ Date.UTC(
1691
+ ref.getUTCFullYear(),
1692
+ ref.getUTCMonth(),
1693
+ ref.getUTCDate() + 1
1694
+ )
1695
+ );
1696
+ } else if (weekday2) {
1697
+ const targetDay = WEEKDAY_MAP[weekday2.toLowerCase()];
1698
+ if (targetDay !== void 0) {
1699
+ const currentDay = ref.getUTCDay();
1700
+ let daysToAdd = (targetDay - currentDay + 7) % 7;
1701
+ if (daysToAdd === 0) {
1702
+ daysToAdd = 7;
1703
+ }
1704
+ baseDate = new Date(
1705
+ Date.UTC(
1706
+ ref.getUTCFullYear(),
1707
+ ref.getUTCMonth(),
1708
+ ref.getUTCDate() + daysToAdd
1709
+ )
1710
+ );
1711
+ }
1712
+ }
1713
+ const start = new Date(
1714
+ Date.UTC(
1715
+ baseDate.getUTCFullYear(),
1716
+ baseDate.getUTCMonth(),
1717
+ baseDate.getUTCDate(),
1718
+ 18,
1719
+ 0
1720
+ )
1721
+ );
1722
+ const end = new Date(
1723
+ Date.UTC(
1724
+ baseDate.getUTCFullYear(),
1725
+ baseDate.getUTCMonth(),
1726
+ baseDate.getUTCDate(),
1727
+ 23,
1728
+ 59
1729
+ )
1730
+ );
1731
+ return { start, end };
1732
+ }
1425
1733
  function getBasePeriodDates(node, opts, year) {
1426
1734
  const ref = opts.referenceDate;
1735
+ if (node.period === "weekend") {
1736
+ return getWeekendDates(ref, node.relative || "this", opts);
1737
+ }
1738
+ if (node.period === "tonight") {
1739
+ return getNightDates(ref, void 0, void 0);
1740
+ }
1741
+ if (node.period === "night") {
1742
+ return getNightDates(ref, node.relative, node.weekday);
1743
+ }
1744
+ if (node.period === "fortnight") {
1745
+ const count = node.count || 1;
1746
+ const relative = node.relative;
1747
+ const twoWeeks = 14 * MS_PER_DAY;
1748
+ if (relative === "last") {
1749
+ const end2 = new Date(ref);
1750
+ const start2 = new Date(ref.getTime() - twoWeeks);
1751
+ return { start: start2, end: end2 };
1752
+ }
1753
+ const start = new Date(ref);
1754
+ const end = new Date(ref.getTime() + count * twoWeeks);
1755
+ return { start, end };
1756
+ }
1427
1757
  if (node.period === "quarter" && node.quarter) {
1428
1758
  return getQuarterDates(
1429
1759
  node.quarter,
@@ -1586,7 +1916,7 @@ function convertASTToResult(ast, opts, originalInput) {
1586
1916
  end = new Date(Date.UTC(year, month2 + 1, 0));
1587
1917
  }
1588
1918
  if (endNode.yearOnly) {
1589
- const year = endNode.year;
1919
+ const year = endNode.year ?? opts.referenceDate.getUTCFullYear();
1590
1920
  end = new Date(Date.UTC(year, 11, 31));
1591
1921
  }
1592
1922
  } else if (endNode.nodeType === "fuzzy") {
@@ -1617,6 +1947,7 @@ function convertASTToResult(ast, opts, originalInput) {
1617
1947
  case "fuzzy": {
1618
1948
  const { start, end } = convertFuzzyNode(expression, opts);
1619
1949
  const mod = expression.modifier;
1950
+ const period = expression.period;
1620
1951
  if (mod === "start" || mod === "beginning") {
1621
1952
  const periodDates = convertFuzzyNodeWithoutModifier(expression, opts);
1622
1953
  return { type: "date", date: periodDates.start, title };
@@ -1625,6 +1956,10 @@ function convertASTToResult(ast, opts, originalInput) {
1625
1956
  const periodDates = convertFuzzyNodeWithoutModifier(expression, opts);
1626
1957
  return { type: "date", date: periodDates.end, title };
1627
1958
  }
1959
+ if (period === "weekend" || period === "night" || period === "tonight" || period === "fortnight") {
1960
+ const duration = end.getTime() - start.getTime();
1961
+ return { type: "span", start, end, duration, title };
1962
+ }
1628
1963
  return { type: "fuzzy", start, end, approximate: true, title };
1629
1964
  }
1630
1965
  case "relative": {
@@ -1641,6 +1976,81 @@ function convertASTToResult(ast, opts, originalInput) {
1641
1976
  }
1642
1977
  return { type: "span", start, end, duration, title };
1643
1978
  }
1979
+ case "relativeDate": {
1980
+ const durationNode = expression.duration;
1981
+ const baseNode = expression.baseDate;
1982
+ const timeSpec = expression.time;
1983
+ let baseDate;
1984
+ if (baseNode) {
1985
+ baseDate = convertDateNode(baseNode, opts);
1986
+ } else {
1987
+ baseDate = new Date(opts.referenceDate);
1988
+ }
1989
+ const durationValue = durationNode.value;
1990
+ const durationUnit = durationNode.unit;
1991
+ const direction = expression.direction === "past" ? -1 : 1;
1992
+ let resultDate;
1993
+ if (durationUnit === "month") {
1994
+ resultDate = new Date(
1995
+ Date.UTC(
1996
+ baseDate.getUTCFullYear(),
1997
+ baseDate.getUTCMonth() + direction * durationValue,
1998
+ baseDate.getUTCDate(),
1999
+ baseDate.getUTCHours(),
2000
+ baseDate.getUTCMinutes()
2001
+ )
2002
+ );
2003
+ } else if (durationUnit === "year") {
2004
+ resultDate = new Date(
2005
+ Date.UTC(
2006
+ baseDate.getUTCFullYear() + direction * durationValue,
2007
+ baseDate.getUTCMonth(),
2008
+ baseDate.getUTCDate(),
2009
+ baseDate.getUTCHours(),
2010
+ baseDate.getUTCMinutes()
2011
+ )
2012
+ );
2013
+ } else {
2014
+ const durationMs = convertDurationNode(durationNode);
2015
+ resultDate = new Date(baseDate.getTime() + direction * durationMs);
2016
+ }
2017
+ if (timeSpec) {
2018
+ if ("special" in timeSpec) {
2019
+ if (timeSpec.special === "noon") {
2020
+ resultDate = new Date(
2021
+ Date.UTC(
2022
+ resultDate.getUTCFullYear(),
2023
+ resultDate.getUTCMonth(),
2024
+ resultDate.getUTCDate(),
2025
+ 12,
2026
+ 0
2027
+ )
2028
+ );
2029
+ } else if (timeSpec.special === "midnight") {
2030
+ resultDate = new Date(
2031
+ Date.UTC(
2032
+ resultDate.getUTCFullYear(),
2033
+ resultDate.getUTCMonth(),
2034
+ resultDate.getUTCDate(),
2035
+ 0,
2036
+ 0
2037
+ )
2038
+ );
2039
+ }
2040
+ } else {
2041
+ resultDate = new Date(
2042
+ Date.UTC(
2043
+ resultDate.getUTCFullYear(),
2044
+ resultDate.getUTCMonth(),
2045
+ resultDate.getUTCDate(),
2046
+ timeSpec.hour,
2047
+ timeSpec.minute
2048
+ )
2049
+ );
2050
+ }
2051
+ }
2052
+ return { type: "date", date: resultDate, title };
2053
+ }
1644
2054
  default:
1645
2055
  return null;
1646
2056
  }
@@ -1663,13 +2073,13 @@ function stripUnmatchedPunctuation(input) {
1663
2073
  openParens = (result.match(/\(/g) || []).length;
1664
2074
  closeParens = (result.match(/\)/g) || []).length;
1665
2075
  while (openParens > closeParens) {
1666
- result = result.replace("(", "");
2076
+ result = result.replace(/\(/, "");
1667
2077
  openParens--;
1668
2078
  }
1669
2079
  openBrackets = (result.match(/\[/g) || []).length;
1670
2080
  closeBrackets = (result.match(/]/g) || []).length;
1671
2081
  while (openBrackets > closeBrackets) {
1672
- result = result.replace("[", "");
2082
+ result = result.replace(/\[/, "");
1673
2083
  openBrackets--;
1674
2084
  }
1675
2085
  return {