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<Array<any> >*/ = [ - ["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<fmt.length && fmt.charAt(i).toLowerCase() === c) o+=c; + o = c; while(++i < fmt.length && fmt.charAt(i).toLowerCase() === c) o+=c; if(c === 'm' && lst.toLowerCase() === 'h') c = 'M'; if(c === 'h') c = hr; out[out.length] = {t:c, v:o}; lst = c; break; case 'A': case 'a': - q={t:c, v:c}; + var q={t:c, v:c}; if(dt==null) dt=parse_date_code(v, opts); if(fmt.substr(i, 3).toUpperCase() === "A/P") { if(dt!=null) q.v = dt.H >= 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 } <script src="ssf.js"></script> <script> /*jshint browser:true */ -/*global frac */ -var V = document.getElementById('val'); +/*global SSF */ var F = document.getElementById('fmt'); +var V = document.getElementById('val'); var N = document.getElementById('num'); var T = document.getElementById('txt'); diff --git a/misc/flow.js b/misc/flow.js index b81c296..3572e3c 100644 --- a/misc/flow.js +++ b/misc/flow.js @@ -1,9 +1,31 @@ /*# vim: set ts=2: */ /*:: +type SSFTable = {[key:number|string]:string}; +type SSFDate = { + D:number; T:number; + y:number; m:number; d:number; q:number; + H:number; M:number; S:number; u:number; +}; + type SSFModule = { - format(fmt:string|number, v:any, o:any):string; - is_date(fmt:string):boolean; + format:(fmt:string|number, v:any, o:any)=>string; + + is_date:(fmt:string)=>boolean; + parse_date_code:(v:number,opts:any)=>?SSFDate; + + load:(fmt:string, idx:?number)=>number; + get_table:()=>SSFTable; + load_table:(table:any)=>void; + _table:SSFTable; + init_table:any; + + _general_int:(v:number)=>string; + _general_num:(v:number)=>string; + _general:(v:number, o:?any)=>string; + _eval:any; + _split:any; + version:string; }; */ diff --git a/package.json b/package.json index f0e3cd9..e88e961 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ssf", - "version": "0.9.4", + "version": "0.10.0", "author": "SheetJS", "description": "Format data using ECMA-376 spreadsheet Format Codes", "keywords": [ "format", "sprintf", "spreadsheet" ], @@ -9,13 +9,13 @@ }, "main": "./ssf", "dependencies": { - "voc":"", - "colors":"0.6.2", + "voc":"~1.0.0", "frac":"~1.0.6" }, "devDependencies": { - "mocha":"", - "@sheetjs/uglify-js":"" + "mocha":"~2.5.3", + "blanket": "~1.2.3", + "@sheetjs/uglify-js":"~2.7.3" }, "repository": { "type":"git", "url":"git://github.com/SheetJS/ssf.git" }, "scripts": { diff --git a/ssf.flow.js b/ssf.flow.js index 7ba1a52..48c79b4 100644 --- a/ssf.flow.js +++ b/ssf.flow.js @@ -1,8 +1,10 @@ /* ssf.js (C) 2013-present SheetJS -- http://sheetjs.com */ +/* vim: set ts=2: */ /*jshint -W041 */ -var SSF = {}; -var make_ssf = function make_ssf(SSF){ -SSF.version = '0.9.4'; +/*:: declare var DO_NOT_EXPORT_SSF: any; */ +var SSF/*:SSFModule*/ = ({}/*:any*/); +var make_ssf = function make_ssf(SSF/*:SSFModule*/){ +SSF.version = '0.10.0'; 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; } function pad0(v/*:any*/,d/*:number*/)/*:string*/{var t=""+v; return t.length>=d?t:fill('0',d-t.length)+t;} @@ -13,16 +15,9 @@ function pad0r2(v/*:any*/,d/*:number*/)/*:string*/{var t=""+v; return t.length>= var p2_32 = Math.pow(2,32); function pad0r(v/*:any*/,d/*:number*/)/*:string*/{if(v>p2_32||v<-p2_32) return pad0r1(v,d); var i = Math.round(v); return pad0r2(i,d); } function isgeneral(s/*:string*/, i/*:?number*/)/*:boolean*/ { i = i || 0; return s.length >= 7 + i && (s.charCodeAt(i)|32) === 103 && (s.charCodeAt(i+1)|32) === 101 && (s.charCodeAt(i+2)|32) === 110 && (s.charCodeAt(i+3)|32) === 101 && (s.charCodeAt(i+4)|32) === 114 && (s.charCodeAt(i+5)|32) === 97 && (s.charCodeAt(i+6)|32) === 108; } -/* Options */ -var opts_fmt/*:Array<Array<any> >*/ = [ - ["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; +/*:: +type SSF_write_num = {(type:string, fmt:string, val:number):string}; +*/ var days/*:Array<Array<string> >*/ = [ ['Sun', 'Sunday'], ['Mon', 'Monday'], @@ -101,7 +96,47 @@ function frac(x/*:number*/, D/*:number*/, mixed/*:?boolean*/)/*:Array<number>*/ var q = Math.floor(sgn * P/Q); return [q, sgn*P - q*Q, Q]; } -function general_fmt_int(v/*:number*/)/*:string*/ { return ""+v; } +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); +} +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)$/; @@ -132,50 +167,20 @@ 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); } SSF._general = general_fmt; -function fix_hijri(/*date, o*/) { return 0; } -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; +function fix_hijri(/*::date, o*/) { return 0; } /*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; @@ -217,23 +222,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; @@ -248,12 +247,13 @@ function write_date(type/*:number*/, fmt/*:string*/, val, ss0/*:?number*/)/*:str } /*jshint +W086 */ 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; } -var write_num = (function make_write_num(){ +var write_num/*:SSF_write_num*/ = (function make_write_num(){ var pct1 = /%/g; function write_num_pct(type/*:string*/, fmt/*:string*/, val/*:number*/)/*:string*/{ var sfmt = fmt.replace(pct1,""), mul = fmt.length - sfmt.length; @@ -591,7 +591,7 @@ function fmt_is_date(fmt/*:string*/)/*:boolean*/ { } SSF.is_date = fmt_is_date; 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) { @@ -619,12 +619,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<fmt.length && fmt.charAt(i).toLowerCase() === c) o+=c; + o = c; while(++i < fmt.length && fmt.charAt(i).toLowerCase() === c) o+=c; if(c === 'm' && lst.toLowerCase() === 'h') c = 'M'; if(c === 'h') c = hr; out[out.length] = {t:c, v:o}; lst = c; break; case 'A': case 'a': - q={t:c, v:c}; + var q={t:c, v:c}; if(dt==null) dt=parse_date_code(v, opts); if(fmt.substr(i, 3).toUpperCase() === "A/P") { if(dt!=null) q.v = dt.H >= 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'; } @@ -647,7 +647,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 */ @@ -656,7 +656,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': @@ -807,7 +807,7 @@ function chkcond(v, rr) { } return false; } -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; @@ -842,20 +842,39 @@ 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"; else if(v === "" || v == null) return ""; return eval_fmt(f[1], v, o, f[0]); } +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; 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; }; 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/ssf.js b/ssf.js index 4c77d22..e1896d6 100644 --- a/ssf.js +++ b/ssf.js @@ -1,8 +1,9 @@ /* ssf.js (C) 2013-present SheetJS -- http://sheetjs.com */ +/* vim: set ts=2: */ /*jshint -W041 */ -var SSF = {}; +var SSF = ({}); var make_ssf = function make_ssf(SSF){ -SSF.version = '0.9.4'; +SSF.version = '0.10.0'; 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; } function pad0(v,d){var t=""+v; return t.length>=d?t:fill('0',d-t.length)+t;} @@ -13,16 +14,6 @@ function pad0r2(v,d){var t=""+v; return t.length>=d?t:fill('0',d-t.length)+t;} var p2_32 = Math.pow(2,32); function pad0r(v,d){if(v>p2_32||v<-p2_32) return pad0r1(v,d); var i = Math.round(v); return pad0r2(i,d); } function isgeneral(s, i) { i = i || 0; return s.length >= 7 + i && (s.charCodeAt(i)|32) === 103 && (s.charCodeAt(i+1)|32) === 101 && (s.charCodeAt(i+2)|32) === 110 && (s.charCodeAt(i+3)|32) === 101 && (s.charCodeAt(i+4)|32) === 114 && (s.charCodeAt(i+5)|32) === 97 && (s.charCodeAt(i+6)|32) === 108; } -/* Options */ -var opts_fmt = [ - ["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; var days = [ ['Sun', 'Sunday'], ['Mon', 'Monday'], @@ -101,7 +92,47 @@ function frac(x, D, mixed) { var q = Math.floor(sgn * P/Q); return [q, sgn*P - q*Q, Q]; } -function general_fmt_int(v) { return ""+v; } +function parse_date_code(v,opts,b2) { + 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, date1904) { + 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); +} +function general_fmt_int(v) { 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)$/; @@ -132,50 +163,20 @@ return function general_fmt_num(v) { return gfn5(gfn4(o)); };})(); SSF._general_num = general_fmt_num; -function general_fmt(v) { +function general_fmt(v, opts) { 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); } SSF._general = general_fmt; -function fix_hijri(/*date, o*/) { return 0; } -function parse_date_code(v,opts,b2) { - 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; +function fix_hijri() { return 0; } /*jshint -W086 */ function write_date(type, fmt, val, ss0) { var o="", ss=0, tt=0, y = val.y, out, outl = 0; @@ -217,22 +218,16 @@ function write_date(type, fmt, val, ss0) { 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(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 >= 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; - } + 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; @@ -247,9 +242,10 @@ if(ss0 >= 2) tt = ss0 === 3 ? 1000 : 100; } /*jshint +W086 */ function commaify(s) { - 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; } var write_num = (function make_write_num(){ @@ -588,7 +584,7 @@ function fmt_is_date(fmt) { } SSF.is_date = fmt_is_date; function eval_fmt(fmt, v, opts, flen) { - 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) { @@ -616,12 +612,12 @@ function eval_fmt(fmt, v, opts, flen) { 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<fmt.length && fmt.charAt(i).toLowerCase() === c) o+=c; + o = c; while(++i < fmt.length && fmt.charAt(i).toLowerCase() === c) o+=c; if(c === 'm' && lst.toLowerCase() === 'h') c = 'M'; if(c === 'h') c = hr; out[out.length] = {t:c, v:o}; lst = c; break; case 'A': case 'a': - q={t:c, v:c}; + var q={t:c, v:c}; if(dt==null) dt=parse_date_code(v, opts); if(fmt.substr(i, 3).toUpperCase() === "A/P") { if(dt!=null) q.v = dt.H >= 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'; } @@ -644,7 +640,7 @@ function eval_fmt(fmt, v, opts, flen) { /* 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 */ @@ -653,7 +649,7 @@ function eval_fmt(fmt, v, opts, flen) { 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': @@ -836,18 +832,34 @@ function format(fmt,v,o) { 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"; else if(v === "" || v == null) return ""; return eval_fmt(f[1], v, o, f[0]); } +function load_entry(fmt, idx) { + if(typeof idx != 'number') { + idx = +idx || -1; +for(var i = 0; i < 0x0188; ++i) { +if(table_fmt[i] == undefined) { if(idx < 0) idx = i; continue; } + if(table_fmt[i] == fmt) { idx = i; break; } + } +if(idx < 0) idx = 0x187; + } +table_fmt[idx] = fmt; + return idx; +} +SSF.load = load_entry; SSF._table = table_fmt; -SSF.load = function load_entry(fmt, idx) { table_fmt[idx] = fmt; }; -SSF.format = format; SSF.get_table = function get_table() { return table_fmt; }; -SSF.load_table = function load_table(tbl) { for(var i=0; i!=0x0188; ++i) if(tbl[i] !== undefined) SSF.load(tbl[i], i); }; +SSF.load_table = function load_table(tbl) { + for(var i=0; i!=0x0188; ++i) + if(tbl[i] !== undefined) load_entry(tbl[i], i); +}; SSF.init_table = init_table; +SSF.format = format; }; make_ssf(SSF); /*global module */ diff --git a/test/date.json b/test/date.json new file mode 100644 index 0000000..f3e0c19 --- /dev/null +++ b/test/date.json @@ -0,0 +1,22 @@ +[ + ["new Date(1900,0,1)", "1900-01-01 00:00:00", false], + ["new Date(1900,1,1)", "1900-02-01 00:00:00", false], + ["new Date(1900,2,1)", "1900-03-01 00:00:00", false], + ["new Date(1900,3,1)", "1900-04-01 00:00:00", false], + ["new Date(2000,0,1)", "2000-01-01 00:00:00", false], + ["new Date(2000,1,1)", "2000-02-01 00:00:00", false], + ["new Date(2000,2,1)", "2000-03-01 00:00:00", false], + ["new Date(2000,3,1)", "2000-04-01 00:00:00", false], + ["new Date(2000,0,1)", "2000-01-01 00:00:00", true], + ["new Date(2000,1,3)", "2000-02-03 00:00:00", true], + ["new Date(2000,2,5)", "2000-03-05 00:00:00", true], + ["new Date(2000,3,7)", "2000-04-07 00:00:00", true], + ["new Date(2100,0,1)", "2100-01-01 00:00:00", false], + ["new Date(2100,1,1)", "2100-02-01 00:00:00", false], + ["new Date(2100,2,1)", "2100-03-01 00:00:00", false], + ["new Date(2100,3,1)", "2100-04-01 00:00:00", false], + ["new Date(2100,0,1)", "2100-01-01 00:00:00", true], + ["new Date(2100,1,3)", "2100-02-03 00:00:00", true], + ["new Date(2100,2,5)", "2100-03-05 00:00:00", true], + ["new Date(2100,3,7)", "2100-04-07 00:00:00", true] +] diff --git a/test/dates.js b/test/dates.js new file mode 100644 index 0000000..1630b12 --- /dev/null +++ b/test/dates.js @@ -0,0 +1,12 @@ +/* vim: set ts=2: */ +/*jshint -W041 */ +/*jshint loopfunc:true, mocha:true, node:true */ +var SSF = require('../'); +var fs = require('fs'), assert = require('assert'); +var data = JSON.parse(fs.readFileSync('./test/date.json','utf8')); + +describe('date values', function() { + it('should roundtrip dates', function() { data.forEach(function(d) { + assert.equal(SSF.format("yyyy-mm-dd HH:MM:SS", eval(d[0]), {date1904:!!d[2]}), d[1]); + }); }); +}); diff --git a/test/general.js b/test/general.js index b17d5dd..7b06f95 100644 --- a/test/general.js +++ b/test/general.js @@ -15,4 +15,12 @@ describe('General format', function() { assert.equal(SSF.format("General", undefined), ""); assert.equal(SSF.format("General", null), ""); }); + it('should handle dates', function() { + assert.equal(SSF.format("General", new Date(2017, 1, 19)), "2/19/17"); + assert.equal(SSF.format("General", new Date(2017, 1, 19), {date1904:true}), "2/19/17"); + assert.equal(SSF.format("General", new Date(1901, 0, 1)), "1/1/01"); + if(SSF.format("General", new Date(1901, 0, 1), {date1904:true}) == "1/1/01") throw new Error("date1904 invalid date"); + assert.equal(SSF.format("General", new Date(1904, 0, 1)), "1/1/04"); + assert.equal(SSF.format("General", new Date(1904, 0, 1), {date1904:true}), "1/1/04"); + }); });