@cj-tech-master/excelts 1.4.2 → 1.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser/excelts.iife.js +87 -25
- package/dist/browser/excelts.iife.js.map +1 -1
- package/dist/browser/excelts.iife.min.js +3 -3
- package/dist/cjs/utils/cell-format.js +13 -9
- package/dist/cjs/utils/sheet-utils.js +125 -15
- package/dist/esm/utils/cell-format.js +13 -9
- package/dist/esm/utils/sheet-utils.js +125 -15
- package/dist/types/utils/sheet-utils.d.ts +8 -2
- package/package.json +1 -1
|
@@ -4,30 +4,140 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { Workbook } from "../doc/workbook.js";
|
|
6
6
|
import { colCache } from "./col-cache.js";
|
|
7
|
-
import { dateToExcel } from "./utils.js";
|
|
8
7
|
import { format as cellFormat } from "./cell-format.js";
|
|
8
|
+
/**
|
|
9
|
+
* Convert a Date object back to Excel serial number without timezone issues.
|
|
10
|
+
* This reverses the excelToDate conversion exactly.
|
|
11
|
+
* excelToDate uses: new Date(Math.round((v - 25569) * 24 * 3600 * 1000))
|
|
12
|
+
* So we reverse it: (date.getTime() / (24 * 3600 * 1000)) + 25569
|
|
13
|
+
*/
|
|
14
|
+
function dateToExcelSerial(d) {
|
|
15
|
+
return d.getTime() / (24 * 3600 * 1000) + 25569;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Check if format is a pure time format (no date components like y, m for month, d)
|
|
19
|
+
* Time formats only contain: h, m (minutes in time context), s, AM/PM
|
|
20
|
+
* Excludes elapsed time formats like [h]:mm:ss which should keep full serial number
|
|
21
|
+
*/
|
|
22
|
+
function isTimeOnlyFormat(fmt) {
|
|
23
|
+
// Remove quoted strings first
|
|
24
|
+
const cleaned = fmt.replace(/"[^"]*"/g, "");
|
|
25
|
+
// Elapsed time formats [h], [m], [s] should NOT be treated as time-only
|
|
26
|
+
// They need the full serial number to calculate total hours/minutes/seconds
|
|
27
|
+
if (/\[[hms]\]/i.test(cleaned)) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
// Remove color codes and conditions (but we already checked for [h], [m], [s])
|
|
31
|
+
const withoutBrackets = cleaned.replace(/\[[^\]]*\]/g, "");
|
|
32
|
+
// Check if it has time components (h, s, or AM/PM)
|
|
33
|
+
const hasTimeComponents = /[hs]/i.test(withoutBrackets) || /AM\/PM|A\/P/i.test(withoutBrackets);
|
|
34
|
+
// Check if it has date components (y, d, or m not adjacent to h/s which would make it minutes)
|
|
35
|
+
// In Excel: "m" after "h" or before "s" is minutes, otherwise it's month
|
|
36
|
+
const hasDateComponents = /[yd]/i.test(withoutBrackets);
|
|
37
|
+
// If it has time but no date components, it's a time-only format
|
|
38
|
+
// Also check for standalone 'm' that's not minutes (not near h or s)
|
|
39
|
+
if (hasDateComponents) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
// Check for month 'm' - if 'm' exists but not in h:m or m:s context, it's a date format
|
|
43
|
+
if (/m/i.test(withoutBrackets) && !hasTimeComponents) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
return hasTimeComponents;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Check if format is a date format (contains y, d, or month-m)
|
|
50
|
+
* Used to determine if dateFormat override should be applied
|
|
51
|
+
*/
|
|
52
|
+
function isDateFormat(fmt) {
|
|
53
|
+
// Remove quoted strings first
|
|
54
|
+
const cleaned = fmt.replace(/"[^"]*"/g, "");
|
|
55
|
+
// Elapsed time formats [h], [m], [s] are NOT date formats
|
|
56
|
+
if (/\[[hms]\]/i.test(cleaned)) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
// Remove color codes and conditions
|
|
60
|
+
const withoutBrackets = cleaned.replace(/\[[^\]]*\]/g, "");
|
|
61
|
+
// Check for year or day components
|
|
62
|
+
if (/[yd]/i.test(withoutBrackets)) {
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
// Check for month 'm' - only if it's NOT in time context (not near h or s)
|
|
66
|
+
// In Excel: "m" after "h" or before "s" is minutes, otherwise it's month
|
|
67
|
+
if (/m/i.test(withoutBrackets)) {
|
|
68
|
+
const hasTimeComponents = /[hs]/i.test(withoutBrackets) || /AM\/PM|A\/P/i.test(withoutBrackets);
|
|
69
|
+
// If no time components, 'm' is month
|
|
70
|
+
if (!hasTimeComponents) {
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
// If has time components, need to check if 'm' is month or minutes
|
|
74
|
+
// Simplified: if format has both date-like and time-like patterns, consider it a date format
|
|
75
|
+
// e.g., "m/d/yy h:mm" - has 'm' as month and 'mm' as minutes
|
|
76
|
+
}
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Format a value (Date, number, boolean, string) according to the given format
|
|
81
|
+
* Handles timezone-independent conversion for Date objects
|
|
82
|
+
* @param value - The value to format
|
|
83
|
+
* @param fmt - The format string to use
|
|
84
|
+
* @param dateFormat - Optional override format for date values (not applied to time or elapsed time formats)
|
|
85
|
+
*/
|
|
86
|
+
function formatValue(value, fmt, dateFormat) {
|
|
87
|
+
// Date object - convert back to Excel serial number
|
|
88
|
+
if (value instanceof Date) {
|
|
89
|
+
let serial = dateToExcelSerial(value);
|
|
90
|
+
// For time-only formats, use only the fractional part (time portion)
|
|
91
|
+
if (isTimeOnlyFormat(fmt)) {
|
|
92
|
+
serial = serial % 1;
|
|
93
|
+
if (serial < 0) {
|
|
94
|
+
serial += 1;
|
|
95
|
+
}
|
|
96
|
+
return cellFormat(fmt, serial);
|
|
97
|
+
}
|
|
98
|
+
// Only apply dateFormat override to actual date formats
|
|
99
|
+
// (not elapsed time formats like [h]:mm:ss)
|
|
100
|
+
const actualFmt = dateFormat && isDateFormat(fmt) ? dateFormat : fmt;
|
|
101
|
+
return cellFormat(actualFmt, serial);
|
|
102
|
+
}
|
|
103
|
+
// Number/Boolean/String - let cellFormat handle it
|
|
104
|
+
return cellFormat(fmt, value);
|
|
105
|
+
}
|
|
9
106
|
/**
|
|
10
107
|
* Get formatted display text for a cell value
|
|
11
108
|
* Returns the value formatted according to the cell's numFmt
|
|
12
|
-
* This matches Excel's display exactly
|
|
109
|
+
* This matches Excel's display exactly (timezone-independent)
|
|
110
|
+
* @param cell - The cell to get display text for
|
|
111
|
+
* @param dateFormat - Optional override format for date values
|
|
13
112
|
*/
|
|
14
|
-
function getCellDisplayText(cell) {
|
|
113
|
+
function getCellDisplayText(cell, dateFormat) {
|
|
15
114
|
const value = cell.value;
|
|
16
115
|
const fmt = cell.numFmt || "General";
|
|
17
116
|
// Null/undefined
|
|
18
117
|
if (value == null) {
|
|
19
118
|
return "";
|
|
20
119
|
}
|
|
21
|
-
// Date
|
|
22
|
-
if (value instanceof Date
|
|
23
|
-
|
|
24
|
-
|
|
120
|
+
// Date/Number/Boolean/String - format directly
|
|
121
|
+
if (value instanceof Date ||
|
|
122
|
+
typeof value === "number" ||
|
|
123
|
+
typeof value === "boolean" ||
|
|
124
|
+
typeof value === "string") {
|
|
125
|
+
return formatValue(value, fmt, dateFormat);
|
|
25
126
|
}
|
|
26
|
-
//
|
|
27
|
-
if (typeof value === "
|
|
28
|
-
|
|
127
|
+
// Formula type - use the result value
|
|
128
|
+
if (typeof value === "object" && "formula" in value) {
|
|
129
|
+
const result = value.result;
|
|
130
|
+
if (result == null) {
|
|
131
|
+
return "";
|
|
132
|
+
}
|
|
133
|
+
if (result instanceof Date ||
|
|
134
|
+
typeof result === "number" ||
|
|
135
|
+
typeof result === "boolean" ||
|
|
136
|
+
typeof result === "string") {
|
|
137
|
+
return formatValue(result, fmt, dateFormat);
|
|
138
|
+
}
|
|
29
139
|
}
|
|
30
|
-
// Fallback to cell.text for other types (rich text, hyperlink, error,
|
|
140
|
+
// Fallback to cell.text for other types (rich text, hyperlink, error, etc.)
|
|
31
141
|
return cell.text;
|
|
32
142
|
}
|
|
33
143
|
// =============================================================================
|
|
@@ -263,7 +373,7 @@ export function sheetToJson(worksheet, opts) {
|
|
|
263
373
|
let isEmpty = true;
|
|
264
374
|
for (let col = startCol; col <= endCol; col++) {
|
|
265
375
|
const cell = worksheet.getCell(row, col);
|
|
266
|
-
const val = o.raw === false ? getCellDisplayText(cell).trim() : cell.value;
|
|
376
|
+
const val = o.raw === false ? getCellDisplayText(cell, o.dateFormat).trim() : cell.value;
|
|
267
377
|
if (val != null && val !== "") {
|
|
268
378
|
rowData[col - startCol] = val;
|
|
269
379
|
isEmpty = false;
|
|
@@ -291,7 +401,7 @@ export function sheetToJson(worksheet, opts) {
|
|
|
291
401
|
let isEmpty = true;
|
|
292
402
|
for (let col = startCol; col <= endCol; col++) {
|
|
293
403
|
const cell = worksheet.getCell(row, col);
|
|
294
|
-
const val = o.raw === false ? getCellDisplayText(cell).trim() : cell.value;
|
|
404
|
+
const val = o.raw === false ? getCellDisplayText(cell, o.dateFormat).trim() : cell.value;
|
|
295
405
|
const key = encodeCol(col - 1); // 0-indexed for encodeCol
|
|
296
406
|
if (val != null && val !== "") {
|
|
297
407
|
rowData[key] = val;
|
|
@@ -318,7 +428,7 @@ export function sheetToJson(worksheet, opts) {
|
|
|
318
428
|
const colIdx = col - startCol;
|
|
319
429
|
const key = headerOpt[colIdx] ?? `__EMPTY_${colIdx}`;
|
|
320
430
|
const cell = worksheet.getCell(row, col);
|
|
321
|
-
const val = o.raw === false ? getCellDisplayText(cell).trim() : cell.value;
|
|
431
|
+
const val = o.raw === false ? getCellDisplayText(cell, o.dateFormat).trim() : cell.value;
|
|
322
432
|
if (val != null && val !== "") {
|
|
323
433
|
rowData[key] = val;
|
|
324
434
|
isEmpty = false;
|
|
@@ -360,7 +470,7 @@ export function sheetToJson(worksheet, opts) {
|
|
|
360
470
|
let isEmpty = true;
|
|
361
471
|
for (let col = startCol; col <= endCol; col++) {
|
|
362
472
|
const cell = worksheet.getCell(row, col);
|
|
363
|
-
const val = o.raw === false ? getCellDisplayText(cell).trim() : cell.value;
|
|
473
|
+
const val = o.raw === false ? getCellDisplayText(cell, o.dateFormat).trim() : cell.value;
|
|
364
474
|
const key = headers[col - startCol];
|
|
365
475
|
if (val != null && val !== "") {
|
|
366
476
|
rowData[key] = val;
|
|
@@ -73,7 +73,7 @@ export interface JSON2SheetOpts {
|
|
|
73
73
|
/** Use specified field order (default Object.keys) */
|
|
74
74
|
header?: string[];
|
|
75
75
|
/** Use specified date format in string output */
|
|
76
|
-
|
|
76
|
+
dateFormat?: string;
|
|
77
77
|
/** Store dates as type d (default is n) */
|
|
78
78
|
cellDates?: boolean;
|
|
79
79
|
/** If true, do not include header row in output */
|
|
@@ -117,6 +117,12 @@ export interface Sheet2JSONOpts {
|
|
|
117
117
|
defval?: CellValue;
|
|
118
118
|
/** Include blank lines in the output */
|
|
119
119
|
blankrows?: boolean;
|
|
120
|
+
/**
|
|
121
|
+
* Override format for date values (not applied to time-only formats).
|
|
122
|
+
* When provided, Date values will be formatted using this format string.
|
|
123
|
+
* @example "dd/mm/yyyy" or "yyyy-mm-dd"
|
|
124
|
+
*/
|
|
125
|
+
dateFormat?: string;
|
|
120
126
|
}
|
|
121
127
|
/**
|
|
122
128
|
* Convert worksheet to JSON array (xlsx compatible)
|
|
@@ -164,7 +170,7 @@ export interface AOA2SheetOpts {
|
|
|
164
170
|
/** Use specified cell as starting point */
|
|
165
171
|
origin?: Origin;
|
|
166
172
|
/** Use specified date format in string output */
|
|
167
|
-
|
|
173
|
+
dateFormat?: string;
|
|
168
174
|
/** Store dates as type d (default is n) */
|
|
169
175
|
cellDates?: boolean;
|
|
170
176
|
}
|