@supercmd/calculator 1.0.2 → 1.0.3

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/dist/index.js CHANGED
@@ -1870,7 +1870,10 @@ var DATE_PATTERNS = [
1870
1870
  /^(?:what(?:'s| is) )?(?:the )?(?:current )?date(?: today)?$/i,
1871
1871
  /^(?:(?:what(?:'s|s| is))\s+today|(?:what(?:'s|s| is))\s+today'?s date|what day is it|what date is .+|todays date|today'?s date|date today|current date|what day is .+|when is .+)$/i,
1872
1872
  // Days between dates
1873
- /^(?:days?\s+)?(?:between|from)\s+.+\s+(?:to|and|until)\s+.+$/i
1873
+ /^(?:days?\s+)?(?:between|from)\s+.+\s+(?:to|and|until)\s+.+$/i,
1874
+ // Days until
1875
+ /^(?:days?\s+)?(?:until|till|to)\s+.+$/i,
1876
+ /^(?:how many days?\s+)?(?:until|till|to|before)\s+.+$/i
1874
1877
  ];
1875
1878
  function tryDateIntent(input) {
1876
1879
  for (const pattern of DATE_PATTERNS) {
@@ -2643,6 +2646,25 @@ function evaluateDate(query) {
2643
2646
  return formatDateResult(query, d, { iso: d.toISOString(), unix: Math.floor(d.getTime() / 1e3) });
2644
2647
  }
2645
2648
  }
2649
+ const daysUntilMatch = lower.match(/^(?:(?:how many )?days?\s+)?(?:until|till|to|before)\s+(.+)$/);
2650
+ if (daysUntilMatch) {
2651
+ const target = parseDateTarget(daysUntilMatch[1].trim());
2652
+ if (target) {
2653
+ const now = /* @__PURE__ */ new Date();
2654
+ now.setHours(0, 0, 0, 0);
2655
+ const targetDate = new Date(target);
2656
+ targetDate.setHours(0, 0, 0, 0);
2657
+ const diffMs = targetDate.getTime() - now.getTime();
2658
+ const diffDays = Math.round(diffMs / (1e3 * 60 * 60 * 24));
2659
+ return {
2660
+ type: "date",
2661
+ input: query,
2662
+ result: diffDays,
2663
+ formatted: `${diffDays} days`,
2664
+ metadata: { from: now.toISOString(), to: target.toISOString() }
2665
+ };
2666
+ }
2667
+ }
2646
2668
  const betweenMatch = lower.match(/^(?:days?\s+)?(?:between|from)\s+(.+)\s+(?:to|and|until)\s+(.+)$/);
2647
2669
  if (betweenMatch) {
2648
2670
  const d1 = parseFlexibleDate(betweenMatch[1].trim());
@@ -2735,6 +2757,82 @@ function formatDateResult(input, d, extraMeta) {
2735
2757
  }
2736
2758
  };
2737
2759
  }
2760
+ function getNextOccurrence(month, day) {
2761
+ const now = /* @__PURE__ */ new Date();
2762
+ const thisYear = new Date(now.getFullYear(), month - 1, day);
2763
+ if (thisYear.getTime() >= new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime()) {
2764
+ return thisYear;
2765
+ }
2766
+ return new Date(now.getFullYear() + 1, month - 1, day);
2767
+ }
2768
+ var NAMED_DATES = {
2769
+ "christmas": () => getNextOccurrence(12, 25),
2770
+ "christmas day": () => getNextOccurrence(12, 25),
2771
+ "christmas eve": () => getNextOccurrence(12, 24),
2772
+ "new year": () => getNextOccurrence(1, 1),
2773
+ "new years": () => getNextOccurrence(1, 1),
2774
+ "new year's": () => getNextOccurrence(1, 1),
2775
+ "new year's day": () => getNextOccurrence(1, 1),
2776
+ "new years day": () => getNextOccurrence(1, 1),
2777
+ "new years eve": () => getNextOccurrence(12, 31),
2778
+ "new year's eve": () => getNextOccurrence(12, 31),
2779
+ "valentines": () => getNextOccurrence(2, 14),
2780
+ "valentines day": () => getNextOccurrence(2, 14),
2781
+ "valentine's day": () => getNextOccurrence(2, 14),
2782
+ "halloween": () => getNextOccurrence(10, 31),
2783
+ "independence day": () => getNextOccurrence(7, 4),
2784
+ "st patricks day": () => getNextOccurrence(3, 17),
2785
+ "st patrick's day": () => getNextOccurrence(3, 17)
2786
+ };
2787
+ var MONTH_NAMES = {
2788
+ january: 1,
2789
+ jan: 1,
2790
+ february: 2,
2791
+ feb: 2,
2792
+ march: 3,
2793
+ mar: 3,
2794
+ april: 4,
2795
+ apr: 4,
2796
+ may: 5,
2797
+ june: 6,
2798
+ jun: 6,
2799
+ july: 7,
2800
+ jul: 7,
2801
+ august: 8,
2802
+ aug: 8,
2803
+ september: 9,
2804
+ sep: 9,
2805
+ sept: 9,
2806
+ october: 10,
2807
+ oct: 10,
2808
+ november: 11,
2809
+ nov: 11,
2810
+ december: 12,
2811
+ dec: 12
2812
+ };
2813
+ function parseMonthDay(str) {
2814
+ const cleaned = str.replace(/(\d+)(?:st|nd|rd|th)/gi, "$1");
2815
+ let match = cleaned.match(/^(\d{1,2})\s+([a-z]+)$/i);
2816
+ if (match) {
2817
+ const day = parseInt(match[1]);
2818
+ const month = MONTH_NAMES[match[2].toLowerCase()];
2819
+ if (month && day >= 1 && day <= 31) return getNextOccurrence(month, day);
2820
+ }
2821
+ match = cleaned.match(/^([a-z]+)\s+(\d{1,2})$/i);
2822
+ if (match) {
2823
+ const month = MONTH_NAMES[match[1].toLowerCase()];
2824
+ const day = parseInt(match[2]);
2825
+ if (month && day >= 1 && day <= 31) return getNextOccurrence(month, day);
2826
+ }
2827
+ return null;
2828
+ }
2829
+ function parseDateTarget(str) {
2830
+ const lower = str.toLowerCase().trim();
2831
+ if (NAMED_DATES[lower]) return NAMED_DATES[lower]();
2832
+ const monthDay = parseMonthDay(lower);
2833
+ if (monthDay) return monthDay;
2834
+ return parseFlexibleDate(lower);
2835
+ }
2738
2836
  function parseFlexibleDate(str) {
2739
2837
  if (str === "today" || str === "now" || str === "date") return /* @__PURE__ */ new Date();
2740
2838
  if (str === "tomorrow") {
@@ -2873,6 +2971,46 @@ async function calculate(input, options) {
2873
2971
  const locale = options?.locale ?? "en-US";
2874
2972
  const precision = options?.precision ?? 10;
2875
2973
  const rateProvider = options?.rateProvider ?? getDefaultProvider();
2974
+ const convMathMatch = trimmed.match(
2975
+ /^(.+?\s+(?:to|in)\s+[a-zA-Z$€£¥₹₩₽₺₦₵₪฿]+(?:\s+[a-zA-Z]+)?)\s*([+\-*/^])\s*(.+)$/i
2976
+ );
2977
+ if (convMathMatch) {
2978
+ const convPart = convMathMatch[1].trim();
2979
+ const operator = convMathMatch[2];
2980
+ const mathPart = convMathMatch[3].trim();
2981
+ const convIntent = detectIntent(convPart);
2982
+ if (convIntent.kind === "currency" || convIntent.kind === "crypto" || convIntent.kind === "unit") {
2983
+ let convResult;
2984
+ switch (convIntent.kind) {
2985
+ case "currency":
2986
+ convResult = await evaluateCurrency(convIntent.amount, convIntent.from, convIntent.to, rateProvider, locale, precision);
2987
+ break;
2988
+ case "crypto":
2989
+ convResult = await evaluateCrypto(convIntent.amount, convIntent.from, convIntent.to, rateProvider, locale, precision);
2990
+ break;
2991
+ case "unit":
2992
+ convResult = evaluateUnit(convIntent.amount, convIntent.from, convIntent.to, locale, precision);
2993
+ break;
2994
+ }
2995
+ if (convResult.type !== "error" && typeof convResult.result === "number") {
2996
+ const mathExpr = `${convResult.result} ${operator} ${mathPart}`;
2997
+ const mathResult = evaluateMath(mathExpr, locale, precision);
2998
+ if (mathResult.type !== "error") {
2999
+ return {
3000
+ ...mathResult,
3001
+ type: convResult.type,
3002
+ input: trimmed,
3003
+ metadata: {
3004
+ ...mathResult.metadata,
3005
+ conversionResult: convResult.result,
3006
+ conversionFormatted: convResult.formatted,
3007
+ operation: `${convResult.formatted} ${operator} ${mathPart}`
3008
+ }
3009
+ };
3010
+ }
3011
+ }
3012
+ }
3013
+ }
2876
3014
  const intent = detectIntent(trimmed);
2877
3015
  switch (intent.kind) {
2878
3016
  case "time":
package/dist/index.mjs CHANGED
@@ -1833,7 +1833,10 @@ var DATE_PATTERNS = [
1833
1833
  /^(?:what(?:'s| is) )?(?:the )?(?:current )?date(?: today)?$/i,
1834
1834
  /^(?:(?:what(?:'s|s| is))\s+today|(?:what(?:'s|s| is))\s+today'?s date|what day is it|what date is .+|todays date|today'?s date|date today|current date|what day is .+|when is .+)$/i,
1835
1835
  // Days between dates
1836
- /^(?:days?\s+)?(?:between|from)\s+.+\s+(?:to|and|until)\s+.+$/i
1836
+ /^(?:days?\s+)?(?:between|from)\s+.+\s+(?:to|and|until)\s+.+$/i,
1837
+ // Days until
1838
+ /^(?:days?\s+)?(?:until|till|to)\s+.+$/i,
1839
+ /^(?:how many days?\s+)?(?:until|till|to|before)\s+.+$/i
1837
1840
  ];
1838
1841
  function tryDateIntent(input) {
1839
1842
  for (const pattern of DATE_PATTERNS) {
@@ -2606,6 +2609,25 @@ function evaluateDate(query) {
2606
2609
  return formatDateResult(query, d, { iso: d.toISOString(), unix: Math.floor(d.getTime() / 1e3) });
2607
2610
  }
2608
2611
  }
2612
+ const daysUntilMatch = lower.match(/^(?:(?:how many )?days?\s+)?(?:until|till|to|before)\s+(.+)$/);
2613
+ if (daysUntilMatch) {
2614
+ const target = parseDateTarget(daysUntilMatch[1].trim());
2615
+ if (target) {
2616
+ const now = /* @__PURE__ */ new Date();
2617
+ now.setHours(0, 0, 0, 0);
2618
+ const targetDate = new Date(target);
2619
+ targetDate.setHours(0, 0, 0, 0);
2620
+ const diffMs = targetDate.getTime() - now.getTime();
2621
+ const diffDays = Math.round(diffMs / (1e3 * 60 * 60 * 24));
2622
+ return {
2623
+ type: "date",
2624
+ input: query,
2625
+ result: diffDays,
2626
+ formatted: `${diffDays} days`,
2627
+ metadata: { from: now.toISOString(), to: target.toISOString() }
2628
+ };
2629
+ }
2630
+ }
2609
2631
  const betweenMatch = lower.match(/^(?:days?\s+)?(?:between|from)\s+(.+)\s+(?:to|and|until)\s+(.+)$/);
2610
2632
  if (betweenMatch) {
2611
2633
  const d1 = parseFlexibleDate(betweenMatch[1].trim());
@@ -2698,6 +2720,82 @@ function formatDateResult(input, d, extraMeta) {
2698
2720
  }
2699
2721
  };
2700
2722
  }
2723
+ function getNextOccurrence(month, day) {
2724
+ const now = /* @__PURE__ */ new Date();
2725
+ const thisYear = new Date(now.getFullYear(), month - 1, day);
2726
+ if (thisYear.getTime() >= new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime()) {
2727
+ return thisYear;
2728
+ }
2729
+ return new Date(now.getFullYear() + 1, month - 1, day);
2730
+ }
2731
+ var NAMED_DATES = {
2732
+ "christmas": () => getNextOccurrence(12, 25),
2733
+ "christmas day": () => getNextOccurrence(12, 25),
2734
+ "christmas eve": () => getNextOccurrence(12, 24),
2735
+ "new year": () => getNextOccurrence(1, 1),
2736
+ "new years": () => getNextOccurrence(1, 1),
2737
+ "new year's": () => getNextOccurrence(1, 1),
2738
+ "new year's day": () => getNextOccurrence(1, 1),
2739
+ "new years day": () => getNextOccurrence(1, 1),
2740
+ "new years eve": () => getNextOccurrence(12, 31),
2741
+ "new year's eve": () => getNextOccurrence(12, 31),
2742
+ "valentines": () => getNextOccurrence(2, 14),
2743
+ "valentines day": () => getNextOccurrence(2, 14),
2744
+ "valentine's day": () => getNextOccurrence(2, 14),
2745
+ "halloween": () => getNextOccurrence(10, 31),
2746
+ "independence day": () => getNextOccurrence(7, 4),
2747
+ "st patricks day": () => getNextOccurrence(3, 17),
2748
+ "st patrick's day": () => getNextOccurrence(3, 17)
2749
+ };
2750
+ var MONTH_NAMES = {
2751
+ january: 1,
2752
+ jan: 1,
2753
+ february: 2,
2754
+ feb: 2,
2755
+ march: 3,
2756
+ mar: 3,
2757
+ april: 4,
2758
+ apr: 4,
2759
+ may: 5,
2760
+ june: 6,
2761
+ jun: 6,
2762
+ july: 7,
2763
+ jul: 7,
2764
+ august: 8,
2765
+ aug: 8,
2766
+ september: 9,
2767
+ sep: 9,
2768
+ sept: 9,
2769
+ october: 10,
2770
+ oct: 10,
2771
+ november: 11,
2772
+ nov: 11,
2773
+ december: 12,
2774
+ dec: 12
2775
+ };
2776
+ function parseMonthDay(str) {
2777
+ const cleaned = str.replace(/(\d+)(?:st|nd|rd|th)/gi, "$1");
2778
+ let match = cleaned.match(/^(\d{1,2})\s+([a-z]+)$/i);
2779
+ if (match) {
2780
+ const day = parseInt(match[1]);
2781
+ const month = MONTH_NAMES[match[2].toLowerCase()];
2782
+ if (month && day >= 1 && day <= 31) return getNextOccurrence(month, day);
2783
+ }
2784
+ match = cleaned.match(/^([a-z]+)\s+(\d{1,2})$/i);
2785
+ if (match) {
2786
+ const month = MONTH_NAMES[match[1].toLowerCase()];
2787
+ const day = parseInt(match[2]);
2788
+ if (month && day >= 1 && day <= 31) return getNextOccurrence(month, day);
2789
+ }
2790
+ return null;
2791
+ }
2792
+ function parseDateTarget(str) {
2793
+ const lower = str.toLowerCase().trim();
2794
+ if (NAMED_DATES[lower]) return NAMED_DATES[lower]();
2795
+ const monthDay = parseMonthDay(lower);
2796
+ if (monthDay) return monthDay;
2797
+ return parseFlexibleDate(lower);
2798
+ }
2701
2799
  function parseFlexibleDate(str) {
2702
2800
  if (str === "today" || str === "now" || str === "date") return /* @__PURE__ */ new Date();
2703
2801
  if (str === "tomorrow") {
@@ -2836,6 +2934,46 @@ async function calculate(input, options) {
2836
2934
  const locale = options?.locale ?? "en-US";
2837
2935
  const precision = options?.precision ?? 10;
2838
2936
  const rateProvider = options?.rateProvider ?? getDefaultProvider();
2937
+ const convMathMatch = trimmed.match(
2938
+ /^(.+?\s+(?:to|in)\s+[a-zA-Z$€£¥₹₩₽₺₦₵₪฿]+(?:\s+[a-zA-Z]+)?)\s*([+\-*/^])\s*(.+)$/i
2939
+ );
2940
+ if (convMathMatch) {
2941
+ const convPart = convMathMatch[1].trim();
2942
+ const operator = convMathMatch[2];
2943
+ const mathPart = convMathMatch[3].trim();
2944
+ const convIntent = detectIntent(convPart);
2945
+ if (convIntent.kind === "currency" || convIntent.kind === "crypto" || convIntent.kind === "unit") {
2946
+ let convResult;
2947
+ switch (convIntent.kind) {
2948
+ case "currency":
2949
+ convResult = await evaluateCurrency(convIntent.amount, convIntent.from, convIntent.to, rateProvider, locale, precision);
2950
+ break;
2951
+ case "crypto":
2952
+ convResult = await evaluateCrypto(convIntent.amount, convIntent.from, convIntent.to, rateProvider, locale, precision);
2953
+ break;
2954
+ case "unit":
2955
+ convResult = evaluateUnit(convIntent.amount, convIntent.from, convIntent.to, locale, precision);
2956
+ break;
2957
+ }
2958
+ if (convResult.type !== "error" && typeof convResult.result === "number") {
2959
+ const mathExpr = `${convResult.result} ${operator} ${mathPart}`;
2960
+ const mathResult = evaluateMath(mathExpr, locale, precision);
2961
+ if (mathResult.type !== "error") {
2962
+ return {
2963
+ ...mathResult,
2964
+ type: convResult.type,
2965
+ input: trimmed,
2966
+ metadata: {
2967
+ ...mathResult.metadata,
2968
+ conversionResult: convResult.result,
2969
+ conversionFormatted: convResult.formatted,
2970
+ operation: `${convResult.formatted} ${operator} ${mathPart}`
2971
+ }
2972
+ };
2973
+ }
2974
+ }
2975
+ }
2976
+ }
2839
2977
  const intent = detectIntent(trimmed);
2840
2978
  switch (intent.kind) {
2841
2979
  case "time":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supercmd/calculator",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "Powerful natural language calculator — math, units, currency, crypto, time zones, dates",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",