@supercmd/calculator 1.0.0 → 1.0.2

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.
Files changed (3) hide show
  1. package/dist/index.js +327 -60
  2. package/dist/index.mjs +327 -60
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -931,7 +931,7 @@ for (const cat of UNIT_CATEGORIES) {
931
931
  }
932
932
  }
933
933
  function lookupUnit(token) {
934
- return _unitIndex.get(token.toLowerCase());
934
+ return _unitIndex.get(normalizeUnitToken(token));
935
935
  }
936
936
  function convertUnit(value, from, to) {
937
937
  if (from.category !== to.category) {
@@ -946,6 +946,9 @@ function convertUnit(value, from, to) {
946
946
  const baseValue = value * from.def.factor;
947
947
  return baseValue / to.def.factor;
948
948
  }
949
+ function normalizeUnitToken(token) {
950
+ return token.toLowerCase().trim().replace(/\s+/g, " ").replace(/²/g, "2").replace(/³/g, "3");
951
+ }
949
952
 
950
953
  // src/data/timezones.ts
951
954
  var TIMEZONE_MAP = {
@@ -1018,8 +1021,11 @@ var TIMEZONE_MAP = {
1018
1021
  "buenos aires": "America/Argentina/Buenos_Aires",
1019
1022
  argentina: "America/Argentina/Buenos_Aires",
1020
1023
  lima: "America/Lima",
1024
+ peru: "America/Lima",
1021
1025
  bogota: "America/Bogota",
1026
+ colombia: "America/Bogota",
1022
1027
  santiago: "America/Santiago",
1028
+ chile: "America/Santiago",
1023
1029
  caracas: "America/Caracas",
1024
1030
  quito: "America/Guayaquil",
1025
1031
  // ─── Europe ───────────────────────────────────────────
@@ -1303,6 +1309,9 @@ var COUNTRY_TZ = {
1303
1309
  canada: "America/Toronto",
1304
1310
  mexico: "America/Mexico_City",
1305
1311
  brazil: "America/Sao_Paulo",
1312
+ colombia: "America/Bogota",
1313
+ peru: "America/Lima",
1314
+ chile: "America/Santiago",
1306
1315
  uk: "Europe/London",
1307
1316
  "united kingdom": "Europe/London",
1308
1317
  britain: "Europe/London",
@@ -1373,6 +1382,58 @@ function resolveTimezone(name) {
1373
1382
  }
1374
1383
 
1375
1384
  // src/providers/default.ts
1385
+ var STATIC_FIAT_PER_USD = {
1386
+ USD: 1,
1387
+ EUR: 0.92,
1388
+ GBP: 0.79,
1389
+ INR: 83.1,
1390
+ JPY: 150,
1391
+ CNY: 7.2,
1392
+ AUD: 1.52,
1393
+ CAD: 1.35,
1394
+ CHF: 0.88,
1395
+ KRW: 1330,
1396
+ SGD: 1.34,
1397
+ HKD: 7.8,
1398
+ NZD: 1.64,
1399
+ SEK: 10.5,
1400
+ NOK: 10.7,
1401
+ DKK: 6.85,
1402
+ MXN: 17.1,
1403
+ BRL: 5,
1404
+ THB: 35.8,
1405
+ PHP: 56.2,
1406
+ IDR: 15700,
1407
+ MYR: 4.48,
1408
+ ZAR: 18.4,
1409
+ AED: 3.67,
1410
+ SAR: 3.75,
1411
+ TRY: 32.1,
1412
+ PLN: 3.95,
1413
+ CZK: 23.2,
1414
+ RUB: 92
1415
+ };
1416
+ var STATIC_CRYPTO_USD = {
1417
+ BTC: 92e3,
1418
+ ETH: 3500,
1419
+ SOL: 180,
1420
+ XRP: 2.3,
1421
+ DOGE: 0.18,
1422
+ ADA: 0.8,
1423
+ MATIC: 0.95,
1424
+ DOT: 7.5,
1425
+ LTC: 95,
1426
+ AVAX: 42,
1427
+ LINK: 20,
1428
+ UNI: 12,
1429
+ ATOM: 10,
1430
+ XLM: 0.12,
1431
+ TON: 6,
1432
+ SHIB: 25e-6,
1433
+ BNB: 650,
1434
+ USDT: 1,
1435
+ USDC: 1
1436
+ };
1376
1437
  async function fetchJSON(url, timeoutMs = 8e3) {
1377
1438
  const controller = new AbortController();
1378
1439
  const timer = setTimeout(() => controller.abort(), timeoutMs);
@@ -1395,6 +1456,30 @@ async function withFallback(fns) {
1395
1456
  }
1396
1457
  throw lastError ?? new Error("All providers failed");
1397
1458
  }
1459
+ async function staticFiatRate(base, target) {
1460
+ const from = STATIC_FIAT_PER_USD[base];
1461
+ const to = STATIC_FIAT_PER_USD[target];
1462
+ if (from === void 0 || to === void 0) {
1463
+ throw new Error(`Static fiat rate unavailable for ${base}/${target}`);
1464
+ }
1465
+ return to / from;
1466
+ }
1467
+ async function staticCryptoRate(from, to) {
1468
+ const fromCrypto = STATIC_CRYPTO_USD[from];
1469
+ const toCrypto = STATIC_CRYPTO_USD[to];
1470
+ if (fromCrypto !== void 0 && toCrypto !== void 0) {
1471
+ return fromCrypto / toCrypto;
1472
+ }
1473
+ if (fromCrypto !== void 0) {
1474
+ const usdToTarget = await staticFiatRate("USD", to);
1475
+ return fromCrypto * usdToTarget;
1476
+ }
1477
+ if (toCrypto !== void 0) {
1478
+ const fromToUsd = await staticFiatRate(from, "USD");
1479
+ return fromToUsd / toCrypto;
1480
+ }
1481
+ throw new Error(`Static crypto rate unavailable for ${from}/${to}`);
1482
+ }
1398
1483
  async function frankfurterRate(base, target) {
1399
1484
  const data = await fetchJSON(
1400
1485
  `https://api.frankfurter.dev/v1/latest?base=${base}&symbols=${target}`
@@ -1644,7 +1729,8 @@ async function getCryptoRateWithFiatFallback(from, to, getFiat) {
1644
1729
  return await withFallback([
1645
1730
  () => coingeckoRate(from, to),
1646
1731
  () => binanceRate(from, to),
1647
- () => coincapRate(from, to)
1732
+ () => coincapRate(from, to),
1733
+ () => staticCryptoRate(from, to)
1648
1734
  ]);
1649
1735
  } catch {
1650
1736
  }
@@ -1652,7 +1738,8 @@ async function getCryptoRateWithFiatFallback(from, to, getFiat) {
1652
1738
  const cryptoToUsd = await withFallback([
1653
1739
  () => coingeckoRate(from, "USD"),
1654
1740
  () => binanceRate(from, "USDT"),
1655
- () => coincapRate(from, "USD")
1741
+ () => coincapRate(from, "USD"),
1742
+ () => staticCryptoRate(from, "USD")
1656
1743
  ]);
1657
1744
  if (to === "USD" || to === "USDT" || to === "USDC") return cryptoToUsd;
1658
1745
  const usdToFiat = await getFiat("USD", to);
@@ -1669,11 +1756,12 @@ async function getCryptoRateWithFiatFallback(from, to, getFiat) {
1669
1756
  async () => {
1670
1757
  const p = await coincapRate(to, "USD");
1671
1758
  return p === 0 ? Promise.reject("zero") : 1 / p;
1672
- }
1759
+ },
1760
+ () => staticCryptoRate("USD", to)
1673
1761
  ]);
1674
1762
  return fiatToUsd * usdToCrypto;
1675
1763
  }
1676
- throw new Error(`Could not resolve rate for ${from} \u2192 ${to}`);
1764
+ return staticCryptoRate(from, to);
1677
1765
  }
1678
1766
  function createDefaultProvider() {
1679
1767
  return {
@@ -1681,14 +1769,16 @@ function createDefaultProvider() {
1681
1769
  return withFallback([
1682
1770
  () => frankfurterRate(base, target),
1683
1771
  () => exchangeRateApiRate(base, target),
1684
- () => fawazCurrencyRate(base, target)
1772
+ () => fawazCurrencyRate(base, target),
1773
+ () => staticFiatRate(base, target)
1685
1774
  ]);
1686
1775
  },
1687
1776
  async getCryptoRate(base, target) {
1688
1777
  const getFiat = (b, t) => withFallback([
1689
1778
  () => frankfurterRate(b, t),
1690
1779
  () => exchangeRateApiRate(b, t),
1691
- () => fawazCurrencyRate(b, t)
1780
+ () => fawazCurrencyRate(b, t),
1781
+ () => staticFiatRate(b, t)
1692
1782
  ]);
1693
1783
  return getCryptoRateWithFiatFallback(base, target, getFiat);
1694
1784
  }
@@ -1697,28 +1787,46 @@ function createDefaultProvider() {
1697
1787
 
1698
1788
  // src/parser/intent.ts
1699
1789
  function detectIntent(input) {
1700
- const trimmed = input.trim();
1790
+ const trimmed = normalizeWhitespace(input);
1701
1791
  if (!trimmed) {
1702
1792
  return { kind: "math", expression: "0" };
1703
1793
  }
1704
1794
  return tryTimeIntent(trimmed) ?? tryDateIntent(trimmed) ?? tryCurrencyOrCryptoIntent(trimmed) ?? tryUnitIntent(trimmed) ?? { kind: "math", expression: trimmed };
1705
1795
  }
1706
- var TIME_IN_PATTERN = /^(?:what(?:'s| is) )?(?:the )?(?:current )?time (?:in|at) (.+)$/i;
1707
- var TIME_CONVERT_PATTERN = /^(.+?) to (.+?) time$/i;
1796
+ var TIME_IN_PATTERNS = [
1797
+ /^(?:what(?:'s| is) )?(?:the )?(?:current )?time (?:in|at) (.+)$/i,
1798
+ /^(?:what time is it|whats the time|what is the time|current time|time now) (?:in|at) (.+)$/i,
1799
+ /^(?:now|current time) in (.+)$/i
1800
+ ];
1801
+ var TIME_EXPLICIT_CONVERT_PATTERN = /^(midnight|noon|\d{1,2}(?::\d{2})?(?:\s*(?:am|pm))?)\s+(?:in\s+)?(.+?)\s+to\s+(.+?)(?:\s+time)?$/i;
1802
+ var TIME_CONVERT_PATTERN = /^(.+?) to (.+?)(?: time)?$/i;
1708
1803
  var TIME_NOW_PATTERN = /^(?:what(?:'s| is) )?(?:the )?(?:current )?time(?: now)?$/i;
1709
1804
  function tryTimeIntent(input) {
1710
1805
  let match;
1711
- match = input.match(TIME_IN_PATTERN);
1806
+ for (const pattern of TIME_IN_PATTERNS) {
1807
+ match = input.match(pattern);
1808
+ if (match) {
1809
+ const place = normalizeTimePlace(match[1]);
1810
+ const tz2 = resolveTimezone(place);
1811
+ if (tz2) return { kind: "time", query: input, to: tz2 };
1812
+ return { kind: "time", query: input, to: place };
1813
+ }
1814
+ }
1815
+ match = input.match(TIME_EXPLICIT_CONVERT_PATTERN);
1712
1816
  if (match) {
1713
- const place = match[1].trim();
1714
- const tz2 = resolveTimezone(place);
1715
- if (tz2) return { kind: "time", query: place, to: tz2 };
1716
- return { kind: "time", query: place, to: place };
1817
+ const time = match[1].trim();
1818
+ const from = normalizeTimePlace(match[2]);
1819
+ const to = normalizeTimePlace(match[3]);
1820
+ const fromTz = resolveTimezone(from);
1821
+ const toTz = resolveTimezone(to);
1822
+ if (fromTz && toTz) {
1823
+ return { kind: "time", query: input, from: fromTz, to: toTz, time };
1824
+ }
1717
1825
  }
1718
1826
  match = input.match(TIME_CONVERT_PATTERN);
1719
1827
  if (match) {
1720
- const from = match[1].trim();
1721
- const to = match[2].trim();
1828
+ const from = normalizeTimePlace(match[1]);
1829
+ const to = normalizeTimePlace(match[2]);
1722
1830
  const fromTz = resolveTimezone(from);
1723
1831
  const toTz = resolveTimezone(to);
1724
1832
  if (fromTz || toTz) {
@@ -1734,11 +1842,11 @@ function tryTimeIntent(input) {
1734
1842
  }
1735
1843
  const placeSuffixMatch = input.match(/^(.+?)\s+(?:time|now|time now)$/i);
1736
1844
  if (placeSuffixMatch) {
1737
- const place = placeSuffixMatch[1].trim();
1845
+ const place = normalizeTimePlace(placeSuffixMatch[1]);
1738
1846
  const tz2 = resolveTimezone(place);
1739
1847
  if (tz2) return { kind: "time", query: place, to: tz2 };
1740
1848
  }
1741
- const tz = resolveTimezone(input);
1849
+ const tz = resolveTimezone(normalizeTimePlace(input));
1742
1850
  if (tz) {
1743
1851
  return { kind: "time", query: input, to: tz };
1744
1852
  }
@@ -1747,11 +1855,12 @@ function tryTimeIntent(input) {
1747
1855
  var DATE_PATTERNS = [
1748
1856
  // Relative dates
1749
1857
  /^(today|now|tomorrow|yesterday)$/i,
1858
+ /^(date|day after tomorrow|day before yesterday)$/i,
1750
1859
  /^(next|last|this)\s+(monday|tuesday|wednesday|thursday|friday|saturday|sunday|week|month|year)$/i,
1751
1860
  /^(\d+)\s+(days?|weeks?|months?|years?|hours?|minutes?|seconds?)\s+(from now|ago|from today|from tomorrow)$/i,
1752
1861
  /^in\s+(\d+)\s+(days?|weeks?|months?|years?|hours?|minutes?|seconds?)$/i,
1753
1862
  // Unix timestamp
1754
- /^(?:unix\s+)?(?:timestamp\s+)?(\d{10,13})$/i,
1863
+ /^(?:unix\s+)?(?:timestamp\s+)?(\d{10,13}|0)$/i,
1755
1864
  /^(?:unix|timestamp|epoch)\s+(\d{10,13})$/i,
1756
1865
  // "to unix" / "to timestamp"
1757
1866
  /^.+\s+(?:to|in)\s+(?:unix|timestamp|epoch)$/i,
@@ -1759,6 +1868,7 @@ var DATE_PATTERNS = [
1759
1868
  /^\d{4}-\d{2}-\d{2}(?:T\d{2}:\d{2})?/,
1760
1869
  // "date" queries
1761
1870
  /^(?:what(?:'s| is) )?(?:the )?(?:current )?date(?: today)?$/i,
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,
1762
1872
  // Days between dates
1763
1873
  /^(?:days?\s+)?(?:between|from)\s+.+\s+(?:to|and|until)\s+.+$/i
1764
1874
  ];
@@ -1772,7 +1882,8 @@ function tryDateIntent(input) {
1772
1882
  }
1773
1883
  var CONVERSION_PATTERN = /^([\d.,]+)?\s*([a-zA-Z$€£¥₹₩₽₺₦₵₪฿]+(?:\s+[a-zA-Z]+)?)\s+(?:to|in|into|as|=)\s+([a-zA-Z$€£¥₹₩₽₺₦₵₪฿]+(?:\s+[a-zA-Z]+)?)$/i;
1774
1884
  function tryCurrencyOrCryptoIntent(input) {
1775
- const match = input.match(CONVERSION_PATTERN);
1885
+ const normalized = normalizeConversionInput(input);
1886
+ const match = normalized.match(CONVERSION_PATTERN);
1776
1887
  if (!match) return null;
1777
1888
  const amount = match[1] ? parseFloat(match[1].replace(/,/g, "")) : 1;
1778
1889
  const fromToken = match[2].trim();
@@ -1795,13 +1906,12 @@ function tryCurrencyOrCryptoIntent(input) {
1795
1906
  var UNIT_PATTERN = /^(-?[\d.,]+)\s*([a-zA-Z°/µμ'"²³]+(?:\s+[a-zA-Z]+(?:\s+[a-zA-Z]+)?)?)\s+(?:to|in|into|as|=)\s+([a-zA-Z°/µμ'"²³]+(?:\s+[a-zA-Z]+(?:\s+[a-zA-Z]+)?)?)$/i;
1796
1907
  var UNIT_PATTERN_NO_SPACE = /^(-?[\d.,]+)([a-zA-Z°]+)\s+(?:to|in|into|as)\s+([a-zA-Z°]+(?:\s+[a-zA-Z]+)?)$/i;
1797
1908
  function tryUnitIntent(input) {
1798
- const match = input.match(UNIT_PATTERN) || input.match(UNIT_PATTERN_NO_SPACE);
1909
+ const normalized = normalizeConversionInput(input);
1910
+ const match = normalized.match(UNIT_PATTERN) || normalized.match(UNIT_PATTERN_NO_SPACE);
1799
1911
  if (!match) return null;
1800
1912
  const amount = parseFloat(match[1].replace(/,/g, ""));
1801
1913
  const fromToken = match[2].trim().toLowerCase();
1802
1914
  const toToken = match[3].trim().toLowerCase();
1803
- if (resolveFiat(fromToken) || resolveCrypto(fromToken)) return null;
1804
- if (resolveFiat(toToken) || resolveCrypto(toToken)) return null;
1805
1915
  const fromUnit = lookupUnit(fromToken);
1806
1916
  const toUnit = lookupUnit(toToken);
1807
1917
  if (fromUnit && toUnit && fromUnit.category === toUnit.category) {
@@ -1813,8 +1923,22 @@ function tryUnitIntent(input) {
1813
1923
  category: fromUnit.category.name
1814
1924
  };
1815
1925
  }
1926
+ if (resolveFiat(fromToken) || resolveCrypto(fromToken)) return null;
1927
+ if (resolveFiat(toToken) || resolveCrypto(toToken)) return null;
1816
1928
  return null;
1817
1929
  }
1930
+ function normalizeWhitespace(input) {
1931
+ return input.trim().replace(/\s+/g, " ");
1932
+ }
1933
+ function normalizeConversionInput(input) {
1934
+ let normalized = normalizeWhitespace(input);
1935
+ normalized = normalized.replace(/^convert\s+/i, "").replace(/^how much is\s+/i, "").replace(/^what(?:'s|s| is)\s+/i, "");
1936
+ normalized = normalized.replace(/^([$€£¥₹₩₽₺₦₵₪฿])\s*([\d.,]+)/, "$2 $1");
1937
+ return normalized;
1938
+ }
1939
+ function normalizeTimePlace(value) {
1940
+ return normalizeWhitespace(value).replace(/^(?:time|current time|time now|now)\s+(?:in|at)\s+/i, "").replace(/^(?:in|at)\s+/i, "").replace(/\s+(?:time|now|time now)$/i, "").trim();
1941
+ }
1818
1942
 
1819
1943
  // src/evaluators/math.ts
1820
1944
  var CONSTANTS = {
@@ -1965,19 +2089,10 @@ var Parser = class {
1965
2089
  if (right === 0) throw new Error("Division by zero");
1966
2090
  left /= right;
1967
2091
  } else if (op === "%") {
1968
- const savedPos = this.pos;
1969
- this.skipWhitespace();
1970
- this.pos++;
1971
- this.skipWhitespace();
1972
- const next = this.expr[this.pos];
1973
- if (next === void 0 || /[+\-*/^)|&<>]/.test(next)) {
1974
- this.pos = savedPos;
1975
- break;
1976
- } else {
1977
- this.pos = savedPos;
1978
- this.consume();
1979
- left = left % this.parseExponentiation();
1980
- }
2092
+ this.consume();
2093
+ left = left % this.parseExponentiation();
2094
+ } else if (op && /[([a-zA-Zπτφ_]/.test(op)) {
2095
+ left *= this.parseExponentiation();
1981
2096
  } else {
1982
2097
  break;
1983
2098
  }
@@ -2019,7 +2134,7 @@ var Parser = class {
2019
2134
  if (this.expr[this.pos] === "!") {
2020
2135
  this.pos++;
2021
2136
  value = factorial(value);
2022
- } else if (this.expr[this.pos] === "%") {
2137
+ } else if (this.expr[this.pos] === "%" && this.shouldTreatAsPercentage()) {
2023
2138
  this.pos++;
2024
2139
  value = value / 100;
2025
2140
  } else {
@@ -2045,6 +2160,12 @@ var Parser = class {
2045
2160
  }
2046
2161
  throw new Error(`Unexpected character: '${this.expr[this.pos]}' at position ${this.pos}`);
2047
2162
  }
2163
+ shouldTreatAsPercentage() {
2164
+ let i = this.pos + 1;
2165
+ while (i < this.expr.length && /\s/.test(this.expr[i])) i++;
2166
+ const next = this.expr[i];
2167
+ return next === void 0 || /[+\-*/^)%|&<>]/.test(next);
2168
+ }
2048
2169
  charAt(i) {
2049
2170
  return i < this.expr.length ? this.expr[i] : "";
2050
2171
  }
@@ -2088,13 +2209,15 @@ var Parser = class {
2088
2209
  const name = this.expr.slice(start, this.pos).toLowerCase();
2089
2210
  this.skipWhitespace();
2090
2211
  if (this.expr[this.pos] === "(") {
2212
+ if (name === "max" || name === "min") {
2213
+ const args = this.parseArguments();
2214
+ if (args.length === 0) throw new Error(`Function '${name}' requires at least one argument`);
2215
+ return name === "max" ? Math.max(...args) : Math.min(...args);
2216
+ }
2091
2217
  if (FUNCTIONS2[name]) {
2092
- this.consume("(");
2093
- const a = this.parseBitwiseOr();
2094
- this.consume(",");
2095
- const b = this.parseBitwiseOr();
2096
- this.consume(")");
2097
- return FUNCTIONS2[name](a, b);
2218
+ const args = this.parseArguments();
2219
+ if (args.length !== 2) throw new Error(`Function '${name}' expects 2 arguments`);
2220
+ return FUNCTIONS2[name](args[0], args[1]);
2098
2221
  }
2099
2222
  if (FUNCTIONS[name]) {
2100
2223
  this.consume("(");
@@ -2107,6 +2230,26 @@ var Parser = class {
2107
2230
  if (CONSTANTS[name] !== void 0) return CONSTANTS[name];
2108
2231
  throw new Error(`Unknown identifier: '${name}'`);
2109
2232
  }
2233
+ parseArguments() {
2234
+ const args = [];
2235
+ this.consume("(");
2236
+ this.skipWhitespace();
2237
+ if (this.expr[this.pos] === ")") {
2238
+ this.consume(")");
2239
+ return args;
2240
+ }
2241
+ while (true) {
2242
+ args.push(this.parseBitwiseOr());
2243
+ this.skipWhitespace();
2244
+ if (this.expr[this.pos] === ",") {
2245
+ this.pos++;
2246
+ continue;
2247
+ }
2248
+ break;
2249
+ }
2250
+ this.consume(")");
2251
+ return args;
2252
+ }
2110
2253
  };
2111
2254
  function factorial(n) {
2112
2255
  if (n < 0) throw new Error("Factorial of negative number");
@@ -2118,7 +2261,8 @@ function factorial(n) {
2118
2261
  }
2119
2262
  function evaluateMath(expression, locale = "en-US", precision = 10) {
2120
2263
  try {
2121
- const parser = new Parser(expression);
2264
+ const normalizedExpression = normalizeMathExpression(expression);
2265
+ const parser = new Parser(normalizedExpression);
2122
2266
  const result = parser.parse();
2123
2267
  let formatted;
2124
2268
  if (Number.isInteger(result) && Math.abs(result) < Number.MAX_SAFE_INTEGER) {
@@ -2143,7 +2287,10 @@ function evaluateMath(expression, locale = "en-US", precision = 10) {
2143
2287
  input: expression,
2144
2288
  result,
2145
2289
  formatted,
2146
- metadata
2290
+ metadata: {
2291
+ ...metadata,
2292
+ normalizedExpression
2293
+ }
2147
2294
  };
2148
2295
  } catch (err) {
2149
2296
  return {
@@ -2153,6 +2300,41 @@ function evaluateMath(expression, locale = "en-US", precision = 10) {
2153
2300
  };
2154
2301
  }
2155
2302
  }
2303
+ function normalizeMathExpression(expression) {
2304
+ let normalized = expression.trim().replace(/\s+/g, " ");
2305
+ normalized = normalized.replace(/^(?:(?:what(?:'s|s| is))\s+the\s+|(?:what(?:'s|s| is))\s+|calculate\s+)/i, "");
2306
+ normalized = normalized.replace(/^the\s+/i, "");
2307
+ const wrappers = [
2308
+ [/^square root of (.+)$/i, "sqrt($1)"],
2309
+ [/^cube root of (.+)$/i, "cbrt($1)"],
2310
+ [/^log of (.+)$/i, "log($1)"],
2311
+ [/^sine of (.+)$/i, "sin($1)"],
2312
+ [/^cosine of (.+)$/i, "cos($1)"],
2313
+ [/^tangent of (.+)$/i, "tan($1)"],
2314
+ [/^absolute value of (.+)$/i, "abs($1)"],
2315
+ [/^factorial of (.+)$/i, "($1)!"],
2316
+ [/^half of (.+)$/i, "($1) / 2"],
2317
+ [/^double (.+)$/i, "2 * ($1)"],
2318
+ [/^triple (.+)$/i, "3 * ($1)"],
2319
+ [/^one third of (.+)$/i, "($1) / 3"],
2320
+ [/^one quarter of (.+)$/i, "($1) / 4"],
2321
+ [/^remainder of (.+?) divided by (.+)$/i, "($1) % ($2)"],
2322
+ [/^(.+?) to the power of (.+)$/i, "($1) ^ ($2)"],
2323
+ [/^(.+?) raised to (.+)$/i, "($1) ^ ($2)"],
2324
+ [/^(.+?) squared$/i, "($1) ^ 2"],
2325
+ [/^(.+?) cubed$/i, "($1) ^ 3"],
2326
+ [/^(\d+(?:\.\d+)?) percent of (.+)$/i, "($1%) * ($2)"],
2327
+ [/^(.+?%) of (.+)$/i, "($1) * ($2)"]
2328
+ ];
2329
+ for (const [pattern, replacement] of wrappers) {
2330
+ if (pattern.test(normalized)) {
2331
+ normalized = normalized.replace(pattern, replacement);
2332
+ break;
2333
+ }
2334
+ }
2335
+ normalized = normalized.replace(/\bdivided by\b/gi, "/").replace(/\btimes\b/gi, "*").replace(/\bplus\b/gi, "+").replace(/\bminus\b/gi, "-");
2336
+ return normalized.replace(/\s+/g, " ").trim();
2337
+ }
2156
2338
 
2157
2339
  // src/evaluators/unit.ts
2158
2340
  function evaluateUnit(amount, fromToken, toToken, locale = "en-US", precision = 10) {
@@ -2190,7 +2372,7 @@ function evaluateUnit(amount, fromToken, toToken, locale = "en-US", precision =
2190
2372
  }
2191
2373
 
2192
2374
  // src/evaluators/time.ts
2193
- function evaluateTime(query, fromTz, toTz, localTz) {
2375
+ function evaluateTime(query, fromTz, toTz, explicitTime, localTz) {
2194
2376
  const now = /* @__PURE__ */ new Date();
2195
2377
  const userTz = localTz ?? Intl.DateTimeFormat().resolvedOptions().timeZone;
2196
2378
  try {
@@ -2223,18 +2405,20 @@ function evaluateTime(query, fromTz, toTz, localTz) {
2223
2405
  const resolvedFrom = resolveTimezone(fromTz) ?? fromTz;
2224
2406
  const resolvedTo = resolveTimezone(toTz) ?? toTz;
2225
2407
  try {
2226
- const fromTime = formatTimeInZone(now, resolvedFrom);
2227
- const toTime = formatTimeInZone(now, resolvedTo);
2228
- const formatted = `${fromTime} \u2192 ${toTime}`;
2408
+ const sourceDate = explicitTime ? buildDateInZone(explicitTime, resolvedFrom, now) : now;
2409
+ const fromTime = explicitTime ? `${explicitTime} (${getShortTzName(resolvedFrom)})` : formatTimeInZone(sourceDate, resolvedFrom);
2410
+ const toTime = formatTimeInZone(sourceDate, resolvedTo);
2411
+ const formatted = toTime;
2229
2412
  return {
2230
2413
  type: "time",
2231
2414
  input: query,
2232
2415
  result: formatted,
2233
2416
  formatted,
2234
2417
  metadata: {
2418
+ explicitTime,
2235
2419
  from: { timezone: resolvedFrom, time: fromTime },
2236
2420
  to: { timezone: resolvedTo, time: toTime },
2237
- iso: now.toISOString()
2421
+ iso: sourceDate.toISOString()
2238
2422
  }
2239
2423
  };
2240
2424
  } catch {
@@ -2254,6 +2438,55 @@ function evaluateTime(query, fromTz, toTz, localTz) {
2254
2438
  };
2255
2439
  }
2256
2440
  }
2441
+ function buildDateInZone(timeExpression, timezone, anchor) {
2442
+ const { hour, minute } = parseTimeExpression(timeExpression);
2443
+ const parts = new Intl.DateTimeFormat("en-CA", {
2444
+ timeZone: timezone,
2445
+ year: "numeric",
2446
+ month: "2-digit",
2447
+ day: "2-digit"
2448
+ }).formatToParts(anchor);
2449
+ const year = Number(parts.find((p) => p.type === "year")?.value);
2450
+ const month = Number(parts.find((p) => p.type === "month")?.value);
2451
+ const day = Number(parts.find((p) => p.type === "day")?.value);
2452
+ const utcGuess = new Date(Date.UTC(year, month - 1, day, hour, minute, 0));
2453
+ const offset = getTimeZoneOffsetMs(utcGuess, timezone);
2454
+ return new Date(utcGuess.getTime() - offset);
2455
+ }
2456
+ function parseTimeExpression(value) {
2457
+ const trimmed = value.trim().toLowerCase();
2458
+ if (trimmed === "midnight") return { hour: 0, minute: 0 };
2459
+ if (trimmed === "noon") return { hour: 12, minute: 0 };
2460
+ const match = trimmed.match(/^(\d{1,2})(?::(\d{2}))?\s*(am|pm)?$/);
2461
+ if (!match) throw new Error(`Invalid time: '${value}'`);
2462
+ let hour = parseInt(match[1], 10);
2463
+ const minute = parseInt(match[2] ?? "0", 10);
2464
+ const meridiem = match[3];
2465
+ if (meridiem === "am" && hour === 12) hour = 0;
2466
+ if (meridiem === "pm" && hour < 12) hour += 12;
2467
+ if (hour > 23 || minute > 59) throw new Error(`Invalid time: '${value}'`);
2468
+ return { hour, minute };
2469
+ }
2470
+ function getTimeZoneOffsetMs(date, timezone) {
2471
+ const parts = new Intl.DateTimeFormat("en-US", {
2472
+ timeZone: timezone,
2473
+ hour12: false,
2474
+ year: "numeric",
2475
+ month: "2-digit",
2476
+ day: "2-digit",
2477
+ hour: "2-digit",
2478
+ minute: "2-digit",
2479
+ second: "2-digit"
2480
+ }).formatToParts(date);
2481
+ const year = Number(parts.find((p) => p.type === "year")?.value);
2482
+ const month = Number(parts.find((p) => p.type === "month")?.value);
2483
+ const day = Number(parts.find((p) => p.type === "day")?.value);
2484
+ const rawHour = Number(parts.find((p) => p.type === "hour")?.value);
2485
+ const hour = rawHour % 24;
2486
+ const minute = Number(parts.find((p) => p.type === "minute")?.value);
2487
+ const second = Number(parts.find((p) => p.type === "second")?.value);
2488
+ return Date.UTC(year, month - 1, day, hour, minute, second) - date.getTime();
2489
+ }
2257
2490
  function formatTimeInZone(date, timezone) {
2258
2491
  const timePart = new Intl.DateTimeFormat("en-US", {
2259
2492
  timeZone: timezone,
@@ -2290,7 +2523,7 @@ var DAY_NAMES = ["sunday", "monday", "tuesday", "wednesday", "thursday", "friday
2290
2523
  function evaluateDate(query) {
2291
2524
  const lower = query.toLowerCase().trim();
2292
2525
  try {
2293
- if (lower === "today" || lower === "now" || /^(?:what(?:'s| is) )?(?:the )?(?:current )?date(?: today)?$/.test(lower)) {
2526
+ if (lower === "today" || lower === "now" || lower === "date" || /^(?:(?:what(?:'s|s| is))\s+today|(?:what(?:'s|s| is))\s+today'?s date|what day is it|todays date|today'?s date|date today|current date|what is the date today)$/.test(lower) || /^(?:what(?:'s| is) )?(?:the )?(?:current )?date(?: today)?$/.test(lower)) {
2294
2527
  return formatDateResult(query, /* @__PURE__ */ new Date());
2295
2528
  }
2296
2529
  if (lower === "tomorrow") {
@@ -2303,6 +2536,26 @@ function evaluateDate(query) {
2303
2536
  d.setDate(d.getDate() - 1);
2304
2537
  return formatDateResult(query, d);
2305
2538
  }
2539
+ if (lower === "day after tomorrow") {
2540
+ const d = /* @__PURE__ */ new Date();
2541
+ d.setDate(d.getDate() + 2);
2542
+ return formatDateResult(query, d);
2543
+ }
2544
+ if (lower === "day before yesterday") {
2545
+ const d = /* @__PURE__ */ new Date();
2546
+ d.setDate(d.getDate() - 2);
2547
+ return formatDateResult(query, d);
2548
+ }
2549
+ const whatDateMatch = lower.match(/^what date is (.+)$/);
2550
+ if (whatDateMatch) {
2551
+ const d = parseFlexibleDate(whatDateMatch[1]);
2552
+ if (d) return formatDateResult(query, d);
2553
+ }
2554
+ const whenIsMatch = lower.match(/^(?:what day is|when is)\s+(.+)$/);
2555
+ if (whenIsMatch) {
2556
+ const d = parseFlexibleDate(whenIsMatch[1]);
2557
+ if (d) return formatDateResult(query, d);
2558
+ }
2306
2559
  const nextLastMatch = lower.match(/^(next|last|this)\s+(.+)$/);
2307
2560
  if (nextLastMatch) {
2308
2561
  const direction = nextLastMatch[1];
@@ -2350,7 +2603,7 @@ function evaluateDate(query) {
2350
2603
  const d = applyOffset(/* @__PURE__ */ new Date(), amount, unit);
2351
2604
  return formatDateResult(query, d);
2352
2605
  }
2353
- const unixMatch = lower.match(/^(?:unix\s+)?(?:timestamp\s+)?(\d{10,13})$/);
2606
+ const unixMatch = lower.match(/^(?:unix\s+)?(?:timestamp\s+)?(\d{10,13}|0)$/);
2354
2607
  if (unixMatch) {
2355
2608
  const ts = parseInt(unixMatch[1]);
2356
2609
  const ms = ts > 9999999999 ? ts : ts * 1e3;
@@ -2370,14 +2623,11 @@ function evaluateDate(query) {
2370
2623
  if (toUnixMatch) {
2371
2624
  const dateStr = toUnixMatch[1].trim();
2372
2625
  let d;
2373
- if (dateStr === "now" || dateStr === "today") {
2374
- d = /* @__PURE__ */ new Date();
2375
- } else {
2376
- d = new Date(dateStr);
2377
- }
2378
- if (isNaN(d.getTime())) {
2626
+ const parsed = parseFlexibleDate(dateStr);
2627
+ if (!parsed) {
2379
2628
  return { type: "error", input: query, error: `Cannot parse date: '${dateStr}'` };
2380
2629
  }
2630
+ d = parsed;
2381
2631
  const unix = Math.floor(d.getTime() / 1e3);
2382
2632
  return {
2383
2633
  type: "date",
@@ -2486,7 +2736,7 @@ function formatDateResult(input, d, extraMeta) {
2486
2736
  };
2487
2737
  }
2488
2738
  function parseFlexibleDate(str) {
2489
- if (str === "today" || str === "now") return /* @__PURE__ */ new Date();
2739
+ if (str === "today" || str === "now" || str === "date") return /* @__PURE__ */ new Date();
2490
2740
  if (str === "tomorrow") {
2491
2741
  const d2 = /* @__PURE__ */ new Date();
2492
2742
  d2.setDate(d2.getDate() + 1);
@@ -2497,6 +2747,22 @@ function parseFlexibleDate(str) {
2497
2747
  d2.setDate(d2.getDate() - 1);
2498
2748
  return d2;
2499
2749
  }
2750
+ if (str === "day after tomorrow") {
2751
+ const d2 = /* @__PURE__ */ new Date();
2752
+ d2.setDate(d2.getDate() + 2);
2753
+ return d2;
2754
+ }
2755
+ if (str === "day before yesterday") {
2756
+ const d2 = /* @__PURE__ */ new Date();
2757
+ d2.setDate(d2.getDate() - 2);
2758
+ return d2;
2759
+ }
2760
+ const relativeDay = str.match(/^(next|last|this)\s+(.+)$/);
2761
+ if (relativeDay) {
2762
+ const direction = relativeDay[1];
2763
+ const dayIndex = DAY_NAMES.indexOf(relativeDay[2]);
2764
+ if (dayIndex !== -1) return getRelativeDay(dayIndex, direction);
2765
+ }
2500
2766
  const d = new Date(str);
2501
2767
  return isNaN(d.getTime()) ? null : d;
2502
2768
  }
@@ -2614,6 +2880,7 @@ async function calculate(input, options) {
2614
2880
  intent.query,
2615
2881
  intent.from,
2616
2882
  intent.to,
2883
+ intent.time,
2617
2884
  options?.timezone
2618
2885
  );
2619
2886
  case "date":
package/dist/index.mjs CHANGED
@@ -894,7 +894,7 @@ for (const cat of UNIT_CATEGORIES) {
894
894
  }
895
895
  }
896
896
  function lookupUnit(token) {
897
- return _unitIndex.get(token.toLowerCase());
897
+ return _unitIndex.get(normalizeUnitToken(token));
898
898
  }
899
899
  function convertUnit(value, from, to) {
900
900
  if (from.category !== to.category) {
@@ -909,6 +909,9 @@ function convertUnit(value, from, to) {
909
909
  const baseValue = value * from.def.factor;
910
910
  return baseValue / to.def.factor;
911
911
  }
912
+ function normalizeUnitToken(token) {
913
+ return token.toLowerCase().trim().replace(/\s+/g, " ").replace(/²/g, "2").replace(/³/g, "3");
914
+ }
912
915
 
913
916
  // src/data/timezones.ts
914
917
  var TIMEZONE_MAP = {
@@ -981,8 +984,11 @@ var TIMEZONE_MAP = {
981
984
  "buenos aires": "America/Argentina/Buenos_Aires",
982
985
  argentina: "America/Argentina/Buenos_Aires",
983
986
  lima: "America/Lima",
987
+ peru: "America/Lima",
984
988
  bogota: "America/Bogota",
989
+ colombia: "America/Bogota",
985
990
  santiago: "America/Santiago",
991
+ chile: "America/Santiago",
986
992
  caracas: "America/Caracas",
987
993
  quito: "America/Guayaquil",
988
994
  // ─── Europe ───────────────────────────────────────────
@@ -1266,6 +1272,9 @@ var COUNTRY_TZ = {
1266
1272
  canada: "America/Toronto",
1267
1273
  mexico: "America/Mexico_City",
1268
1274
  brazil: "America/Sao_Paulo",
1275
+ colombia: "America/Bogota",
1276
+ peru: "America/Lima",
1277
+ chile: "America/Santiago",
1269
1278
  uk: "Europe/London",
1270
1279
  "united kingdom": "Europe/London",
1271
1280
  britain: "Europe/London",
@@ -1336,6 +1345,58 @@ function resolveTimezone(name) {
1336
1345
  }
1337
1346
 
1338
1347
  // src/providers/default.ts
1348
+ var STATIC_FIAT_PER_USD = {
1349
+ USD: 1,
1350
+ EUR: 0.92,
1351
+ GBP: 0.79,
1352
+ INR: 83.1,
1353
+ JPY: 150,
1354
+ CNY: 7.2,
1355
+ AUD: 1.52,
1356
+ CAD: 1.35,
1357
+ CHF: 0.88,
1358
+ KRW: 1330,
1359
+ SGD: 1.34,
1360
+ HKD: 7.8,
1361
+ NZD: 1.64,
1362
+ SEK: 10.5,
1363
+ NOK: 10.7,
1364
+ DKK: 6.85,
1365
+ MXN: 17.1,
1366
+ BRL: 5,
1367
+ THB: 35.8,
1368
+ PHP: 56.2,
1369
+ IDR: 15700,
1370
+ MYR: 4.48,
1371
+ ZAR: 18.4,
1372
+ AED: 3.67,
1373
+ SAR: 3.75,
1374
+ TRY: 32.1,
1375
+ PLN: 3.95,
1376
+ CZK: 23.2,
1377
+ RUB: 92
1378
+ };
1379
+ var STATIC_CRYPTO_USD = {
1380
+ BTC: 92e3,
1381
+ ETH: 3500,
1382
+ SOL: 180,
1383
+ XRP: 2.3,
1384
+ DOGE: 0.18,
1385
+ ADA: 0.8,
1386
+ MATIC: 0.95,
1387
+ DOT: 7.5,
1388
+ LTC: 95,
1389
+ AVAX: 42,
1390
+ LINK: 20,
1391
+ UNI: 12,
1392
+ ATOM: 10,
1393
+ XLM: 0.12,
1394
+ TON: 6,
1395
+ SHIB: 25e-6,
1396
+ BNB: 650,
1397
+ USDT: 1,
1398
+ USDC: 1
1399
+ };
1339
1400
  async function fetchJSON(url, timeoutMs = 8e3) {
1340
1401
  const controller = new AbortController();
1341
1402
  const timer = setTimeout(() => controller.abort(), timeoutMs);
@@ -1358,6 +1419,30 @@ async function withFallback(fns) {
1358
1419
  }
1359
1420
  throw lastError ?? new Error("All providers failed");
1360
1421
  }
1422
+ async function staticFiatRate(base, target) {
1423
+ const from = STATIC_FIAT_PER_USD[base];
1424
+ const to = STATIC_FIAT_PER_USD[target];
1425
+ if (from === void 0 || to === void 0) {
1426
+ throw new Error(`Static fiat rate unavailable for ${base}/${target}`);
1427
+ }
1428
+ return to / from;
1429
+ }
1430
+ async function staticCryptoRate(from, to) {
1431
+ const fromCrypto = STATIC_CRYPTO_USD[from];
1432
+ const toCrypto = STATIC_CRYPTO_USD[to];
1433
+ if (fromCrypto !== void 0 && toCrypto !== void 0) {
1434
+ return fromCrypto / toCrypto;
1435
+ }
1436
+ if (fromCrypto !== void 0) {
1437
+ const usdToTarget = await staticFiatRate("USD", to);
1438
+ return fromCrypto * usdToTarget;
1439
+ }
1440
+ if (toCrypto !== void 0) {
1441
+ const fromToUsd = await staticFiatRate(from, "USD");
1442
+ return fromToUsd / toCrypto;
1443
+ }
1444
+ throw new Error(`Static crypto rate unavailable for ${from}/${to}`);
1445
+ }
1361
1446
  async function frankfurterRate(base, target) {
1362
1447
  const data = await fetchJSON(
1363
1448
  `https://api.frankfurter.dev/v1/latest?base=${base}&symbols=${target}`
@@ -1607,7 +1692,8 @@ async function getCryptoRateWithFiatFallback(from, to, getFiat) {
1607
1692
  return await withFallback([
1608
1693
  () => coingeckoRate(from, to),
1609
1694
  () => binanceRate(from, to),
1610
- () => coincapRate(from, to)
1695
+ () => coincapRate(from, to),
1696
+ () => staticCryptoRate(from, to)
1611
1697
  ]);
1612
1698
  } catch {
1613
1699
  }
@@ -1615,7 +1701,8 @@ async function getCryptoRateWithFiatFallback(from, to, getFiat) {
1615
1701
  const cryptoToUsd = await withFallback([
1616
1702
  () => coingeckoRate(from, "USD"),
1617
1703
  () => binanceRate(from, "USDT"),
1618
- () => coincapRate(from, "USD")
1704
+ () => coincapRate(from, "USD"),
1705
+ () => staticCryptoRate(from, "USD")
1619
1706
  ]);
1620
1707
  if (to === "USD" || to === "USDT" || to === "USDC") return cryptoToUsd;
1621
1708
  const usdToFiat = await getFiat("USD", to);
@@ -1632,11 +1719,12 @@ async function getCryptoRateWithFiatFallback(from, to, getFiat) {
1632
1719
  async () => {
1633
1720
  const p = await coincapRate(to, "USD");
1634
1721
  return p === 0 ? Promise.reject("zero") : 1 / p;
1635
- }
1722
+ },
1723
+ () => staticCryptoRate("USD", to)
1636
1724
  ]);
1637
1725
  return fiatToUsd * usdToCrypto;
1638
1726
  }
1639
- throw new Error(`Could not resolve rate for ${from} \u2192 ${to}`);
1727
+ return staticCryptoRate(from, to);
1640
1728
  }
1641
1729
  function createDefaultProvider() {
1642
1730
  return {
@@ -1644,14 +1732,16 @@ function createDefaultProvider() {
1644
1732
  return withFallback([
1645
1733
  () => frankfurterRate(base, target),
1646
1734
  () => exchangeRateApiRate(base, target),
1647
- () => fawazCurrencyRate(base, target)
1735
+ () => fawazCurrencyRate(base, target),
1736
+ () => staticFiatRate(base, target)
1648
1737
  ]);
1649
1738
  },
1650
1739
  async getCryptoRate(base, target) {
1651
1740
  const getFiat = (b, t) => withFallback([
1652
1741
  () => frankfurterRate(b, t),
1653
1742
  () => exchangeRateApiRate(b, t),
1654
- () => fawazCurrencyRate(b, t)
1743
+ () => fawazCurrencyRate(b, t),
1744
+ () => staticFiatRate(b, t)
1655
1745
  ]);
1656
1746
  return getCryptoRateWithFiatFallback(base, target, getFiat);
1657
1747
  }
@@ -1660,28 +1750,46 @@ function createDefaultProvider() {
1660
1750
 
1661
1751
  // src/parser/intent.ts
1662
1752
  function detectIntent(input) {
1663
- const trimmed = input.trim();
1753
+ const trimmed = normalizeWhitespace(input);
1664
1754
  if (!trimmed) {
1665
1755
  return { kind: "math", expression: "0" };
1666
1756
  }
1667
1757
  return tryTimeIntent(trimmed) ?? tryDateIntent(trimmed) ?? tryCurrencyOrCryptoIntent(trimmed) ?? tryUnitIntent(trimmed) ?? { kind: "math", expression: trimmed };
1668
1758
  }
1669
- var TIME_IN_PATTERN = /^(?:what(?:'s| is) )?(?:the )?(?:current )?time (?:in|at) (.+)$/i;
1670
- var TIME_CONVERT_PATTERN = /^(.+?) to (.+?) time$/i;
1759
+ var TIME_IN_PATTERNS = [
1760
+ /^(?:what(?:'s| is) )?(?:the )?(?:current )?time (?:in|at) (.+)$/i,
1761
+ /^(?:what time is it|whats the time|what is the time|current time|time now) (?:in|at) (.+)$/i,
1762
+ /^(?:now|current time) in (.+)$/i
1763
+ ];
1764
+ var TIME_EXPLICIT_CONVERT_PATTERN = /^(midnight|noon|\d{1,2}(?::\d{2})?(?:\s*(?:am|pm))?)\s+(?:in\s+)?(.+?)\s+to\s+(.+?)(?:\s+time)?$/i;
1765
+ var TIME_CONVERT_PATTERN = /^(.+?) to (.+?)(?: time)?$/i;
1671
1766
  var TIME_NOW_PATTERN = /^(?:what(?:'s| is) )?(?:the )?(?:current )?time(?: now)?$/i;
1672
1767
  function tryTimeIntent(input) {
1673
1768
  let match;
1674
- match = input.match(TIME_IN_PATTERN);
1769
+ for (const pattern of TIME_IN_PATTERNS) {
1770
+ match = input.match(pattern);
1771
+ if (match) {
1772
+ const place = normalizeTimePlace(match[1]);
1773
+ const tz2 = resolveTimezone(place);
1774
+ if (tz2) return { kind: "time", query: input, to: tz2 };
1775
+ return { kind: "time", query: input, to: place };
1776
+ }
1777
+ }
1778
+ match = input.match(TIME_EXPLICIT_CONVERT_PATTERN);
1675
1779
  if (match) {
1676
- const place = match[1].trim();
1677
- const tz2 = resolveTimezone(place);
1678
- if (tz2) return { kind: "time", query: place, to: tz2 };
1679
- return { kind: "time", query: place, to: place };
1780
+ const time = match[1].trim();
1781
+ const from = normalizeTimePlace(match[2]);
1782
+ const to = normalizeTimePlace(match[3]);
1783
+ const fromTz = resolveTimezone(from);
1784
+ const toTz = resolveTimezone(to);
1785
+ if (fromTz && toTz) {
1786
+ return { kind: "time", query: input, from: fromTz, to: toTz, time };
1787
+ }
1680
1788
  }
1681
1789
  match = input.match(TIME_CONVERT_PATTERN);
1682
1790
  if (match) {
1683
- const from = match[1].trim();
1684
- const to = match[2].trim();
1791
+ const from = normalizeTimePlace(match[1]);
1792
+ const to = normalizeTimePlace(match[2]);
1685
1793
  const fromTz = resolveTimezone(from);
1686
1794
  const toTz = resolveTimezone(to);
1687
1795
  if (fromTz || toTz) {
@@ -1697,11 +1805,11 @@ function tryTimeIntent(input) {
1697
1805
  }
1698
1806
  const placeSuffixMatch = input.match(/^(.+?)\s+(?:time|now|time now)$/i);
1699
1807
  if (placeSuffixMatch) {
1700
- const place = placeSuffixMatch[1].trim();
1808
+ const place = normalizeTimePlace(placeSuffixMatch[1]);
1701
1809
  const tz2 = resolveTimezone(place);
1702
1810
  if (tz2) return { kind: "time", query: place, to: tz2 };
1703
1811
  }
1704
- const tz = resolveTimezone(input);
1812
+ const tz = resolveTimezone(normalizeTimePlace(input));
1705
1813
  if (tz) {
1706
1814
  return { kind: "time", query: input, to: tz };
1707
1815
  }
@@ -1710,11 +1818,12 @@ function tryTimeIntent(input) {
1710
1818
  var DATE_PATTERNS = [
1711
1819
  // Relative dates
1712
1820
  /^(today|now|tomorrow|yesterday)$/i,
1821
+ /^(date|day after tomorrow|day before yesterday)$/i,
1713
1822
  /^(next|last|this)\s+(monday|tuesday|wednesday|thursday|friday|saturday|sunday|week|month|year)$/i,
1714
1823
  /^(\d+)\s+(days?|weeks?|months?|years?|hours?|minutes?|seconds?)\s+(from now|ago|from today|from tomorrow)$/i,
1715
1824
  /^in\s+(\d+)\s+(days?|weeks?|months?|years?|hours?|minutes?|seconds?)$/i,
1716
1825
  // Unix timestamp
1717
- /^(?:unix\s+)?(?:timestamp\s+)?(\d{10,13})$/i,
1826
+ /^(?:unix\s+)?(?:timestamp\s+)?(\d{10,13}|0)$/i,
1718
1827
  /^(?:unix|timestamp|epoch)\s+(\d{10,13})$/i,
1719
1828
  // "to unix" / "to timestamp"
1720
1829
  /^.+\s+(?:to|in)\s+(?:unix|timestamp|epoch)$/i,
@@ -1722,6 +1831,7 @@ var DATE_PATTERNS = [
1722
1831
  /^\d{4}-\d{2}-\d{2}(?:T\d{2}:\d{2})?/,
1723
1832
  // "date" queries
1724
1833
  /^(?:what(?:'s| is) )?(?:the )?(?:current )?date(?: today)?$/i,
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,
1725
1835
  // Days between dates
1726
1836
  /^(?:days?\s+)?(?:between|from)\s+.+\s+(?:to|and|until)\s+.+$/i
1727
1837
  ];
@@ -1735,7 +1845,8 @@ function tryDateIntent(input) {
1735
1845
  }
1736
1846
  var CONVERSION_PATTERN = /^([\d.,]+)?\s*([a-zA-Z$€£¥₹₩₽₺₦₵₪฿]+(?:\s+[a-zA-Z]+)?)\s+(?:to|in|into|as|=)\s+([a-zA-Z$€£¥₹₩₽₺₦₵₪฿]+(?:\s+[a-zA-Z]+)?)$/i;
1737
1847
  function tryCurrencyOrCryptoIntent(input) {
1738
- const match = input.match(CONVERSION_PATTERN);
1848
+ const normalized = normalizeConversionInput(input);
1849
+ const match = normalized.match(CONVERSION_PATTERN);
1739
1850
  if (!match) return null;
1740
1851
  const amount = match[1] ? parseFloat(match[1].replace(/,/g, "")) : 1;
1741
1852
  const fromToken = match[2].trim();
@@ -1758,13 +1869,12 @@ function tryCurrencyOrCryptoIntent(input) {
1758
1869
  var UNIT_PATTERN = /^(-?[\d.,]+)\s*([a-zA-Z°/µμ'"²³]+(?:\s+[a-zA-Z]+(?:\s+[a-zA-Z]+)?)?)\s+(?:to|in|into|as|=)\s+([a-zA-Z°/µμ'"²³]+(?:\s+[a-zA-Z]+(?:\s+[a-zA-Z]+)?)?)$/i;
1759
1870
  var UNIT_PATTERN_NO_SPACE = /^(-?[\d.,]+)([a-zA-Z°]+)\s+(?:to|in|into|as)\s+([a-zA-Z°]+(?:\s+[a-zA-Z]+)?)$/i;
1760
1871
  function tryUnitIntent(input) {
1761
- const match = input.match(UNIT_PATTERN) || input.match(UNIT_PATTERN_NO_SPACE);
1872
+ const normalized = normalizeConversionInput(input);
1873
+ const match = normalized.match(UNIT_PATTERN) || normalized.match(UNIT_PATTERN_NO_SPACE);
1762
1874
  if (!match) return null;
1763
1875
  const amount = parseFloat(match[1].replace(/,/g, ""));
1764
1876
  const fromToken = match[2].trim().toLowerCase();
1765
1877
  const toToken = match[3].trim().toLowerCase();
1766
- if (resolveFiat(fromToken) || resolveCrypto(fromToken)) return null;
1767
- if (resolveFiat(toToken) || resolveCrypto(toToken)) return null;
1768
1878
  const fromUnit = lookupUnit(fromToken);
1769
1879
  const toUnit = lookupUnit(toToken);
1770
1880
  if (fromUnit && toUnit && fromUnit.category === toUnit.category) {
@@ -1776,8 +1886,22 @@ function tryUnitIntent(input) {
1776
1886
  category: fromUnit.category.name
1777
1887
  };
1778
1888
  }
1889
+ if (resolveFiat(fromToken) || resolveCrypto(fromToken)) return null;
1890
+ if (resolveFiat(toToken) || resolveCrypto(toToken)) return null;
1779
1891
  return null;
1780
1892
  }
1893
+ function normalizeWhitespace(input) {
1894
+ return input.trim().replace(/\s+/g, " ");
1895
+ }
1896
+ function normalizeConversionInput(input) {
1897
+ let normalized = normalizeWhitespace(input);
1898
+ normalized = normalized.replace(/^convert\s+/i, "").replace(/^how much is\s+/i, "").replace(/^what(?:'s|s| is)\s+/i, "");
1899
+ normalized = normalized.replace(/^([$€£¥₹₩₽₺₦₵₪฿])\s*([\d.,]+)/, "$2 $1");
1900
+ return normalized;
1901
+ }
1902
+ function normalizeTimePlace(value) {
1903
+ return normalizeWhitespace(value).replace(/^(?:time|current time|time now|now)\s+(?:in|at)\s+/i, "").replace(/^(?:in|at)\s+/i, "").replace(/\s+(?:time|now|time now)$/i, "").trim();
1904
+ }
1781
1905
 
1782
1906
  // src/evaluators/math.ts
1783
1907
  var CONSTANTS = {
@@ -1928,19 +2052,10 @@ var Parser = class {
1928
2052
  if (right === 0) throw new Error("Division by zero");
1929
2053
  left /= right;
1930
2054
  } else if (op === "%") {
1931
- const savedPos = this.pos;
1932
- this.skipWhitespace();
1933
- this.pos++;
1934
- this.skipWhitespace();
1935
- const next = this.expr[this.pos];
1936
- if (next === void 0 || /[+\-*/^)|&<>]/.test(next)) {
1937
- this.pos = savedPos;
1938
- break;
1939
- } else {
1940
- this.pos = savedPos;
1941
- this.consume();
1942
- left = left % this.parseExponentiation();
1943
- }
2055
+ this.consume();
2056
+ left = left % this.parseExponentiation();
2057
+ } else if (op && /[([a-zA-Zπτφ_]/.test(op)) {
2058
+ left *= this.parseExponentiation();
1944
2059
  } else {
1945
2060
  break;
1946
2061
  }
@@ -1982,7 +2097,7 @@ var Parser = class {
1982
2097
  if (this.expr[this.pos] === "!") {
1983
2098
  this.pos++;
1984
2099
  value = factorial(value);
1985
- } else if (this.expr[this.pos] === "%") {
2100
+ } else if (this.expr[this.pos] === "%" && this.shouldTreatAsPercentage()) {
1986
2101
  this.pos++;
1987
2102
  value = value / 100;
1988
2103
  } else {
@@ -2008,6 +2123,12 @@ var Parser = class {
2008
2123
  }
2009
2124
  throw new Error(`Unexpected character: '${this.expr[this.pos]}' at position ${this.pos}`);
2010
2125
  }
2126
+ shouldTreatAsPercentage() {
2127
+ let i = this.pos + 1;
2128
+ while (i < this.expr.length && /\s/.test(this.expr[i])) i++;
2129
+ const next = this.expr[i];
2130
+ return next === void 0 || /[+\-*/^)%|&<>]/.test(next);
2131
+ }
2011
2132
  charAt(i) {
2012
2133
  return i < this.expr.length ? this.expr[i] : "";
2013
2134
  }
@@ -2051,13 +2172,15 @@ var Parser = class {
2051
2172
  const name = this.expr.slice(start, this.pos).toLowerCase();
2052
2173
  this.skipWhitespace();
2053
2174
  if (this.expr[this.pos] === "(") {
2175
+ if (name === "max" || name === "min") {
2176
+ const args = this.parseArguments();
2177
+ if (args.length === 0) throw new Error(`Function '${name}' requires at least one argument`);
2178
+ return name === "max" ? Math.max(...args) : Math.min(...args);
2179
+ }
2054
2180
  if (FUNCTIONS2[name]) {
2055
- this.consume("(");
2056
- const a = this.parseBitwiseOr();
2057
- this.consume(",");
2058
- const b = this.parseBitwiseOr();
2059
- this.consume(")");
2060
- return FUNCTIONS2[name](a, b);
2181
+ const args = this.parseArguments();
2182
+ if (args.length !== 2) throw new Error(`Function '${name}' expects 2 arguments`);
2183
+ return FUNCTIONS2[name](args[0], args[1]);
2061
2184
  }
2062
2185
  if (FUNCTIONS[name]) {
2063
2186
  this.consume("(");
@@ -2070,6 +2193,26 @@ var Parser = class {
2070
2193
  if (CONSTANTS[name] !== void 0) return CONSTANTS[name];
2071
2194
  throw new Error(`Unknown identifier: '${name}'`);
2072
2195
  }
2196
+ parseArguments() {
2197
+ const args = [];
2198
+ this.consume("(");
2199
+ this.skipWhitespace();
2200
+ if (this.expr[this.pos] === ")") {
2201
+ this.consume(")");
2202
+ return args;
2203
+ }
2204
+ while (true) {
2205
+ args.push(this.parseBitwiseOr());
2206
+ this.skipWhitespace();
2207
+ if (this.expr[this.pos] === ",") {
2208
+ this.pos++;
2209
+ continue;
2210
+ }
2211
+ break;
2212
+ }
2213
+ this.consume(")");
2214
+ return args;
2215
+ }
2073
2216
  };
2074
2217
  function factorial(n) {
2075
2218
  if (n < 0) throw new Error("Factorial of negative number");
@@ -2081,7 +2224,8 @@ function factorial(n) {
2081
2224
  }
2082
2225
  function evaluateMath(expression, locale = "en-US", precision = 10) {
2083
2226
  try {
2084
- const parser = new Parser(expression);
2227
+ const normalizedExpression = normalizeMathExpression(expression);
2228
+ const parser = new Parser(normalizedExpression);
2085
2229
  const result = parser.parse();
2086
2230
  let formatted;
2087
2231
  if (Number.isInteger(result) && Math.abs(result) < Number.MAX_SAFE_INTEGER) {
@@ -2106,7 +2250,10 @@ function evaluateMath(expression, locale = "en-US", precision = 10) {
2106
2250
  input: expression,
2107
2251
  result,
2108
2252
  formatted,
2109
- metadata
2253
+ metadata: {
2254
+ ...metadata,
2255
+ normalizedExpression
2256
+ }
2110
2257
  };
2111
2258
  } catch (err) {
2112
2259
  return {
@@ -2116,6 +2263,41 @@ function evaluateMath(expression, locale = "en-US", precision = 10) {
2116
2263
  };
2117
2264
  }
2118
2265
  }
2266
+ function normalizeMathExpression(expression) {
2267
+ let normalized = expression.trim().replace(/\s+/g, " ");
2268
+ normalized = normalized.replace(/^(?:(?:what(?:'s|s| is))\s+the\s+|(?:what(?:'s|s| is))\s+|calculate\s+)/i, "");
2269
+ normalized = normalized.replace(/^the\s+/i, "");
2270
+ const wrappers = [
2271
+ [/^square root of (.+)$/i, "sqrt($1)"],
2272
+ [/^cube root of (.+)$/i, "cbrt($1)"],
2273
+ [/^log of (.+)$/i, "log($1)"],
2274
+ [/^sine of (.+)$/i, "sin($1)"],
2275
+ [/^cosine of (.+)$/i, "cos($1)"],
2276
+ [/^tangent of (.+)$/i, "tan($1)"],
2277
+ [/^absolute value of (.+)$/i, "abs($1)"],
2278
+ [/^factorial of (.+)$/i, "($1)!"],
2279
+ [/^half of (.+)$/i, "($1) / 2"],
2280
+ [/^double (.+)$/i, "2 * ($1)"],
2281
+ [/^triple (.+)$/i, "3 * ($1)"],
2282
+ [/^one third of (.+)$/i, "($1) / 3"],
2283
+ [/^one quarter of (.+)$/i, "($1) / 4"],
2284
+ [/^remainder of (.+?) divided by (.+)$/i, "($1) % ($2)"],
2285
+ [/^(.+?) to the power of (.+)$/i, "($1) ^ ($2)"],
2286
+ [/^(.+?) raised to (.+)$/i, "($1) ^ ($2)"],
2287
+ [/^(.+?) squared$/i, "($1) ^ 2"],
2288
+ [/^(.+?) cubed$/i, "($1) ^ 3"],
2289
+ [/^(\d+(?:\.\d+)?) percent of (.+)$/i, "($1%) * ($2)"],
2290
+ [/^(.+?%) of (.+)$/i, "($1) * ($2)"]
2291
+ ];
2292
+ for (const [pattern, replacement] of wrappers) {
2293
+ if (pattern.test(normalized)) {
2294
+ normalized = normalized.replace(pattern, replacement);
2295
+ break;
2296
+ }
2297
+ }
2298
+ normalized = normalized.replace(/\bdivided by\b/gi, "/").replace(/\btimes\b/gi, "*").replace(/\bplus\b/gi, "+").replace(/\bminus\b/gi, "-");
2299
+ return normalized.replace(/\s+/g, " ").trim();
2300
+ }
2119
2301
 
2120
2302
  // src/evaluators/unit.ts
2121
2303
  function evaluateUnit(amount, fromToken, toToken, locale = "en-US", precision = 10) {
@@ -2153,7 +2335,7 @@ function evaluateUnit(amount, fromToken, toToken, locale = "en-US", precision =
2153
2335
  }
2154
2336
 
2155
2337
  // src/evaluators/time.ts
2156
- function evaluateTime(query, fromTz, toTz, localTz) {
2338
+ function evaluateTime(query, fromTz, toTz, explicitTime, localTz) {
2157
2339
  const now = /* @__PURE__ */ new Date();
2158
2340
  const userTz = localTz ?? Intl.DateTimeFormat().resolvedOptions().timeZone;
2159
2341
  try {
@@ -2186,18 +2368,20 @@ function evaluateTime(query, fromTz, toTz, localTz) {
2186
2368
  const resolvedFrom = resolveTimezone(fromTz) ?? fromTz;
2187
2369
  const resolvedTo = resolveTimezone(toTz) ?? toTz;
2188
2370
  try {
2189
- const fromTime = formatTimeInZone(now, resolvedFrom);
2190
- const toTime = formatTimeInZone(now, resolvedTo);
2191
- const formatted = `${fromTime} \u2192 ${toTime}`;
2371
+ const sourceDate = explicitTime ? buildDateInZone(explicitTime, resolvedFrom, now) : now;
2372
+ const fromTime = explicitTime ? `${explicitTime} (${getShortTzName(resolvedFrom)})` : formatTimeInZone(sourceDate, resolvedFrom);
2373
+ const toTime = formatTimeInZone(sourceDate, resolvedTo);
2374
+ const formatted = toTime;
2192
2375
  return {
2193
2376
  type: "time",
2194
2377
  input: query,
2195
2378
  result: formatted,
2196
2379
  formatted,
2197
2380
  metadata: {
2381
+ explicitTime,
2198
2382
  from: { timezone: resolvedFrom, time: fromTime },
2199
2383
  to: { timezone: resolvedTo, time: toTime },
2200
- iso: now.toISOString()
2384
+ iso: sourceDate.toISOString()
2201
2385
  }
2202
2386
  };
2203
2387
  } catch {
@@ -2217,6 +2401,55 @@ function evaluateTime(query, fromTz, toTz, localTz) {
2217
2401
  };
2218
2402
  }
2219
2403
  }
2404
+ function buildDateInZone(timeExpression, timezone, anchor) {
2405
+ const { hour, minute } = parseTimeExpression(timeExpression);
2406
+ const parts = new Intl.DateTimeFormat("en-CA", {
2407
+ timeZone: timezone,
2408
+ year: "numeric",
2409
+ month: "2-digit",
2410
+ day: "2-digit"
2411
+ }).formatToParts(anchor);
2412
+ const year = Number(parts.find((p) => p.type === "year")?.value);
2413
+ const month = Number(parts.find((p) => p.type === "month")?.value);
2414
+ const day = Number(parts.find((p) => p.type === "day")?.value);
2415
+ const utcGuess = new Date(Date.UTC(year, month - 1, day, hour, minute, 0));
2416
+ const offset = getTimeZoneOffsetMs(utcGuess, timezone);
2417
+ return new Date(utcGuess.getTime() - offset);
2418
+ }
2419
+ function parseTimeExpression(value) {
2420
+ const trimmed = value.trim().toLowerCase();
2421
+ if (trimmed === "midnight") return { hour: 0, minute: 0 };
2422
+ if (trimmed === "noon") return { hour: 12, minute: 0 };
2423
+ const match = trimmed.match(/^(\d{1,2})(?::(\d{2}))?\s*(am|pm)?$/);
2424
+ if (!match) throw new Error(`Invalid time: '${value}'`);
2425
+ let hour = parseInt(match[1], 10);
2426
+ const minute = parseInt(match[2] ?? "0", 10);
2427
+ const meridiem = match[3];
2428
+ if (meridiem === "am" && hour === 12) hour = 0;
2429
+ if (meridiem === "pm" && hour < 12) hour += 12;
2430
+ if (hour > 23 || minute > 59) throw new Error(`Invalid time: '${value}'`);
2431
+ return { hour, minute };
2432
+ }
2433
+ function getTimeZoneOffsetMs(date, timezone) {
2434
+ const parts = new Intl.DateTimeFormat("en-US", {
2435
+ timeZone: timezone,
2436
+ hour12: false,
2437
+ year: "numeric",
2438
+ month: "2-digit",
2439
+ day: "2-digit",
2440
+ hour: "2-digit",
2441
+ minute: "2-digit",
2442
+ second: "2-digit"
2443
+ }).formatToParts(date);
2444
+ const year = Number(parts.find((p) => p.type === "year")?.value);
2445
+ const month = Number(parts.find((p) => p.type === "month")?.value);
2446
+ const day = Number(parts.find((p) => p.type === "day")?.value);
2447
+ const rawHour = Number(parts.find((p) => p.type === "hour")?.value);
2448
+ const hour = rawHour % 24;
2449
+ const minute = Number(parts.find((p) => p.type === "minute")?.value);
2450
+ const second = Number(parts.find((p) => p.type === "second")?.value);
2451
+ return Date.UTC(year, month - 1, day, hour, minute, second) - date.getTime();
2452
+ }
2220
2453
  function formatTimeInZone(date, timezone) {
2221
2454
  const timePart = new Intl.DateTimeFormat("en-US", {
2222
2455
  timeZone: timezone,
@@ -2253,7 +2486,7 @@ var DAY_NAMES = ["sunday", "monday", "tuesday", "wednesday", "thursday", "friday
2253
2486
  function evaluateDate(query) {
2254
2487
  const lower = query.toLowerCase().trim();
2255
2488
  try {
2256
- if (lower === "today" || lower === "now" || /^(?:what(?:'s| is) )?(?:the )?(?:current )?date(?: today)?$/.test(lower)) {
2489
+ if (lower === "today" || lower === "now" || lower === "date" || /^(?:(?:what(?:'s|s| is))\s+today|(?:what(?:'s|s| is))\s+today'?s date|what day is it|todays date|today'?s date|date today|current date|what is the date today)$/.test(lower) || /^(?:what(?:'s| is) )?(?:the )?(?:current )?date(?: today)?$/.test(lower)) {
2257
2490
  return formatDateResult(query, /* @__PURE__ */ new Date());
2258
2491
  }
2259
2492
  if (lower === "tomorrow") {
@@ -2266,6 +2499,26 @@ function evaluateDate(query) {
2266
2499
  d.setDate(d.getDate() - 1);
2267
2500
  return formatDateResult(query, d);
2268
2501
  }
2502
+ if (lower === "day after tomorrow") {
2503
+ const d = /* @__PURE__ */ new Date();
2504
+ d.setDate(d.getDate() + 2);
2505
+ return formatDateResult(query, d);
2506
+ }
2507
+ if (lower === "day before yesterday") {
2508
+ const d = /* @__PURE__ */ new Date();
2509
+ d.setDate(d.getDate() - 2);
2510
+ return formatDateResult(query, d);
2511
+ }
2512
+ const whatDateMatch = lower.match(/^what date is (.+)$/);
2513
+ if (whatDateMatch) {
2514
+ const d = parseFlexibleDate(whatDateMatch[1]);
2515
+ if (d) return formatDateResult(query, d);
2516
+ }
2517
+ const whenIsMatch = lower.match(/^(?:what day is|when is)\s+(.+)$/);
2518
+ if (whenIsMatch) {
2519
+ const d = parseFlexibleDate(whenIsMatch[1]);
2520
+ if (d) return formatDateResult(query, d);
2521
+ }
2269
2522
  const nextLastMatch = lower.match(/^(next|last|this)\s+(.+)$/);
2270
2523
  if (nextLastMatch) {
2271
2524
  const direction = nextLastMatch[1];
@@ -2313,7 +2566,7 @@ function evaluateDate(query) {
2313
2566
  const d = applyOffset(/* @__PURE__ */ new Date(), amount, unit);
2314
2567
  return formatDateResult(query, d);
2315
2568
  }
2316
- const unixMatch = lower.match(/^(?:unix\s+)?(?:timestamp\s+)?(\d{10,13})$/);
2569
+ const unixMatch = lower.match(/^(?:unix\s+)?(?:timestamp\s+)?(\d{10,13}|0)$/);
2317
2570
  if (unixMatch) {
2318
2571
  const ts = parseInt(unixMatch[1]);
2319
2572
  const ms = ts > 9999999999 ? ts : ts * 1e3;
@@ -2333,14 +2586,11 @@ function evaluateDate(query) {
2333
2586
  if (toUnixMatch) {
2334
2587
  const dateStr = toUnixMatch[1].trim();
2335
2588
  let d;
2336
- if (dateStr === "now" || dateStr === "today") {
2337
- d = /* @__PURE__ */ new Date();
2338
- } else {
2339
- d = new Date(dateStr);
2340
- }
2341
- if (isNaN(d.getTime())) {
2589
+ const parsed = parseFlexibleDate(dateStr);
2590
+ if (!parsed) {
2342
2591
  return { type: "error", input: query, error: `Cannot parse date: '${dateStr}'` };
2343
2592
  }
2593
+ d = parsed;
2344
2594
  const unix = Math.floor(d.getTime() / 1e3);
2345
2595
  return {
2346
2596
  type: "date",
@@ -2449,7 +2699,7 @@ function formatDateResult(input, d, extraMeta) {
2449
2699
  };
2450
2700
  }
2451
2701
  function parseFlexibleDate(str) {
2452
- if (str === "today" || str === "now") return /* @__PURE__ */ new Date();
2702
+ if (str === "today" || str === "now" || str === "date") return /* @__PURE__ */ new Date();
2453
2703
  if (str === "tomorrow") {
2454
2704
  const d2 = /* @__PURE__ */ new Date();
2455
2705
  d2.setDate(d2.getDate() + 1);
@@ -2460,6 +2710,22 @@ function parseFlexibleDate(str) {
2460
2710
  d2.setDate(d2.getDate() - 1);
2461
2711
  return d2;
2462
2712
  }
2713
+ if (str === "day after tomorrow") {
2714
+ const d2 = /* @__PURE__ */ new Date();
2715
+ d2.setDate(d2.getDate() + 2);
2716
+ return d2;
2717
+ }
2718
+ if (str === "day before yesterday") {
2719
+ const d2 = /* @__PURE__ */ new Date();
2720
+ d2.setDate(d2.getDate() - 2);
2721
+ return d2;
2722
+ }
2723
+ const relativeDay = str.match(/^(next|last|this)\s+(.+)$/);
2724
+ if (relativeDay) {
2725
+ const direction = relativeDay[1];
2726
+ const dayIndex = DAY_NAMES.indexOf(relativeDay[2]);
2727
+ if (dayIndex !== -1) return getRelativeDay(dayIndex, direction);
2728
+ }
2463
2729
  const d = new Date(str);
2464
2730
  return isNaN(d.getTime()) ? null : d;
2465
2731
  }
@@ -2577,6 +2843,7 @@ async function calculate(input, options) {
2577
2843
  intent.query,
2578
2844
  intent.from,
2579
2845
  intent.to,
2846
+ intent.time,
2580
2847
  options?.timezone
2581
2848
  );
2582
2849
  case "date":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supercmd/calculator",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
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",