forked from sheetjs/sheetjs
fix rounding issue
See: #2560 - Improved rounding to account for minutes, hours, etc. - Added tests for rounding dates/times
This commit is contained in:
parent
a373597294
commit
c580ffcbc2
@ -1,5 +1,6 @@
|
||||
function parse_date_code(v/*:number*/,opts/*:?any*/,b2/*:?boolean*/) {
|
||||
if(v > 2958465 || v < 0) return null;
|
||||
opts.b2 = b2 || false;
|
||||
var date = (v|0), time = Math.floor(86400 * (v - date)), dow=0;
|
||||
var dout=[];
|
||||
var out={D:date, T:time, u:86400*(v-date)-time,y:0,m:0,d:0,H:0,M:0,S:0,q:0};
|
||||
|
@ -1,4 +1,5 @@
|
||||
/*jshint -W086 */
|
||||
var ROUNDING_FLAG = "rounding is necessary"
|
||||
function write_date(type/*:number*/, fmt/*:string*/, val, ss0/*:?number*/)/*:string*/ {
|
||||
var o="", ss=0, tt=0, y = val.y, out, outl = 0;
|
||||
switch(type) {
|
||||
@ -45,7 +46,7 @@ function write_date(type/*:number*/, fmt/*:string*/, val, ss0/*:?number*/)/*:str
|
||||
if(ss0 >= 2) tt = ss0 === 3 ? 1000 : 100;
|
||||
else tt = ss0 === 1 ? 10 : 1;
|
||||
ss = Math.round((tt)*(val.S + val.u));
|
||||
if(ss >= 60*tt) ss = 0;
|
||||
if(ss >= 60*tt) throw ROUNDING_FLAG;
|
||||
if(fmt === 's') return ss === 0 ? "0" : ""+ss/tt;
|
||||
o = pad0(ss,2 + ss0);
|
||||
if(fmt === 'ss') return o.substr(0,2);
|
||||
|
@ -115,31 +115,7 @@ function eval_fmt(fmt/*:string*/, v/*:any*/, opts/*:any*/, flen/*:number*/) {
|
||||
}
|
||||
|
||||
/* replace fields */
|
||||
var nstr = "", jj;
|
||||
for(i=0; i < out.length; ++i) {
|
||||
switch(out[i].t) {
|
||||
case 't': case 'T': case ' ': case 'D': break;
|
||||
case 'X': out[i].v = ""; out[i].t = ";"; break;
|
||||
case 'd': case 'm': case 'y': case 'h': case 'H': case 'M': case 's': case 'e': case 'b': case 'Z':
|
||||
/*::if(!dt) throw "unreachable"; */
|
||||
out[i].v = write_date(out[i].t.charCodeAt(0), out[i].v, dt, ss0);
|
||||
out[i].t = 't'; break;
|
||||
case 'n': case '?':
|
||||
jj = i+1;
|
||||
while(out[jj] != null && (
|
||||
(c=out[jj].t) === "?" || c === "D" ||
|
||||
((c === " " || c === "t") && out[jj+1] != null && (out[jj+1].t === '?' || out[jj+1].t === "t" && out[jj+1].v === '/')) ||
|
||||
(out[i].t === '(' && (c === ' ' || c === 'n' || c === ')')) ||
|
||||
(c === 't' && (out[jj].v === '/' || out[jj].v === ' ' && out[jj+1] != null && out[jj+1].t == '?'))
|
||||
)) {
|
||||
out[i].v += out[jj].v;
|
||||
out[jj] = {v:"", t:";"}; ++jj;
|
||||
}
|
||||
nstr += out[i].v;
|
||||
i = jj-1; break;
|
||||
case 'G': out[i].t = 't'; out[i].v = general_fmt(v,opts); break;
|
||||
}
|
||||
}
|
||||
var {nstr,out} = replace_fields(out, dt, ss0, v, opts);
|
||||
var vv = "", myv, ostr;
|
||||
if(nstr.length > 0) {
|
||||
if(nstr.charCodeAt(0) == 40) /* '(' */ {
|
||||
@ -153,7 +129,7 @@ function eval_fmt(fmt/*:string*/, v/*:any*/, opts/*:any*/, flen/*:number*/) {
|
||||
out[0].v = "-" + out[0].v;
|
||||
}
|
||||
}
|
||||
jj=ostr.length-1;
|
||||
var jj=ostr.length-1;
|
||||
var decpt = out.length;
|
||||
for(i=0; i < out.length; ++i) if(out[i] != null && out[i].t != 't' && out[i].v.indexOf(".") > -1) { decpt = i; break; }
|
||||
var lasti=out.length;
|
||||
@ -205,4 +181,83 @@ function eval_fmt(fmt/*:string*/, v/*:any*/, opts/*:any*/, flen/*:number*/) {
|
||||
for(i=0; i !== out.length; ++i) if(out[i] != null) retval += out[i].v;
|
||||
return retval;
|
||||
}
|
||||
function replace_fields(fields, dt, ss0, v, opts) {
|
||||
var out = [];
|
||||
for (var i = 0; i < fields.length; i++) {out[i] = {t: fields[i].t, v: fields[i].v}}
|
||||
var nstr = "", jj;
|
||||
for(i=0; i < out.length; ++i) {
|
||||
switch(out[i].t) {
|
||||
case 't': case 'T': case ' ': case 'D': break;
|
||||
case 'X': out[i].v = ""; out[i].t = ";"; break;
|
||||
case 'd': case 'm': case 'y': case 'h': case 'H': case 'M': case 's': case 'e': case 'b': case 'Z':
|
||||
/*::if(!dt) throw "unreachable"; */
|
||||
try {
|
||||
out[i].v = write_date(out[i].t.charCodeAt(0), out[i].v, dt, ss0);
|
||||
} catch (e) {
|
||||
if (e === ROUNDING_FLAG) {
|
||||
round_up_date(dt, opts);
|
||||
return replace_fields(fields, dt, ss0, v, opts);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
out[i].t = 't'; break;
|
||||
case 'n': case '?':
|
||||
jj = i+1;
|
||||
while(out[jj] != null && (
|
||||
(c=out[jj].t) === "?" || c === "D" ||
|
||||
((c === " " || c === "t") && out[jj+1] != null && (out[jj+1].t === '?' || out[jj+1].t === "t" && out[jj+1].v === '/')) ||
|
||||
(out[i].t === '(' && (c === ' ' || c === 'n' || c === ')')) ||
|
||||
(c === 't' && (out[jj].v === '/' || out[jj].v === ' ' && out[jj+1] != null && out[jj+1].t == '?'))
|
||||
)) {
|
||||
out[i].v += out[jj].v;
|
||||
out[jj] = {v:"", t:";"}; ++jj;
|
||||
}
|
||||
nstr += out[i].v;
|
||||
i = jj-1; break;
|
||||
case 'G': out[i].t = 't'; out[i].v = general_fmt(v,opts); break;
|
||||
}
|
||||
}
|
||||
return {nstr: nstr, out: out};
|
||||
}
|
||||
function round_up_date(out, opts) {
|
||||
if (!opts) opts = {};
|
||||
var tmp = new Date(out.y, out.m - 1, out.d, out.H, out.M, out.S);
|
||||
var oldDate = tmp.getDate();
|
||||
tmp.setSeconds(out.S + 1);
|
||||
var use1900 = !opts.date1904 && !opts.b2;
|
||||
if (tmp.getDate() !== oldDate) {
|
||||
if (out.D === 0 && use1900) {
|
||||
// 0 corresponds with Jan 0th, 1900
|
||||
out.y = 1900;
|
||||
out.m = 1;
|
||||
out.d = 1;
|
||||
out.q = (tmp.getDay() + 6) % 7;
|
||||
} else if (out.D === 60 && use1900) {
|
||||
// Excel & SSF have an intentional bug where they treat 1900 as a leap year
|
||||
// The 60th day (Feb 29) rounds up to Mar 1
|
||||
out.y = 1900;
|
||||
out.m = 3;
|
||||
out.d = 1;
|
||||
out.q = 4;
|
||||
} else if (out.D == 59 && use1900) {
|
||||
// Excel & SSF have an intentional bug where they treat 1900 as a leap year
|
||||
// The 59th day (Feb 28) rounds up to Feb 29
|
||||
out.y = 1900;
|
||||
out.m = 2;
|
||||
out.d = 29;
|
||||
out.q = 3;
|
||||
} else {
|
||||
out.y = tmp.getFullYear();
|
||||
out.m = tmp.getMonth() + 1;
|
||||
out.d = tmp.getDate();
|
||||
out.q = out.D < 60 && use1900 ? (tmp.getDay() + 6) % 7 : tmp.getDay();
|
||||
}
|
||||
out.D += 1;
|
||||
}
|
||||
out.H = tmp.getHours();
|
||||
out.M = tmp.getMinutes();
|
||||
out.S = tmp.getSeconds();
|
||||
out.u = 0;
|
||||
out.T += 1;
|
||||
}
|
||||
SSF._eval = eval_fmt;
|
||||
|
@ -3,7 +3,7 @@
|
||||
/*jshint -W041 */
|
||||
/*:: declare var DO_NOT_EXPORT_SSF: any; */
|
||||
var SSF/*:SSFModule*/ = ({}/*:any*/);
|
||||
var make_ssf = function make_ssf(SSF/*:SSFModule*/){
|
||||
function make_ssf(SSF/*:SSFModule*/){
|
||||
SSF.version = '0.11.2';
|
||||
function _strrev(x/*:string*/)/*:string*/ { var o = "", i = x.length-1; while(i>=0) o += x.charAt(i--); return o; }
|
||||
function fill(c/*:string*/,l/*:number*/)/*:string*/ { var o = ""; while(o.length < l) o+=c; return o; }
|
||||
@ -163,6 +163,7 @@ function frac(x/*:number*/, D/*:number*/, mixed/*:?boolean*/)/*:Array<number>*/
|
||||
}
|
||||
function parse_date_code(v/*:number*/,opts/*:?any*/,b2/*:?boolean*/) {
|
||||
if(v > 2958465 || v < 0) return null;
|
||||
opts.b2 = b2 || false;
|
||||
var date = (v|0), time = Math.floor(86400 * (v - date)), dow=0;
|
||||
var dout=[];
|
||||
var out={D:date, T:time, u:86400*(v-date)-time,y:0,m:0,d:0,H:0,M:0,S:0,q:0};
|
||||
@ -201,10 +202,6 @@ function datenum_local(v/*:Date*/, date1904/*:?boolean*/)/*:number*/ {
|
||||
else if(v >= base1904) epoch += 24*60*60*1000;
|
||||
return (epoch - (dnthresh + (v.getTimezoneOffset() - basedate.getTimezoneOffset()) * 60000)) / (24 * 60 * 60 * 1000);
|
||||
}
|
||||
/* The longest 32-bit integer text is "-4294967296", exactly 11 chars */
|
||||
function general_fmt_int(v/*:number*/)/*:string*/ { return v.toString(10); }
|
||||
SSF._general_int = general_fmt_int;
|
||||
|
||||
/* ECMA-376 18.8.30 numFmt*/
|
||||
/* Note: `toPrecision` uses standard form when prec > E and E >= -6 */
|
||||
var general_fmt_num = (function make_general_fmt_num() {
|
||||
@ -257,6 +254,7 @@ SSF._general_num = general_fmt_num;
|
||||
- "up to 11 characters" displayed for numbers
|
||||
- Default date format (code 14) used for Dates
|
||||
|
||||
The longest 32-bit integer text is "-2147483648", exactly 11 chars
|
||||
TODO: technically the display depends on the width of the cell
|
||||
*/
|
||||
function general_fmt(v/*:any*/, opts/*:any*/) {
|
||||
@ -281,6 +279,7 @@ function fix_hijri(date/*:Date*/, o/*:[number, number, number]*/) {
|
||||
}
|
||||
var THAI_DIGITS = "\u0E50\u0E51\u0E52\u0E53\u0E54\u0E55\u0E56\u0E57\u0E58\u0E59".split("");
|
||||
/*jshint -W086 */
|
||||
var ROUNDING_FLAG = "rounding is necessary"
|
||||
function write_date(type/*:number*/, fmt/*:string*/, val, ss0/*:?number*/)/*:string*/ {
|
||||
var o="", ss=0, tt=0, y = val.y, out, outl = 0;
|
||||
switch(type) {
|
||||
@ -327,7 +326,7 @@ function write_date(type/*:number*/, fmt/*:string*/, val, ss0/*:?number*/)/*:str
|
||||
if(ss0 >= 2) tt = ss0 === 3 ? 1000 : 100;
|
||||
else tt = ss0 === 1 ? 10 : 1;
|
||||
ss = Math.round((tt)*(val.S + val.u));
|
||||
if(ss >= 60*tt) ss = 0;
|
||||
if(ss >= 60*tt) throw ROUNDING_FLAG;
|
||||
if(fmt === 's') return ss === 0 ? "0" : ""+ss/tt;
|
||||
o = pad0(ss,2 + ss0);
|
||||
if(fmt === 'ss') return o.substr(0,2);
|
||||
@ -810,31 +809,7 @@ function eval_fmt(fmt/*:string*/, v/*:any*/, opts/*:any*/, flen/*:number*/) {
|
||||
}
|
||||
|
||||
/* replace fields */
|
||||
var nstr = "", jj;
|
||||
for(i=0; i < out.length; ++i) {
|
||||
switch(out[i].t) {
|
||||
case 't': case 'T': case ' ': case 'D': break;
|
||||
case 'X': out[i].v = ""; out[i].t = ";"; break;
|
||||
case 'd': case 'm': case 'y': case 'h': case 'H': case 'M': case 's': case 'e': case 'b': case 'Z':
|
||||
/*::if(!dt) throw "unreachable"; */
|
||||
out[i].v = write_date(out[i].t.charCodeAt(0), out[i].v, dt, ss0);
|
||||
out[i].t = 't'; break;
|
||||
case 'n': case '?':
|
||||
jj = i+1;
|
||||
while(out[jj] != null && (
|
||||
(c=out[jj].t) === "?" || c === "D" ||
|
||||
((c === " " || c === "t") && out[jj+1] != null && (out[jj+1].t === '?' || out[jj+1].t === "t" && out[jj+1].v === '/')) ||
|
||||
(out[i].t === '(' && (c === ' ' || c === 'n' || c === ')')) ||
|
||||
(c === 't' && (out[jj].v === '/' || out[jj].v === ' ' && out[jj+1] != null && out[jj+1].t == '?'))
|
||||
)) {
|
||||
out[i].v += out[jj].v;
|
||||
out[jj] = {v:"", t:";"}; ++jj;
|
||||
}
|
||||
nstr += out[i].v;
|
||||
i = jj-1; break;
|
||||
case 'G': out[i].t = 't'; out[i].v = general_fmt(v,opts); break;
|
||||
}
|
||||
}
|
||||
var {nstr,out} = replace_fields(out, dt, ss0, v, opts);
|
||||
var vv = "", myv, ostr;
|
||||
if(nstr.length > 0) {
|
||||
if(nstr.charCodeAt(0) == 40) /* '(' */ {
|
||||
@ -848,7 +823,7 @@ function eval_fmt(fmt/*:string*/, v/*:any*/, opts/*:any*/, flen/*:number*/) {
|
||||
out[0].v = "-" + out[0].v;
|
||||
}
|
||||
}
|
||||
jj=ostr.length-1;
|
||||
var jj=ostr.length-1;
|
||||
var decpt = out.length;
|
||||
for(i=0; i < out.length; ++i) if(out[i] != null && out[i].t != 't' && out[i].v.indexOf(".") > -1) { decpt = i; break; }
|
||||
var lasti=out.length;
|
||||
@ -900,6 +875,85 @@ function eval_fmt(fmt/*:string*/, v/*:any*/, opts/*:any*/, flen/*:number*/) {
|
||||
for(i=0; i !== out.length; ++i) if(out[i] != null) retval += out[i].v;
|
||||
return retval;
|
||||
}
|
||||
function replace_fields(fields, dt, ss0, v, opts) {
|
||||
var out = [];
|
||||
for (var i = 0; i < fields.length; i++) {out[i] = {t: fields[i].t, v: fields[i].v}}
|
||||
var nstr = "", jj;
|
||||
for(i=0; i < out.length; ++i) {
|
||||
switch(out[i].t) {
|
||||
case 't': case 'T': case ' ': case 'D': break;
|
||||
case 'X': out[i].v = ""; out[i].t = ";"; break;
|
||||
case 'd': case 'm': case 'y': case 'h': case 'H': case 'M': case 's': case 'e': case 'b': case 'Z':
|
||||
/*::if(!dt) throw "unreachable"; */
|
||||
try {
|
||||
out[i].v = write_date(out[i].t.charCodeAt(0), out[i].v, dt, ss0);
|
||||
} catch (e) {
|
||||
if (e === ROUNDING_FLAG) {
|
||||
round_up_date(dt, opts);
|
||||
return replace_fields(fields, dt, ss0, v, opts);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
out[i].t = 't'; break;
|
||||
case 'n': case '?':
|
||||
jj = i+1;
|
||||
while(out[jj] != null && (
|
||||
(c=out[jj].t) === "?" || c === "D" ||
|
||||
((c === " " || c === "t") && out[jj+1] != null && (out[jj+1].t === '?' || out[jj+1].t === "t" && out[jj+1].v === '/')) ||
|
||||
(out[i].t === '(' && (c === ' ' || c === 'n' || c === ')')) ||
|
||||
(c === 't' && (out[jj].v === '/' || out[jj].v === ' ' && out[jj+1] != null && out[jj+1].t == '?'))
|
||||
)) {
|
||||
out[i].v += out[jj].v;
|
||||
out[jj] = {v:"", t:";"}; ++jj;
|
||||
}
|
||||
nstr += out[i].v;
|
||||
i = jj-1; break;
|
||||
case 'G': out[i].t = 't'; out[i].v = general_fmt(v,opts); break;
|
||||
}
|
||||
}
|
||||
return {nstr: nstr, out: out};
|
||||
}
|
||||
function round_up_date(out, opts) {
|
||||
if (!opts) opts = {};
|
||||
var tmp = new Date(out.y, out.m - 1, out.d, out.H, out.M, out.S);
|
||||
var oldDate = tmp.getDate();
|
||||
tmp.setSeconds(out.S + 1);
|
||||
var use1900 = !opts.date1904 && !opts.b2;
|
||||
if (tmp.getDate() !== oldDate) {
|
||||
if (out.D === 0 && use1900) {
|
||||
// 0 corresponds with Jan 0th, 1900
|
||||
out.y = 1900;
|
||||
out.m = 1;
|
||||
out.d = 1;
|
||||
out.q = (tmp.getDay() + 6) % 7;
|
||||
} else if (out.D === 60 && use1900) {
|
||||
// Excel & SSF have an intentional bug where they treat 1900 as a leap year
|
||||
// The 60th day (Feb 29) rounds up to Mar 1
|
||||
out.y = 1900;
|
||||
out.m = 3;
|
||||
out.d = 1;
|
||||
out.q = 4;
|
||||
} else if (out.D == 59 && use1900) {
|
||||
// Excel & SSF have an intentional bug where they treat 1900 as a leap year
|
||||
// The 59th day (Feb 28) rounds up to Feb 29
|
||||
out.y = 1900;
|
||||
out.m = 2;
|
||||
out.d = 29;
|
||||
out.q = 3;
|
||||
} else {
|
||||
out.y = tmp.getFullYear();
|
||||
out.m = tmp.getMonth() + 1;
|
||||
out.d = tmp.getDate();
|
||||
out.q = out.D < 60 && use1900 ? (tmp.getDay() + 6) % 7 : tmp.getDay();
|
||||
}
|
||||
out.D += 1;
|
||||
}
|
||||
out.H = tmp.getHours();
|
||||
out.M = tmp.getMinutes();
|
||||
out.S = tmp.getSeconds();
|
||||
out.u = 0;
|
||||
out.T += 1;
|
||||
}
|
||||
SSF._eval = eval_fmt;
|
||||
var cfregex = /\[[=<>]/;
|
||||
var cfregex2 = /\[(=|>[=]?|<[>=]?)(-?\d+(?:\.\d*)?)\]/;
|
||||
@ -985,7 +1039,8 @@ SSF.load_table = function load_table(tbl/*:SSFTable*/)/*:void*/ {
|
||||
};
|
||||
SSF.init_table = init_table;
|
||||
SSF.format = format;
|
||||
};
|
||||
SSF.choose_format = choose_fmt;
|
||||
}
|
||||
make_ssf(SSF);
|
||||
/*global module */
|
||||
if(typeof module !== 'undefined' && typeof DO_NOT_EXPORT_SSF === 'undefined') module.exports = SSF;
|
||||
|
@ -2,7 +2,7 @@
|
||||
/* vim: set ts=2: */
|
||||
/*jshint -W041 */
|
||||
var SSF = ({});
|
||||
var make_ssf = function make_ssf(SSF){
|
||||
function make_ssf(SSF){
|
||||
SSF.version = '0.11.2';
|
||||
function _strrev(x) { var o = "", i = x.length-1; while(i>=0) o += x.charAt(i--); return o; }
|
||||
function fill(c,l) { var o = ""; while(o.length < l) o+=c; return o; }
|
||||
@ -159,6 +159,7 @@ function frac(x, D, mixed) {
|
||||
}
|
||||
function parse_date_code(v,opts,b2) {
|
||||
if(v > 2958465 || v < 0) return null;
|
||||
opts.b2 = b2 || false;
|
||||
var date = (v|0), time = Math.floor(86400 * (v - date)), dow=0;
|
||||
var dout=[];
|
||||
var out={D:date, T:time, u:86400*(v-date)-time,y:0,m:0,d:0,H:0,M:0,S:0,q:0};
|
||||
@ -197,10 +198,6 @@ function datenum_local(v, date1904) {
|
||||
else if(v >= base1904) epoch += 24*60*60*1000;
|
||||
return (epoch - (dnthresh + (v.getTimezoneOffset() - basedate.getTimezoneOffset()) * 60000)) / (24 * 60 * 60 * 1000);
|
||||
}
|
||||
/* The longest 32-bit integer text is "-4294967296", exactly 11 chars */
|
||||
function general_fmt_int(v) { return v.toString(10); }
|
||||
SSF._general_int = general_fmt_int;
|
||||
|
||||
/* ECMA-376 18.8.30 numFmt*/
|
||||
/* Note: `toPrecision` uses standard form when prec > E and E >= -6 */
|
||||
var general_fmt_num = (function make_general_fmt_num() {
|
||||
@ -253,6 +250,7 @@ SSF._general_num = general_fmt_num;
|
||||
- "up to 11 characters" displayed for numbers
|
||||
- Default date format (code 14) used for Dates
|
||||
|
||||
The longest 32-bit integer text is "-2147483648", exactly 11 chars
|
||||
TODO: technically the display depends on the width of the cell
|
||||
*/
|
||||
function general_fmt(v, opts) {
|
||||
@ -277,6 +275,7 @@ function fix_hijri(date, o) {
|
||||
}
|
||||
var THAI_DIGITS = "\u0E50\u0E51\u0E52\u0E53\u0E54\u0E55\u0E56\u0E57\u0E58\u0E59".split("");
|
||||
/*jshint -W086 */
|
||||
var ROUNDING_FLAG = "rounding is necessary"
|
||||
function write_date(type, fmt, val, ss0) {
|
||||
var o="", ss=0, tt=0, y = val.y, out, outl = 0;
|
||||
switch(type) {
|
||||
@ -322,7 +321,7 @@ function write_date(type, fmt, val, ss0) {
|
||||
if(ss0 >= 2) tt = ss0 === 3 ? 1000 : 100;
|
||||
else tt = ss0 === 1 ? 10 : 1;
|
||||
ss = Math.round((tt)*(val.S + val.u));
|
||||
if(ss >= 60*tt) ss = 0;
|
||||
if(ss >= 60*tt) throw ROUNDING_FLAG;
|
||||
if(fmt === 's') return ss === 0 ? "0" : ""+ss/tt;
|
||||
o = pad0(ss,2 + ss0);
|
||||
if(fmt === 'ss') return o.substr(0,2);
|
||||
@ -801,30 +800,7 @@ if(dt.u >= 0.5) { dt.u = 0; ++dt.S; }
|
||||
}
|
||||
|
||||
/* replace fields */
|
||||
var nstr = "", jj;
|
||||
for(i=0; i < out.length; ++i) {
|
||||
switch(out[i].t) {
|
||||
case 't': case 'T': case ' ': case 'D': break;
|
||||
case 'X': out[i].v = ""; out[i].t = ";"; break;
|
||||
case 'd': case 'm': case 'y': case 'h': case 'H': case 'M': case 's': case 'e': case 'b': case 'Z':
|
||||
out[i].v = write_date(out[i].t.charCodeAt(0), out[i].v, dt, ss0);
|
||||
out[i].t = 't'; break;
|
||||
case 'n': case '?':
|
||||
jj = i+1;
|
||||
while(out[jj] != null && (
|
||||
(c=out[jj].t) === "?" || c === "D" ||
|
||||
((c === " " || c === "t") && out[jj+1] != null && (out[jj+1].t === '?' || out[jj+1].t === "t" && out[jj+1].v === '/')) ||
|
||||
(out[i].t === '(' && (c === ' ' || c === 'n' || c === ')')) ||
|
||||
(c === 't' && (out[jj].v === '/' || out[jj].v === ' ' && out[jj+1] != null && out[jj+1].t == '?'))
|
||||
)) {
|
||||
out[i].v += out[jj].v;
|
||||
out[jj] = {v:"", t:";"}; ++jj;
|
||||
}
|
||||
nstr += out[i].v;
|
||||
i = jj-1; break;
|
||||
case 'G': out[i].t = 't'; out[i].v = general_fmt(v,opts); break;
|
||||
}
|
||||
}
|
||||
var {nstr,out} = replace_fields(out, dt, ss0, v, opts);
|
||||
var vv = "", myv, ostr;
|
||||
if(nstr.length > 0) {
|
||||
if(nstr.charCodeAt(0) == 40) /* '(' */ {
|
||||
@ -838,7 +814,7 @@ out[i].v = write_date(out[i].t.charCodeAt(0), out[i].v, dt, ss0);
|
||||
out[0].v = "-" + out[0].v;
|
||||
}
|
||||
}
|
||||
jj=ostr.length-1;
|
||||
var jj=ostr.length-1;
|
||||
var decpt = out.length;
|
||||
for(i=0; i < out.length; ++i) if(out[i] != null && out[i].t != 't' && out[i].v.indexOf(".") > -1) { decpt = i; break; }
|
||||
var lasti=out.length;
|
||||
@ -890,6 +866,84 @@ out[i].v = write_date(out[i].t.charCodeAt(0), out[i].v, dt, ss0);
|
||||
for(i=0; i !== out.length; ++i) if(out[i] != null) retval += out[i].v;
|
||||
return retval;
|
||||
}
|
||||
function replace_fields(fields, dt, ss0, v, opts) {
|
||||
var out = [];
|
||||
for (var i = 0; i < fields.length; i++) {out[i] = {t: fields[i].t, v: fields[i].v}}
|
||||
var nstr = "", jj;
|
||||
for(i=0; i < out.length; ++i) {
|
||||
switch(out[i].t) {
|
||||
case 't': case 'T': case ' ': case 'D': break;
|
||||
case 'X': out[i].v = ""; out[i].t = ";"; break;
|
||||
case 'd': case 'm': case 'y': case 'h': case 'H': case 'M': case 's': case 'e': case 'b': case 'Z':
|
||||
try {
|
||||
out[i].v = write_date(out[i].t.charCodeAt(0), out[i].v, dt, ss0);
|
||||
} catch (e) {
|
||||
if (e === ROUNDING_FLAG) {
|
||||
round_up_date(dt, opts);
|
||||
return replace_fields(fields, dt, ss0, v, opts);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
out[i].t = 't'; break;
|
||||
case 'n': case '?':
|
||||
jj = i+1;
|
||||
while(out[jj] != null && (
|
||||
(c=out[jj].t) === "?" || c === "D" ||
|
||||
((c === " " || c === "t") && out[jj+1] != null && (out[jj+1].t === '?' || out[jj+1].t === "t" && out[jj+1].v === '/')) ||
|
||||
(out[i].t === '(' && (c === ' ' || c === 'n' || c === ')')) ||
|
||||
(c === 't' && (out[jj].v === '/' || out[jj].v === ' ' && out[jj+1] != null && out[jj+1].t == '?'))
|
||||
)) {
|
||||
out[i].v += out[jj].v;
|
||||
out[jj] = {v:"", t:";"}; ++jj;
|
||||
}
|
||||
nstr += out[i].v;
|
||||
i = jj-1; break;
|
||||
case 'G': out[i].t = 't'; out[i].v = general_fmt(v,opts); break;
|
||||
}
|
||||
}
|
||||
return {nstr: nstr, out: out};
|
||||
}
|
||||
function round_up_date(out, opts) {
|
||||
if (!opts) opts = {};
|
||||
var tmp = new Date(out.y, out.m - 1, out.d, out.H, out.M, out.S);
|
||||
var oldDate = tmp.getDate();
|
||||
tmp.setSeconds(out.S + 1);
|
||||
var use1900 = !opts.date1904 && !opts.b2;
|
||||
if (tmp.getDate() !== oldDate) {
|
||||
if (out.D === 0 && use1900) {
|
||||
// 0 corresponds with Jan 0th, 1900
|
||||
out.y = 1900;
|
||||
out.m = 1;
|
||||
out.d = 1;
|
||||
out.q = (tmp.getDay() + 6) % 7;
|
||||
} else if (out.D === 60 && use1900) {
|
||||
// Excel & SSF have an intentional bug where they treat 1900 as a leap year
|
||||
// The 60th day (Feb 29) rounds up to Mar 1
|
||||
out.y = 1900;
|
||||
out.m = 3;
|
||||
out.d = 1;
|
||||
out.q = 4;
|
||||
} else if (out.D == 59 && use1900) {
|
||||
// Excel & SSF have an intentional bug where they treat 1900 as a leap year
|
||||
// The 59th day (Feb 28) rounds up to Feb 29
|
||||
out.y = 1900;
|
||||
out.m = 2;
|
||||
out.d = 29;
|
||||
out.q = 3;
|
||||
} else {
|
||||
out.y = tmp.getFullYear();
|
||||
out.m = tmp.getMonth() + 1;
|
||||
out.d = tmp.getDate();
|
||||
out.q = out.D < 60 && use1900 ? (tmp.getDay() + 6) % 7 : tmp.getDay();
|
||||
}
|
||||
out.D += 1;
|
||||
}
|
||||
out.H = tmp.getHours();
|
||||
out.M = tmp.getMinutes();
|
||||
out.S = tmp.getSeconds();
|
||||
out.u = 0;
|
||||
out.T += 1;
|
||||
}
|
||||
SSF._eval = eval_fmt;
|
||||
var cfregex = /\[[=<>]/;
|
||||
var cfregex2 = /\[(=|>[=]?|<[>=]?)(-?\d+(?:\.\d*)?)\]/;
|
||||
@ -971,7 +1025,8 @@ SSF.load_table = function load_table(tbl) {
|
||||
};
|
||||
SSF.init_table = init_table;
|
||||
SSF.format = format;
|
||||
};
|
||||
SSF.choose_format = choose_fmt;
|
||||
}
|
||||
make_ssf(SSF);
|
||||
/*global module */
|
||||
if(typeof module !== 'undefined' && typeof DO_NOT_EXPORT_SSF === 'undefined') module.exports = SSF;
|
||||
|
@ -12,21 +12,48 @@ function doit(data) {
|
||||
for(var j = 0; j <= 100; ++j) it(String(j), function() {
|
||||
for(var k = 0; k <= step; ++k,++i) {
|
||||
if(data[i] == null || data[i].length < 3) return;
|
||||
var d = data[i].replace(/#{255}/g,"").split("\t");
|
||||
for(var w = 1; w < headers.length; ++w) {
|
||||
var expected = d[w], actual = SSF.format(headers[w], parseFloat(d[0]), {});
|
||||
if(actual != expected) throw new Error([actual, expected, w, headers[w],d[0],d,i].join("|"));
|
||||
actual = SSF.format(headers[w].toUpperCase(), parseFloat(d[0]), {});
|
||||
if(actual != expected) throw new Error([actual, expected, w, headers[w].toUpperCase(),d[0],d,i].join("|"));
|
||||
}
|
||||
var row = data[i].replace(/#{255}/g,"").split("\t");
|
||||
testRow(row, headers, {})
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function testRow(row, headers, opts) {
|
||||
for(var w = 1; w < headers.length; ++w) {
|
||||
var expected = row[w], actual = SSF.format(headers[w], parseFloat(row[0]), opts);
|
||||
if(actual != expected) throw new Error([actual, expected, w, headers[w],row[0],row].join("|"));
|
||||
actual = SSF.format(headers[w].toUpperCase(), parseFloat(row[0]), opts);
|
||||
if(actual != expected) throw new Error([actual, expected, w, headers[w].toUpperCase(),row[0],row].join("|"));
|
||||
}
|
||||
}
|
||||
|
||||
describe('time formats', function() {
|
||||
doit(process.env.MINTEST ? times.slice(0,4000) : times);
|
||||
});
|
||||
|
||||
describe('time format rounding', function() {
|
||||
var headers=['value', 'yyyy mmm ddd dd hh:mm:ss'];
|
||||
var testCases = [
|
||||
{desc: "rounds up to 1 minute", value: "0.00069", date1904: {"false": "1900 Jan Sat 00 00:01:00", "true": "1904 Jan Fri 01 00:01:00"}},
|
||||
{desc: "rounds up to 2 munutes", value: "0.001388", date1904: {"false": "1900 Jan Sat 00 00:02:00", "true": "1904 Jan Fri 01 00:02:00"}},
|
||||
{desc: "rounds up to 10 minutes", value: "0.00694", date1904: {"false": "1900 Jan Sat 00 00:10:00", "true": "1904 Jan Fri 01 00:10:00"}},
|
||||
{desc: "rounds up to 2 hours", value: "0.08333", date1904: {"false": "1900 Jan Sat 00 02:00:00", "true": "1904 Jan Fri 01 02:00:00"}},
|
||||
{desc: "rounds up day", value: "0.999999", date1904: {"false": "1900 Jan Sun 01 00:00:00", "true": "1904 Jan Sat 02 00:00:00"}},
|
||||
{desc: "rounds up month", value: "31.999999", date1904: {"false": "1900 Feb Wed 01 00:00:00", "true": "1904 Feb Tue 02 00:00:00"}},
|
||||
{desc: "rounds up to 1900 leap day", value: "59.999999", date1904: {"false": "1900 Feb Wed 29 00:00:00", "true": "1904 Mar Tue 01 00:00:00"}},
|
||||
{desc: "rounds 1900 leap day up", value: "60.999999", date1904: {"false": "1900 Mar Thu 01 00:00:00", "true": "1904 Mar Wed 02 00:00:00"}},
|
||||
{desc: "rounds up day in March", value: "77.999999", date1904: {"false": "1900 Mar Sun 18 00:00:00", "true": "1904 Mar Sat 19 00:00:00"}},
|
||||
{desc: "rounds up leap year 1900", value: "366.999999", date1904: {"false": "1901 Jan Tue 01 00:00:00", "true": "1905 Jan Mon 02 00:00:00"}},
|
||||
{desc: "rounds up leap year 1904", value: "365.999999", date1904: {"false": "1900 Dec Mon 31 00:00:00", "true": "1905 Jan Sun 01 00:00:00"}},
|
||||
];
|
||||
[{date1904: true}, {date1904: false}].forEach(opts => {
|
||||
testCases.forEach(testCase => {
|
||||
it(testCase.desc + ` (1904: ${opts.date1904})`,
|
||||
() => testRow([testCase.value, testCase.date1904[`${opts.date1904}`]], headers, opts))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('date formats', function() {
|
||||
doit(process.env.MINTEST ? dates.slice(0,4000) : dates);
|
||||
if(0) doit(process.env.MINTEST ? date2.slice(0,1000) : date2);
|
||||
|
Loading…
Reference in New Issue
Block a user