@bilig/formula 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +16 -0
- package/dist/addressing.d.ts +66 -0
- package/dist/addressing.js +179 -0
- package/dist/addressing.js.map +1 -0
- package/dist/ast.d.ts +74 -0
- package/dist/ast.js +2 -0
- package/dist/ast.js.map +1 -0
- package/dist/binder.d.ts +13 -0
- package/dist/binder.js +1700 -0
- package/dist/binder.js.map +1 -0
- package/dist/builtin-capabilities.d.ts +19 -0
- package/dist/builtin-capabilities.js +861 -0
- package/dist/builtin-capabilities.js.map +1 -0
- package/dist/builtins/complex.d.ts +10 -0
- package/dist/builtins/complex.js +407 -0
- package/dist/builtins/complex.js.map +1 -0
- package/dist/builtins/convert.d.ts +3 -0
- package/dist/builtins/convert.js +362 -0
- package/dist/builtins/convert.js.map +1 -0
- package/dist/builtins/datetime.d.ts +23 -0
- package/dist/builtins/datetime.js +1096 -0
- package/dist/builtins/datetime.js.map +1 -0
- package/dist/builtins/distribution-builtins.d.ts +16 -0
- package/dist/builtins/distribution-builtins.js +517 -0
- package/dist/builtins/distribution-builtins.js.map +1 -0
- package/dist/builtins/distributions.d.ts +34 -0
- package/dist/builtins/distributions.js +722 -0
- package/dist/builtins/distributions.js.map +1 -0
- package/dist/builtins/financial-builtins.d.ts +16 -0
- package/dist/builtins/financial-builtins.js +324 -0
- package/dist/builtins/financial-builtins.js.map +1 -0
- package/dist/builtins/financial.d.ts +11 -0
- package/dist/builtins/financial.js +241 -0
- package/dist/builtins/financial.js.map +1 -0
- package/dist/builtins/fixed-income-builtins.d.ts +14 -0
- package/dist/builtins/fixed-income-builtins.js +598 -0
- package/dist/builtins/fixed-income-builtins.js.map +1 -0
- package/dist/builtins/fixed-income.d.ts +42 -0
- package/dist/builtins/fixed-income.js +668 -0
- package/dist/builtins/fixed-income.js.map +1 -0
- package/dist/builtins/formatting.d.ts +8 -0
- package/dist/builtins/formatting.js +53 -0
- package/dist/builtins/formatting.js.map +1 -0
- package/dist/builtins/logical.d.ts +4 -0
- package/dist/builtins/logical.js +258 -0
- package/dist/builtins/logical.js.map +1 -0
- package/dist/builtins/lookup-array-shape-builtins.d.ts +21 -0
- package/dist/builtins/lookup-array-shape-builtins.js +517 -0
- package/dist/builtins/lookup-array-shape-builtins.js.map +1 -0
- package/dist/builtins/lookup-criteria-builtins.d.ts +16 -0
- package/dist/builtins/lookup-criteria-builtins.js +216 -0
- package/dist/builtins/lookup-criteria-builtins.js.map +1 -0
- package/dist/builtins/lookup-database-builtins.d.ts +17 -0
- package/dist/builtins/lookup-database-builtins.js +294 -0
- package/dist/builtins/lookup-database-builtins.js.map +1 -0
- package/dist/builtins/lookup-financial-builtins.d.ts +11 -0
- package/dist/builtins/lookup-financial-builtins.js +291 -0
- package/dist/builtins/lookup-financial-builtins.js.map +1 -0
- package/dist/builtins/lookup-hypothesis-builtins.d.ts +11 -0
- package/dist/builtins/lookup-hypothesis-builtins.js +57 -0
- package/dist/builtins/lookup-hypothesis-builtins.js.map +1 -0
- package/dist/builtins/lookup-matrix-builtins.d.ts +17 -0
- package/dist/builtins/lookup-matrix-builtins.js +218 -0
- package/dist/builtins/lookup-matrix-builtins.js.map +1 -0
- package/dist/builtins/lookup-order-statistics-builtins.d.ts +18 -0
- package/dist/builtins/lookup-order-statistics-builtins.js +575 -0
- package/dist/builtins/lookup-order-statistics-builtins.js.map +1 -0
- package/dist/builtins/lookup-reference-builtins.d.ts +18 -0
- package/dist/builtins/lookup-reference-builtins.js +300 -0
- package/dist/builtins/lookup-reference-builtins.js.map +1 -0
- package/dist/builtins/lookup-regression-builtins.d.ts +12 -0
- package/dist/builtins/lookup-regression-builtins.js +511 -0
- package/dist/builtins/lookup-regression-builtins.js.map +1 -0
- package/dist/builtins/lookup-sort-filter-builtins.d.ts +20 -0
- package/dist/builtins/lookup-sort-filter-builtins.js +382 -0
- package/dist/builtins/lookup-sort-filter-builtins.js.map +1 -0
- package/dist/builtins/lookup.d.ts +13 -0
- package/dist/builtins/lookup.js +867 -0
- package/dist/builtins/lookup.js.map +1 -0
- package/dist/builtins/math-builtins.d.ts +31 -0
- package/dist/builtins/math-builtins.js +420 -0
- package/dist/builtins/math-builtins.js.map +1 -0
- package/dist/builtins/numeric.d.ts +30 -0
- package/dist/builtins/numeric.js +150 -0
- package/dist/builtins/numeric.js.map +1 -0
- package/dist/builtins/placeholder.d.ts +9 -0
- package/dist/builtins/placeholder.js +540 -0
- package/dist/builtins/placeholder.js.map +1 -0
- package/dist/builtins/radix.d.ts +12 -0
- package/dist/builtins/radix.js +220 -0
- package/dist/builtins/radix.js.map +1 -0
- package/dist/builtins/statistical-builtins.d.ts +13 -0
- package/dist/builtins/statistical-builtins.js +240 -0
- package/dist/builtins/statistical-builtins.js.map +1 -0
- package/dist/builtins/statistics.d.ts +8 -0
- package/dist/builtins/statistics.js +74 -0
- package/dist/builtins/statistics.js.map +1 -0
- package/dist/builtins/text.d.ts +5 -0
- package/dist/builtins/text.js +1879 -0
- package/dist/builtins/text.js.map +1 -0
- package/dist/builtins.d.ts +8 -0
- package/dist/builtins.js +695 -0
- package/dist/builtins.js.map +1 -0
- package/dist/compatibility.d.ts +25 -0
- package/dist/compatibility.js +498 -0
- package/dist/compatibility.js.map +1 -0
- package/dist/compiler.d.ts +29 -0
- package/dist/compiler.js +474 -0
- package/dist/compiler.js.map +1 -0
- package/dist/external-function-adapter.d.ts +32 -0
- package/dist/external-function-adapter.js +42 -0
- package/dist/external-function-adapter.js.map +1 -0
- package/dist/generated/formula-inventory.d.ts +6839 -0
- package/dist/generated/formula-inventory.js +7368 -0
- package/dist/generated/formula-inventory.js.map +1 -0
- package/dist/group-pivot-evaluator.d.ts +28 -0
- package/dist/group-pivot-evaluator.js +435 -0
- package/dist/group-pivot-evaluator.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/js-evaluator.d.ts +107 -0
- package/dist/js-evaluator.js +1651 -0
- package/dist/js-evaluator.js.map +1 -0
- package/dist/lexer.d.ts +6 -0
- package/dist/lexer.js +115 -0
- package/dist/lexer.js.map +1 -0
- package/dist/optimizer.d.ts +2 -0
- package/dist/optimizer.js +353 -0
- package/dist/optimizer.js.map +1 -0
- package/dist/parser.d.ts +2 -0
- package/dist/parser.js +352 -0
- package/dist/parser.js.map +1 -0
- package/dist/program-arena.d.ts +22 -0
- package/dist/program-arena.js +67 -0
- package/dist/program-arena.js.map +1 -0
- package/dist/runtime-values.d.ts +17 -0
- package/dist/runtime-values.js +11 -0
- package/dist/runtime-values.js.map +1 -0
- package/dist/special-call-rewrites.d.ts +2 -0
- package/dist/special-call-rewrites.js +74 -0
- package/dist/special-call-rewrites.js.map +1 -0
- package/dist/translation.d.ts +28 -0
- package/dist/translation.js +569 -0
- package/dist/translation.js.map +1 -0
- package/package.json +53 -0
|
@@ -0,0 +1,1096 @@
|
|
|
1
|
+
import { ErrorCode, ValueTag } from "@bilig/protocol";
|
|
2
|
+
import { createBlockedBuiltinMap, datetimePlaceholderBuiltinNames } from "./placeholder.js";
|
|
3
|
+
const MS_PER_DAY = 86_400_000;
|
|
4
|
+
const SECONDS_PER_DAY = 86_400;
|
|
5
|
+
const EXCEL_EPOCH_UTC_MS = Date.UTC(1899, 11, 31);
|
|
6
|
+
const EXCEL_LEAP_BUG_CUTOFF_UTC_MS = Date.UTC(1900, 2, 1);
|
|
7
|
+
function valueError() {
|
|
8
|
+
return { tag: ValueTag.Error, code: ErrorCode.Value };
|
|
9
|
+
}
|
|
10
|
+
function numberResult(value) {
|
|
11
|
+
return { tag: ValueTag.Number, value };
|
|
12
|
+
}
|
|
13
|
+
function firstError(args) {
|
|
14
|
+
return args.find((arg) => arg.tag === ValueTag.Error);
|
|
15
|
+
}
|
|
16
|
+
function coerceNumber(value) {
|
|
17
|
+
switch (value.tag) {
|
|
18
|
+
case ValueTag.Number:
|
|
19
|
+
return Number.isFinite(value.value) ? value.value : undefined;
|
|
20
|
+
case ValueTag.Boolean:
|
|
21
|
+
return value.value ? 1 : 0;
|
|
22
|
+
case ValueTag.Empty:
|
|
23
|
+
return 0;
|
|
24
|
+
case ValueTag.String:
|
|
25
|
+
case ValueTag.Error:
|
|
26
|
+
return undefined;
|
|
27
|
+
default:
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function coerceText(value) {
|
|
32
|
+
switch (value.tag) {
|
|
33
|
+
case ValueTag.String:
|
|
34
|
+
return value.value;
|
|
35
|
+
case ValueTag.Number:
|
|
36
|
+
return String(value.value);
|
|
37
|
+
case ValueTag.Boolean:
|
|
38
|
+
return value.value ? "TRUE" : "FALSE";
|
|
39
|
+
case ValueTag.Empty:
|
|
40
|
+
return "";
|
|
41
|
+
case ValueTag.Error:
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function integerValue(value, fallback) {
|
|
46
|
+
if (value === undefined) {
|
|
47
|
+
return fallback;
|
|
48
|
+
}
|
|
49
|
+
const numeric = coerceNumber(value);
|
|
50
|
+
if (numeric === undefined || !Number.isFinite(numeric)) {
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
return Math.trunc(numeric);
|
|
54
|
+
}
|
|
55
|
+
function parseDateValueFromText(raw) {
|
|
56
|
+
const trimmed = raw.trim();
|
|
57
|
+
if (trimmed === "") {
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
const parsed = new Date(trimmed);
|
|
61
|
+
if (Number.isNaN(parsed.getTime())) {
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
return Math.floor(utcDateToExcelSerial(parsed));
|
|
65
|
+
}
|
|
66
|
+
function isLeapYear(year) {
|
|
67
|
+
return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
|
|
68
|
+
}
|
|
69
|
+
function isValidBasis(basis) {
|
|
70
|
+
return basis === 0 || basis === 1 || basis === 2 || basis === 3 || basis === 4;
|
|
71
|
+
}
|
|
72
|
+
function yearFracByBasis(startSerial, endSerial, basis) {
|
|
73
|
+
if (!isValidBasis(basis)) {
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
let start = startSerial;
|
|
77
|
+
let end = endSerial;
|
|
78
|
+
if (start > end) {
|
|
79
|
+
[start, end] = [end, start];
|
|
80
|
+
}
|
|
81
|
+
const startParts = excelSerialToDateParts(start);
|
|
82
|
+
const endParts = excelSerialToDateParts(end);
|
|
83
|
+
if (startParts === undefined || endParts === undefined) {
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
let startDay = startParts.day;
|
|
87
|
+
let startMonth = startParts.month;
|
|
88
|
+
let startYear = startParts.year;
|
|
89
|
+
let endDay = endParts.day;
|
|
90
|
+
let endMonth = endParts.month;
|
|
91
|
+
let endYear = endParts.year;
|
|
92
|
+
let totalDays;
|
|
93
|
+
switch (basis) {
|
|
94
|
+
case 0:
|
|
95
|
+
if (startDay === 31) {
|
|
96
|
+
startDay -= 1;
|
|
97
|
+
}
|
|
98
|
+
if (startDay === 30 && endDay === 31) {
|
|
99
|
+
endDay -= 1;
|
|
100
|
+
}
|
|
101
|
+
else if (startMonth === 2 && startDay === (isLeapYear(startYear) ? 29 : 28)) {
|
|
102
|
+
startDay = 30;
|
|
103
|
+
if (endMonth === 2 && endDay === (isLeapYear(endYear) ? 29 : 28)) {
|
|
104
|
+
endDay = 30;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
totalDays = (endYear - startYear) * 360 + (endMonth - startMonth) * 30 + (endDay - startDay);
|
|
108
|
+
break;
|
|
109
|
+
case 1:
|
|
110
|
+
case 2:
|
|
111
|
+
case 3:
|
|
112
|
+
totalDays = end - start;
|
|
113
|
+
break;
|
|
114
|
+
case 4:
|
|
115
|
+
if (startDay === 31) {
|
|
116
|
+
startDay -= 1;
|
|
117
|
+
}
|
|
118
|
+
if (endDay === 31) {
|
|
119
|
+
endDay -= 1;
|
|
120
|
+
}
|
|
121
|
+
totalDays = (endYear - startYear) * 360 + (endMonth - startMonth) * 30 + (endDay - startDay);
|
|
122
|
+
break;
|
|
123
|
+
default:
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
126
|
+
let daysInYear;
|
|
127
|
+
switch (basis) {
|
|
128
|
+
case 1: {
|
|
129
|
+
const yearLength = (year) => (isLeapYear(year) ? 366 : 365);
|
|
130
|
+
if (startYear === endYear) {
|
|
131
|
+
daysInYear = yearLength(startYear);
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
const crossesMultipleYears = endYear !== startYear + 1 ||
|
|
135
|
+
endMonth < startMonth ||
|
|
136
|
+
(endMonth === startMonth && endDay > startDay);
|
|
137
|
+
if (crossesMultipleYears) {
|
|
138
|
+
let total = 0;
|
|
139
|
+
for (let year = startYear; year <= endYear; year += 1) {
|
|
140
|
+
total += yearLength(year);
|
|
141
|
+
}
|
|
142
|
+
daysInYear = total / (endYear - startYear + 1);
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
const startsInLeapYear = isLeapYear(startYear) && (startMonth < 2 || (startMonth === 2 && startDay <= 29));
|
|
146
|
+
const endsInLeapYear = isLeapYear(endYear) && (endMonth > 2 || (endMonth === 2 && endDay === 29));
|
|
147
|
+
daysInYear = startsInLeapYear || endsInLeapYear ? 366 : 365;
|
|
148
|
+
}
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
case 3:
|
|
152
|
+
daysInYear = 365;
|
|
153
|
+
break;
|
|
154
|
+
case 0:
|
|
155
|
+
case 2:
|
|
156
|
+
case 4:
|
|
157
|
+
daysInYear = 360;
|
|
158
|
+
break;
|
|
159
|
+
default:
|
|
160
|
+
return undefined;
|
|
161
|
+
}
|
|
162
|
+
return totalDays / daysInYear;
|
|
163
|
+
}
|
|
164
|
+
function createDays360Builtin() {
|
|
165
|
+
return (...args) => {
|
|
166
|
+
const error = firstError(args);
|
|
167
|
+
if (error) {
|
|
168
|
+
return error;
|
|
169
|
+
}
|
|
170
|
+
if (args.length < 2 || args.length > 3) {
|
|
171
|
+
return valueError();
|
|
172
|
+
}
|
|
173
|
+
const startSerial = truncArg(args[0]);
|
|
174
|
+
const endSerial = truncArg(args[1]);
|
|
175
|
+
const method = args[2] === undefined ? 0 : integerValue(args[2], 0);
|
|
176
|
+
if (method === undefined || (method !== 0 && method !== 1)) {
|
|
177
|
+
return valueError();
|
|
178
|
+
}
|
|
179
|
+
if (typeof startSerial !== "number") {
|
|
180
|
+
return startSerial;
|
|
181
|
+
}
|
|
182
|
+
if (typeof endSerial !== "number") {
|
|
183
|
+
return endSerial;
|
|
184
|
+
}
|
|
185
|
+
const startParts = excelSerialToDateParts(startSerial);
|
|
186
|
+
const endParts = excelSerialToDateParts(endSerial);
|
|
187
|
+
if (!startParts || !endParts) {
|
|
188
|
+
return valueError();
|
|
189
|
+
}
|
|
190
|
+
let startDay = startParts.day;
|
|
191
|
+
let endDay = endParts.day;
|
|
192
|
+
if (method === 0) {
|
|
193
|
+
if (startDay === 31) {
|
|
194
|
+
startDay = 30;
|
|
195
|
+
}
|
|
196
|
+
if (endDay === 31 && startDay >= 30) {
|
|
197
|
+
endDay = 30;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
if (startDay === 31) {
|
|
202
|
+
startDay = 30;
|
|
203
|
+
}
|
|
204
|
+
if (endDay === 31) {
|
|
205
|
+
endDay = 30;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return numberResult((endParts.year - startParts.year) * 360 +
|
|
209
|
+
(endParts.month - startParts.month) * 30 +
|
|
210
|
+
(endDay - startDay));
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
function createIsoWeeknumBuiltin() {
|
|
214
|
+
return (...args) => {
|
|
215
|
+
const error = firstError(args);
|
|
216
|
+
if (error) {
|
|
217
|
+
return error;
|
|
218
|
+
}
|
|
219
|
+
if (args.length !== 1) {
|
|
220
|
+
return valueError();
|
|
221
|
+
}
|
|
222
|
+
const serial = truncArg(args[0]);
|
|
223
|
+
if (typeof serial !== "number") {
|
|
224
|
+
return serial;
|
|
225
|
+
}
|
|
226
|
+
const parts = excelSerialToDateParts(serial);
|
|
227
|
+
if (parts === undefined) {
|
|
228
|
+
return valueError();
|
|
229
|
+
}
|
|
230
|
+
const date = new Date(Date.UTC(parts.year, parts.month - 1, parts.day));
|
|
231
|
+
const dow = date.getUTCDay();
|
|
232
|
+
const dayShift = dow === 0 ? 7 : dow;
|
|
233
|
+
const shifted = new Date(date.getTime());
|
|
234
|
+
shifted.setUTCDate(date.getUTCDate() + 4 - dayShift);
|
|
235
|
+
const yearStart = new Date(Date.UTC(shifted.getUTCFullYear(), 0, 1));
|
|
236
|
+
const dayOfYear = Math.floor((shifted.getTime() - yearStart.getTime()) / MS_PER_DAY) + 1;
|
|
237
|
+
return numberResult(Math.floor((dayOfYear - 1) / 7) + 1);
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
function createTimeValueBuiltin() {
|
|
241
|
+
return (value) => {
|
|
242
|
+
if (value === undefined) {
|
|
243
|
+
return valueError();
|
|
244
|
+
}
|
|
245
|
+
const error = firstError([value]);
|
|
246
|
+
if (error) {
|
|
247
|
+
return error;
|
|
248
|
+
}
|
|
249
|
+
const text = coerceText(value);
|
|
250
|
+
if (text === undefined) {
|
|
251
|
+
return valueError();
|
|
252
|
+
}
|
|
253
|
+
const trimmed = text.trim();
|
|
254
|
+
const amPmMatch = trimmed.match(/^(.+?)\s+([aApP][mM])$/);
|
|
255
|
+
const hasMeridiem = amPmMatch !== null;
|
|
256
|
+
const timeText = hasMeridiem ? (amPmMatch?.[1] ?? "") : trimmed;
|
|
257
|
+
const timeParts = timeText.split(":");
|
|
258
|
+
if (timeParts.length < 2 || timeParts.length > 3) {
|
|
259
|
+
return valueError();
|
|
260
|
+
}
|
|
261
|
+
const [hoursText, minutesText, secondsText = "0"] = timeParts;
|
|
262
|
+
const hours = Number(hoursText);
|
|
263
|
+
const minutes = Number(minutesText);
|
|
264
|
+
const seconds = Number(secondsText);
|
|
265
|
+
if (Number.isNaN(hours) ||
|
|
266
|
+
Number.isNaN(minutes) ||
|
|
267
|
+
Number.isNaN(seconds) ||
|
|
268
|
+
!Number.isFinite(hours) ||
|
|
269
|
+
!Number.isFinite(minutes) ||
|
|
270
|
+
!Number.isFinite(seconds)) {
|
|
271
|
+
return valueError();
|
|
272
|
+
}
|
|
273
|
+
const truncHours = Math.trunc(hours);
|
|
274
|
+
const truncMinutes = Math.trunc(minutes);
|
|
275
|
+
const truncSeconds = Math.trunc(seconds);
|
|
276
|
+
const hasPm = hasMeridiem && amPmMatch?.[2]?.toLowerCase() === "pm";
|
|
277
|
+
if (truncMinutes < 0 || truncMinutes > 59 || truncSeconds < 0 || truncSeconds > 59) {
|
|
278
|
+
return valueError();
|
|
279
|
+
}
|
|
280
|
+
let hourValue = truncHours;
|
|
281
|
+
if (hasMeridiem) {
|
|
282
|
+
if (truncHours < 1 || truncHours > 12) {
|
|
283
|
+
return valueError();
|
|
284
|
+
}
|
|
285
|
+
if (truncHours === 12) {
|
|
286
|
+
hourValue = hasPm ? 12 : 0;
|
|
287
|
+
}
|
|
288
|
+
else if (hasPm) {
|
|
289
|
+
hourValue = truncHours + 12;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
else if (truncHours === 24 && truncMinutes === 0 && truncSeconds === 0) {
|
|
293
|
+
hourValue = 0;
|
|
294
|
+
}
|
|
295
|
+
else if (truncHours < 0 || truncHours > 23) {
|
|
296
|
+
return valueError();
|
|
297
|
+
}
|
|
298
|
+
return numberResult((hourValue * 3600 + truncMinutes * 60 + truncSeconds) / SECONDS_PER_DAY);
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
function createYearfracBuiltin() {
|
|
302
|
+
return (...args) => {
|
|
303
|
+
const error = firstError(args);
|
|
304
|
+
if (error) {
|
|
305
|
+
return error;
|
|
306
|
+
}
|
|
307
|
+
if (args.length < 2 || args.length > 3) {
|
|
308
|
+
return valueError();
|
|
309
|
+
}
|
|
310
|
+
const startSerial = truncArg(args[0]);
|
|
311
|
+
const endSerial = truncArg(args[1]);
|
|
312
|
+
const basis = args[2] === undefined ? 0 : integerValue(args[2]);
|
|
313
|
+
if (typeof startSerial !== "number" ||
|
|
314
|
+
typeof endSerial !== "number" ||
|
|
315
|
+
basis === undefined ||
|
|
316
|
+
!isValidBasis(basis)) {
|
|
317
|
+
return valueError();
|
|
318
|
+
}
|
|
319
|
+
const fraction = yearFracByBasis(startSerial, endSerial, basis);
|
|
320
|
+
return fraction === undefined ? valueError() : numberResult(fraction);
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
function truncArg(value) {
|
|
324
|
+
if (value.tag === ValueTag.Error) {
|
|
325
|
+
return value;
|
|
326
|
+
}
|
|
327
|
+
const coerced = coerceNumber(value);
|
|
328
|
+
if (coerced === undefined) {
|
|
329
|
+
return valueError();
|
|
330
|
+
}
|
|
331
|
+
return Math.trunc(coerced);
|
|
332
|
+
}
|
|
333
|
+
function floorDateSerial(serial) {
|
|
334
|
+
return Math.floor(serial);
|
|
335
|
+
}
|
|
336
|
+
function isErrorValue(value) {
|
|
337
|
+
return !("size" in value);
|
|
338
|
+
}
|
|
339
|
+
function normalizeSecondOfDay(serial) {
|
|
340
|
+
if (!Number.isFinite(serial) || serial < 0) {
|
|
341
|
+
return undefined;
|
|
342
|
+
}
|
|
343
|
+
const fraction = serial - floorDateSerial(serial);
|
|
344
|
+
const normalizedFraction = fraction < 0 ? fraction + 1 : fraction;
|
|
345
|
+
return Math.floor(normalizedFraction * SECONDS_PER_DAY + 1e-9) % SECONDS_PER_DAY;
|
|
346
|
+
}
|
|
347
|
+
function isExcelLeapBugDate(parts) {
|
|
348
|
+
return parts.year === 1900 && parts.month === 2 && parts.day === 29;
|
|
349
|
+
}
|
|
350
|
+
function daysInExcelMonth(year, month) {
|
|
351
|
+
if (year === 1900 && month === 2) {
|
|
352
|
+
return 29;
|
|
353
|
+
}
|
|
354
|
+
return new Date(Date.UTC(year, month, 0)).getUTCDate();
|
|
355
|
+
}
|
|
356
|
+
function normalizeMonth(year, month) {
|
|
357
|
+
const zeroBased = year * 12 + (month - 1);
|
|
358
|
+
const normalizedYear = Math.floor(zeroBased / 12);
|
|
359
|
+
const normalizedMonth = zeroBased - normalizedYear * 12 + 1;
|
|
360
|
+
return { year: normalizedYear, month: normalizedMonth };
|
|
361
|
+
}
|
|
362
|
+
export function excelSerialToDateParts(serial) {
|
|
363
|
+
if (!Number.isFinite(serial)) {
|
|
364
|
+
return undefined;
|
|
365
|
+
}
|
|
366
|
+
const whole = floorDateSerial(serial);
|
|
367
|
+
if (whole === 60) {
|
|
368
|
+
return { year: 1900, month: 2, day: 29 };
|
|
369
|
+
}
|
|
370
|
+
const adjustedWhole = whole < 60 ? whole : whole - 1;
|
|
371
|
+
const date = new Date(EXCEL_EPOCH_UTC_MS + adjustedWhole * MS_PER_DAY);
|
|
372
|
+
if (Number.isNaN(date.getTime())) {
|
|
373
|
+
return undefined;
|
|
374
|
+
}
|
|
375
|
+
return {
|
|
376
|
+
year: date.getUTCFullYear(),
|
|
377
|
+
month: date.getUTCMonth() + 1,
|
|
378
|
+
day: date.getUTCDate(),
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
export function excelDatePartsToSerial(year, month, day) {
|
|
382
|
+
if (!Number.isFinite(year) || !Number.isFinite(month) || !Number.isFinite(day)) {
|
|
383
|
+
return undefined;
|
|
384
|
+
}
|
|
385
|
+
let adjustedYear = Math.trunc(year);
|
|
386
|
+
const adjustedMonth = Math.trunc(month);
|
|
387
|
+
const adjustedDay = Math.trunc(day);
|
|
388
|
+
if (adjustedYear >= 0 && adjustedYear <= 1899) {
|
|
389
|
+
adjustedYear += 1900;
|
|
390
|
+
}
|
|
391
|
+
if (adjustedYear < 0 || adjustedYear > 9999) {
|
|
392
|
+
return undefined;
|
|
393
|
+
}
|
|
394
|
+
if (adjustedYear === 1900 && adjustedMonth === 2 && adjustedDay === 29) {
|
|
395
|
+
return 60;
|
|
396
|
+
}
|
|
397
|
+
const normalized = new Date(Date.UTC(adjustedYear, adjustedMonth - 1, adjustedDay));
|
|
398
|
+
if (Number.isNaN(normalized.getTime())) {
|
|
399
|
+
return undefined;
|
|
400
|
+
}
|
|
401
|
+
return utcDateToExcelSerial(normalized);
|
|
402
|
+
}
|
|
403
|
+
export function utcDateToExcelSerial(date) {
|
|
404
|
+
const midnightUtcMs = Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate());
|
|
405
|
+
let daySerial = (midnightUtcMs - EXCEL_EPOCH_UTC_MS) / MS_PER_DAY;
|
|
406
|
+
if (midnightUtcMs >= EXCEL_LEAP_BUG_CUTOFF_UTC_MS) {
|
|
407
|
+
daySerial += 1;
|
|
408
|
+
}
|
|
409
|
+
const dayFraction = (date.getUTCHours() * 3_600_000 +
|
|
410
|
+
date.getUTCMinutes() * 60_000 +
|
|
411
|
+
date.getUTCSeconds() * 1_000 +
|
|
412
|
+
date.getUTCMilliseconds()) /
|
|
413
|
+
MS_PER_DAY;
|
|
414
|
+
return daySerial + dayFraction;
|
|
415
|
+
}
|
|
416
|
+
export function addMonthsToExcelDate(serial, offsetMonths) {
|
|
417
|
+
const start = excelSerialToDateParts(serial);
|
|
418
|
+
if (!start || !Number.isFinite(offsetMonths)) {
|
|
419
|
+
return undefined;
|
|
420
|
+
}
|
|
421
|
+
const shifted = normalizeMonth(start.year, start.month + Math.trunc(offsetMonths));
|
|
422
|
+
const day = Math.min(start.day, daysInExcelMonth(shifted.year, shifted.month));
|
|
423
|
+
if (shifted.year < 0 || shifted.year > 9999) {
|
|
424
|
+
return undefined;
|
|
425
|
+
}
|
|
426
|
+
if (shifted.year === 1900 && shifted.month === 2 && day === 29) {
|
|
427
|
+
return 60;
|
|
428
|
+
}
|
|
429
|
+
return excelDatePartsToSerial(shifted.year, shifted.month, day);
|
|
430
|
+
}
|
|
431
|
+
export function endOfMonthExcelDate(serial, offsetMonths) {
|
|
432
|
+
const start = excelSerialToDateParts(serial);
|
|
433
|
+
if (!start || !Number.isFinite(offsetMonths)) {
|
|
434
|
+
return undefined;
|
|
435
|
+
}
|
|
436
|
+
const shifted = normalizeMonth(start.year, start.month + Math.trunc(offsetMonths));
|
|
437
|
+
const day = daysInExcelMonth(shifted.year, shifted.month);
|
|
438
|
+
if (shifted.year < 0 || shifted.year > 9999) {
|
|
439
|
+
return undefined;
|
|
440
|
+
}
|
|
441
|
+
if (isExcelLeapBugDate({ year: shifted.year, month: shifted.month, day })) {
|
|
442
|
+
return 60;
|
|
443
|
+
}
|
|
444
|
+
return excelDatePartsToSerial(shifted.year, shifted.month, day);
|
|
445
|
+
}
|
|
446
|
+
function datedifValue(startSerial, endSerial, unit) {
|
|
447
|
+
if (startSerial > endSerial) {
|
|
448
|
+
return undefined;
|
|
449
|
+
}
|
|
450
|
+
const start = excelSerialToDateParts(startSerial);
|
|
451
|
+
const end = excelSerialToDateParts(endSerial);
|
|
452
|
+
if (!start || !end) {
|
|
453
|
+
return undefined;
|
|
454
|
+
}
|
|
455
|
+
const totalDays = Math.trunc(endSerial) - Math.trunc(startSerial);
|
|
456
|
+
const totalMonths = (end.year - start.year) * 12 + (end.month - start.month) - (end.day < start.day ? 1 : 0);
|
|
457
|
+
const totalYears = end.year -
|
|
458
|
+
start.year -
|
|
459
|
+
(end.month < start.month || (end.month === start.month && end.day < start.day) ? 1 : 0);
|
|
460
|
+
switch (unit) {
|
|
461
|
+
case "D":
|
|
462
|
+
return totalDays;
|
|
463
|
+
case "M":
|
|
464
|
+
return totalMonths;
|
|
465
|
+
case "Y":
|
|
466
|
+
return totalYears;
|
|
467
|
+
case "YM":
|
|
468
|
+
return ((totalMonths % 12) + 12) % 12;
|
|
469
|
+
case "YD": {
|
|
470
|
+
let comparisonYear = end.year;
|
|
471
|
+
let comparison = excelDatePartsToSerial(comparisonYear, start.month, start.day);
|
|
472
|
+
if (comparison === undefined || comparison > endSerial) {
|
|
473
|
+
comparisonYear -= 1;
|
|
474
|
+
comparison = excelDatePartsToSerial(comparisonYear, start.month, start.day);
|
|
475
|
+
}
|
|
476
|
+
return comparison === undefined ? undefined : Math.trunc(endSerial) - Math.trunc(comparison);
|
|
477
|
+
}
|
|
478
|
+
case "MD":
|
|
479
|
+
if (end.day >= start.day) {
|
|
480
|
+
return end.day - start.day;
|
|
481
|
+
}
|
|
482
|
+
return daysInExcelMonth(end.year, end.month === 1 ? 12 : end.month - 1) - start.day + end.day;
|
|
483
|
+
default:
|
|
484
|
+
return undefined;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
function normalizeTimeSerial(hours, minutes, seconds) {
|
|
488
|
+
if (!Number.isFinite(hours) || !Number.isFinite(minutes) || !Number.isFinite(seconds)) {
|
|
489
|
+
return undefined;
|
|
490
|
+
}
|
|
491
|
+
if (hours < 0 || minutes < 0 || seconds < 0) {
|
|
492
|
+
return undefined;
|
|
493
|
+
}
|
|
494
|
+
if (hours > 32_767 || minutes > 32_767 || seconds > 32_767) {
|
|
495
|
+
return undefined;
|
|
496
|
+
}
|
|
497
|
+
const totalSeconds = Math.trunc(hours) * 3600 + Math.trunc(minutes) * 60 + Math.trunc(seconds);
|
|
498
|
+
return (((totalSeconds % SECONDS_PER_DAY) + SECONDS_PER_DAY) % SECONDS_PER_DAY) / SECONDS_PER_DAY;
|
|
499
|
+
}
|
|
500
|
+
export function createDateBuiltin() {
|
|
501
|
+
return (...args) => {
|
|
502
|
+
const error = firstError(args);
|
|
503
|
+
if (error) {
|
|
504
|
+
return error;
|
|
505
|
+
}
|
|
506
|
+
if (args.length !== 3) {
|
|
507
|
+
return valueError();
|
|
508
|
+
}
|
|
509
|
+
const year = truncArg(args[0]);
|
|
510
|
+
const month = truncArg(args[1]);
|
|
511
|
+
const day = truncArg(args[2]);
|
|
512
|
+
if (typeof year !== "number")
|
|
513
|
+
return year;
|
|
514
|
+
if (typeof month !== "number")
|
|
515
|
+
return month;
|
|
516
|
+
if (typeof day !== "number")
|
|
517
|
+
return day;
|
|
518
|
+
const serial = excelDatePartsToSerial(year, month, day);
|
|
519
|
+
return serial === undefined ? valueError() : numberResult(serial);
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
export function createDateValueBuiltin() {
|
|
523
|
+
return (dateText) => {
|
|
524
|
+
if (dateText === undefined) {
|
|
525
|
+
return valueError();
|
|
526
|
+
}
|
|
527
|
+
const error = firstError([dateText]);
|
|
528
|
+
if (error) {
|
|
529
|
+
return error;
|
|
530
|
+
}
|
|
531
|
+
const asNumber = toNumberValueDateValue(dateText);
|
|
532
|
+
if (asNumber !== undefined) {
|
|
533
|
+
return numberResult(asNumber);
|
|
534
|
+
}
|
|
535
|
+
const text = coerceText(dateText);
|
|
536
|
+
if (text === undefined) {
|
|
537
|
+
return valueError();
|
|
538
|
+
}
|
|
539
|
+
const serial = parseDateValueFromText(text);
|
|
540
|
+
return serial === undefined ? valueError() : numberResult(serial);
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
function toNumberValueDateValue(value) {
|
|
544
|
+
const numeric = coerceNumber(value);
|
|
545
|
+
if (numeric === undefined) {
|
|
546
|
+
return undefined;
|
|
547
|
+
}
|
|
548
|
+
const truncated = Math.trunc(numeric);
|
|
549
|
+
return Number.isFinite(truncated) ? truncated : undefined;
|
|
550
|
+
}
|
|
551
|
+
function createDatePartBuiltin(part) {
|
|
552
|
+
return (value) => {
|
|
553
|
+
const error = firstError([value]);
|
|
554
|
+
if (error) {
|
|
555
|
+
return error;
|
|
556
|
+
}
|
|
557
|
+
const serial = coerceNumber(value);
|
|
558
|
+
if (serial === undefined) {
|
|
559
|
+
return valueError();
|
|
560
|
+
}
|
|
561
|
+
const parts = excelSerialToDateParts(serial);
|
|
562
|
+
return parts ? numberResult(parts[part]) : valueError();
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
function createTimeBuiltin() {
|
|
566
|
+
return (...args) => {
|
|
567
|
+
const error = firstError(args);
|
|
568
|
+
if (error) {
|
|
569
|
+
return error;
|
|
570
|
+
}
|
|
571
|
+
if (args.length !== 3) {
|
|
572
|
+
return valueError();
|
|
573
|
+
}
|
|
574
|
+
const hour = truncArg(args[0]);
|
|
575
|
+
const minute = truncArg(args[1]);
|
|
576
|
+
const second = truncArg(args[2]);
|
|
577
|
+
if (typeof hour !== "number")
|
|
578
|
+
return hour;
|
|
579
|
+
if (typeof minute !== "number")
|
|
580
|
+
return minute;
|
|
581
|
+
if (typeof second !== "number")
|
|
582
|
+
return second;
|
|
583
|
+
const serial = normalizeTimeSerial(hour, minute, second);
|
|
584
|
+
return serial === undefined ? valueError() : numberResult(serial);
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
function createTimePartBuiltin(part) {
|
|
588
|
+
return (...args) => {
|
|
589
|
+
const error = firstError(args);
|
|
590
|
+
if (error) {
|
|
591
|
+
return error;
|
|
592
|
+
}
|
|
593
|
+
const [value] = args;
|
|
594
|
+
if (value === undefined) {
|
|
595
|
+
return valueError();
|
|
596
|
+
}
|
|
597
|
+
const serial = coerceNumber(value);
|
|
598
|
+
if (serial === undefined) {
|
|
599
|
+
return valueError();
|
|
600
|
+
}
|
|
601
|
+
const seconds = normalizeSecondOfDay(serial);
|
|
602
|
+
if (seconds === undefined) {
|
|
603
|
+
return valueError();
|
|
604
|
+
}
|
|
605
|
+
switch (part) {
|
|
606
|
+
case "hour":
|
|
607
|
+
return numberResult(Math.floor(seconds / 3600));
|
|
608
|
+
case "minute":
|
|
609
|
+
return numberResult(Math.floor((seconds % 3600) / 60));
|
|
610
|
+
case "second":
|
|
611
|
+
return numberResult(seconds % 60);
|
|
612
|
+
}
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
function createWeekdayBuiltin() {
|
|
616
|
+
return (...args) => {
|
|
617
|
+
const error = firstError(args);
|
|
618
|
+
if (error) {
|
|
619
|
+
return error;
|
|
620
|
+
}
|
|
621
|
+
if (args.length < 1 || args.length > 2) {
|
|
622
|
+
return valueError();
|
|
623
|
+
}
|
|
624
|
+
const serial = coerceNumber(args[0]);
|
|
625
|
+
if (serial === undefined || serial < 0) {
|
|
626
|
+
return valueError();
|
|
627
|
+
}
|
|
628
|
+
const whole = floorDateSerial(serial);
|
|
629
|
+
const adjustedWhole = whole < 60 ? whole : whole - 1;
|
|
630
|
+
const sundayOne = (((adjustedWhole % 7) + 7) % 7) + 1;
|
|
631
|
+
if (args.length === 1) {
|
|
632
|
+
return numberResult(sundayOne);
|
|
633
|
+
}
|
|
634
|
+
const returnType = truncArg(args[1]);
|
|
635
|
+
if (typeof returnType !== "number") {
|
|
636
|
+
return returnType;
|
|
637
|
+
}
|
|
638
|
+
if (returnType === 3) {
|
|
639
|
+
return numberResult(sundayOne === 1 ? 6 : sundayOne - 2);
|
|
640
|
+
}
|
|
641
|
+
const startDayMap = {
|
|
642
|
+
1: 1,
|
|
643
|
+
2: 2,
|
|
644
|
+
11: 2,
|
|
645
|
+
12: 3,
|
|
646
|
+
13: 4,
|
|
647
|
+
14: 5,
|
|
648
|
+
15: 6,
|
|
649
|
+
16: 7,
|
|
650
|
+
17: 1,
|
|
651
|
+
};
|
|
652
|
+
const startDay = startDayMap[returnType];
|
|
653
|
+
if (startDay === undefined) {
|
|
654
|
+
return valueError();
|
|
655
|
+
}
|
|
656
|
+
return numberResult(((sundayOne - startDay + 7) % 7) + 1);
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
function createDaysBuiltin() {
|
|
660
|
+
return (...args) => {
|
|
661
|
+
const error = firstError(args);
|
|
662
|
+
if (error) {
|
|
663
|
+
return error;
|
|
664
|
+
}
|
|
665
|
+
if (args.length !== 2) {
|
|
666
|
+
return valueError();
|
|
667
|
+
}
|
|
668
|
+
const endSerial = truncArg(args[0]);
|
|
669
|
+
const startSerial = truncArg(args[1]);
|
|
670
|
+
if (typeof endSerial !== "number") {
|
|
671
|
+
return endSerial;
|
|
672
|
+
}
|
|
673
|
+
if (typeof startSerial !== "number") {
|
|
674
|
+
return startSerial;
|
|
675
|
+
}
|
|
676
|
+
return numberResult(endSerial - startSerial);
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
function createWeeknumBuiltin() {
|
|
680
|
+
return (...args) => {
|
|
681
|
+
const error = firstError(args);
|
|
682
|
+
if (error) {
|
|
683
|
+
return error;
|
|
684
|
+
}
|
|
685
|
+
if (args.length < 1 || args.length > 2) {
|
|
686
|
+
return valueError();
|
|
687
|
+
}
|
|
688
|
+
const serial = truncArg(args[0]);
|
|
689
|
+
if (typeof serial !== "number") {
|
|
690
|
+
return serial;
|
|
691
|
+
}
|
|
692
|
+
const returnType = args[1] === undefined ? 1 : truncArg(args[1]);
|
|
693
|
+
if (typeof returnType !== "number") {
|
|
694
|
+
return returnType;
|
|
695
|
+
}
|
|
696
|
+
const dateParts = excelSerialToDateParts(serial);
|
|
697
|
+
if (!dateParts) {
|
|
698
|
+
return valueError();
|
|
699
|
+
}
|
|
700
|
+
let weekStartDay;
|
|
701
|
+
if (returnType === 1 || returnType === 17) {
|
|
702
|
+
weekStartDay = 0;
|
|
703
|
+
}
|
|
704
|
+
else if (returnType === 2 || returnType === 11) {
|
|
705
|
+
weekStartDay = 1;
|
|
706
|
+
}
|
|
707
|
+
else if (returnType === 12) {
|
|
708
|
+
weekStartDay = 2;
|
|
709
|
+
}
|
|
710
|
+
else if (returnType === 13) {
|
|
711
|
+
weekStartDay = 3;
|
|
712
|
+
}
|
|
713
|
+
else if (returnType === 14) {
|
|
714
|
+
weekStartDay = 4;
|
|
715
|
+
}
|
|
716
|
+
else if (returnType === 15) {
|
|
717
|
+
weekStartDay = 5;
|
|
718
|
+
}
|
|
719
|
+
else if (returnType === 16) {
|
|
720
|
+
weekStartDay = 6;
|
|
721
|
+
}
|
|
722
|
+
else {
|
|
723
|
+
return valueError();
|
|
724
|
+
}
|
|
725
|
+
const serialJan1 = excelDatePartsToSerial(dateParts.year, 1, 1);
|
|
726
|
+
if (serialJan1 === undefined) {
|
|
727
|
+
return valueError();
|
|
728
|
+
}
|
|
729
|
+
const adjustedJan1 = serialJan1 < 60 ? Math.floor(serialJan1) : Math.floor(serialJan1) - 1;
|
|
730
|
+
const jan1Weekday = ((adjustedJan1 % 7) + 7) % 7;
|
|
731
|
+
const shift = (jan1Weekday - weekStartDay + 7) % 7;
|
|
732
|
+
let dayOfYear = dateParts.day;
|
|
733
|
+
for (let month = 1; month < dateParts.month; month += 1) {
|
|
734
|
+
dayOfYear += daysInExcelMonth(dateParts.year, month);
|
|
735
|
+
}
|
|
736
|
+
return numberResult(Math.floor((dayOfYear - 1 + shift) / 7) + 1);
|
|
737
|
+
};
|
|
738
|
+
}
|
|
739
|
+
function isWeekendSerial(serial) {
|
|
740
|
+
const whole = floorDateSerial(serial);
|
|
741
|
+
const adjustedWhole = whole < 60 ? whole : whole - 1;
|
|
742
|
+
const dow = ((adjustedWhole % 7) + 7) % 7;
|
|
743
|
+
return dow === 0 || dow === 6;
|
|
744
|
+
}
|
|
745
|
+
function weekendSerialDay(serial) {
|
|
746
|
+
const whole = floorDateSerial(serial);
|
|
747
|
+
const adjustedWhole = whole < 60 ? whole : whole - 1;
|
|
748
|
+
return ((adjustedWhole % 7) + 7) % 7;
|
|
749
|
+
}
|
|
750
|
+
function weekendMaskFromCode(code) {
|
|
751
|
+
const twoDayWeekendMap = {
|
|
752
|
+
1: [6, 0],
|
|
753
|
+
2: [0, 1],
|
|
754
|
+
3: [1, 2],
|
|
755
|
+
4: [2, 3],
|
|
756
|
+
5: [3, 4],
|
|
757
|
+
6: [4, 5],
|
|
758
|
+
7: [5, 6],
|
|
759
|
+
};
|
|
760
|
+
if (code >= 1 && code <= 7) {
|
|
761
|
+
return new Set(twoDayWeekendMap[code]);
|
|
762
|
+
}
|
|
763
|
+
if (code >= 11 && code <= 17) {
|
|
764
|
+
return new Set([(code - 10) % 7]);
|
|
765
|
+
}
|
|
766
|
+
return undefined;
|
|
767
|
+
}
|
|
768
|
+
function weekendMaskFromString(maskText) {
|
|
769
|
+
const trimmed = maskText.trim();
|
|
770
|
+
if (!/^[01]{7}$/.test(trimmed) || trimmed === "1111111") {
|
|
771
|
+
return undefined;
|
|
772
|
+
}
|
|
773
|
+
const days = new Set();
|
|
774
|
+
for (let index = 0; index < trimmed.length; index += 1) {
|
|
775
|
+
if (trimmed[index] !== "1") {
|
|
776
|
+
continue;
|
|
777
|
+
}
|
|
778
|
+
const dow = index === 6 ? 0 : index + 1;
|
|
779
|
+
days.add(dow);
|
|
780
|
+
}
|
|
781
|
+
return days;
|
|
782
|
+
}
|
|
783
|
+
function normalizeWeekendMask(weekendArg) {
|
|
784
|
+
if (weekendArg === undefined) {
|
|
785
|
+
return new Set([0, 6]);
|
|
786
|
+
}
|
|
787
|
+
if (weekendArg.tag === ValueTag.Error) {
|
|
788
|
+
return weekendArg;
|
|
789
|
+
}
|
|
790
|
+
if (weekendArg.tag === ValueTag.String) {
|
|
791
|
+
const mask = weekendMaskFromString(weekendArg.value);
|
|
792
|
+
return mask ?? valueError();
|
|
793
|
+
}
|
|
794
|
+
const code = integerValue(weekendArg);
|
|
795
|
+
if (code === undefined) {
|
|
796
|
+
return valueError();
|
|
797
|
+
}
|
|
798
|
+
const mask = weekendMaskFromCode(code);
|
|
799
|
+
return mask ?? valueError();
|
|
800
|
+
}
|
|
801
|
+
function normalizeHolidayDateSet(holidays) {
|
|
802
|
+
if (!holidays || holidays.length === 0) {
|
|
803
|
+
return new Set();
|
|
804
|
+
}
|
|
805
|
+
const set = new Set();
|
|
806
|
+
for (const holiday of holidays) {
|
|
807
|
+
const raw = coerceNumber(holiday);
|
|
808
|
+
if (raw === undefined) {
|
|
809
|
+
return valueError();
|
|
810
|
+
}
|
|
811
|
+
set.add(Math.trunc(raw));
|
|
812
|
+
}
|
|
813
|
+
return set;
|
|
814
|
+
}
|
|
815
|
+
function isWeekendWithMask(serial, weekendDays) {
|
|
816
|
+
return weekendDays.has(weekendSerialDay(serial));
|
|
817
|
+
}
|
|
818
|
+
function createWorkdayBuiltin() {
|
|
819
|
+
return (...args) => {
|
|
820
|
+
const error = firstError(args);
|
|
821
|
+
if (error) {
|
|
822
|
+
return error;
|
|
823
|
+
}
|
|
824
|
+
if (args.length < 2) {
|
|
825
|
+
return valueError();
|
|
826
|
+
}
|
|
827
|
+
const start = truncArg(args[0]);
|
|
828
|
+
const offset = truncArg(args[1]);
|
|
829
|
+
if (typeof start !== "number") {
|
|
830
|
+
return start;
|
|
831
|
+
}
|
|
832
|
+
if (typeof offset !== "number") {
|
|
833
|
+
return offset;
|
|
834
|
+
}
|
|
835
|
+
const holidays = normalizeHolidayDateSet(args.slice(2));
|
|
836
|
+
if (isErrorValue(holidays)) {
|
|
837
|
+
return holidays;
|
|
838
|
+
}
|
|
839
|
+
const isWorkday = (value) => !isWeekendSerial(value) && !holidays.has(Math.trunc(value));
|
|
840
|
+
let cursor = Math.trunc(start);
|
|
841
|
+
const direction = offset >= 0 ? 1 : -1;
|
|
842
|
+
while (!isWorkday(cursor)) {
|
|
843
|
+
cursor += direction;
|
|
844
|
+
}
|
|
845
|
+
let remaining = Math.abs(offset);
|
|
846
|
+
while (remaining > 0) {
|
|
847
|
+
cursor += direction;
|
|
848
|
+
if (isWorkday(cursor)) {
|
|
849
|
+
remaining -= 1;
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
return numberResult(cursor);
|
|
853
|
+
};
|
|
854
|
+
}
|
|
855
|
+
function createNetworkdaysBuiltin() {
|
|
856
|
+
return (...args) => {
|
|
857
|
+
const error = firstError(args);
|
|
858
|
+
if (error) {
|
|
859
|
+
return error;
|
|
860
|
+
}
|
|
861
|
+
if (args.length < 2) {
|
|
862
|
+
return valueError();
|
|
863
|
+
}
|
|
864
|
+
const start = truncArg(args[0]);
|
|
865
|
+
const end = truncArg(args[1]);
|
|
866
|
+
if (typeof start !== "number") {
|
|
867
|
+
return start;
|
|
868
|
+
}
|
|
869
|
+
if (typeof end !== "number") {
|
|
870
|
+
return end;
|
|
871
|
+
}
|
|
872
|
+
const holidays = normalizeHolidayDateSet(args.slice(2));
|
|
873
|
+
if (isErrorValue(holidays)) {
|
|
874
|
+
return holidays;
|
|
875
|
+
}
|
|
876
|
+
const isWorkday = (value) => !isWeekendSerial(value) && !holidays.has(Math.trunc(value));
|
|
877
|
+
const step = start <= end ? 1 : -1;
|
|
878
|
+
let count = 0;
|
|
879
|
+
for (let cursor = Math.trunc(start);; cursor += step) {
|
|
880
|
+
if (isWorkday(cursor)) {
|
|
881
|
+
count += step;
|
|
882
|
+
}
|
|
883
|
+
if (cursor === Math.trunc(end)) {
|
|
884
|
+
break;
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
return numberResult(count);
|
|
888
|
+
};
|
|
889
|
+
}
|
|
890
|
+
function createWorkdayIntlBuiltin() {
|
|
891
|
+
return (...args) => {
|
|
892
|
+
const error = firstError(args);
|
|
893
|
+
if (error) {
|
|
894
|
+
return error;
|
|
895
|
+
}
|
|
896
|
+
if (args.length < 2 || args.length > 4) {
|
|
897
|
+
return valueError();
|
|
898
|
+
}
|
|
899
|
+
const start = truncArg(args[0]);
|
|
900
|
+
const offset = truncArg(args[1]);
|
|
901
|
+
if (typeof start !== "number") {
|
|
902
|
+
return start;
|
|
903
|
+
}
|
|
904
|
+
if (typeof offset !== "number") {
|
|
905
|
+
return offset;
|
|
906
|
+
}
|
|
907
|
+
const weekendDays = normalizeWeekendMask(args[2]);
|
|
908
|
+
if (isErrorValue(weekendDays)) {
|
|
909
|
+
return weekendDays;
|
|
910
|
+
}
|
|
911
|
+
const holidays = normalizeHolidayDateSet(args[3] === undefined ? undefined : [args[3]]);
|
|
912
|
+
if (isErrorValue(holidays)) {
|
|
913
|
+
return holidays;
|
|
914
|
+
}
|
|
915
|
+
const isWorkday = (value) => !isWeekendWithMask(value, weekendDays) && !holidays.has(Math.trunc(value));
|
|
916
|
+
let cursor = Math.trunc(start);
|
|
917
|
+
const direction = offset >= 0 ? 1 : -1;
|
|
918
|
+
while (!isWorkday(cursor)) {
|
|
919
|
+
cursor += direction;
|
|
920
|
+
}
|
|
921
|
+
let remaining = Math.abs(offset);
|
|
922
|
+
while (remaining > 0) {
|
|
923
|
+
cursor += direction;
|
|
924
|
+
if (isWorkday(cursor)) {
|
|
925
|
+
remaining -= 1;
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
return numberResult(cursor);
|
|
929
|
+
};
|
|
930
|
+
}
|
|
931
|
+
function createNetworkdaysIntlBuiltin() {
|
|
932
|
+
return (...args) => {
|
|
933
|
+
const error = firstError(args);
|
|
934
|
+
if (error) {
|
|
935
|
+
return error;
|
|
936
|
+
}
|
|
937
|
+
if (args.length < 2 || args.length > 4) {
|
|
938
|
+
return valueError();
|
|
939
|
+
}
|
|
940
|
+
const start = truncArg(args[0]);
|
|
941
|
+
const end = truncArg(args[1]);
|
|
942
|
+
if (typeof start !== "number") {
|
|
943
|
+
return start;
|
|
944
|
+
}
|
|
945
|
+
if (typeof end !== "number") {
|
|
946
|
+
return end;
|
|
947
|
+
}
|
|
948
|
+
const weekendDays = normalizeWeekendMask(args[2]);
|
|
949
|
+
if (isErrorValue(weekendDays)) {
|
|
950
|
+
return weekendDays;
|
|
951
|
+
}
|
|
952
|
+
const holidays = normalizeHolidayDateSet(args[3] === undefined ? undefined : [args[3]]);
|
|
953
|
+
if (isErrorValue(holidays)) {
|
|
954
|
+
return holidays;
|
|
955
|
+
}
|
|
956
|
+
const isWorkday = (value) => !isWeekendWithMask(value, weekendDays) && !holidays.has(Math.trunc(value));
|
|
957
|
+
const step = start <= end ? 1 : -1;
|
|
958
|
+
let count = 0;
|
|
959
|
+
for (let cursor = Math.trunc(start);; cursor += step) {
|
|
960
|
+
if (isWorkday(cursor)) {
|
|
961
|
+
count += step;
|
|
962
|
+
}
|
|
963
|
+
if (cursor === Math.trunc(end)) {
|
|
964
|
+
break;
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
return numberResult(count);
|
|
968
|
+
};
|
|
969
|
+
}
|
|
970
|
+
export function createTodayBuiltin(now = () => new Date()) {
|
|
971
|
+
return (...args) => {
|
|
972
|
+
const error = firstError(args);
|
|
973
|
+
if (error) {
|
|
974
|
+
return error;
|
|
975
|
+
}
|
|
976
|
+
if (args.length > 0) {
|
|
977
|
+
return valueError();
|
|
978
|
+
}
|
|
979
|
+
return numberResult(Math.floor(utcDateToExcelSerial(now())));
|
|
980
|
+
};
|
|
981
|
+
}
|
|
982
|
+
export function createNowBuiltin(now = () => new Date()) {
|
|
983
|
+
return (...args) => {
|
|
984
|
+
const error = firstError(args);
|
|
985
|
+
if (error) {
|
|
986
|
+
return error;
|
|
987
|
+
}
|
|
988
|
+
if (args.length > 0) {
|
|
989
|
+
return valueError();
|
|
990
|
+
}
|
|
991
|
+
return numberResult(utcDateToExcelSerial(now()));
|
|
992
|
+
};
|
|
993
|
+
}
|
|
994
|
+
export function createRandBuiltin(random = () => Math.random()) {
|
|
995
|
+
return (...args) => {
|
|
996
|
+
const error = firstError(args);
|
|
997
|
+
if (error) {
|
|
998
|
+
return error;
|
|
999
|
+
}
|
|
1000
|
+
if (args.length > 0) {
|
|
1001
|
+
return valueError();
|
|
1002
|
+
}
|
|
1003
|
+
const next = random();
|
|
1004
|
+
if (!Number.isFinite(next)) {
|
|
1005
|
+
return valueError();
|
|
1006
|
+
}
|
|
1007
|
+
const bounded = Math.min(Math.max(next, 0), 1 - Number.EPSILON);
|
|
1008
|
+
return numberResult(bounded);
|
|
1009
|
+
};
|
|
1010
|
+
}
|
|
1011
|
+
export function createEdateBuiltin() {
|
|
1012
|
+
return (startDate, months) => {
|
|
1013
|
+
const error = firstError([startDate, months]);
|
|
1014
|
+
if (error) {
|
|
1015
|
+
return error;
|
|
1016
|
+
}
|
|
1017
|
+
const startSerial = coerceNumber(startDate);
|
|
1018
|
+
const monthOffset = truncArg(months);
|
|
1019
|
+
if (startSerial === undefined) {
|
|
1020
|
+
return valueError();
|
|
1021
|
+
}
|
|
1022
|
+
if (typeof monthOffset !== "number") {
|
|
1023
|
+
return monthOffset;
|
|
1024
|
+
}
|
|
1025
|
+
const serial = addMonthsToExcelDate(startSerial, monthOffset);
|
|
1026
|
+
return serial === undefined ? valueError() : numberResult(serial);
|
|
1027
|
+
};
|
|
1028
|
+
}
|
|
1029
|
+
export function createEomonthBuiltin() {
|
|
1030
|
+
return (startDate, months) => {
|
|
1031
|
+
const error = firstError([startDate, months]);
|
|
1032
|
+
if (error) {
|
|
1033
|
+
return error;
|
|
1034
|
+
}
|
|
1035
|
+
const startSerial = coerceNumber(startDate);
|
|
1036
|
+
const monthOffset = truncArg(months);
|
|
1037
|
+
if (startSerial === undefined) {
|
|
1038
|
+
return valueError();
|
|
1039
|
+
}
|
|
1040
|
+
if (typeof monthOffset !== "number") {
|
|
1041
|
+
return monthOffset;
|
|
1042
|
+
}
|
|
1043
|
+
const serial = endOfMonthExcelDate(startSerial, monthOffset);
|
|
1044
|
+
return serial === undefined ? valueError() : numberResult(serial);
|
|
1045
|
+
};
|
|
1046
|
+
}
|
|
1047
|
+
export function createDatedifBuiltin() {
|
|
1048
|
+
return (...args) => {
|
|
1049
|
+
const error = firstError(args);
|
|
1050
|
+
if (error) {
|
|
1051
|
+
return error;
|
|
1052
|
+
}
|
|
1053
|
+
if (args.length !== 3) {
|
|
1054
|
+
return valueError();
|
|
1055
|
+
}
|
|
1056
|
+
const startSerial = truncArg(args[0]);
|
|
1057
|
+
const endSerial = truncArg(args[1]);
|
|
1058
|
+
const unit = coerceText(args[2])?.trim().toUpperCase();
|
|
1059
|
+
if (typeof startSerial !== "number" || typeof endSerial !== "number" || !unit) {
|
|
1060
|
+
return valueError();
|
|
1061
|
+
}
|
|
1062
|
+
const value = datedifValue(startSerial, endSerial, unit);
|
|
1063
|
+
return value === undefined ? valueError() : numberResult(value);
|
|
1064
|
+
};
|
|
1065
|
+
}
|
|
1066
|
+
const datetimePlaceholderBuiltins = createBlockedBuiltinMap(datetimePlaceholderBuiltinNames);
|
|
1067
|
+
export const datetimeBuiltins = {
|
|
1068
|
+
DATE: createDateBuiltin(),
|
|
1069
|
+
DATEVALUE: createDateValueBuiltin(),
|
|
1070
|
+
YEAR: createDatePartBuiltin("year"),
|
|
1071
|
+
MONTH: createDatePartBuiltin("month"),
|
|
1072
|
+
DAY: createDatePartBuiltin("day"),
|
|
1073
|
+
TIME: createTimeBuiltin(),
|
|
1074
|
+
HOUR: createTimePartBuiltin("hour"),
|
|
1075
|
+
MINUTE: createTimePartBuiltin("minute"),
|
|
1076
|
+
SECOND: createTimePartBuiltin("second"),
|
|
1077
|
+
WEEKDAY: createWeekdayBuiltin(),
|
|
1078
|
+
DAYS: createDaysBuiltin(),
|
|
1079
|
+
WEEKNUM: createWeeknumBuiltin(),
|
|
1080
|
+
DAYS360: createDays360Builtin(),
|
|
1081
|
+
ISOWEEKNUM: createIsoWeeknumBuiltin(),
|
|
1082
|
+
TIMEVALUE: createTimeValueBuiltin(),
|
|
1083
|
+
YEARFRAC: createYearfracBuiltin(),
|
|
1084
|
+
WORKDAY: createWorkdayBuiltin(),
|
|
1085
|
+
"WORKDAY.INTL": createWorkdayIntlBuiltin(),
|
|
1086
|
+
NETWORKDAYS: createNetworkdaysBuiltin(),
|
|
1087
|
+
"NETWORKDAYS.INTL": createNetworkdaysIntlBuiltin(),
|
|
1088
|
+
TODAY: createTodayBuiltin(),
|
|
1089
|
+
NOW: createNowBuiltin(),
|
|
1090
|
+
RAND: createRandBuiltin(),
|
|
1091
|
+
EDATE: createEdateBuiltin(),
|
|
1092
|
+
EOMONTH: createEomonthBuiltin(),
|
|
1093
|
+
DATEDIF: createDatedifBuiltin(),
|
|
1094
|
+
...datetimePlaceholderBuiltins,
|
|
1095
|
+
};
|
|
1096
|
+
//# sourceMappingURL=datetime.js.map
|