@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.
- package/dist/browser/index.d.ts +1 -0
- package/dist/browser/index.js +2 -0
- package/dist/browser/modules/excel/cell.d.ts +18 -0
- package/dist/browser/modules/excel/cell.js +21 -0
- package/dist/browser/modules/excel/utils/cell-format.js +85 -13
- package/dist/browser/modules/excel/workbook.browser.d.ts +57 -0
- package/dist/browser/modules/excel/workbook.browser.js +49 -0
- package/dist/browser/modules/excel/xlsx/defaultnumformats.js +3 -3
- package/dist/browser/modules/formula/compile/binder.js +48 -6
- package/dist/browser/modules/formula/compile/bound-ast.d.ts +16 -2
- package/dist/browser/modules/formula/compile/bound-ast.js +1 -0
- package/dist/browser/modules/formula/compile/compiled-formula.js +41 -8
- package/dist/browser/modules/formula/functions/_shared.d.ts +19 -0
- package/dist/browser/modules/formula/functions/_shared.js +47 -0
- package/dist/browser/modules/formula/functions/conditional.js +103 -22
- package/dist/browser/modules/formula/functions/date.js +105 -23
- package/dist/browser/modules/formula/functions/dynamic-array.js +173 -69
- package/dist/browser/modules/formula/functions/engineering.d.ts +2 -2
- package/dist/browser/modules/formula/functions/engineering.js +103 -151
- package/dist/browser/modules/formula/functions/financial.js +210 -184
- package/dist/browser/modules/formula/functions/lookup.js +224 -157
- package/dist/browser/modules/formula/functions/math.d.ts +26 -0
- package/dist/browser/modules/formula/functions/math.js +249 -69
- package/dist/browser/modules/formula/functions/statistical.js +221 -171
- package/dist/browser/modules/formula/functions/text.js +112 -52
- package/dist/browser/modules/formula/integration/calculate-formulas-impl.js +20 -1
- package/dist/browser/modules/formula/materialize/build-writeback-plan.js +10 -6
- package/dist/browser/modules/formula/materialize/types.d.ts +15 -0
- package/dist/browser/modules/formula/runtime/evaluator.d.ts +8 -0
- package/dist/browser/modules/formula/runtime/evaluator.js +582 -162
- package/dist/browser/modules/formula/runtime/function-registry.d.ts +5 -0
- package/dist/browser/modules/formula/runtime/function-registry.js +59 -13
- package/dist/browser/modules/formula/runtime/values.d.ts +13 -0
- package/dist/browser/modules/formula/runtime/values.js +20 -2
- package/dist/browser/modules/formula/syntax/ast.d.ts +14 -2
- package/dist/browser/modules/formula/syntax/ast.js +1 -0
- package/dist/browser/modules/formula/syntax/parser.js +29 -7
- package/dist/browser/modules/formula/syntax/token-types.d.ts +4 -0
- package/dist/browser/modules/formula/syntax/token-types.js +9 -0
- package/dist/browser/modules/formula/syntax/tokenizer.js +76 -19
- package/dist/cjs/index.js +7 -2
- package/dist/cjs/modules/excel/cell.js +21 -0
- package/dist/cjs/modules/excel/utils/cell-format.js +85 -13
- package/dist/cjs/modules/excel/workbook.browser.js +49 -0
- package/dist/cjs/modules/excel/xlsx/defaultnumformats.js +3 -3
- package/dist/cjs/modules/formula/compile/binder.js +48 -6
- package/dist/cjs/modules/formula/compile/compiled-formula.js +41 -8
- package/dist/cjs/modules/formula/functions/_shared.js +48 -0
- package/dist/cjs/modules/formula/functions/conditional.js +103 -22
- package/dist/cjs/modules/formula/functions/date.js +104 -22
- package/dist/cjs/modules/formula/functions/dynamic-array.js +173 -69
- package/dist/cjs/modules/formula/functions/engineering.js +109 -157
- package/dist/cjs/modules/formula/functions/financial.js +209 -183
- package/dist/cjs/modules/formula/functions/lookup.js +224 -157
- package/dist/cjs/modules/formula/functions/math.js +254 -70
- package/dist/cjs/modules/formula/functions/statistical.js +222 -172
- package/dist/cjs/modules/formula/functions/text.js +112 -52
- package/dist/cjs/modules/formula/integration/calculate-formulas-impl.js +20 -1
- package/dist/cjs/modules/formula/materialize/build-writeback-plan.js +10 -6
- package/dist/cjs/modules/formula/runtime/evaluator.js +581 -161
- package/dist/cjs/modules/formula/runtime/function-registry.js +57 -11
- package/dist/cjs/modules/formula/runtime/values.js +21 -2
- package/dist/cjs/modules/formula/syntax/parser.js +29 -7
- package/dist/cjs/modules/formula/syntax/token-types.js +9 -0
- package/dist/cjs/modules/formula/syntax/tokenizer.js +76 -19
- package/dist/esm/index.js +2 -0
- package/dist/esm/modules/excel/cell.js +21 -0
- package/dist/esm/modules/excel/utils/cell-format.js +85 -13
- package/dist/esm/modules/excel/workbook.browser.js +49 -0
- package/dist/esm/modules/excel/xlsx/defaultnumformats.js +3 -3
- package/dist/esm/modules/formula/compile/binder.js +48 -6
- package/dist/esm/modules/formula/compile/bound-ast.js +1 -0
- package/dist/esm/modules/formula/compile/compiled-formula.js +41 -8
- package/dist/esm/modules/formula/functions/_shared.js +47 -0
- package/dist/esm/modules/formula/functions/conditional.js +103 -22
- package/dist/esm/modules/formula/functions/date.js +105 -23
- package/dist/esm/modules/formula/functions/dynamic-array.js +173 -69
- package/dist/esm/modules/formula/functions/engineering.js +103 -151
- package/dist/esm/modules/formula/functions/financial.js +210 -184
- package/dist/esm/modules/formula/functions/lookup.js +224 -157
- package/dist/esm/modules/formula/functions/math.js +249 -69
- package/dist/esm/modules/formula/functions/statistical.js +221 -171
- package/dist/esm/modules/formula/functions/text.js +112 -52
- package/dist/esm/modules/formula/integration/calculate-formulas-impl.js +20 -1
- package/dist/esm/modules/formula/materialize/build-writeback-plan.js +10 -6
- package/dist/esm/modules/formula/runtime/evaluator.js +582 -162
- package/dist/esm/modules/formula/runtime/function-registry.js +59 -13
- package/dist/esm/modules/formula/runtime/values.js +20 -2
- package/dist/esm/modules/formula/syntax/ast.js +1 -0
- package/dist/esm/modules/formula/syntax/parser.js +29 -7
- package/dist/esm/modules/formula/syntax/token-types.js +9 -0
- package/dist/esm/modules/formula/syntax/tokenizer.js +76 -19
- package/dist/iife/excelts.iife.js +1502 -1379
- package/dist/iife/excelts.iife.js.map +1 -1
- package/dist/iife/excelts.iife.min.js +26 -26
- package/dist/types/index.d.ts +1 -0
- package/dist/types/modules/excel/cell.d.ts +18 -0
- package/dist/types/modules/excel/workbook.browser.d.ts +57 -0
- package/dist/types/modules/formula/compile/bound-ast.d.ts +16 -2
- package/dist/types/modules/formula/functions/_shared.d.ts +19 -0
- package/dist/types/modules/formula/functions/engineering.d.ts +2 -2
- package/dist/types/modules/formula/functions/math.d.ts +26 -0
- package/dist/types/modules/formula/materialize/types.d.ts +15 -0
- package/dist/types/modules/formula/runtime/evaluator.d.ts +8 -0
- package/dist/types/modules/formula/runtime/function-registry.d.ts +5 -0
- package/dist/types/modules/formula/runtime/values.d.ts +13 -0
- package/dist/types/modules/formula/syntax/ast.d.ts +14 -2
- package/dist/types/modules/formula/syntax/token-types.d.ts +4 -0
- 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
|
-
|
|
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() +
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
444
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
844
|
-
|
|
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);
|