From 80c1a0fec756eec3aac637791843ccdef6cc1c1a Mon Sep 17 00:00:00 2001 From: SheetJS Date: Fri, 28 Jul 2017 16:24:37 -0400 Subject: [PATCH] version bump 0.10.0: - pin dependencies - JS Date object support - resolved some out of bounds accesses - load scans for available index if not specified - flow improvements --- .travis.yml | 17 ++++ Makefile | 2 +- README.md | 16 +++- bits/00_header.js | 6 +- bits/01_version.js | 2 +- bits/10_opts.js | 10 --- bits/10_types.js | 3 + bits/35_datecode.js | 40 ++++++++++ bits/40_general.js | 10 ++- bits/45_hijri.js | 2 +- bits/50_date.js | 60 +++------------ bits/56_commaify.js | 7 +- bits/57_numhead.js | 2 +- bits/82_eval.js | 10 +-- bits/90_main.js | 3 +- bits/95_load.js | 17 ++++ bits/98_exports.js | 10 ++- bits/99_footer.js | 1 - index.html | 4 +- misc/flow.js | 26 ++++++- package.json | 10 +-- ssf.flow.js | 183 ++++++++++++++++++++++++-------------------- ssf.js | 162 +++++++++++++++++++++------------------ test/date.json | 22 ++++++ test/dates.js | 12 +++ test/general.js | 8 ++ 26 files changed, 393 insertions(+), 252 deletions(-) delete mode 100644 bits/10_opts.js create mode 100644 bits/10_types.js create mode 100644 bits/35_datecode.js create mode 100644 bits/95_load.js create mode 100644 test/date.json create mode 100644 test/dates.js diff --git a/.travis.yml b/.travis.yml index f31a7c7..0afdba9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,6 @@ language: node_js node_js: + - "8" - "7" - "6" - "5" @@ -8,6 +9,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/Makefile b/Makefile index 5ffb9b5..8d5e6de 100755 --- a/Makefile +++ b/Makefile @@ -51,7 +51,7 @@ travis: ## Run test suite with minimal output .PHONY: ctest ctest: ## Build browser test fixtures - browserify -t brfs test/{dateNF,exp,fraction,general,implied,oddities,utilities,comma,valid}.js > ctest/test.js + browserify -t brfs test/{dateNF,dates,exp,fraction,general,implied,oddities,utilities,comma,valid}.js > ctest/test.js .PHONY: ctestserv ctestserv: ## Start a test server on port 8000 diff --git a/README.md b/README.md index 54132fa..114b22a 100644 --- a/README.md +++ b/README.md @@ -29,9 +29,19 @@ define `DO_NOT_EXPORT_SSF`. ## Usage -`SSF.format(fmt, val, opts)` formats `val` using the format `fmt`. If `fmt` is -a string, it will be parsed and evaluated. If `fmt` is a `number`, the actual -format will be the corresponding entry in the internal format table. +`SSF.format(fmt, val, opts)` formats `val` using the format `fmt`. + +If `fmt` is a string, it will be parsed and evaluated. If `fmt` is a `number`, +the actual format will be the corresponding entry in the internal format table. +For a raw numeric format like `000`, the value should be passed as a string. + +Date arguments are interpreted in the local time of the JS client. + +The options argument may contain the following keys: + +| Option Name | Default | Description | +| :---------- | :-----: | :--------------------------------------------------- | +| date1904 | false | Use 1904 date system if true, 1900 system if false | ### Manipulating the Internal Format Table diff --git a/bits/00_header.js b/bits/00_header.js index cd5b3e3..f18c262 100644 --- a/bits/00_header.js +++ b/bits/00_header.js @@ -1,4 +1,6 @@ /* ssf.js (C) 2013-present SheetJS -- http://sheetjs.com */ +/* vim: set ts=2: */ /*jshint -W041 */ -var SSF = {}; -var make_ssf = function make_ssf(SSF){ +/*:: declare var DO_NOT_EXPORT_SSF: any; */ +var SSF/*:SSFModule*/ = ({}/*:any*/); +var make_ssf = function make_ssf(SSF/*:SSFModule*/){ diff --git a/bits/01_version.js b/bits/01_version.js index 9c32d95..2c1d4d2 100644 --- a/bits/01_version.js +++ b/bits/01_version.js @@ -1 +1 @@ -SSF.version = '0.9.4'; +SSF.version = '0.10.0'; diff --git a/bits/10_opts.js b/bits/10_opts.js deleted file mode 100644 index fec7177..0000000 --- a/bits/10_opts.js +++ /dev/null @@ -1,10 +0,0 @@ -/* Options */ -var opts_fmt/*:Array >*/ = [ - ["date1904", 0], - ["output", ""], - ["WTF", false] -]; -function fixopts(o){ - for(var y = 0; y != opts_fmt.length; ++y) if(o[opts_fmt[y][0]]===undefined) o[opts_fmt[y][0]]=opts_fmt[y][1]; -} -SSF.opts = opts_fmt; diff --git a/bits/10_types.js b/bits/10_types.js new file mode 100644 index 0000000..bbc1a92 --- /dev/null +++ b/bits/10_types.js @@ -0,0 +1,3 @@ +/*:: +type SSF_write_num = {(type:string, fmt:string, val:number):string}; +*/ diff --git a/bits/35_datecode.js b/bits/35_datecode.js new file mode 100644 index 0000000..e111e30 --- /dev/null +++ b/bits/35_datecode.js @@ -0,0 +1,40 @@ +function parse_date_code(v/*:number*/,opts/*:?any*/,b2/*:?boolean*/) { + if(v > 2958465 || v < 0) return null; + 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}; + if(Math.abs(out.u) < 1e-6) out.u = 0; + if(opts && opts.date1904) date += 1462; + if(out.u > 0.9999) { + out.u = 0; + if(++time == 86400) { out.T = time = 0; ++date; ++out.D; } + } + if(date === 60) {dout = b2 ? [1317,10,29] : [1900,2,29]; dow=3;} + else if(date === 0) {dout = b2 ? [1317,8,29] : [1900,1,0]; dow=6;} + else { + if(date > 60) --date; + /* 1 = Jan 1 1900 in Gregorian */ + var d = new Date(1900, 0, 1); + d.setDate(d.getDate() + date - 1); + dout = [d.getFullYear(), d.getMonth()+1,d.getDate()]; + dow = d.getDay(); + if(date < 60) dow = (dow + 6) % 7; + if(b2) dow = fix_hijri(d, dout); + } + out.y = dout[0]; out.m = dout[1]; out.d = dout[2]; + out.S = time % 60; time = Math.floor(time / 60); + out.M = time % 60; time = Math.floor(time / 60); + out.H = time; + out.q = dow; + return out; +} +SSF.parse_date_code = parse_date_code; +var basedate = new Date(1899, 11, 31, 0, 0, 0); +var dnthresh = basedate.getTime(); +var base1904 = new Date(1900, 2, 1, 0, 0, 0); +function datenum_local(v/*:Date*/, date1904/*:?boolean*/)/*:number*/ { + var epoch = v.getTime(); + if(date1904) epoch -= 1461*24*60*60*1000; + else if(v >= base1904) epoch += 24*60*60*1000; + return (epoch - (dnthresh + (v.getTimezoneOffset() - basedate.getTimezoneOffset()) * 60000)) / (24 * 60 * 60 * 1000); +} diff --git a/bits/40_general.js b/bits/40_general.js index c1e021b..67d1cb7 100644 --- a/bits/40_general.js +++ b/bits/40_general.js @@ -1,4 +1,4 @@ -function general_fmt_int(v/*:number*/)/*:string*/ { return ""+v; } +function general_fmt_int(v/*:number*/)/*:string*/ { return v.toString(10); } SSF._general_int = general_fmt_int; var general_fmt_num = (function make_general_fmt_num() { var gnr1 = /\.(\d*[1-9])0+$/, gnr2 = /\.0*$/, gnr4 = /\.(\d*[1-9])0+/, gnr5 = /\.0*[Ee]/, gnr6 = /(E[+-])(\d)$/; @@ -29,13 +29,15 @@ return function general_fmt_num(v/*:number*/)/*:string*/ { return gfn5(gfn4(o)); };})(); SSF._general_num = general_fmt_num; -function general_fmt(v/*:any*/) { +function general_fmt(v/*:any*/, opts/*:any*/) { switch(typeof v) { case 'string': return v; case 'boolean': return v ? "TRUE" : "FALSE"; - case 'number': return (v|0) === v ? general_fmt_int(v/*, opts*/) : general_fmt_num(v/*, opts*/); + case 'number': return (v|0) === v ? general_fmt_int(v) : general_fmt_num(v); case 'undefined': return ""; - case 'object': if(v == null) return ""; + case 'object': + if(v == null) return ""; + if(v instanceof Date) return format(14, datenum_local(v, opts && opts.date1904), opts); } throw new Error("unsupported value in General format: " + v); } diff --git a/bits/45_hijri.js b/bits/45_hijri.js index b22a59e..d577431 100644 --- a/bits/45_hijri.js +++ b/bits/45_hijri.js @@ -1 +1 @@ -function fix_hijri(/*date, o*/) { return 0; } +function fix_hijri(/*::date, o*/) { return 0; } diff --git a/bits/50_date.js b/bits/50_date.js index 2d8df8e..1ed11a2 100644 --- a/bits/50_date.js +++ b/bits/50_date.js @@ -1,35 +1,3 @@ -function parse_date_code(v/*:number*/,opts/*:?any*/,b2/*:?boolean*/) { - if(v > 2958465 || v < 0) return null; - 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}; - if(Math.abs(out.u) < 1e-6) out.u = 0; - fixopts(opts != null ? opts : (opts=[])); - if(opts.date1904) date += 1462; - if(out.u > 0.9999) { - out.u = 0; - if(++time == 86400) { out.T = time = 0; ++date; ++out.D; } - } - if(date === 60) {dout = b2 ? [1317,10,29] : [1900,2,29]; dow=3;} - else if(date === 0) {dout = b2 ? [1317,8,29] : [1900,1,0]; dow=6;} - else { - if(date > 60) --date; - /* 1 = Jan 1 1900 in Gregorian */ - var d = new Date(1900, 0, 1); - d.setDate(d.getDate() + date - 1); - dout = [d.getFullYear(), d.getMonth()+1,d.getDate()]; - dow = d.getDay(); - if(date < 60) dow = (dow + 6) % 7; - if(b2) dow = fix_hijri(d, dout); - } - out.y = dout[0]; out.m = dout[1]; out.d = dout[2]; - out.S = time % 60; time = Math.floor(time / 60); - out.M = time % 60; time = Math.floor(time / 60); - out.H = time; - out.q = dow; - return out; -} -SSF.parse_date_code = parse_date_code; /*jshint -W086 */ function write_date(type/*:number*/, fmt/*:string*/, val, ss0/*:?number*/)/*:string*/ { var o="", ss=0, tt=0, y = val.y, out, outl = 0; @@ -71,23 +39,17 @@ function write_date(type/*:number*/, fmt/*:string*/, val, ss0/*:?number*/)/*:str default: throw 'bad minute format: ' + fmt; } break; case 115: /* 's' seconds */ - if(val.u === 0) switch(fmt) { - case 's': case 'ss': return pad0(val.S, fmt.length); - case '.0': case '.00': case '.000': - } - switch(fmt) { - case 's': case 'ss': case '.0': case '.00': case '.000': - /*::if(!ss0) ss0 = 0; */ - 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(fmt === 's') return ss === 0 ? "0" : ""+ss/tt; - o = pad0(ss,2 + ss0); - if(fmt === 'ss') return o.substr(0,2); - return "." + o.substr(2,fmt.length-1); - default: throw 'bad second format: ' + fmt; - } + if(fmt != 's' && fmt != 'ss' && fmt != '.0' && fmt != '.00' && fmt != '.000') throw 'bad second format: ' + fmt; + if(val.u === 0 && (fmt == "s" || fmt == "ss")) return pad0(val.S, fmt.length); + /*::if(!ss0) ss0 = 0; */ + 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(fmt === 's') return ss === 0 ? "0" : ""+ss/tt; + o = pad0(ss,2 + ss0); + if(fmt === 'ss') return o.substr(0,2); + return "." + o.substr(2,fmt.length-1); case 90: /* 'Z' absolute time */ switch(fmt) { case '[h]': case '[hh]': out = val.D*24+val.H; break; diff --git a/bits/56_commaify.js b/bits/56_commaify.js index 249e1f3..f99ca9e 100644 --- a/bits/56_commaify.js +++ b/bits/56_commaify.js @@ -1,6 +1,7 @@ function commaify(s/*:string*/)/*:string*/ { - if(s.length <= 3) return s; - var j = (s.length % 3), o = s.substr(0,j); - for(; j!=s.length; j+=3) o+=(o.length > 0 ? "," : "") + s.substr(j,3); + var w = 3; + if(s.length <= w) return s; + var j = (s.length % w), o = s.substr(0,j); + for(; j!=s.length; j+=w) o+=(o.length > 0 ? "," : "") + s.substr(j,w); return o; } diff --git a/bits/57_numhead.js b/bits/57_numhead.js index 6452285..d20b8bd 100644 --- a/bits/57_numhead.js +++ b/bits/57_numhead.js @@ -1 +1 @@ -var write_num = (function make_write_num(){ +var write_num/*:SSF_write_num*/ = (function make_write_num(){ diff --git a/bits/82_eval.js b/bits/82_eval.js index c1e5f30..5ff3776 100644 --- a/bits/82_eval.js +++ b/bits/82_eval.js @@ -1,5 +1,5 @@ function eval_fmt(fmt/*:string*/, v/*:any*/, opts/*:any*/, flen/*:number*/) { - var out = [], o = "", i = 0, c = "", lst='t', q, dt, j, cc; + var out = [], o = "", i = 0, c = "", lst='t', dt, j, cc; var hr='H'; /* Tokenize */ while(i < fmt.length) { @@ -27,12 +27,12 @@ function eval_fmt(fmt/*:string*/, v/*:any*/, opts/*:any*/, flen/*:number*/) { case 'm': case 'd': case 'y': case 'h': case 's': case 'e': case 'g': if(v < 0) return ""; if(dt==null) { dt=parse_date_code(v, opts); if(dt==null) return ""; } - o = c; while(++i= 12 ? "P" : "A"; q.t = 'T'; hr='h';i+=3;} else if(fmt.substr(i,5).toUpperCase() === "AM/PM") { if(dt!=null) q.v = dt.H >= 12 ? "PM" : "AM"; q.t = 'T'; i+=5; hr='h'; } @@ -55,7 +55,7 @@ function eval_fmt(fmt/*:string*/, v/*:any*/, opts/*:any*/, flen/*:number*/) { /* Numbers */ case '.': if(dt != null) { - o = c; while((c=fmt.charAt(++i)) === "0") o += c; + o = c; while(++i < fmt.length && (c=fmt.charAt(i)) === "0") o += c; out[out.length] = {t:'s', v:o}; break; } /* falls through */ @@ -64,7 +64,7 @@ function eval_fmt(fmt/*:string*/, v/*:any*/, opts/*:any*/, flen/*:number*/) { out[out.length] = {t:'n', v:o}; break; case '?': o = c; while(fmt.charAt(++i) === c) o+=c; - q={t:c, v:o}; out[out.length] = q; lst = c; break; + out[out.length] = {t:c, v:o}; lst = c; break; case '*': ++i; if(fmt.charAt(i) == ' ' || fmt.charAt(i) == '*') ++i; break; // ** case '(': case ')': out[out.length] = {t:(flen===1?'t':c), v:c}; ++i; break; case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': diff --git a/bits/90_main.js b/bits/90_main.js index a1c7b72..87f81e7 100644 --- a/bits/90_main.js +++ b/bits/90_main.js @@ -1,4 +1,4 @@ -function choose_fmt(f/*:string*/, v) { +function choose_fmt(f/*:string*/, v/*:any*/) { var fmt = split_fmt(f); var l = fmt.length, lat = fmt[l-1].indexOf("@"); if(l<4 && lat>-1) --l; @@ -33,6 +33,7 @@ function format(fmt/*:string|number*/,v/*:any*/,o/*:?any*/) { break; } if(isgeneral(sfmt,0)) return general_fmt(v, o); + if(v instanceof Date) v = datenum_local(v, o.date1904); var f = choose_fmt(sfmt, v); if(isgeneral(f[1])) return general_fmt(v, o); if(v === true) v = "TRUE"; else if(v === false) v = "FALSE"; diff --git a/bits/95_load.js b/bits/95_load.js new file mode 100644 index 0000000..d8dbe27 --- /dev/null +++ b/bits/95_load.js @@ -0,0 +1,17 @@ +function load_entry(fmt/*:string*/, idx/*:?number*/)/*:number*/ { + if(typeof idx != 'number') { + idx = +idx || -1; +/*::if(typeof idx != 'number') return 0x188; */ + for(var i = 0; i < 0x0188; ++i) { +/*::if(typeof idx != 'number') return 0x188; */ + if(table_fmt[i] == undefined) { if(idx < 0) idx = i; continue; } + if(table_fmt[i] == fmt) { idx = i; break; } + } +/*::if(typeof idx != 'number') return 0x188; */ + if(idx < 0) idx = 0x187; + } +/*::if(typeof idx != 'number') return 0x188; */ + table_fmt[idx] = fmt; + return idx; +} +SSF.load = load_entry; diff --git a/bits/98_exports.js b/bits/98_exports.js index f086c80..bb3dd02 100644 --- a/bits/98_exports.js +++ b/bits/98_exports.js @@ -1,6 +1,8 @@ SSF._table = table_fmt; -SSF.load = function load_entry(fmt/*:string*/, idx/*:number*/) { table_fmt[idx] = fmt; }; -SSF.format = format; -SSF.get_table = function get_table() { return table_fmt; }; -SSF.load_table = function load_table(tbl/*:{[n:number]:string}*/) { for(var i=0; i!=0x0188; ++i) if(tbl[i] !== undefined) SSF.load(tbl[i], i); }; +SSF.get_table = function get_table()/*:SSFTable*/ { return table_fmt; }; +SSF.load_table = function load_table(tbl/*:SSFTable*/)/*:void*/ { + for(var i=0; i!=0x0188; ++i) + if(tbl[i] !== undefined) load_entry(tbl[i], i); +}; SSF.init_table = init_table; +SSF.format = format; diff --git a/bits/99_footer.js b/bits/99_footer.js index 036c6e1..d52d65e 100644 --- a/bits/99_footer.js +++ b/bits/99_footer.js @@ -1,5 +1,4 @@ }; make_ssf(SSF); /*global module */ -/*:: declare var DO_NOT_EXPORT_SSF: any; */ if(typeof module !== 'undefined' && typeof DO_NOT_EXPORT_SSF === 'undefined') module.exports = SSF; diff --git a/index.html b/index.html index ac821b0..13ec075 100644 --- a/index.html +++ b/index.html @@ -26,9 +26,9 @@ a { text-decoration: none }