diff --git a/.travis.yml b/.travis.yml
index 9144608..749b489 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,5 +1,6 @@
language: node_js
node_js:
+ - "8"
- "7"
- "6"
# note: travis has been acting up on old versions of node
@@ -9,6 +10,22 @@ node_js:
# - "0.10"
# - "0.9"
# - "0.8"
+matrix:
+ include:
+ - node_js: "6"
+ env: TZ="America/New_York"
+ - node_js: "8"
+ env: TZ="America/Los_Angeles"
+ - node_js: "6"
+ env: TZ="Europe/London"
+ - node_js: "8"
+ env: TZ="Europe/Berlin"
+ - node_js: "6"
+ env: TZ="Asia/Kolkata"
+ - node_js: "7"
+ env: TZ="Asia/Shanghai"
+ - node_js: "8"
+ env: TZ="Asia/Seoul"
before_install:
- "npm install -g npm@4.3.0"
- "npm install -g mocha@2.x voc"
diff --git a/README.md b/README.md
index 01af5e0..b0b82c2 100644
--- a/README.md
+++ b/README.md
@@ -82,6 +82,7 @@ enhancements, additional features by request, and dedicated support.
+ [Workbook File Properties](#workbook-file-properties)
* [Workbook-Level Attributes](#workbook-level-attributes)
+ [Defined Names](#defined-names)
+ + [Miscellaneous Workbook Properties](#miscellaneous-workbook-properties)
* [Document Features](#document-features)
+ [Formulae](#formulae)
+ [Column Properties](#column-properties)
@@ -719,6 +720,9 @@ will not be generated; the parser `sheetStubs` option must be set to `true`.
#### Dates
+
+ Excel Date Code details (click to show)
+
By default, Excel stores dates as numbers with a format code that specifies date
processing. For example, the date `19-Feb-17` is stored as the number `42785`
with a number format of `d-mmm-yy`. The `SSF` module understands number formats
@@ -730,6 +734,32 @@ string. The formatter converts the date back to a number.
The default behavior for all parsers is to generate number cells. Setting
`cellDates` to true will force the generators to store dates.
+
+
+
+ Time Zones and Dates (click to show)
+
+Excel has no native concept of universal time. All times are specified in the
+local time zone. Excel limitations prevent specifying true absolute dates.
+
+Following Excel, this library treats all dates as relative to local time zone.
+
+
+
+
+ Epochs: 1900 and 1904 (click to show)
+
+Excel supports two epochs (January 1 1900 and January 1 1904), see
+["1900 vs. 1904 Date System" article](http://support2.microsoft.com/kb/180162).
+The workbook's epoch can be determined by examining the workbook's
+`wb.Workbook.WBProps.date1904` property:
+
+```js
+!!(((wb.Workbook||{}).WBProps||{}).date1904)
+```
+
+
+
### Sheet Objects
Each key that does not start with `!` maps to a cell (using `A-1` notation)
@@ -850,12 +880,7 @@ first row of the chartsheet is the underlying header.
custom properties. Since the XLS standard properties deviate from the XLSX
standard, XLS parsing stores core properties in both places.
-`wb.WBProps` includes more workbook-level properties:
-
-- Excel supports two epochs (January 1 1900 and January 1 1904), see
- [1900 vs. 1904 Date System](http://support2.microsoft.com/kb/180162).
- The workbook's epoch can be determined by examining the workbook's
- `wb.WBProps.date1904` property.
+`wb.Workbook` stores [workbook-level attributes](#workbook-level-attributes).
#### Workbook File Properties
@@ -902,7 +927,7 @@ XLSX.write(wb, {Props:{Author:"SheetJS"}});
### Workbook-Level Attributes
-`wb.Workbook` stores workbook level attributes.
+`wb.Workbook` stores workbook-level attributes.
#### Defined Names
@@ -917,12 +942,21 @@ XLSX.write(wb, {Props:{Author:"SheetJS"}});
| `Name` | Case-sensitive name. Standard rules apply ** |
| `Ref` | A1-style Reference (e.g. `"Sheet1!$A$1:$D$20"`) |
| `Comment` | Comment (only applicable for XLS/XLSX/XLSB) |
+
Excel allows two sheet-scoped defined names to share the same name. However, a
sheet-scoped name cannot collide with a workbook-scope name. Workbook writers
may not enforce this constraint.
+#### Miscellaneous Workbook Properties
+
+`wb.Workbook.WBProps` holds other workbook properties:
+
+| Key | Description |
+|:-----------|:----------------------------------------------------|
+| `date1904` | epoch: 0/false for 1900 system, 1/true for 1904 |
+
### Document Features
Even for basic features like date storage, the official Excel formats store the
diff --git a/bits/11_ssfutils.js b/bits/11_ssfutils.js
index f2d0c41..a700c68 100644
--- a/bits/11_ssfutils.js
+++ b/bits/11_ssfutils.js
@@ -18,3 +18,30 @@ var XLMLFormatMap/*{[string]:string}*/ = ({
"On/Off": '"Yes";"Yes";"No";@'
}/*:any*/);
+/* dateNF parse TODO: move to SSF */
+var dateNFregex = /[dD]+|[mM]+|[yYeE]+|[Hh]+|[Ss]+/g;
+function dateNF_regex(dateNF/*:string|number*/)/*:RegExp*/ {
+ var fmt = typeof dateNF == "number" ? SSF._table[dateNF] : dateNF;
+ fmt = fmt.replace(dateNFregex, "(\\d+)");
+ return new RegExp("^" + fmt + "$");
+}
+function dateNF_fix(str/*:string*/, dateNF/*:string*/, match/*:Array*/)/*:string*/ {
+ var Y = -1, m = -1, d = -1, H = -1, M = -1, S = -1;
+ (dateNF.match(dateNFregex)||[]).forEach(function(n, i) {
+ var v = parseInt(match[i+1], 10);
+ switch(n.toLowerCase().charAt(0)) {
+ case 'y': Y = v; break; case 'd': d = v; break;
+ case 'h': H = v; break; case 's': S = v; break;
+ case 'm': if(H >= 0) M = v; else m = v; break;
+ }
+ });
+ if(S >= 0 && M == -1 && m >= 0) { M = m; m = -1; }
+ var datestr = (("" + (Y>=0?Y: new Date().getFullYear())).slice(-4) + "-" + ("00" + (m>=1?m:1)).slice(-2) + "-" + ("00" + (d>=1?d:1)).slice(-2));
+ if(datestr.length == 7) datestr = "0" + datestr;
+ if(datestr.length == 8) datestr = "20" + datestr;
+ var timestr = (("00" + (H>=0?H:0)).slice(-2) + ":" + ("00" + (M>=0?M:0)).slice(-2) + ":" + ("00" + (S>=0?S:0)).slice(-2));
+ if(H == -1 && M == -1 && S == -1) return datestr;
+ if(Y == -1 && m == -1 && d == -1) return timestr;
+ return datestr + "T" + timestr;
+}
+
diff --git a/bits/20_jsutils.js b/bits/20_jsutils.js
index 1b217a8..92c8156 100644
--- a/bits/20_jsutils.js
+++ b/bits/20_jsutils.js
@@ -29,22 +29,17 @@ function evert_arr(obj/*:any*/)/*:EvertArrType*/ {
return o;
}
+var basedate = new Date(1899, 11, 30, 0, 0, 0); // 2209161600000
+var dnthresh = basedate.getTime() + (new Date().getTimezoneOffset() - basedate.getTimezoneOffset()) * 60000;
function datenum(v/*:Date*/, date1904/*:?boolean*/)/*:number*/ {
var epoch = v.getTime();
if(date1904) epoch += 1462*24*60*60*1000;
- return (epoch + 2209161600000) / (24 * 60 * 60 * 1000);
+ return (epoch - dnthresh) / (24 * 60 * 60 * 1000);
}
function numdate(v/*:number*/)/*:Date*/ {
- var date = SSF.parse_date_code(v);
- var val = new Date();
- if(date == null) throw new Error("Bad Date Code: " + v);
- val.setUTCDate(date.d);
- val.setUTCMonth(date.m-1);
- val.setUTCFullYear(date.y);
- val.setUTCHours(date.H);
- val.setUTCMinutes(date.M);
- val.setUTCSeconds(date.S);
- return val;
+ var out = new Date();
+ out.setTime(v * 24 * 60 * 60 * 1000 + dnthresh);
+ return out;
}
/* ISO 8601 Duration */
@@ -77,9 +72,15 @@ function parse_isodur(s) {
var good_pd_date = new Date('2017-02-19T19:06:09.000Z');
if(isNaN(good_pd_date.getFullYear())) good_pd_date = new Date('2/19/17');
var good_pd = good_pd_date.getFullYear() == 2017;
-function parseDate(str/*:string|Date*/)/*:Date*/ {
+/* parses aa date as a local date */
+function parseDate(str/*:string|Date*/, fixdate/*:?number*/)/*:Date*/ {
var d = new Date(str);
- if(good_pd) return d;
+ if(good_pd) {
+ /*:: if(fixdate == null) fixdate = 0; */
+ if(fixdate > 0) d.setTime(d.getTime() + d.getTimezoneOffset() * 60 * 1000);
+ else if(fixdate < 0) d.setTime(d.getTime() - d.getTimezoneOffset() * 60 * 1000);
+ return d;
+ }
if(str instanceof Date) return str;
if(good_pd_date.getFullYear() == 1917 && !isNaN(d.getFullYear())) {
var s = d.getFullYear();
@@ -87,7 +88,7 @@ function parseDate(str/*:string|Date*/)/*:Date*/ {
d.setFullYear(d.getFullYear() + 100); return d;
}
var n = str.match(/\d+/g)||["2017","2","19","0","0","0"];
- return new Date(Date.UTC(+n[0], +n[1] - 1, +n[2], (+n[3]||0), (+n[4]||0), (+n[5]||0)));
+ return new Date(+n[0], +n[1] - 1, +n[2], (+n[3]||0), (+n[4]||0), (+n[5]||0));
}
function cc2str(arr/*:Array*/)/*:string*/ {
diff --git a/bits/22_xmlutils.js b/bits/22_xmlutils.js
index d5e8080..9d462f8 100644
--- a/bits/22_xmlutils.js
+++ b/bits/22_xmlutils.js
@@ -74,7 +74,7 @@ var xlml_unfixstr/*:StringConv*/ = (function() {
function parsexmlbool(value/*:any*/, tag/*:?string*/)/*:boolean*/ {
switch(value) {
- case '1': case 'true': case 'TRUE': return true;
+ case 1: case true: case '1': case 'true': case 'TRUE': return true;
/* case '0': case 'false': case 'FALSE':*/
default: return false;
}
diff --git a/bits/40_harb.js b/bits/40_harb.js
index 03c0b34..31e121f 100644
--- a/bits/40_harb.js
+++ b/bits/40_harb.js
@@ -523,6 +523,7 @@ var PRN = (function() {
var R = 0, C = 0, v = 0;
var start = 0, end = 0, sepcc = sep.charCodeAt(0), instr = false, cc=0;
str = str.replace(/\r\n/mg, "\n");
+ var _re/*:?RegExp*/ = o.dateNF != null ? dateNF_regex(o.dateNF) : null;
function finish_cell() {
var s = str.slice(start, end);
var cell = ({}/*:any*/);
@@ -530,10 +531,12 @@ var PRN = (function() {
else if(s == "TRUE") { cell.t = 'b'; cell.v = true; }
else if(s == "FALSE") { cell.t = 'b'; cell.v = false; }
else if(!isNaN(v = +s)) { cell.t = 'n'; cell.w = s; cell.v = v; }
- else if(!isNaN(fuzzydate(s).getDate())) {
+ else if(!isNaN(fuzzydate(s).getDate()) || _re && s.match(_re)) {
cell.z = o.dateNF || SSF._table[14];
- if(o.cellDates) { cell.t = 'd'; cell.v = parseDate(s); }
- else { cell.t = 'n'; cell.v = datenum(parseDate(s)); }
+ var k = 0;
+ if(_re && s.match(_re)){ s=dateNF_fix(s, o.dateNF, (s.match(_re)||[])); k=1; }
+ if(o.cellDates) { cell.t = 'd'; cell.v = parseDate(s, k); }
+ else { cell.t = 'n'; cell.v = datenum(parseDate(s, k)); }
cell.w = SSF.format(cell.z, cell.v instanceof Date ? datenum(cell.v):cell.v);
} else {
cell.t = 's';
diff --git a/bits/67_wsxml.js b/bits/67_wsxml.js
index 2da4576..d79c28c 100644
--- a/bits/67_wsxml.js
+++ b/bits/67_wsxml.js
@@ -194,7 +194,7 @@ function write_ws_xml_cell(cell, ref, ws, opts, idx, wb) {
case 'n': vv = ''+cell.v; break;
case 'e': vv = BErr[cell.v]; break;
case 'd':
- if(opts.cellDates) vv = parseDate(cell.v).toISOString();
+ if(opts.cellDates) vv = parseDate(cell.v, -1).toISOString();
else {
cell.t = 'n';
vv = ''+(cell.v = datenum(parseDate(cell.v)));
@@ -347,7 +347,8 @@ return function parse_ws_xml_data(sdata, s, opts, guess, themes, styles) {
break;
case 'b': p.v = parsexmlbool(p.v); break;
case 'd':
- if(!opts.cellDates) { p.v = datenum(parseDate(p.v)); p.t = 'n'; }
+ if(opts.cellDates) p.v = parseDate(p.v, 1);
+ else { p.v = datenum(parseDate(p.v, 1)); p.t = 'n'; }
break;
/* error string in .w, number in .v */
case 'e':
@@ -364,9 +365,7 @@ return function parse_ws_xml_data(sdata, s, opts, guess, themes, styles) {
}
}
safe_format(p, fmtid, fillid, opts, themes, styles);
- if(opts.cellDates && do_format && p.t == 'n' && SSF.is_date(SSF._table[fmtid])) {
- var _d = SSF.parse_date_code(p.v); if(_d) { p.t = 'd'; p.v = new Date(Date.UTC(_d.y, _d.m-1,_d.d,_d.H,_d.M,_d.S,_d.u)); }
- }
+ if(opts.cellDates && do_format && p.t == 'n' && SSF.is_date(SSF._table[fmtid])) { p.t = 'd'; p.v = numdate(p.v); }
if(dense) {
var _r = decode_cell(tag.r);
if(!s[_r.r]) s[_r.r] = [];
diff --git a/bits/68_wsbin.js b/bits/68_wsbin.js
index fbd2f4e..d95e3be 100644
--- a/bits/68_wsbin.js
+++ b/bits/68_wsbin.js
@@ -470,7 +470,7 @@ function parse_ws_bin(data, _opts, rels, wb, themes, styles)/*:Worksheet*/ {
if(refguess.e.r < row.r) refguess.e.r = row.r;
if(refguess.e.c < C) refguess.e.c = C;
if(opts.cellDates && cf && p.t == 'n' && SSF.is_date(SSF._table[cf.ifmt])) {
- var _d = SSF.parse_date_code(p.v); if(_d) { p.t = 'd'; p.v = new Date(Date.UTC(_d.y, _d.m-1,_d.d,_d.H,_d.M,_d.S,_d.u)); }
+ var _d = SSF.parse_date_code(p.v); if(_d) { p.t = 'd'; p.v = new Date(_d.y, _d.m-1,_d.d,_d.H,_d.M,_d.S,_d.u); }
}
break;
diff --git a/bits/71_wbcommon.js b/bits/71_wbcommon.js
index 627d5ad..ed815b8 100644
--- a/bits/71_wbcommon.js
+++ b/bits/71_wbcommon.js
@@ -1,37 +1,36 @@
/* 18.2.28 (CT_WorkbookProtection) Defaults */
var WBPropsDef = [
- ['allowRefreshQuery', '0'],
- ['autoCompressPictures', '1'],
- ['backupFile', '0'],
- ['checkCompatibility', '0'],
- ['codeName', ''],
- ['date1904', '0'],
- ['dateCompatibility', '1'],
- //['defaultThemeVersion', '0'],
- ['filterPrivacy', '0'],
- ['hidePivotFieldList', '0'],
- ['promptedSolutions', '0'],
- ['publishItems', '0'],
- ['refreshAllConnections', false],
- ['saveExternalLinkValues', '1'],
- ['showBorderUnselectedTables', '1'],
- ['showInkAnnotation', '1'],
- ['showObjects', 'all'],
- ['showPivotChartFilter', '0']
- //['updateLinks', 'userSet']
+ ['allowRefreshQuery', false, "bool"],
+ ['autoCompressPictures', true, "bool"],
+ ['backupFile', false, "bool"],
+ ['checkCompatibility', false, "bool"],
+ ['codeName', ''],
+ ['date1904', false, "bool"],
+ ['defaultThemeVersion', 0, "int"],
+ ['filterPrivacy', false, "bool"],
+ ['hidePivotFieldList', false, "bool"],
+ ['promptedSolutions', false, "bool"],
+ ['publishItems', false, "bool"],
+ ['refreshAllConnections', false, "bool"],
+ ['saveExternalLinkValues', true, "bool"],
+ ['showBorderUnselectedTables', true, "bool"],
+ ['showInkAnnotation', true, "bool"],
+ ['showObjects', 'all'],
+ ['showPivotChartFilter', false, "bool"],
+ ['updateLinks', 'userSet']
];
/* 18.2.30 (CT_BookView) Defaults */
var WBViewDef = [
- ['activeTab', '0'],
- ['autoFilterDateGrouping', '1'],
- ['firstSheet', '0'],
- ['minimized', '0'],
- ['showHorizontalScroll', '1'],
- ['showSheetTabs', '1'],
- ['showVerticalScroll', '1'],
- ['tabRatio', '600'],
- ['visibility', 'visible']
+ ['activeTab', 0, "int"],
+ ['autoFilterDateGrouping', true, "bool"],
+ ['firstSheet', 0, "int"],
+ ['minimized', false, "bool"],
+ ['showHorizontalScroll', true, "bool"],
+ ['showSheetTabs', true, "bool"],
+ ['showVerticalScroll', true, "bool"],
+ ['tabRatio', 600, "int"],
+ ['visibility', 'visible']
//window{Height,Width}, {x,y}Window
];
@@ -80,12 +79,20 @@ function push_defaults_array(target, defaults) {
for(var j = 0; j != target.length; ++j) { var w = target[j];
for(var i=0; i != defaults.length; ++i) { var z = defaults[i];
if(w[z[0]] == null) w[z[0]] = z[1];
+ else switch(z[2]) {
+ case "bool": if(typeof w[z[0]] == "string") w[z[0]] = parsexmlbool(w[z[0]], z[0]); break;
+ case "int": if(typeof w[z[0]] == "string") w[z[0]] = parseInt(w[z[0]], 10); break;
+ }
}
}
}
function push_defaults(target, defaults) {
for(var i = 0; i != defaults.length; ++i) { var z = defaults[i];
if(target[z[0]] == null) target[z[0]] = z[1];
+ else switch(z[2]) {
+ case "bool": if(typeof target[z[0]] == "string") target[z[0]] = parsexmlbool(target[z[0]], z[0]); break;
+ case "int": if(typeof target[z[0]] == "string") target[z[0]] = parseInt(target[z[0]], 10); break;
+ }
}
}
@@ -99,6 +106,13 @@ function parse_wb_defaults(wb) {
_ssfopts.date1904 = parsexmlbool(wb.WBProps.date1904, 'date1904');
}
+function safe1904(wb/*:Workbook*/)/*:string*/ {
+ /* TODO: store date1904 somewhere else */
+ if(!wb.Workbook) return "false";
+ if(!wb.Workbook.WBProps) return "false";
+ return parsexmlbool(wb.Workbook.WBProps.date1904) ? "true" : "false";
+}
+
var badchars = "][*?\/\\".split("");
function check_ws_name(n/*:string*/, safe/*:?boolean*/)/*:boolean*/ {
if(n.length > 31) { if(safe) return false; throw new Error("Sheet names cannot exceed 31 chars"); }
diff --git a/bits/72_wbxml.js b/bits/72_wbxml.js
index fac8cf5..3fcf219 100644
--- a/bits/72_wbxml.js
+++ b/bits/72_wbxml.js
@@ -26,8 +26,17 @@ function parse_wb_xml(data, opts)/*:WorkbookFile*/ {
case '': break;
/* 18.2.28 workbookPr CT_WorkbookPr ? */
- case '': delete y[0]; wb.WBProps = y; break;
+ case '':
+ WBPropsDef.forEach(function(w) {
+ if(y[w[0]] == null) return;
+ switch(w[2]) {
+ case "bool": wb.WBProps[w[0]] = parsexmlbool(y[w[0]], w[0]); break;
+ case "int": wb.WBProps[w[0]] = parseInt(y[w[0]], 10); break;
+ default: wb.WBProps[w[0]] = y[w[0]];
+ }
+ });
+ break;
case '': break;
/* 18.2.29 workbookProtection CT_WorkbookProtection ? */
@@ -148,14 +157,6 @@ var WB_XML_ROOT = writextag('workbook', null, {
'xmlns:r': XMLNS.r
});
-function safe1904(wb/*:Workbook*/)/*:string*/ {
- /* TODO: store date1904 somewhere else */
- if(!wb.Workbook) return "false";
- if(!wb.Workbook.WBProps) return "false";
- // $FlowIgnore
- return parsexmlbool(wb.Workbook.WBProps.date1904) ? "true" : "false";
-}
-
function write_wb_xml(wb/*:Workbook*/, opts/*:?WriteOpts*/)/*:string*/ {
var o = [XML_HEADER];
o[o.length] = WB_XML_ROOT;
diff --git a/bits/73_wbbin.js b/bits/73_wbbin.js
index ad426dd..2028d9e 100644
--- a/bits/73_wbbin.js
+++ b/bits/73_wbbin.js
@@ -18,10 +18,27 @@ function write_BrtBundleSh(data, o) {
/* [MS-XLSB] 2.4.807 BrtWbProp */
function parse_BrtWbProp(data, length) {
- data.read_shift(4);
- var dwThemeVersion = data.read_shift(4);
+ var o = {};
+ var flags = data.read_shift(4);
+ o.defaultThemeVersion = data.read_shift(4);
var strName = (length > 8) ? parse_XLWideString(data) : "";
- return [dwThemeVersion, strName];
+ if(strName.length > 0) o.codeName = strName;
+ o.autoCompressPictures = !!(flags & 0x10000);
+ o.backupFile = !!(flags & 0x40);
+ o.checkCompatibility = !!(flags & 0x1000);
+ o.date1904 = !!(flags & 0x01);
+ o.filterPrivacy = !!(flags & 0x08);
+ o.hidePivotFieldList = !!(flags & 0x400);
+ o.promptedSolutions = !!(flags & 0x10);
+ o.publishItems = !!(flags & 0x800);
+ o.refreshAllConnections = !!(flags & 0x40000);
+ o.saveExternalLinkValues = !!(flags & 0x80);
+ o.showBorderUnselectedTables = !!(flags & 0x04);
+ o.showInkAnnotation = !!(flags & 0x20);
+ o.showObjects = ["all", "placeholders", "none"][(flags >> 13) & 0x03];
+ o.showPivotChartFilter = !!(flags & 0x8000);
+ o.updateLinks = ["userSet", "never", "always"][(flags >> 8) & 0x03];
+ return o;
}
function write_BrtWbProp(data, o) {
if(!o) o = new_buf(72);
@@ -85,6 +102,9 @@ function parse_wb_bin(data, opts)/*:WorkbookFile*/ {
break;
case 0x040C: /* 'BrtNameExt' */ break;
+ case 0x0099: /* 'BrtWbProp' */
+ wb.WBProps = val; break;
+
/* case 'BrtModelTimeGroupingCalcCol' */
/* case 'BrtRevisionPtr' */
/* case 'BrtUid' */
@@ -116,7 +136,6 @@ function parse_wb_bin(data, opts)/*:WorkbookFile*/ {
case 0x0822: /* 'BrtTimelineCachePivotCacheID' */
case 0x018D: /* 'BrtUserBookView' */
case 0x009A: /* 'BrtWbFactoid' */
- case 0x0099: /* 'BrtWbProp' */
case 0x045D: /* 'BrtWbProp14' */
case 0x0229: /* 'BrtWebOpt' */
case 0x082B: /* 'BrtWorkBookPr15' */
diff --git a/bits/75_xlml.js b/bits/75_xlml.js
index 40fc5b0..c80af9a 100644
--- a/bits/75_xlml.js
+++ b/bits/75_xlml.js
@@ -74,7 +74,7 @@ function safe_format_xlml(cell/*:Cell*/, nf, o) {
var z = XLMLFormatMap[nf]||nf||"General";
if(o.cellNF) cell.z = z;
if(o.cellDates && cell.t == 'n' && SSF.is_date(z)) {
- var _d = SSF.parse_date_code(cell.v); if(_d) { cell.t = 'd'; cell.v = new Date(Date.UTC(_d.y, _d.m-1,_d.d,_d.H,_d.M,_d.S,_d.u)); }
+ var _d = SSF.parse_date_code(cell.v); if(_d) { cell.t = 'd'; cell.v = new Date(_d.y, _d.m-1,_d.d,_d.H,_d.M,_d.S,_d.u); }
}
} catch(e) { if(o.WTF) throw e; }
}
@@ -191,7 +191,7 @@ function parse_xlml_xml(d, opts)/*:Workbook*/ {
var cstys = [], csty, seencol = false;
var arrayf = [];
var rowinfo = [], rowobj = {};
- var Workbook/*:WBWBProps*/ = { Sheets:[] }, wsprops = {};
+ var Workbook/*:WBWBProps*/ = ({ Sheets:[], WBProps:{date1904:false} }/*:any*/), wsprops = {};
xlmlregex.lastIndex = 0;
str = str.replace(//mg,"");
while((Rn = xlmlregex.exec(str))) switch(Rn[3]) {
@@ -472,6 +472,10 @@ function parse_xlml_xml(d, opts)/*:Workbook*/ {
/* ExcelWorkbook */
case 'ExcelWorkbook': switch(Rn[3]) {
+ case 'Date1904':
+ /*:: if(!Workbook.WBProps) Workbook.WBProps = {}; */
+ Workbook.WBProps.date1904 = true;
+ break;
case 'WindowHeight': break;
case 'WindowWidth': break;
case 'WindowTopX': break;
@@ -490,7 +494,6 @@ function parse_xlml_xml(d, opts)/*:Workbook*/ {
case 'Dll': break;
case 'AcceptLabelsInFormulas': break;
case 'DoNotSaveLinkValues': break;
- case 'Date1904': break;
case 'Iteration': break;
case 'MaxIterations': break;
case 'MaxChange': break;
diff --git a/bits/76_xls.js b/bits/76_xls.js
index 1651b81..e229b12 100644
--- a/bits/76_xls.js
+++ b/bits/76_xls.js
@@ -71,7 +71,7 @@ function safe_format_xf(p/*:any*/, opts/*:ParseOpts*/, date1904/*:?boolean*/) {
}
else p.w = SSF.format(fmtid,p.v, {date1904:!!date1904});
if(opts.cellDates && fmtid && p.t == 'n' && SSF.is_date(SSF._table[fmtid])) {
- var _d = SSF.parse_date_code(p.v); if(_d) { p.t = 'd'; p.v = new Date(Date.UTC(_d.y, _d.m-1,_d.d,_d.H,_d.M,_d.S,_d.u)); }
+ var _d = SSF.parse_date_code(p.v); if(_d) { p.t = 'd'; p.v = new Date(_d.y, _d.m-1,_d.d,_d.H,_d.M,_d.S,_d.u); }
}
} catch(e) { if(opts.WTF) throw e; }
}
@@ -101,7 +101,7 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ {
var cell_valid = true;
var XFs = []; /* XF records */
var palette = [];
- var Workbook/*:WBWBProps*/ = ({ Sheets:[] }/*:any*/), wsprops = {};
+ var Workbook/*:WBWBProps*/ = ({ Sheets:[], WBProps:{date1904:false} }/*:any*/), wsprops = {};
var get_rgb = function getrgb(icv) {
if(icv < 8) return XLSIcv[icv];
if(icv < 64) return palette[icv-8] || XLSIcv[icv];
@@ -213,7 +213,9 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ {
/* nested switch statements to workaround V8 128 limit */
switch(Rn) {
/* Workbook Options */
- case 'Date1904': wb.opts.Date1904 = val; break;
+ case 'Date1904':
+ /*:: if(!Workbook.WBProps) Workbook.WBProps = {}; */
+ wb.opts.Date1904 = Workbook.WBProps.date1904 = val; break;
case 'WriteProtect': wb.opts.WriteProtect = true; break;
case 'FilePass':
if(!opts.enc) blob.l = 0;
diff --git a/docbits/52_datatype.md b/docbits/52_datatype.md
index 8a3c0fa..45c69d8 100644
--- a/docbits/52_datatype.md
+++ b/docbits/52_datatype.md
@@ -44,6 +44,9 @@ will not be generated; the parser `sheetStubs` option must be set to `true`.
#### Dates
+
+ Excel Date Code details (click to show)
+
By default, Excel stores dates as numbers with a format code that specifies date
processing. For example, the date `19-Feb-17` is stored as the number `42785`
with a number format of `d-mmm-yy`. The `SSF` module understands number formats
@@ -55,3 +58,29 @@ string. The formatter converts the date back to a number.
The default behavior for all parsers is to generate number cells. Setting
`cellDates` to true will force the generators to store dates.
+
+
+
+ Time Zones and Dates (click to show)
+
+Excel has no native concept of universal time. All times are specified in the
+local time zone. Excel limitations prevent specifying true absolute dates.
+
+Following Excel, this library treats all dates as relative to local time zone.
+
+
+
+
+ Epochs: 1900 and 1904 (click to show)
+
+Excel supports two epochs (January 1 1900 and January 1 1904), see
+["1900 vs. 1904 Date System" article](http://support2.microsoft.com/kb/180162).
+The workbook's epoch can be determined by examining the workbook's
+`wb.Workbook.WBProps.date1904` property:
+
+```js
+!!(((wb.Workbook||{}).WBProps||{}).date1904)
+```
+
+
+
diff --git a/docbits/55_wbobject.md b/docbits/55_wbobject.md
index ec73af2..d143c4a 100644
--- a/docbits/55_wbobject.md
+++ b/docbits/55_wbobject.md
@@ -8,10 +8,5 @@
custom properties. Since the XLS standard properties deviate from the XLSX
standard, XLS parsing stores core properties in both places.
-`wb.WBProps` includes more workbook-level properties:
-
-- Excel supports two epochs (January 1 1900 and January 1 1904), see
- [1900 vs. 1904 Date System](http://support2.microsoft.com/kb/180162).
- The workbook's epoch can be determined by examining the workbook's
- `wb.WBProps.date1904` property.
+`wb.Workbook` stores [workbook-level attributes](#workbook-level-attributes).
diff --git a/docbits/57_wbbook.md b/docbits/57_wbbook.md
index a88cfd7..0eaaabe 100644
--- a/docbits/57_wbbook.md
+++ b/docbits/57_wbbook.md
@@ -1,6 +1,6 @@
### Workbook-Level Attributes
-`wb.Workbook` stores workbook level attributes.
+`wb.Workbook` stores workbook-level attributes.
#### Defined Names
@@ -15,9 +15,18 @@
| `Name` | Case-sensitive name. Standard rules apply ** |
| `Ref` | A1-style Reference (e.g. `"Sheet1!$A$1:$D$20"`) |
| `Comment` | Comment (only applicable for XLS/XLSX/XLSB) |
+
Excel allows two sheet-scoped defined names to share the same name. However, a
sheet-scoped name cannot collide with a workbook-scope name. Workbook writers
may not enforce this constraint.
+#### Miscellaneous Workbook Properties
+
+`wb.Workbook.WBProps` holds other workbook properties:
+
+| Key | Description |
+|:-----------|:----------------------------------------------------|
+| `date1904` | epoch: 0/false for 1900 system, 1/true for 1904 |
+
diff --git a/misc/docs/SUMMARY.md b/misc/docs/SUMMARY.md
index ebefceb..8a467ec 100644
--- a/misc/docs/SUMMARY.md
+++ b/misc/docs/SUMMARY.md
@@ -30,6 +30,7 @@
+ [Workbook File Properties](README.md#workbook-file-properties)
* [Workbook-Level Attributes](README.md#workbook-level-attributes)
+ [Defined Names](README.md#defined-names)
+ + [Miscellaneous Workbook Properties](README.md#miscellaneous-workbook-properties)
* [Document Features](README.md#document-features)
+ [Formulae](README.md#formulae)
+ [Column Properties](README.md#column-properties)
diff --git a/misc/flow.js b/misc/flow.js
index 8c73aed..201a4e1 100644
--- a/misc/flow.js
+++ b/misc/flow.js
@@ -26,12 +26,17 @@ type Workbook = {
type WBWBProps = {
Sheets: Array;
Names?: Array;
+ WBProps?: WBProps;
+};
+
+type WBProps = {
+ date1904?: boolean;
};
type WBWSProp = {
Hidden?: number;
name?: string;
-}
+};
interface CellAddress {
r:number;
diff --git a/multiformat.lst b/multiformat.lst
index 02ec001..2dcec1c 100644
--- a/multiformat.lst
+++ b/multiformat.lst
@@ -6,15 +6,12 @@ AutoFilter .xls .xlsb .xlsx .xml
# note: XLML only supports sheets, ods does not support dialog
BlankSheetTypes .xls .xlsb .xlsm
NumberFormatCondition .xls .xlsb .xlsm .xml
-#RkNumber .xls .xlsb .xlsx .xls.xml .xlsb.xml .xlsx.xml
RkNumber .xls .xlsb .xlsx .xls.xml
#calendar_stress_test .xls .xlsb .xlsx .xml
cell_style_simple .xls .xlsb .xlsx .xml
# no-csv (newline character \r vs \n)
-#comments_stress_test .xls .xlsb .xlsx .xls.xml .xlsb.xml .xlsx.xml
comments_stress_test .xls .xlsb .xlsx .xls.xml
# yes-csv
-#custom_properties .xls .xlsb .xlsx .xls.xml .xlsb.xml .xlsx.xml
custom_properties .xls .xlsb .xlsx .xls.xml
defined_names_simple .xls .xlsb .xlsx .xml
# no-csv (randbetween) note: ODS does not support many XLSX functions
@@ -23,27 +20,22 @@ formula_stress_test .xls .xlsb .xlsx .xls.xml
formulae_test_simple .xls .xlsb .xlsx .xml
hyperlink_stress_test_2011 .xls .xlsb .xlsx .xml
#large_strings .xls .xlsb .xlsx .xls.xml .xlsb.xml .xlsx.xml
-#merge_cells .xls .xlsb .xlsx .xls.xml .xlsb.xml .xlsx.xml
merge_cells .xls .xlsb .xlsx .xls.xml
# no-formula (filename-references in XLSX encoding as [0])
-#named_ranges_2011 .xls .xlsb .xlsx .xls.xml .xlsb.xml .xlsx.xml
named_ranges_2011 .xls .xlsb .xlsx .xls.xml
# yes-formula
# no-csv (macro serialization in xml)
# no-formula (rounding issue in IE8)
-#number_format .xls .xlsb .xlsm .xls.xml .xlsb.xml .xlsm.xml
number_format .xls .xlsb .xlsm .xls.xml
# yes-formula
# yes-csv
number_format_entities .xls .xlsb .xlsx .xml
pivot_table_named_range .xls .xlsb .xlsx .xml
pivot_table_test .xls .xlsb .xlsm
-#rich_text_stress .xls .xlsb .xlsx .xls.xml .xlsb.xml .xlsx.xml
rich_text_stress .xls .xlsb .xlsx .xls.xml
smart_tags_2007 .xls .xlsb .xlsx .xml
sushi .xls .xlsb .xlsx .xml
text_and_numbers .xls .xlsb .xlsx .xml
#time_stress_test_1 .xls .xlsb .xlsx .xml
-#xlsx-stream-d-date-cell .xls .xlsb .xlsx .xls.xml .xlsb.xml .xlsx.xml
# no-formula (rounding issue in IE8)
xlsx-stream-d-date-cell .xls .xlsb .xlsx .xls.xml
diff --git a/test.js b/test.js
index 2754e8a..813dd8b 100644
--- a/test.js
+++ b/test.js
@@ -1502,10 +1502,12 @@ describe('invalid files', function() {
});
});
+var basedate = new Date(1899, 11, 30, 0, 0, 0); // 2209161600000
+var dnthresh = basedate.getTime() + (new Date().getTimezoneOffset() - basedate.getTimezoneOffset()) * 60000;
function datenum(v/*:Date*/, date1904/*:?boolean*/)/*:number*/ {
var epoch = v.getTime();
if(date1904) epoch += 1462*24*60*60*1000;
- return (epoch + 2209161600000) / (24 * 60 * 60 * 1000);
+ return (epoch - dnthresh) / (24 * 60 * 60 * 1000);
}
describe('json output', function() {
@@ -1685,7 +1687,17 @@ describe('csv', function() {
var cell = get_cell(X.read(b, opts).Sheets.Sheet1, "C3");
assert.equal(cell.w, '14-02-19');
});
-
+ it('should interpret dateNF', function() {
+ var bb = "1,2,3,\nTRUE,FALSE,,sheetjs\nfoo,bar,2/3/14,0.3\n,,,\nbaz,,qux,\n";
+ var opts = {type:"binary", cellDates:true, dateNF:'m/d/yy'};
+ var cell = get_cell(X.read(bb, opts).Sheets.Sheet1, "C3");
+ assert.equal(cell.v.getMonth(), 1);
+ assert.equal(cell.w, "2/3/14");
+ opts = {type:"binary", cellDates:true, dateNF:'d/m/yy'};
+ cell = get_cell(X.read(bb, opts).Sheets.Sheet1, "C3");
+ assert.equal(cell.v.getMonth(), 2);
+ assert.equal(cell.w, "2/3/14");
+ });
});
describe('output', function(){
var data, ws;
diff --git a/types/index.d.ts b/types/index.d.ts
index 090a309..6ac060e 100644
--- a/types/index.d.ts
+++ b/types/index.d.ts
@@ -237,6 +237,15 @@ export interface WBProps {
/** Defined Names */
Names?: DefinedName[];
+
+ /** Other Workbook Properties */
+ WBProps?: WorkbookProperties;
+}
+
+/** Other Workbook Properties */
+export interface WorkbookProperties {
+ /** Worksheet Epoch (1904 if true, 1900 if false) */
+ date1904?: boolean;
}
export interface ColInfo {
diff --git a/xlsx.flow.js b/xlsx.flow.js
index 32a5423..85c56bd 100644
--- a/xlsx.flow.js
+++ b/xlsx.flow.js
@@ -993,6 +993,33 @@ var XLMLFormatMap/*{[string]:string}*/ = ({
"On/Off": '"Yes";"Yes";"No";@'
}/*:any*/);
+/* dateNF parse TODO: move to SSF */
+var dateNFregex = /[dD]+|[mM]+|[yYeE]+|[Hh]+|[Ss]+/g;
+function dateNF_regex(dateNF/*:string|number*/)/*:RegExp*/ {
+ var fmt = typeof dateNF == "number" ? SSF._table[dateNF] : dateNF;
+ fmt = fmt.replace(dateNFregex, "(\\d+)");
+ return new RegExp("^" + fmt + "$");
+}
+function dateNF_fix(str/*:string*/, dateNF/*:string*/, match/*:Array*/)/*:string*/ {
+ var Y = -1, m = -1, d = -1, H = -1, M = -1, S = -1;
+ (dateNF.match(dateNFregex)||[]).forEach(function(n, i) {
+ var v = parseInt(match[i+1], 10);
+ switch(n.toLowerCase().charAt(0)) {
+ case 'y': Y = v; break; case 'd': d = v; break;
+ case 'h': H = v; break; case 's': S = v; break;
+ case 'm': if(H >= 0) M = v; else m = v; break;
+ }
+ });
+ if(S >= 0 && M == -1 && m >= 0) { M = m; m = -1; }
+ var datestr = (("" + (Y>=0?Y: new Date().getFullYear())).slice(-4) + "-" + ("00" + (m>=1?m:1)).slice(-2) + "-" + ("00" + (d>=1?d:1)).slice(-2));
+ if(datestr.length == 7) datestr = "0" + datestr;
+ if(datestr.length == 8) datestr = "20" + datestr;
+ var timestr = (("00" + (H>=0?H:0)).slice(-2) + ":" + ("00" + (M>=0?M:0)).slice(-2) + ":" + ("00" + (S>=0?S:0)).slice(-2));
+ if(H == -1 && M == -1 && S == -1) return datestr;
+ if(Y == -1 && m == -1 && d == -1) return timestr;
+ return datestr + "T" + timestr;
+}
+
var DO_NOT_EXPORT_CFB = true;
/*::
declare var Base64:any;
@@ -1435,22 +1462,17 @@ function evert_arr(obj/*:any*/)/*:EvertArrType*/ {
return o;
}
+var basedate = new Date(1899, 11, 30, 0, 0, 0); // 2209161600000
+var dnthresh = basedate.getTime() + (new Date().getTimezoneOffset() - basedate.getTimezoneOffset()) * 60000;
function datenum(v/*:Date*/, date1904/*:?boolean*/)/*:number*/ {
var epoch = v.getTime();
if(date1904) epoch += 1462*24*60*60*1000;
- return (epoch + 2209161600000) / (24 * 60 * 60 * 1000);
+ return (epoch - dnthresh) / (24 * 60 * 60 * 1000);
}
function numdate(v/*:number*/)/*:Date*/ {
- var date = SSF.parse_date_code(v);
- var val = new Date();
- if(date == null) throw new Error("Bad Date Code: " + v);
- val.setUTCDate(date.d);
- val.setUTCMonth(date.m-1);
- val.setUTCFullYear(date.y);
- val.setUTCHours(date.H);
- val.setUTCMinutes(date.M);
- val.setUTCSeconds(date.S);
- return val;
+ var out = new Date();
+ out.setTime(v * 24 * 60 * 60 * 1000 + dnthresh);
+ return out;
}
/* ISO 8601 Duration */
@@ -1483,9 +1505,15 @@ function parse_isodur(s) {
var good_pd_date = new Date('2017-02-19T19:06:09.000Z');
if(isNaN(good_pd_date.getFullYear())) good_pd_date = new Date('2/19/17');
var good_pd = good_pd_date.getFullYear() == 2017;
-function parseDate(str/*:string|Date*/)/*:Date*/ {
+/* parses aa date as a local date */
+function parseDate(str/*:string|Date*/, fixdate/*:?number*/)/*:Date*/ {
var d = new Date(str);
- if(good_pd) return d;
+ if(good_pd) {
+ /*:: if(fixdate == null) fixdate = 0; */
+ if(fixdate > 0) d.setTime(d.getTime() + d.getTimezoneOffset() * 60 * 1000);
+ else if(fixdate < 0) d.setTime(d.getTime() - d.getTimezoneOffset() * 60 * 1000);
+ return d;
+ }
if(str instanceof Date) return str;
if(good_pd_date.getFullYear() == 1917 && !isNaN(d.getFullYear())) {
var s = d.getFullYear();
@@ -1493,7 +1521,7 @@ function parseDate(str/*:string|Date*/)/*:Date*/ {
d.setFullYear(d.getFullYear() + 100); return d;
}
var n = str.match(/\d+/g)||["2017","2","19","0","0","0"];
- return new Date(Date.UTC(+n[0], +n[1] - 1, +n[2], (+n[3]||0), (+n[4]||0), (+n[5]||0)));
+ return new Date(+n[0], +n[1] - 1, +n[2], (+n[3]||0), (+n[4]||0), (+n[5]||0));
}
function cc2str(arr/*:Array*/)/*:string*/ {
@@ -1681,7 +1709,7 @@ var xlml_unfixstr/*:StringConv*/ = (function() {
function parsexmlbool(value/*:any*/, tag/*:?string*/)/*:boolean*/ {
switch(value) {
- case '1': case 'true': case 'TRUE': return true;
+ case 1: case true: case '1': case 'true': case 'TRUE': return true;
/* case '0': case 'false': case 'FALSE':*/
default: return false;
}
@@ -5666,6 +5694,7 @@ var PRN = (function() {
var R = 0, C = 0, v = 0;
var start = 0, end = 0, sepcc = sep.charCodeAt(0), instr = false, cc=0;
str = str.replace(/\r\n/mg, "\n");
+ var _re/*:?RegExp*/ = o.dateNF != null ? dateNF_regex(o.dateNF) : null;
function finish_cell() {
var s = str.slice(start, end);
var cell = ({}/*:any*/);
@@ -5673,10 +5702,12 @@ var PRN = (function() {
else if(s == "TRUE") { cell.t = 'b'; cell.v = true; }
else if(s == "FALSE") { cell.t = 'b'; cell.v = false; }
else if(!isNaN(v = +s)) { cell.t = 'n'; cell.w = s; cell.v = v; }
- else if(!isNaN(fuzzydate(s).getDate())) {
+ else if(!isNaN(fuzzydate(s).getDate()) || _re && s.match(_re)) {
cell.z = o.dateNF || SSF._table[14];
- if(o.cellDates) { cell.t = 'd'; cell.v = parseDate(s); }
- else { cell.t = 'n'; cell.v = datenum(parseDate(s)); }
+ var k = 0;
+ if(_re && s.match(_re)){ s=dateNF_fix(s, o.dateNF, (s.match(_re)||[])); k=1; }
+ if(o.cellDates) { cell.t = 'd'; cell.v = parseDate(s, k); }
+ else { cell.t = 'n'; cell.v = datenum(parseDate(s, k)); }
cell.w = SSF.format(cell.z, cell.v instanceof Date ? datenum(cell.v):cell.v);
} else {
cell.t = 's';
@@ -10849,7 +10880,7 @@ function write_ws_xml_cell(cell, ref, ws, opts, idx, wb) {
case 'n': vv = ''+cell.v; break;
case 'e': vv = BErr[cell.v]; break;
case 'd':
- if(opts.cellDates) vv = parseDate(cell.v).toISOString();
+ if(opts.cellDates) vv = parseDate(cell.v, -1).toISOString();
else {
cell.t = 'n';
vv = ''+(cell.v = datenum(parseDate(cell.v)));
@@ -11002,7 +11033,8 @@ return function parse_ws_xml_data(sdata, s, opts, guess, themes, styles) {
break;
case 'b': p.v = parsexmlbool(p.v); break;
case 'd':
- if(!opts.cellDates) { p.v = datenum(parseDate(p.v)); p.t = 'n'; }
+ if(opts.cellDates) p.v = parseDate(p.v, 1);
+ else { p.v = datenum(parseDate(p.v, 1)); p.t = 'n'; }
break;
/* error string in .w, number in .v */
case 'e':
@@ -11019,9 +11051,7 @@ return function parse_ws_xml_data(sdata, s, opts, guess, themes, styles) {
}
}
safe_format(p, fmtid, fillid, opts, themes, styles);
- if(opts.cellDates && do_format && p.t == 'n' && SSF.is_date(SSF._table[fmtid])) {
- var _d = SSF.parse_date_code(p.v); if(_d) { p.t = 'd'; p.v = new Date(Date.UTC(_d.y, _d.m-1,_d.d,_d.H,_d.M,_d.S,_d.u)); }
- }
+ if(opts.cellDates && do_format && p.t == 'n' && SSF.is_date(SSF._table[fmtid])) { p.t = 'd'; p.v = numdate(p.v); }
if(dense) {
var _r = decode_cell(tag.r);
if(!s[_r.r]) s[_r.r] = [];
@@ -11638,7 +11668,7 @@ function parse_ws_bin(data, _opts, rels, wb, themes, styles)/*:Worksheet*/ {
if(refguess.e.r < row.r) refguess.e.r = row.r;
if(refguess.e.c < C) refguess.e.c = C;
if(opts.cellDates && cf && p.t == 'n' && SSF.is_date(SSF._table[cf.ifmt])) {
- var _d = SSF.parse_date_code(p.v); if(_d) { p.t = 'd'; p.v = new Date(Date.UTC(_d.y, _d.m-1,_d.d,_d.H,_d.M,_d.S,_d.u)); }
+ var _d = SSF.parse_date_code(p.v); if(_d) { p.t = 'd'; p.v = new Date(_d.y, _d.m-1,_d.d,_d.H,_d.M,_d.S,_d.u); }
}
break;
@@ -12120,38 +12150,37 @@ function write_cs_bin(idx/*:number*/, opts, wb/*:Workbook*/, rels) {
}
/* 18.2.28 (CT_WorkbookProtection) Defaults */
var WBPropsDef = [
- ['allowRefreshQuery', '0'],
- ['autoCompressPictures', '1'],
- ['backupFile', '0'],
- ['checkCompatibility', '0'],
- ['codeName', ''],
- ['date1904', '0'],
- ['dateCompatibility', '1'],
- //['defaultThemeVersion', '0'],
- ['filterPrivacy', '0'],
- ['hidePivotFieldList', '0'],
- ['promptedSolutions', '0'],
- ['publishItems', '0'],
- ['refreshAllConnections', false],
- ['saveExternalLinkValues', '1'],
- ['showBorderUnselectedTables', '1'],
- ['showInkAnnotation', '1'],
- ['showObjects', 'all'],
- ['showPivotChartFilter', '0']
- //['updateLinks', 'userSet']
+ ['allowRefreshQuery', false, "bool"],
+ ['autoCompressPictures', true, "bool"],
+ ['backupFile', false, "bool"],
+ ['checkCompatibility', false, "bool"],
+ ['codeName', ''],
+ ['date1904', false, "bool"],
+ ['defaultThemeVersion', 0, "int"],
+ ['filterPrivacy', false, "bool"],
+ ['hidePivotFieldList', false, "bool"],
+ ['promptedSolutions', false, "bool"],
+ ['publishItems', false, "bool"],
+ ['refreshAllConnections', false, "bool"],
+ ['saveExternalLinkValues', true, "bool"],
+ ['showBorderUnselectedTables', true, "bool"],
+ ['showInkAnnotation', true, "bool"],
+ ['showObjects', 'all'],
+ ['showPivotChartFilter', false, "bool"],
+ ['updateLinks', 'userSet']
];
/* 18.2.30 (CT_BookView) Defaults */
var WBViewDef = [
- ['activeTab', '0'],
- ['autoFilterDateGrouping', '1'],
- ['firstSheet', '0'],
- ['minimized', '0'],
- ['showHorizontalScroll', '1'],
- ['showSheetTabs', '1'],
- ['showVerticalScroll', '1'],
- ['tabRatio', '600'],
- ['visibility', 'visible']
+ ['activeTab', 0, "int"],
+ ['autoFilterDateGrouping', true, "bool"],
+ ['firstSheet', 0, "int"],
+ ['minimized', false, "bool"],
+ ['showHorizontalScroll', true, "bool"],
+ ['showSheetTabs', true, "bool"],
+ ['showVerticalScroll', true, "bool"],
+ ['tabRatio', 600, "int"],
+ ['visibility', 'visible']
//window{Height,Width}, {x,y}Window
];
@@ -12200,12 +12229,20 @@ function push_defaults_array(target, defaults) {
for(var j = 0; j != target.length; ++j) { var w = target[j];
for(var i=0; i != defaults.length; ++i) { var z = defaults[i];
if(w[z[0]] == null) w[z[0]] = z[1];
+ else switch(z[2]) {
+ case "bool": if(typeof w[z[0]] == "string") w[z[0]] = parsexmlbool(w[z[0]], z[0]); break;
+ case "int": if(typeof w[z[0]] == "string") w[z[0]] = parseInt(w[z[0]], 10); break;
+ }
}
}
}
function push_defaults(target, defaults) {
for(var i = 0; i != defaults.length; ++i) { var z = defaults[i];
if(target[z[0]] == null) target[z[0]] = z[1];
+ else switch(z[2]) {
+ case "bool": if(typeof target[z[0]] == "string") target[z[0]] = parsexmlbool(target[z[0]], z[0]); break;
+ case "int": if(typeof target[z[0]] == "string") target[z[0]] = parseInt(target[z[0]], 10); break;
+ }
}
}
@@ -12219,6 +12256,13 @@ function parse_wb_defaults(wb) {
_ssfopts.date1904 = parsexmlbool(wb.WBProps.date1904, 'date1904');
}
+function safe1904(wb/*:Workbook*/)/*:string*/ {
+ /* TODO: store date1904 somewhere else */
+ if(!wb.Workbook) return "false";
+ if(!wb.Workbook.WBProps) return "false";
+ return parsexmlbool(wb.Workbook.WBProps.date1904) ? "true" : "false";
+}
+
var badchars = "][*?\/\\".split("");
function check_ws_name(n/*:string*/, safe/*:?boolean*/)/*:boolean*/ {
if(n.length > 31) { if(safe) return false; throw new Error("Sheet names cannot exceed 31 chars"); }
@@ -12269,8 +12313,17 @@ function parse_wb_xml(data, opts)/*:WorkbookFile*/ {
case '': break;
/* 18.2.28 workbookPr CT_WorkbookPr ? */
- case '': delete y[0]; wb.WBProps = y; break;
+ case '':
+ WBPropsDef.forEach(function(w) {
+ if(y[w[0]] == null) return;
+ switch(w[2]) {
+ case "bool": wb.WBProps[w[0]] = parsexmlbool(y[w[0]], w[0]); break;
+ case "int": wb.WBProps[w[0]] = parseInt(y[w[0]], 10); break;
+ default: wb.WBProps[w[0]] = y[w[0]];
+ }
+ });
+ break;
case '': break;
/* 18.2.29 workbookProtection CT_WorkbookProtection ? */
@@ -12391,14 +12444,6 @@ var WB_XML_ROOT = writextag('workbook', null, {
'xmlns:r': XMLNS.r
});
-function safe1904(wb/*:Workbook*/)/*:string*/ {
- /* TODO: store date1904 somewhere else */
- if(!wb.Workbook) return "false";
- if(!wb.Workbook.WBProps) return "false";
- // $FlowIgnore
- return parsexmlbool(wb.Workbook.WBProps.date1904) ? "true" : "false";
-}
-
function write_wb_xml(wb/*:Workbook*/, opts/*:?WriteOpts*/)/*:string*/ {
var o = [XML_HEADER];
o[o.length] = WB_XML_ROOT;
@@ -12476,10 +12521,27 @@ function write_BrtBundleSh(data, o) {
/* [MS-XLSB] 2.4.807 BrtWbProp */
function parse_BrtWbProp(data, length) {
- data.read_shift(4);
- var dwThemeVersion = data.read_shift(4);
+ var o = {};
+ var flags = data.read_shift(4);
+ o.defaultThemeVersion = data.read_shift(4);
var strName = (length > 8) ? parse_XLWideString(data) : "";
- return [dwThemeVersion, strName];
+ if(strName.length > 0) o.codeName = strName;
+ o.autoCompressPictures = !!(flags & 0x10000);
+ o.backupFile = !!(flags & 0x40);
+ o.checkCompatibility = !!(flags & 0x1000);
+ o.date1904 = !!(flags & 0x01);
+ o.filterPrivacy = !!(flags & 0x08);
+ o.hidePivotFieldList = !!(flags & 0x400);
+ o.promptedSolutions = !!(flags & 0x10);
+ o.publishItems = !!(flags & 0x800);
+ o.refreshAllConnections = !!(flags & 0x40000);
+ o.saveExternalLinkValues = !!(flags & 0x80);
+ o.showBorderUnselectedTables = !!(flags & 0x04);
+ o.showInkAnnotation = !!(flags & 0x20);
+ o.showObjects = ["all", "placeholders", "none"][(flags >> 13) & 0x03];
+ o.showPivotChartFilter = !!(flags & 0x8000);
+ o.updateLinks = ["userSet", "never", "always"][(flags >> 8) & 0x03];
+ return o;
}
function write_BrtWbProp(data, o) {
if(!o) o = new_buf(72);
@@ -12543,6 +12605,9 @@ function parse_wb_bin(data, opts)/*:WorkbookFile*/ {
break;
case 0x040C: /* 'BrtNameExt' */ break;
+ case 0x0099: /* 'BrtWbProp' */
+ wb.WBProps = val; break;
+
/* case 'BrtModelTimeGroupingCalcCol' */
/* case 'BrtRevisionPtr' */
/* case 'BrtUid' */
@@ -12574,7 +12639,6 @@ function parse_wb_bin(data, opts)/*:WorkbookFile*/ {
case 0x0822: /* 'BrtTimelineCachePivotCacheID' */
case 0x018D: /* 'BrtUserBookView' */
case 0x009A: /* 'BrtWbFactoid' */
- case 0x0099: /* 'BrtWbProp' */
case 0x045D: /* 'BrtWbProp14' */
case 0x0229: /* 'BrtWebOpt' */
case 0x082B: /* 'BrtWorkBookPr15' */
@@ -12861,7 +12925,7 @@ function safe_format_xlml(cell/*:Cell*/, nf, o) {
var z = XLMLFormatMap[nf]||nf||"General";
if(o.cellNF) cell.z = z;
if(o.cellDates && cell.t == 'n' && SSF.is_date(z)) {
- var _d = SSF.parse_date_code(cell.v); if(_d) { cell.t = 'd'; cell.v = new Date(Date.UTC(_d.y, _d.m-1,_d.d,_d.H,_d.M,_d.S,_d.u)); }
+ var _d = SSF.parse_date_code(cell.v); if(_d) { cell.t = 'd'; cell.v = new Date(_d.y, _d.m-1,_d.d,_d.H,_d.M,_d.S,_d.u); }
}
} catch(e) { if(o.WTF) throw e; }
}
@@ -12978,7 +13042,7 @@ function parse_xlml_xml(d, opts)/*:Workbook*/ {
var cstys = [], csty, seencol = false;
var arrayf = [];
var rowinfo = [], rowobj = {};
- var Workbook/*:WBWBProps*/ = { Sheets:[] }, wsprops = {};
+ var Workbook/*:WBWBProps*/ = ({ Sheets:[], WBProps:{date1904:false} }/*:any*/), wsprops = {};
xlmlregex.lastIndex = 0;
str = str.replace(//mg,"");
while((Rn = xlmlregex.exec(str))) switch(Rn[3]) {
@@ -13259,6 +13323,10 @@ function parse_xlml_xml(d, opts)/*:Workbook*/ {
/* ExcelWorkbook */
case 'ExcelWorkbook': switch(Rn[3]) {
+ case 'Date1904':
+ /*:: if(!Workbook.WBProps) Workbook.WBProps = {}; */
+ Workbook.WBProps.date1904 = true;
+ break;
case 'WindowHeight': break;
case 'WindowWidth': break;
case 'WindowTopX': break;
@@ -13277,7 +13345,6 @@ function parse_xlml_xml(d, opts)/*:Workbook*/ {
case 'Dll': break;
case 'AcceptLabelsInFormulas': break;
case 'DoNotSaveLinkValues': break;
- case 'Date1904': break;
case 'Iteration': break;
case 'MaxIterations': break;
case 'MaxChange': break;
@@ -13938,7 +14005,7 @@ function safe_format_xf(p/*:any*/, opts/*:ParseOpts*/, date1904/*:?boolean*/) {
}
else p.w = SSF.format(fmtid,p.v, {date1904:!!date1904});
if(opts.cellDates && fmtid && p.t == 'n' && SSF.is_date(SSF._table[fmtid])) {
- var _d = SSF.parse_date_code(p.v); if(_d) { p.t = 'd'; p.v = new Date(Date.UTC(_d.y, _d.m-1,_d.d,_d.H,_d.M,_d.S,_d.u)); }
+ var _d = SSF.parse_date_code(p.v); if(_d) { p.t = 'd'; p.v = new Date(_d.y, _d.m-1,_d.d,_d.H,_d.M,_d.S,_d.u); }
}
} catch(e) { if(opts.WTF) throw e; }
}
@@ -13968,7 +14035,7 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ {
var cell_valid = true;
var XFs = []; /* XF records */
var palette = [];
- var Workbook/*:WBWBProps*/ = ({ Sheets:[] }/*:any*/), wsprops = {};
+ var Workbook/*:WBWBProps*/ = ({ Sheets:[], WBProps:{date1904:false} }/*:any*/), wsprops = {};
var get_rgb = function getrgb(icv) {
if(icv < 8) return XLSIcv[icv];
if(icv < 64) return palette[icv-8] || XLSIcv[icv];
@@ -14080,7 +14147,9 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ {
/* nested switch statements to workaround V8 128 limit */
switch(Rn) {
/* Workbook Options */
- case 'Date1904': wb.opts.Date1904 = val; break;
+ case 'Date1904':
+ /*:: if(!Workbook.WBProps) Workbook.WBProps = {}; */
+ wb.opts.Date1904 = Workbook.WBProps.date1904 = val; break;
case 'WriteProtect': wb.opts.WriteProtect = true; break;
case 'FilePass':
if(!opts.enc) blob.l = 0;
diff --git a/xlsx.js b/xlsx.js
index 3193e9e..8e03d2f 100644
--- a/xlsx.js
+++ b/xlsx.js
@@ -967,6 +967,33 @@ var XLMLFormatMap/*{[string]:string}*/ = ({
"On/Off": '"Yes";"Yes";"No";@'
});
+/* dateNF parse TODO: move to SSF */
+var dateNFregex = /[dD]+|[mM]+|[yYeE]+|[Hh]+|[Ss]+/g;
+function dateNF_regex(dateNF) {
+ var fmt = typeof dateNF == "number" ? SSF._table[dateNF] : dateNF;
+ fmt = fmt.replace(dateNFregex, "(\\d+)");
+ return new RegExp("^" + fmt + "$");
+}
+function dateNF_fix(str, dateNF, match) {
+ var Y = -1, m = -1, d = -1, H = -1, M = -1, S = -1;
+ (dateNF.match(dateNFregex)||[]).forEach(function(n, i) {
+ var v = parseInt(match[i+1], 10);
+ switch(n.toLowerCase().charAt(0)) {
+ case 'y': Y = v; break; case 'd': d = v; break;
+ case 'h': H = v; break; case 's': S = v; break;
+ case 'm': if(H >= 0) M = v; else m = v; break;
+ }
+ });
+ if(S >= 0 && M == -1 && m >= 0) { M = m; m = -1; }
+ var datestr = (("" + (Y>=0?Y: new Date().getFullYear())).slice(-4) + "-" + ("00" + (m>=1?m:1)).slice(-2) + "-" + ("00" + (d>=1?d:1)).slice(-2));
+ if(datestr.length == 7) datestr = "0" + datestr;
+ if(datestr.length == 8) datestr = "20" + datestr;
+ var timestr = (("00" + (H>=0?H:0)).slice(-2) + ":" + ("00" + (M>=0?M:0)).slice(-2) + ":" + ("00" + (S>=0?S:0)).slice(-2));
+ if(H == -1 && M == -1 && S == -1) return datestr;
+ if(Y == -1 && m == -1 && d == -1) return timestr;
+ return datestr + "T" + timestr;
+}
+
var DO_NOT_EXPORT_CFB = true;
/* cfb.js (C) 2013-present SheetJS -- http://sheetjs.com */
/* vim: set ts=2: */
@@ -1385,22 +1412,17 @@ function evert_arr(obj) {
return o;
}
+var basedate = new Date(1899, 11, 30, 0, 0, 0); // 2209161600000
+var dnthresh = basedate.getTime() + (new Date().getTimezoneOffset() - basedate.getTimezoneOffset()) * 60000;
function datenum(v, date1904) {
var epoch = v.getTime();
if(date1904) epoch += 1462*24*60*60*1000;
- return (epoch + 2209161600000) / (24 * 60 * 60 * 1000);
+ return (epoch - dnthresh) / (24 * 60 * 60 * 1000);
}
function numdate(v) {
- var date = SSF.parse_date_code(v);
- var val = new Date();
- if(date == null) throw new Error("Bad Date Code: " + v);
- val.setUTCDate(date.d);
- val.setUTCMonth(date.m-1);
- val.setUTCFullYear(date.y);
- val.setUTCHours(date.H);
- val.setUTCMinutes(date.M);
- val.setUTCSeconds(date.S);
- return val;
+ var out = new Date();
+ out.setTime(v * 24 * 60 * 60 * 1000 + dnthresh);
+ return out;
}
/* ISO 8601 Duration */
@@ -1433,9 +1455,14 @@ function parse_isodur(s) {
var good_pd_date = new Date('2017-02-19T19:06:09.000Z');
if(isNaN(good_pd_date.getFullYear())) good_pd_date = new Date('2/19/17');
var good_pd = good_pd_date.getFullYear() == 2017;
-function parseDate(str) {
+/* parses aa date as a local date */
+function parseDate(str, fixdate) {
var d = new Date(str);
- if(good_pd) return d;
+ if(good_pd) {
+if(fixdate > 0) d.setTime(d.getTime() + d.getTimezoneOffset() * 60 * 1000);
+ else if(fixdate < 0) d.setTime(d.getTime() - d.getTimezoneOffset() * 60 * 1000);
+ return d;
+ }
if(str instanceof Date) return str;
if(good_pd_date.getFullYear() == 1917 && !isNaN(d.getFullYear())) {
var s = d.getFullYear();
@@ -1443,7 +1470,7 @@ function parseDate(str) {
d.setFullYear(d.getFullYear() + 100); return d;
}
var n = str.match(/\d+/g)||["2017","2","19","0","0","0"];
- return new Date(Date.UTC(+n[0], +n[1] - 1, +n[2], (+n[3]||0), (+n[4]||0), (+n[5]||0)));
+ return new Date(+n[0], +n[1] - 1, +n[2], (+n[3]||0), (+n[4]||0), (+n[5]||0));
}
function cc2str(arr) {
@@ -1630,7 +1657,7 @@ var xlml_unfixstr = (function() {
function parsexmlbool(value, tag) {
switch(value) {
- case '1': case 'true': case 'TRUE': return true;
+ case 1: case true: case '1': case 'true': case 'TRUE': return true;
/* case '0': case 'false': case 'FALSE':*/
default: return false;
}
@@ -5605,6 +5632,7 @@ var PRN = (function() {
var R = 0, C = 0, v = 0;
var start = 0, end = 0, sepcc = sep.charCodeAt(0), instr = false, cc=0;
str = str.replace(/\r\n/mg, "\n");
+ var _re = o.dateNF != null ? dateNF_regex(o.dateNF) : null;
function finish_cell() {
var s = str.slice(start, end);
var cell = ({});
@@ -5612,10 +5640,12 @@ var PRN = (function() {
else if(s == "TRUE") { cell.t = 'b'; cell.v = true; }
else if(s == "FALSE") { cell.t = 'b'; cell.v = false; }
else if(!isNaN(v = +s)) { cell.t = 'n'; cell.w = s; cell.v = v; }
- else if(!isNaN(fuzzydate(s).getDate())) {
+ else if(!isNaN(fuzzydate(s).getDate()) || _re && s.match(_re)) {
cell.z = o.dateNF || SSF._table[14];
- if(o.cellDates) { cell.t = 'd'; cell.v = parseDate(s); }
- else { cell.t = 'n'; cell.v = datenum(parseDate(s)); }
+ var k = 0;
+ if(_re && s.match(_re)){ s=dateNF_fix(s, o.dateNF, (s.match(_re)||[])); k=1; }
+ if(o.cellDates) { cell.t = 'd'; cell.v = parseDate(s, k); }
+ else { cell.t = 'n'; cell.v = datenum(parseDate(s, k)); }
cell.w = SSF.format(cell.z, cell.v instanceof Date ? datenum(cell.v):cell.v);
} else {
cell.t = 's';
@@ -10784,7 +10814,7 @@ function write_ws_xml_cell(cell, ref, ws, opts, idx, wb) {
case 'n': vv = ''+cell.v; break;
case 'e': vv = BErr[cell.v]; break;
case 'd':
- if(opts.cellDates) vv = parseDate(cell.v).toISOString();
+ if(opts.cellDates) vv = parseDate(cell.v, -1).toISOString();
else {
cell.t = 'n';
vv = ''+(cell.v = datenum(parseDate(cell.v)));
@@ -10937,7 +10967,8 @@ return function parse_ws_xml_data(sdata, s, opts, guess, themes, styles) {
break;
case 'b': p.v = parsexmlbool(p.v); break;
case 'd':
- if(!opts.cellDates) { p.v = datenum(parseDate(p.v)); p.t = 'n'; }
+ if(opts.cellDates) p.v = parseDate(p.v, 1);
+ else { p.v = datenum(parseDate(p.v, 1)); p.t = 'n'; }
break;
/* error string in .w, number in .v */
case 'e':
@@ -10954,9 +10985,7 @@ return function parse_ws_xml_data(sdata, s, opts, guess, themes, styles) {
}
}
safe_format(p, fmtid, fillid, opts, themes, styles);
- if(opts.cellDates && do_format && p.t == 'n' && SSF.is_date(SSF._table[fmtid])) {
- var _d = SSF.parse_date_code(p.v); if(_d) { p.t = 'd'; p.v = new Date(Date.UTC(_d.y, _d.m-1,_d.d,_d.H,_d.M,_d.S,_d.u)); }
- }
+ if(opts.cellDates && do_format && p.t == 'n' && SSF.is_date(SSF._table[fmtid])) { p.t = 'd'; p.v = numdate(p.v); }
if(dense) {
var _r = decode_cell(tag.r);
if(!s[_r.r]) s[_r.r] = [];
@@ -11572,7 +11601,7 @@ function parse_ws_bin(data, _opts, rels, wb, themes, styles) {
if(refguess.e.r < row.r) refguess.e.r = row.r;
if(refguess.e.c < C) refguess.e.c = C;
if(opts.cellDates && cf && p.t == 'n' && SSF.is_date(SSF._table[cf.ifmt])) {
- var _d = SSF.parse_date_code(p.v); if(_d) { p.t = 'd'; p.v = new Date(Date.UTC(_d.y, _d.m-1,_d.d,_d.H,_d.M,_d.S,_d.u)); }
+ var _d = SSF.parse_date_code(p.v); if(_d) { p.t = 'd'; p.v = new Date(_d.y, _d.m-1,_d.d,_d.H,_d.M,_d.S,_d.u); }
}
break;
@@ -12054,38 +12083,37 @@ function write_cs_bin(idx, opts, wb, rels) {
}
/* 18.2.28 (CT_WorkbookProtection) Defaults */
var WBPropsDef = [
- ['allowRefreshQuery', '0'],
- ['autoCompressPictures', '1'],
- ['backupFile', '0'],
- ['checkCompatibility', '0'],
- ['codeName', ''],
- ['date1904', '0'],
- ['dateCompatibility', '1'],
- //['defaultThemeVersion', '0'],
- ['filterPrivacy', '0'],
- ['hidePivotFieldList', '0'],
- ['promptedSolutions', '0'],
- ['publishItems', '0'],
- ['refreshAllConnections', false],
- ['saveExternalLinkValues', '1'],
- ['showBorderUnselectedTables', '1'],
- ['showInkAnnotation', '1'],
- ['showObjects', 'all'],
- ['showPivotChartFilter', '0']
- //['updateLinks', 'userSet']
+ ['allowRefreshQuery', false, "bool"],
+ ['autoCompressPictures', true, "bool"],
+ ['backupFile', false, "bool"],
+ ['checkCompatibility', false, "bool"],
+ ['codeName', ''],
+ ['date1904', false, "bool"],
+ ['defaultThemeVersion', 0, "int"],
+ ['filterPrivacy', false, "bool"],
+ ['hidePivotFieldList', false, "bool"],
+ ['promptedSolutions', false, "bool"],
+ ['publishItems', false, "bool"],
+ ['refreshAllConnections', false, "bool"],
+ ['saveExternalLinkValues', true, "bool"],
+ ['showBorderUnselectedTables', true, "bool"],
+ ['showInkAnnotation', true, "bool"],
+ ['showObjects', 'all'],
+ ['showPivotChartFilter', false, "bool"],
+ ['updateLinks', 'userSet']
];
/* 18.2.30 (CT_BookView) Defaults */
var WBViewDef = [
- ['activeTab', '0'],
- ['autoFilterDateGrouping', '1'],
- ['firstSheet', '0'],
- ['minimized', '0'],
- ['showHorizontalScroll', '1'],
- ['showSheetTabs', '1'],
- ['showVerticalScroll', '1'],
- ['tabRatio', '600'],
- ['visibility', 'visible']
+ ['activeTab', 0, "int"],
+ ['autoFilterDateGrouping', true, "bool"],
+ ['firstSheet', 0, "int"],
+ ['minimized', false, "bool"],
+ ['showHorizontalScroll', true, "bool"],
+ ['showSheetTabs', true, "bool"],
+ ['showVerticalScroll', true, "bool"],
+ ['tabRatio', 600, "int"],
+ ['visibility', 'visible']
//window{Height,Width}, {x,y}Window
];
@@ -12134,12 +12162,20 @@ function push_defaults_array(target, defaults) {
for(var j = 0; j != target.length; ++j) { var w = target[j];
for(var i=0; i != defaults.length; ++i) { var z = defaults[i];
if(w[z[0]] == null) w[z[0]] = z[1];
+ else switch(z[2]) {
+ case "bool": if(typeof w[z[0]] == "string") w[z[0]] = parsexmlbool(w[z[0]], z[0]); break;
+ case "int": if(typeof w[z[0]] == "string") w[z[0]] = parseInt(w[z[0]], 10); break;
+ }
}
}
}
function push_defaults(target, defaults) {
for(var i = 0; i != defaults.length; ++i) { var z = defaults[i];
if(target[z[0]] == null) target[z[0]] = z[1];
+ else switch(z[2]) {
+ case "bool": if(typeof target[z[0]] == "string") target[z[0]] = parsexmlbool(target[z[0]], z[0]); break;
+ case "int": if(typeof target[z[0]] == "string") target[z[0]] = parseInt(target[z[0]], 10); break;
+ }
}
}
@@ -12153,6 +12189,13 @@ function parse_wb_defaults(wb) {
_ssfopts.date1904 = parsexmlbool(wb.WBProps.date1904, 'date1904');
}
+function safe1904(wb) {
+ /* TODO: store date1904 somewhere else */
+ if(!wb.Workbook) return "false";
+ if(!wb.Workbook.WBProps) return "false";
+ return parsexmlbool(wb.Workbook.WBProps.date1904) ? "true" : "false";
+}
+
var badchars = "][*?\/\\".split("");
function check_ws_name(n, safe) {
if(n.length > 31) { if(safe) return false; throw new Error("Sheet names cannot exceed 31 chars"); }
@@ -12203,8 +12246,17 @@ function parse_wb_xml(data, opts) {
case '': break;
/* 18.2.28 workbookPr CT_WorkbookPr ? */
- case '': delete y[0]; wb.WBProps = y; break;
+ case '':
+ WBPropsDef.forEach(function(w) {
+ if(y[w[0]] == null) return;
+ switch(w[2]) {
+ case "bool": wb.WBProps[w[0]] = parsexmlbool(y[w[0]], w[0]); break;
+ case "int": wb.WBProps[w[0]] = parseInt(y[w[0]], 10); break;
+ default: wb.WBProps[w[0]] = y[w[0]];
+ }
+ });
+ break;
case '': break;
/* 18.2.29 workbookProtection CT_WorkbookProtection ? */
@@ -12325,14 +12377,6 @@ var WB_XML_ROOT = writextag('workbook', null, {
'xmlns:r': XMLNS.r
});
-function safe1904(wb) {
- /* TODO: store date1904 somewhere else */
- if(!wb.Workbook) return "false";
- if(!wb.Workbook.WBProps) return "false";
- // $FlowIgnore
- return parsexmlbool(wb.Workbook.WBProps.date1904) ? "true" : "false";
-}
-
function write_wb_xml(wb, opts) {
var o = [XML_HEADER];
o[o.length] = WB_XML_ROOT;
@@ -12410,10 +12454,27 @@ function write_BrtBundleSh(data, o) {
/* [MS-XLSB] 2.4.807 BrtWbProp */
function parse_BrtWbProp(data, length) {
- data.read_shift(4);
- var dwThemeVersion = data.read_shift(4);
+ var o = {};
+ var flags = data.read_shift(4);
+ o.defaultThemeVersion = data.read_shift(4);
var strName = (length > 8) ? parse_XLWideString(data) : "";
- return [dwThemeVersion, strName];
+ if(strName.length > 0) o.codeName = strName;
+ o.autoCompressPictures = !!(flags & 0x10000);
+ o.backupFile = !!(flags & 0x40);
+ o.checkCompatibility = !!(flags & 0x1000);
+ o.date1904 = !!(flags & 0x01);
+ o.filterPrivacy = !!(flags & 0x08);
+ o.hidePivotFieldList = !!(flags & 0x400);
+ o.promptedSolutions = !!(flags & 0x10);
+ o.publishItems = !!(flags & 0x800);
+ o.refreshAllConnections = !!(flags & 0x40000);
+ o.saveExternalLinkValues = !!(flags & 0x80);
+ o.showBorderUnselectedTables = !!(flags & 0x04);
+ o.showInkAnnotation = !!(flags & 0x20);
+ o.showObjects = ["all", "placeholders", "none"][(flags >> 13) & 0x03];
+ o.showPivotChartFilter = !!(flags & 0x8000);
+ o.updateLinks = ["userSet", "never", "always"][(flags >> 8) & 0x03];
+ return o;
}
function write_BrtWbProp(data, o) {
if(!o) o = new_buf(72);
@@ -12477,6 +12538,9 @@ function parse_wb_bin(data, opts) {
break;
case 0x040C: /* 'BrtNameExt' */ break;
+ case 0x0099: /* 'BrtWbProp' */
+ wb.WBProps = val; break;
+
/* case 'BrtModelTimeGroupingCalcCol' */
/* case 'BrtRevisionPtr' */
/* case 'BrtUid' */
@@ -12508,7 +12572,6 @@ function parse_wb_bin(data, opts) {
case 0x0822: /* 'BrtTimelineCachePivotCacheID' */
case 0x018D: /* 'BrtUserBookView' */
case 0x009A: /* 'BrtWbFactoid' */
- case 0x0099: /* 'BrtWbProp' */
case 0x045D: /* 'BrtWbProp14' */
case 0x0229: /* 'BrtWebOpt' */
case 0x082B: /* 'BrtWorkBookPr15' */
@@ -12793,7 +12856,7 @@ function safe_format_xlml(cell, nf, o) {
var z = XLMLFormatMap[nf]||nf||"General";
if(o.cellNF) cell.z = z;
if(o.cellDates && cell.t == 'n' && SSF.is_date(z)) {
- var _d = SSF.parse_date_code(cell.v); if(_d) { cell.t = 'd'; cell.v = new Date(Date.UTC(_d.y, _d.m-1,_d.d,_d.H,_d.M,_d.S,_d.u)); }
+ var _d = SSF.parse_date_code(cell.v); if(_d) { cell.t = 'd'; cell.v = new Date(_d.y, _d.m-1,_d.d,_d.H,_d.M,_d.S,_d.u); }
}
} catch(e) { if(o.WTF) throw e; }
}
@@ -12910,7 +12973,7 @@ function parse_xlml_xml(d, opts) {
var cstys = [], csty, seencol = false;
var arrayf = [];
var rowinfo = [], rowobj = {};
- var Workbook = { Sheets:[] }, wsprops = {};
+ var Workbook = ({ Sheets:[], WBProps:{date1904:false} }), wsprops = {};
xlmlregex.lastIndex = 0;
str = str.replace(//mg,"");
while((Rn = xlmlregex.exec(str))) switch(Rn[3]) {
@@ -13190,6 +13253,9 @@ Workbook.Names.push(_DefinedName);
/* ExcelWorkbook */
case 'ExcelWorkbook': switch(Rn[3]) {
+ case 'Date1904':
+Workbook.WBProps.date1904 = true;
+ break;
case 'WindowHeight': break;
case 'WindowWidth': break;
case 'WindowTopX': break;
@@ -13208,7 +13274,6 @@ Workbook.Names.push(_DefinedName);
case 'Dll': break;
case 'AcceptLabelsInFormulas': break;
case 'DoNotSaveLinkValues': break;
- case 'Date1904': break;
case 'Iteration': break;
case 'MaxIterations': break;
case 'MaxChange': break;
@@ -13868,7 +13933,7 @@ function safe_format_xf(p, opts, date1904) {
}
else p.w = SSF.format(fmtid,p.v, {date1904:!!date1904});
if(opts.cellDates && fmtid && p.t == 'n' && SSF.is_date(SSF._table[fmtid])) {
- var _d = SSF.parse_date_code(p.v); if(_d) { p.t = 'd'; p.v = new Date(Date.UTC(_d.y, _d.m-1,_d.d,_d.H,_d.M,_d.S,_d.u)); }
+ var _d = SSF.parse_date_code(p.v); if(_d) { p.t = 'd'; p.v = new Date(_d.y, _d.m-1,_d.d,_d.H,_d.M,_d.S,_d.u); }
}
} catch(e) { if(opts.WTF) throw e; }
}
@@ -13898,7 +13963,7 @@ function parse_workbook(blob, options) {
var cell_valid = true;
var XFs = []; /* XF records */
var palette = [];
- var Workbook = ({ Sheets:[] }), wsprops = {};
+ var Workbook = ({ Sheets:[], WBProps:{date1904:false} }), wsprops = {};
var get_rgb = function getrgb(icv) {
if(icv < 8) return XLSIcv[icv];
if(icv < 64) return palette[icv-8] || XLSIcv[icv];
@@ -14010,7 +14075,8 @@ function parse_workbook(blob, options) {
/* nested switch statements to workaround V8 128 limit */
switch(Rn) {
/* Workbook Options */
- case 'Date1904': wb.opts.Date1904 = val; break;
+ case 'Date1904':
+wb.opts.Date1904 = Workbook.WBProps.date1904 = val; break;
case 'WriteProtect': wb.opts.WriteProtect = true; break;
case 'FilePass':
if(!opts.enc) blob.l = 0;