@cj-tech-master/excelts 9.3.1 → 9.4.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.
Files changed (109) hide show
  1. package/dist/browser/index.d.ts +1 -0
  2. package/dist/browser/index.js +2 -0
  3. package/dist/browser/modules/excel/cell.d.ts +18 -0
  4. package/dist/browser/modules/excel/cell.js +21 -0
  5. package/dist/browser/modules/excel/utils/cell-format.js +85 -13
  6. package/dist/browser/modules/excel/workbook.browser.d.ts +57 -0
  7. package/dist/browser/modules/excel/workbook.browser.js +49 -0
  8. package/dist/browser/modules/excel/xlsx/defaultnumformats.js +3 -3
  9. package/dist/browser/modules/formula/compile/binder.js +48 -6
  10. package/dist/browser/modules/formula/compile/bound-ast.d.ts +16 -2
  11. package/dist/browser/modules/formula/compile/bound-ast.js +1 -0
  12. package/dist/browser/modules/formula/compile/compiled-formula.js +41 -8
  13. package/dist/browser/modules/formula/functions/_shared.d.ts +19 -0
  14. package/dist/browser/modules/formula/functions/_shared.js +47 -0
  15. package/dist/browser/modules/formula/functions/conditional.js +103 -22
  16. package/dist/browser/modules/formula/functions/date.js +105 -23
  17. package/dist/browser/modules/formula/functions/dynamic-array.js +173 -69
  18. package/dist/browser/modules/formula/functions/engineering.d.ts +2 -2
  19. package/dist/browser/modules/formula/functions/engineering.js +103 -151
  20. package/dist/browser/modules/formula/functions/financial.js +210 -184
  21. package/dist/browser/modules/formula/functions/lookup.js +224 -157
  22. package/dist/browser/modules/formula/functions/math.d.ts +26 -0
  23. package/dist/browser/modules/formula/functions/math.js +249 -69
  24. package/dist/browser/modules/formula/functions/statistical.js +221 -171
  25. package/dist/browser/modules/formula/functions/text.js +112 -52
  26. package/dist/browser/modules/formula/integration/calculate-formulas-impl.js +20 -1
  27. package/dist/browser/modules/formula/materialize/build-writeback-plan.js +10 -6
  28. package/dist/browser/modules/formula/materialize/types.d.ts +15 -0
  29. package/dist/browser/modules/formula/runtime/evaluator.d.ts +8 -0
  30. package/dist/browser/modules/formula/runtime/evaluator.js +582 -162
  31. package/dist/browser/modules/formula/runtime/function-registry.d.ts +5 -0
  32. package/dist/browser/modules/formula/runtime/function-registry.js +59 -13
  33. package/dist/browser/modules/formula/runtime/values.d.ts +13 -0
  34. package/dist/browser/modules/formula/runtime/values.js +20 -2
  35. package/dist/browser/modules/formula/syntax/ast.d.ts +14 -2
  36. package/dist/browser/modules/formula/syntax/ast.js +1 -0
  37. package/dist/browser/modules/formula/syntax/parser.js +29 -7
  38. package/dist/browser/modules/formula/syntax/token-types.d.ts +4 -0
  39. package/dist/browser/modules/formula/syntax/token-types.js +9 -0
  40. package/dist/browser/modules/formula/syntax/tokenizer.js +76 -19
  41. package/dist/cjs/index.js +7 -2
  42. package/dist/cjs/modules/excel/cell.js +21 -0
  43. package/dist/cjs/modules/excel/utils/cell-format.js +85 -13
  44. package/dist/cjs/modules/excel/workbook.browser.js +49 -0
  45. package/dist/cjs/modules/excel/xlsx/defaultnumformats.js +3 -3
  46. package/dist/cjs/modules/formula/compile/binder.js +48 -6
  47. package/dist/cjs/modules/formula/compile/compiled-formula.js +41 -8
  48. package/dist/cjs/modules/formula/functions/_shared.js +48 -0
  49. package/dist/cjs/modules/formula/functions/conditional.js +103 -22
  50. package/dist/cjs/modules/formula/functions/date.js +104 -22
  51. package/dist/cjs/modules/formula/functions/dynamic-array.js +173 -69
  52. package/dist/cjs/modules/formula/functions/engineering.js +109 -157
  53. package/dist/cjs/modules/formula/functions/financial.js +209 -183
  54. package/dist/cjs/modules/formula/functions/lookup.js +224 -157
  55. package/dist/cjs/modules/formula/functions/math.js +254 -70
  56. package/dist/cjs/modules/formula/functions/statistical.js +222 -172
  57. package/dist/cjs/modules/formula/functions/text.js +112 -52
  58. package/dist/cjs/modules/formula/integration/calculate-formulas-impl.js +20 -1
  59. package/dist/cjs/modules/formula/materialize/build-writeback-plan.js +10 -6
  60. package/dist/cjs/modules/formula/runtime/evaluator.js +581 -161
  61. package/dist/cjs/modules/formula/runtime/function-registry.js +57 -11
  62. package/dist/cjs/modules/formula/runtime/values.js +21 -2
  63. package/dist/cjs/modules/formula/syntax/parser.js +29 -7
  64. package/dist/cjs/modules/formula/syntax/token-types.js +9 -0
  65. package/dist/cjs/modules/formula/syntax/tokenizer.js +76 -19
  66. package/dist/esm/index.js +2 -0
  67. package/dist/esm/modules/excel/cell.js +21 -0
  68. package/dist/esm/modules/excel/utils/cell-format.js +85 -13
  69. package/dist/esm/modules/excel/workbook.browser.js +49 -0
  70. package/dist/esm/modules/excel/xlsx/defaultnumformats.js +3 -3
  71. package/dist/esm/modules/formula/compile/binder.js +48 -6
  72. package/dist/esm/modules/formula/compile/bound-ast.js +1 -0
  73. package/dist/esm/modules/formula/compile/compiled-formula.js +41 -8
  74. package/dist/esm/modules/formula/functions/_shared.js +47 -0
  75. package/dist/esm/modules/formula/functions/conditional.js +103 -22
  76. package/dist/esm/modules/formula/functions/date.js +105 -23
  77. package/dist/esm/modules/formula/functions/dynamic-array.js +173 -69
  78. package/dist/esm/modules/formula/functions/engineering.js +103 -151
  79. package/dist/esm/modules/formula/functions/financial.js +210 -184
  80. package/dist/esm/modules/formula/functions/lookup.js +224 -157
  81. package/dist/esm/modules/formula/functions/math.js +249 -69
  82. package/dist/esm/modules/formula/functions/statistical.js +221 -171
  83. package/dist/esm/modules/formula/functions/text.js +112 -52
  84. package/dist/esm/modules/formula/integration/calculate-formulas-impl.js +20 -1
  85. package/dist/esm/modules/formula/materialize/build-writeback-plan.js +10 -6
  86. package/dist/esm/modules/formula/runtime/evaluator.js +582 -162
  87. package/dist/esm/modules/formula/runtime/function-registry.js +59 -13
  88. package/dist/esm/modules/formula/runtime/values.js +20 -2
  89. package/dist/esm/modules/formula/syntax/ast.js +1 -0
  90. package/dist/esm/modules/formula/syntax/parser.js +29 -7
  91. package/dist/esm/modules/formula/syntax/token-types.js +9 -0
  92. package/dist/esm/modules/formula/syntax/tokenizer.js +76 -19
  93. package/dist/iife/excelts.iife.js +1502 -1379
  94. package/dist/iife/excelts.iife.js.map +1 -1
  95. package/dist/iife/excelts.iife.min.js +26 -26
  96. package/dist/types/index.d.ts +1 -0
  97. package/dist/types/modules/excel/cell.d.ts +18 -0
  98. package/dist/types/modules/excel/workbook.browser.d.ts +57 -0
  99. package/dist/types/modules/formula/compile/bound-ast.d.ts +16 -2
  100. package/dist/types/modules/formula/functions/_shared.d.ts +19 -0
  101. package/dist/types/modules/formula/functions/engineering.d.ts +2 -2
  102. package/dist/types/modules/formula/functions/math.d.ts +26 -0
  103. package/dist/types/modules/formula/materialize/types.d.ts +15 -0
  104. package/dist/types/modules/formula/runtime/evaluator.d.ts +8 -0
  105. package/dist/types/modules/formula/runtime/function-registry.d.ts +5 -0
  106. package/dist/types/modules/formula/runtime/values.d.ts +13 -0
  107. package/dist/types/modules/formula/syntax/ast.d.ts +14 -2
  108. package/dist/types/modules/formula/syntax/token-types.d.ts +4 -0
  109. package/package.json +1 -1
@@ -16,7 +16,7 @@
16
16
  * correctly through `excelToDate()`.
17
17
  */
18
18
  import { dateToExcel, excelToDate } from "../../../utils/utils.base.js";
19
- import { RVKind, ERRORS, isError, isArray, toNumberRV, toStringRV, toBooleanRV, rvNumber, rvBoolean } from "../runtime/values.js";
19
+ import { RVKind, ERRORS, isError, isArray, toNumberRV, toStringRV, toBooleanRV, topLeft, rvNumber, rvBoolean } from "../runtime/values.js";
20
20
  import { isDate1904 } from "./_date-context.js";
21
21
  import { argToNumber, checkError } from "./_shared.js";
22
22
  // ============================================================================
@@ -36,6 +36,11 @@ function collectHolidays(arg) {
36
36
  if (isArray(arg)) {
37
37
  for (const row of arg.rows) {
38
38
  for (const cell of row) {
39
+ // Propagate errors from the holidays list rather than silently
40
+ // skipping them — Excel surfaces `#N/A` from a holiday cell.
41
+ if (cell.kind === RVKind.Error) {
42
+ return cell;
43
+ }
39
44
  if (cell.kind === RVKind.Number) {
40
45
  set.add(Math.floor(cell.value));
41
46
  }
@@ -43,6 +48,9 @@ function collectHolidays(arg) {
43
48
  }
44
49
  }
45
50
  else {
51
+ if (arg.kind === RVKind.Error) {
52
+ return arg;
53
+ }
46
54
  const n = toNumberRV(arg);
47
55
  if (n.kind === RVKind.Number) {
48
56
  set.add(Math.floor(n.value));
@@ -206,7 +214,10 @@ export const fnWEEKDAY = args => {
206
214
  return n;
207
215
  }
208
216
  const d = toDate(n.value);
209
- const returnType = args.length > 1 ? argToNumber(args[1]) : rvNumber(1);
217
+ // Blank `return_type` Excel default 1 (Sun=1..Sat=7). Without the
218
+ // blank guard, `argToNumber(BLANK)` coerces to 0 which falls to the
219
+ // default branch and yields a spurious #NUM! for `WEEKDAY(date, )`.
220
+ const returnType = args.length > 1 && args[1].kind !== RVKind.Blank ? argToNumber(args[1]) : rvNumber(1);
210
221
  if (isError(returnType)) {
211
222
  return returnType;
212
223
  }
@@ -239,8 +250,14 @@ export const fnEOMONTH = args => {
239
250
  if (isError(months)) {
240
251
  return months;
241
252
  }
253
+ // Excel truncates `months` toward zero before doing month arithmetic.
254
+ // `Date.UTC` happens to truncate too, but the explicit `Math.trunc`
255
+ // makes the contract visible and protects against engines that might
256
+ // not (or against a future refactor that routes through a different
257
+ // date constructor).
258
+ const m = Math.trunc(months.value);
242
259
  const d = toDate(startDate.value);
243
- const result = new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth() + months.value + 1, 0));
260
+ const result = new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth() + m + 1, 0));
244
261
  return rvNumber(fromDate(result));
245
262
  };
246
263
  export const fnEDATE = args => {
@@ -252,8 +269,19 @@ export const fnEDATE = args => {
252
269
  if (isError(months)) {
253
270
  return months;
254
271
  }
272
+ const m = Math.trunc(months.value);
255
273
  const d = toDate(startDate.value);
256
- const result = new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth() + months.value, d.getUTCDate()));
274
+ // Excel clamps to the last day of the target month when the source day
275
+ // would overflow (e.g. `EDATE(2024-01-31, 1)` → 2024-02-29, not rolling
276
+ // forward into March). JS Date.UTC rolls over by default, so we detect
277
+ // the overflow and clamp explicitly. To do so we first construct the
278
+ // 1st of the target month, read `daysInMonth` via the "day 0 of next
279
+ // month" trick, and cap the original day at that.
280
+ const targetYearMonth = d.getUTCMonth() + m;
281
+ const firstOfTarget = new Date(Date.UTC(d.getUTCFullYear(), targetYearMonth, 1));
282
+ const lastDayOfTarget = new Date(Date.UTC(firstOfTarget.getUTCFullYear(), firstOfTarget.getUTCMonth() + 1, 0)).getUTCDate();
283
+ const clampedDay = Math.min(d.getUTCDate(), lastDayOfTarget);
284
+ const result = new Date(Date.UTC(firstOfTarget.getUTCFullYear(), firstOfTarget.getUTCMonth(), clampedDay));
257
285
  return rvNumber(fromDate(result));
258
286
  };
259
287
  export const fnDATEDIF = args => {
@@ -269,7 +297,7 @@ export const fnDATEDIF = args => {
269
297
  if (endN.value < startN.value) {
270
298
  return ERRORS.NUM;
271
299
  }
272
- const unit = toStringRV(args[2]).toUpperCase();
300
+ const unit = toStringRV(topLeft(args[2])).toUpperCase();
273
301
  const startD = toDate(startN.value);
274
302
  const endD = toDate(endN.value);
275
303
  const sy = startD.getUTCFullYear();
@@ -353,7 +381,9 @@ export const fnWEEKNUM = args => {
353
381
  return n;
354
382
  }
355
383
  const d = toDate(n.value);
356
- const returnType = args.length > 1 ? argToNumber(args[1]) : rvNumber(1);
384
+ // Blank `return_type` Excel default 1 (Sunday start). See WEEKDAY
385
+ // for the same rationale.
386
+ const returnType = args.length > 1 && args[1].kind !== RVKind.Blank ? argToNumber(args[1]) : rvNumber(1);
357
387
  if (isError(returnType)) {
358
388
  return returnType;
359
389
  }
@@ -407,15 +437,44 @@ function networkdaysHelper(startN, endN, holidays) {
407
437
  const s = Math.floor(Math.min(startN, endN));
408
438
  const e = Math.floor(Math.max(startN, endN));
409
439
  const sign = startN <= endN ? 1 : -1;
410
- let count = 0;
411
- for (let d = s; d <= e; d++) {
412
- const dt = toDate(d);
413
- const dow = dt.getUTCDay();
414
- if (dow !== 0 && dow !== 6 && !holidays.has(d)) {
415
- count++;
440
+ // Closed-form weekday count: partition `[s, e]` into whole weeks plus
441
+ // a tail. Each whole week contributes 5 weekdays, regardless of its
442
+ // starting day-of-week. The tail contributes however many of its
443
+ // remaining days fall on Monday..Friday.
444
+ //
445
+ // `getUTCDay()`: Sun=0, Mon=1, …, Sat=6. We compute `dow` once for
446
+ // the start date and then just walk the `tail` days forward by
447
+ // modular arithmetic — no Date allocations in the loop.
448
+ const totalDays = e - s + 1;
449
+ const weeks = Math.floor(totalDays / 7);
450
+ const tail = totalDays % 7;
451
+ let weekdays = weeks * 5;
452
+ if (tail > 0) {
453
+ const startDow = toDate(s).getUTCDay();
454
+ for (let i = 0; i < tail; i++) {
455
+ const dow = (startDow + i) % 7;
456
+ if (dow !== 0 && dow !== 6) {
457
+ weekdays++;
458
+ }
416
459
  }
417
460
  }
418
- return count * sign;
461
+ else {
462
+ // When `totalDays` is an exact multiple of 7 the start DOW still
463
+ // governs whether any holidays from the caller's list fall on a
464
+ // weekday, so we stop here — no tail to walk.
465
+ }
466
+ // Subtract holidays that land on a weekday and fall within [s, e].
467
+ if (holidays.size > 0) {
468
+ for (const h of holidays) {
469
+ if (h >= s && h <= e) {
470
+ const dow = toDate(h).getUTCDay();
471
+ if (dow !== 0 && dow !== 6) {
472
+ weekdays--;
473
+ }
474
+ }
475
+ }
476
+ }
477
+ return weekdays * sign;
419
478
  }
420
479
  export const fnNETWORKDAYS = args => {
421
480
  const startN = argToNumber(args[0]);
@@ -427,7 +486,10 @@ export const fnNETWORKDAYS = args => {
427
486
  return endN;
428
487
  }
429
488
  const holidays = args.length > 2 ? collectHolidays(args[2]) : new Set();
430
- return rvNumber(networkdaysHelper(startN.value, endN.value, holidays));
489
+ if (holidays instanceof Set) {
490
+ return rvNumber(networkdaysHelper(startN.value, endN.value, holidays));
491
+ }
492
+ return holidays;
431
493
  };
432
494
  export const fnWORKDAY = args => {
433
495
  const startN = argToNumber(args[0]);
@@ -439,9 +501,16 @@ export const fnWORKDAY = args => {
439
501
  return days;
440
502
  }
441
503
  const holidays = args.length > 2 ? collectHolidays(args[2]) : new Set();
504
+ if (!(holidays instanceof Set)) {
505
+ return holidays;
506
+ }
442
507
  let current = Math.floor(startN.value);
443
- const step = days.value >= 0 ? 1 : -1;
444
- let remaining = Math.abs(days.value);
508
+ // Excel truncates `days` toward zero. Without this, a fractional input
509
+ // like 2.7 would walk extra iterations until the fractional remainder
510
+ // underflowed past zero, silently producing a wrong result.
511
+ const daysInt = Math.trunc(days.value);
512
+ const step = daysInt >= 0 ? 1 : -1;
513
+ let remaining = Math.abs(daysInt);
445
514
  while (remaining > 0) {
446
515
  current += step;
447
516
  const dt = toDate(current);
@@ -559,7 +628,7 @@ export const fnDATEVALUE = args => {
559
628
  if (err) {
560
629
  return err;
561
630
  }
562
- const text = toStringRV(args[0]).trim();
631
+ const text = toStringRV(topLeft(args[0])).trim();
563
632
  // Lotus 1-2-3 bug: "2/29/1900" or "February 29, 1900" etc. should return 60
564
633
  const lotus29 = /^(2[/-]29[/-]1900|1900[/-]2[/-]29|1900[/-]02[/-]29|02[/-]29[/-]1900|Feb(ruary)?\s+29[,]?\s+1900)$/i;
565
634
  if (lotus29.test(text)) {
@@ -576,7 +645,7 @@ export const fnTIMEVALUE = args => {
576
645
  if (err) {
577
646
  return err;
578
647
  }
579
- const text = toStringRV(args[0]).trim();
648
+ const text = toStringRV(topLeft(args[0])).trim();
580
649
  const parsed = parseTimeOnly(text);
581
650
  if (parsed === null) {
582
651
  return ERRORS.VALUE;
@@ -731,7 +800,7 @@ export const fnDAYS360 = args => {
731
800
  if (isError(endN)) {
732
801
  return endN;
733
802
  }
734
- const methodRV = args.length > 2 ? toBooleanRV(args[2]) : rvBoolean(false);
803
+ const methodRV = args.length > 2 ? toBooleanRV(topLeft(args[2])) : rvBoolean(false);
735
804
  if (isError(methodRV)) {
736
805
  return methodRV;
737
806
  }
@@ -806,11 +875,16 @@ export const fnNETWORKDAYS_INTL = args => {
806
875
  if (isError(endN)) {
807
876
  return endN;
808
877
  }
809
- const weekendArg = args.length > 2 ? argToNumber(args[2]) : rvNumber(1);
878
+ // Blank `weekend` Excel default 1 (Sat+Sun). See getWeekendDays
879
+ // default fallback.
880
+ const weekendArg = args.length > 2 && args[2].kind !== RVKind.Blank ? argToNumber(args[2]) : rvNumber(1);
810
881
  if (isError(weekendArg)) {
811
882
  return weekendArg;
812
883
  }
813
884
  const holidays = args.length > 3 ? collectHolidays(args[3]) : new Set();
885
+ if (!(holidays instanceof Set)) {
886
+ return holidays;
887
+ }
814
888
  const weekendDays = getWeekendDays(weekendArg.value);
815
889
  const s = Math.floor(Math.min(startN.value, endN.value));
816
890
  const e = Math.floor(Math.max(startN.value, endN.value));
@@ -833,15 +907,23 @@ export const fnWORKDAY_INTL = args => {
833
907
  if (isError(days)) {
834
908
  return days;
835
909
  }
836
- const weekendArg = args.length > 2 ? argToNumber(args[2]) : rvNumber(1);
910
+ // Blank `weekend` Excel default 1 (Sat+Sun).
911
+ const weekendArg = args.length > 2 && args[2].kind !== RVKind.Blank ? argToNumber(args[2]) : rvNumber(1);
837
912
  if (isError(weekendArg)) {
838
913
  return weekendArg;
839
914
  }
840
915
  const holidays = args.length > 3 ? collectHolidays(args[3]) : new Set();
916
+ if (!(holidays instanceof Set)) {
917
+ return holidays;
918
+ }
841
919
  const weekendDays = getWeekendDays(weekendArg.value);
842
920
  let current = Math.floor(startN.value);
843
- const step = days.value >= 0 ? 1 : -1;
844
- let remaining = Math.abs(days.value);
921
+ // Truncate `days` toward zero before stepping see WORKDAY for the
922
+ // same rationale. A fractional input like 2.7 would otherwise walk
923
+ // extra iterations and silently produce the wrong result.
924
+ const daysInt = Math.trunc(days.value);
925
+ const step = daysInt >= 0 ? 1 : -1;
926
+ let remaining = Math.abs(daysInt);
845
927
  while (remaining > 0) {
846
928
  current += step;
847
929
  const dt = toDate(current);