From 885b27fda56249e3b859c48176874d434788cdd2 Mon Sep 17 00:00:00 2001 From: SheetJS Date: Wed, 17 Jun 2020 01:13:59 -0400 Subject: [PATCH] version bump 0.11.1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - support 上午/下午 (like AM/PM, supported in en-US and other locales) - `format(number)` guess format if table is missing value - removed entry 65535 from table --- bits/01_version.js | 2 +- bits/25_table.js | 1 - bits/26_defaults.js | 55 +++++++++++++++++++++++++++ bits/45_hijri.js | 8 +++- bits/49_thaidigits.js | 1 + bits/60_number.js | 5 ++- bits/81_fmttype.js | 5 ++- bits/82_eval.js | 9 ++++- bits/90_main.js | 2 + package.json | 2 +- ssf.flow.js | 88 +++++++++++++++++++++++++++++++++++++++---- ssf.js | 88 +++++++++++++++++++++++++++++++++++++++---- test/dateNF.js | 34 +++++++++++++++++ test/negative.js | 2 +- 14 files changed, 276 insertions(+), 26 deletions(-) create mode 100644 bits/26_defaults.js create mode 100644 bits/49_thaidigits.js diff --git a/bits/01_version.js b/bits/01_version.js index dd68d9f..e4eb92e 100644 --- a/bits/01_version.js +++ b/bits/01_version.js @@ -1 +1 @@ -SSF.version = '0.11.0'; +SSF.version = '0.11.1'; diff --git a/bits/25_table.js b/bits/25_table.js index 01644ba..b1f3247 100644 --- a/bits/25_table.js +++ b/bits/25_table.js @@ -28,7 +28,6 @@ function init_table(t/*:any*/) { t[48]= '##0.0E+0'; t[49]= '@'; t[56]= '"上午/下午 "hh"時"mm"分"ss"秒 "'; - t[65535]= 'General'; } var table_fmt = {}; diff --git a/bits/26_defaults.js b/bits/26_defaults.js new file mode 100644 index 0000000..eb817fd --- /dev/null +++ b/bits/26_defaults.js @@ -0,0 +1,55 @@ +/* Defaults determined by systematically testing in Excel 2019 */ + +/* These formats appear to default to other formats in the table */ +var default_map/*:Array*/ = []; +var defi = 0; + +// 5 -> 37 ... 8 -> 40 +for(defi = 5; defi <= 8; ++defi) default_map[defi] = 32 + defi; + +// 23 -> 0 ... 26 -> 0 +for(defi = 23; defi <= 26; ++defi) default_map[defi] = 0; + +// 27 -> 14 ... 31 -> 14 +for(defi = 27; defi <= 31; ++defi) default_map[defi] = 14; +// 50 -> 14 ... 58 -> 14 +for(defi = 50; defi <= 58; ++defi) default_map[defi] = 14; + +// 59 -> 1 ... 62 -> 4 +for(defi = 59; defi <= 62; ++defi) default_map[defi] = defi - 58; +// 67 -> 9 ... 68 -> 10 +for(defi = 67; defi <= 68; ++defi) default_map[defi] = defi - 58; +// 72 -> 14 ... 75 -> 17 +for(defi = 72; defi <= 75; ++defi) default_map[defi] = defi - 58; + +// 69 -> 12 ... 71 -> 14 +for(defi = 67; defi <= 68; ++defi) default_map[defi] = defi - 57; + +// 76 -> 20 ... 78 -> 22 +for(defi = 76; defi <= 78; ++defi) default_map[defi] = defi - 56; + +// 79 -> 45 ... 81 -> 47 +for(defi = 79; defi <= 81; ++defi) default_map[defi] = defi - 34; + +// 82 -> 0 ... 65536 -> 0 (omitted) + +/* These formats technically refer to Accounting formats with no equivalent */ +var default_str/*:Array*/ = []; + +// 5 -- Currency, 0 decimal, black negative +default_str[5] = default_str[63] = '"$"#,##0_);\\("$"#,##0\\)'; +// 6 -- Currency, 0 decimal, red negative +default_str[6] = default_str[64] = '"$"#,##0_);[Red]\\("$"#,##0\\)'; +// 7 -- Currency, 2 decimal, black negative +default_str[7] = default_str[65] = '"$"#,##0.00_);\\("$"#,##0.00\\)'; +// 8 -- Currency, 2 decimal, red negative +default_str[8] = default_str[66] = '"$"#,##0.00_);[Red]\\("$"#,##0.00\\)'; + +// 41 -- Accounting, 0 decimal, No Symbol +default_str[41] = '_(* #,##0_);_(* \\(#,##0\\);_(* "-"_);_(@_)'; +// 42 -- Accounting, 0 decimal, $ Symbol +default_str[42] = '_("$"* #,##0_);_("$"* \\(#,##0\\);_("$"* "-"_);_(@_)'; +// 43 -- Accounting, 2 decimal, No Symbol +default_str[43] = '_(* #,##0.00_);_(* \\(#,##0.00\\);_(* "-"??_);_(@_)'; +// 44 -- Accounting, 2 decimal, $ Symbol +default_str[44] = '_("$"* #,##0.00_);_("$"* \\(#,##0.00\\);_("$"* "-"??_);_(@_)'; diff --git a/bits/45_hijri.js b/bits/45_hijri.js index d577431..7b7fa00 100644 --- a/bits/45_hijri.js +++ b/bits/45_hijri.js @@ -1 +1,7 @@ -function fix_hijri(/*::date, o*/) { return 0; } +function fix_hijri(date/*:Date*/, o/*:[number, number, number]*/) { + /* TODO: properly adjust y/m/d and */ + o[0] -= 581; + var dow = date.getDay(); + if(date < 60) dow = (dow + 6) % 7; + return dow; +} diff --git a/bits/49_thaidigits.js b/bits/49_thaidigits.js new file mode 100644 index 0000000..d7a247c --- /dev/null +++ b/bits/49_thaidigits.js @@ -0,0 +1 @@ +var THAI_DIGITS = "\u0E50\u0E51\u0E52\u0E53\u0E54\u0E55\u0E56\u0E57\u0E58\u0E59".split(""); diff --git a/bits/60_number.js b/bits/60_number.js index 5d13dfc..f978a05 100644 --- a/bits/60_number.js +++ b/bits/60_number.js @@ -64,4 +64,7 @@ function carry(val/*:number*/, d/*:number*/)/*:number*/ { } return 0; } -function flr(val/*:number*/)/*:string*/ { if(val < 2147483647 && val > -2147483648) return ""+(val >= 0 ? (val|0) : (val-1|0)); return ""+Math.floor(val); } +function flr(val/*:number*/)/*:string*/ { + if(val < 2147483647 && val > -2147483648) return ""+(val >= 0 ? (val|0) : (val-1|0)); + return ""+Math.floor(val); +} diff --git a/bits/81_fmttype.js b/bits/81_fmttype.js index 8afe408..812f863 100644 --- a/bits/81_fmttype.js +++ b/bits/81_fmttype.js @@ -1,4 +1,4 @@ -var abstime = /\[[HhMmSs]*\]/; +var abstime = /\[[HhMmSs\u0E0A\u0E19\u0E17]*\]/; function fmt_is_date(fmt/*:string*/)/*:boolean*/ { var i = 0, /*cc = 0,*/ c = "", o = ""; while(i < fmt.length) { @@ -14,9 +14,10 @@ function fmt_is_date(fmt/*:string*/)/*:boolean*/ { case 'M': case 'D': case 'Y': case 'H': case 'S': case 'E': /* falls through */ case 'm': case 'd': case 'y': case 'h': case 's': case 'e': case 'g': return true; - case 'A': case 'a': + case 'A': case 'a': case '上': if(fmt.substr(i, 3).toUpperCase() === "A/P") return true; if(fmt.substr(i, 5).toUpperCase() === "AM/PM") return true; + if(fmt.substr(i, 5).toUpperCase() === "上午/下午") return true; ++i; break; case '[': o = c; diff --git a/bits/82_eval.js b/bits/82_eval.js index f6aa85d..47a02c8 100644 --- a/bits/82_eval.js +++ b/bits/82_eval.js @@ -31,11 +31,12 @@ function eval_fmt(fmt/*:string*/, v/*:any*/, opts/*:any*/, flen/*:number*/) { 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': + case 'A': case 'a': case '上': 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'; } + else if(fmt.substr(i,5).toUpperCase() === "上午/下午") { if(dt!=null) q.v = dt.H >= 12 ? "下午" : "上午"; q.t = 'T'; i+=5; hr='h'; } else { q.t = "t"; ++i; } if(dt==null && q.t === 'T') return ""; out[out.length] = q; lst = c; break; @@ -71,12 +72,14 @@ function eval_fmt(fmt/*:string*/, v/*:any*/, opts/*:any*/, flen/*:number*/) { o = c; while(i < fmt.length && "0123456789".indexOf(fmt.charAt(++i)) > -1) o+=fmt.charAt(i); out[out.length] = {t:'D', v:o}; break; case ' ': out[out.length] = {t:c, v:c}; ++i; break; - case "$": out[out.length] = {t:'t', v:'$'}; ++i; break; + case '$': out[out.length] = {t:'t', v:'$'}; ++i; break; default: if(",$-+/():!^&'~{}<>=€acfijklopqrtuvwxzP".indexOf(c) === -1) throw new Error('unrecognized character ' + c + ' in ' + fmt); out[out.length] = {t:'t', v:c}; ++i; break; } } + + /* Scan for date/time parts */ var bt = 0, ss0 = 0, ssm; for(i=out.length-1, lst='t'; i >= 0; --i) { switch(out[i].t) { @@ -95,6 +98,7 @@ function eval_fmt(fmt/*:string*/, v/*:any*/, opts/*:any*/, flen/*:number*/) { if(bt < 3 && out[i].v.match(/[Ss]/)) bt = 3; } } + /* time rounding depends on presence of minute / second / usec fields */ switch(bt) { case 0: break; case 1: @@ -109,6 +113,7 @@ function eval_fmt(fmt/*:string*/, v/*:any*/, opts/*:any*/, flen/*:number*/) { if(dt.S >= 60) { dt.S = 0; ++dt.M; } break; } + /* replace fields */ var nstr = "", jj; for(i=0; i < out.length; ++i) { diff --git a/bits/90_main.js b/bits/90_main.js index 87f81e7..5637775 100644 --- a/bits/90_main.js +++ b/bits/90_main.js @@ -30,6 +30,8 @@ function format(fmt/*:string|number*/,v/*:any*/,o/*:?any*/) { case "number": if(fmt == 14 && o.dateNF) sfmt = o.dateNF; else sfmt = (o.table != null ? (o.table/*:any*/) : table_fmt)[fmt]; + if(sfmt == null) sfmt = (o.table && o.table[default_map[fmt]]) || table_fmt[default_map[fmt]]; + if(sfmt == null) sfmt = default_str[fmt] || "General"; break; } if(isgeneral(sfmt,0)) return general_fmt(v, o); diff --git a/package.json b/package.json index a984dcd..df0e1ac 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ssf", - "version": "0.11.0", + "version": "0.11.1", "author": "sheetjs", "description": "Format data using ECMA-376 spreadsheet Format Codes", "keywords": [ diff --git a/ssf.flow.js b/ssf.flow.js index 6adb6d0..0face2a 100644 --- a/ssf.flow.js +++ b/ssf.flow.js @@ -4,7 +4,7 @@ /*:: declare var DO_NOT_EXPORT_SSF: any; */ var SSF/*:SSFModule*/ = ({}/*:any*/); var make_ssf = function make_ssf(SSF/*:SSFModule*/){ -SSF.version = '0.11.0'; +SSF.version = '0.11.1'; 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;} @@ -71,11 +71,65 @@ function init_table(t/*:any*/) { t[48]= '##0.0E+0'; t[49]= '@'; t[56]= '"上午/下午 "hh"時"mm"分"ss"秒 "'; - t[65535]= 'General'; } var table_fmt = {}; init_table(table_fmt); +/* Defaults determined by systematically testing in Excel 2019 */ + +/* These formats appear to default to other formats in the table */ +var default_map/*:Array*/ = []; +var defi = 0; + +// 5 -> 37 ... 8 -> 40 +for(defi = 5; defi <= 8; ++defi) default_map[defi] = 32 + defi; + +// 23 -> 0 ... 26 -> 0 +for(defi = 23; defi <= 26; ++defi) default_map[defi] = 0; + +// 27 -> 14 ... 31 -> 14 +for(defi = 27; defi <= 31; ++defi) default_map[defi] = 14; +// 50 -> 14 ... 58 -> 14 +for(defi = 50; defi <= 58; ++defi) default_map[defi] = 14; + +// 59 -> 1 ... 62 -> 4 +for(defi = 59; defi <= 62; ++defi) default_map[defi] = defi - 58; +// 67 -> 9 ... 68 -> 10 +for(defi = 67; defi <= 68; ++defi) default_map[defi] = defi - 58; +// 72 -> 14 ... 75 -> 17 +for(defi = 72; defi <= 75; ++defi) default_map[defi] = defi - 58; + +// 69 -> 12 ... 71 -> 14 +for(defi = 67; defi <= 68; ++defi) default_map[defi] = defi - 57; + +// 76 -> 20 ... 78 -> 22 +for(defi = 76; defi <= 78; ++defi) default_map[defi] = defi - 56; + +// 79 -> 45 ... 81 -> 47 +for(defi = 79; defi <= 81; ++defi) default_map[defi] = defi - 34; + +// 82 -> 0 ... 65536 -> 0 (omitted) + +/* These formats technically refer to Accounting formats with no equivalent */ +var default_str/*:Array*/ = []; + +// 5 -- Currency, 0 decimal, black negative +default_str[5] = default_str[63] = '"$"#,##0_);\\("$"#,##0\\)'; +// 6 -- Currency, 0 decimal, red negative +default_str[6] = default_str[64] = '"$"#,##0_);[Red]\\("$"#,##0\\)'; +// 7 -- Currency, 2 decimal, black negative +default_str[7] = default_str[65] = '"$"#,##0.00_);\\("$"#,##0.00\\)'; +// 8 -- Currency, 2 decimal, red negative +default_str[8] = default_str[66] = '"$"#,##0.00_);[Red]\\("$"#,##0.00\\)'; + +// 41 -- Accounting, 0 decimal, No Symbol +default_str[41] = '_(* #,##0_);_(* \\(#,##0\\);_(* "-"_);_(@_)'; +// 42 -- Accounting, 0 decimal, $ Symbol +default_str[42] = '_("$"* #,##0_);_("$"* \\(#,##0\\);_("$"* "-"_);_(@_)'; +// 43 -- Accounting, 2 decimal, No Symbol +default_str[43] = '_(* #,##0.00_);_(* \\(#,##0.00\\);_(* "-"??_);_(@_)'; +// 44 -- Accounting, 2 decimal, $ Symbol +default_str[44] = '_("$"* #,##0.00_);_("$"* \\(#,##0.00\\);_("$"* "-"??_);_(@_)'; function frac(x/*:number*/, D/*:number*/, mixed/*:?boolean*/)/*:Array*/ { var sgn = x < 0 ? -1 : 1; var B = x * sgn; @@ -180,7 +234,14 @@ function general_fmt(v/*:any*/, opts/*:any*/) { throw new Error("unsupported value in General format: " + v); } SSF._general = general_fmt; -function fix_hijri(/*::date, o*/) { return 0; } +function fix_hijri(date/*:Date*/, o/*:[number, number, number]*/) { + /* TODO: properly adjust y/m/d and */ + o[0] -= 581; + var dow = date.getDay(); + if(date < 60) dow = (dow + 6) % 7; + return dow; +} +var THAI_DIGITS = "\u0E50\u0E51\u0E52\u0E53\u0E54\u0E55\u0E56\u0E57\u0E58\u0E59".split(""); /*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; @@ -325,7 +386,10 @@ function carry(val/*:number*/, d/*:number*/)/*:number*/ { } return 0; } -function flr(val/*:number*/)/*:string*/ { if(val < 2147483647 && val > -2147483648) return ""+(val >= 0 ? (val|0) : (val-1|0)); return ""+Math.floor(val); } +function flr(val/*:number*/)/*:string*/ { + if(val < 2147483647 && val > -2147483648) return ""+(val >= 0 ? (val|0) : (val-1|0)); + return ""+Math.floor(val); +} function write_num_flt(type/*:string*/, fmt/*:string*/, val/*:number*/)/*:string*/ { if(type.charCodeAt(0) === 40 && !fmt.match(closeparen)) { var ffmt = fmt.replace(/\( */,"").replace(/ \)/,"").replace(/\)/,""); @@ -548,7 +612,7 @@ function split_fmt(fmt/*:string*/)/*:Array*/ { return out; } SSF._split = split_fmt; -var abstime = /\[[HhMmSs]*\]/; +var abstime = /\[[HhMmSs\u0E0A\u0E19\u0E17]*\]/; function fmt_is_date(fmt/*:string*/)/*:boolean*/ { var i = 0, /*cc = 0,*/ c = "", o = ""; while(i < fmt.length) { @@ -564,9 +628,10 @@ function fmt_is_date(fmt/*:string*/)/*:boolean*/ { case 'M': case 'D': case 'Y': case 'H': case 'S': case 'E': /* falls through */ case 'm': case 'd': case 'y': case 'h': case 's': case 'e': case 'g': return true; - case 'A': case 'a': + case 'A': case 'a': case '上': if(fmt.substr(i, 3).toUpperCase() === "A/P") return true; if(fmt.substr(i, 5).toUpperCase() === "AM/PM") return true; + if(fmt.substr(i, 5).toUpperCase() === "上午/下午") return true; ++i; break; case '[': o = c; @@ -623,11 +688,12 @@ function eval_fmt(fmt/*:string*/, v/*:any*/, opts/*:any*/, flen/*:number*/) { 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': + case 'A': case 'a': case '上': 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'; } + else if(fmt.substr(i,5).toUpperCase() === "上午/下午") { if(dt!=null) q.v = dt.H >= 12 ? "下午" : "上午"; q.t = 'T'; i+=5; hr='h'; } else { q.t = "t"; ++i; } if(dt==null && q.t === 'T') return ""; out[out.length] = q; lst = c; break; @@ -663,12 +729,14 @@ function eval_fmt(fmt/*:string*/, v/*:any*/, opts/*:any*/, flen/*:number*/) { o = c; while(i < fmt.length && "0123456789".indexOf(fmt.charAt(++i)) > -1) o+=fmt.charAt(i); out[out.length] = {t:'D', v:o}; break; case ' ': out[out.length] = {t:c, v:c}; ++i; break; - case "$": out[out.length] = {t:'t', v:'$'}; ++i; break; + case '$': out[out.length] = {t:'t', v:'$'}; ++i; break; default: if(",$-+/():!^&'~{}<>=€acfijklopqrtuvwxzP".indexOf(c) === -1) throw new Error('unrecognized character ' + c + ' in ' + fmt); out[out.length] = {t:'t', v:c}; ++i; break; } } + + /* Scan for date/time parts */ var bt = 0, ss0 = 0, ssm; for(i=out.length-1, lst='t'; i >= 0; --i) { switch(out[i].t) { @@ -687,6 +755,7 @@ function eval_fmt(fmt/*:string*/, v/*:any*/, opts/*:any*/, flen/*:number*/) { if(bt < 3 && out[i].v.match(/[Ss]/)) bt = 3; } } + /* time rounding depends on presence of minute / second / usec fields */ switch(bt) { case 0: break; case 1: @@ -701,6 +770,7 @@ function eval_fmt(fmt/*:string*/, v/*:any*/, opts/*:any*/, flen/*:number*/) { if(dt.S >= 60) { dt.S = 0; ++dt.M; } break; } + /* replace fields */ var nstr = "", jj; for(i=0; i < out.length; ++i) { @@ -840,6 +910,8 @@ function format(fmt/*:string|number*/,v/*:any*/,o/*:?any*/) { case "number": if(fmt == 14 && o.dateNF) sfmt = o.dateNF; else sfmt = (o.table != null ? (o.table/*:any*/) : table_fmt)[fmt]; + if(sfmt == null) sfmt = (o.table && o.table[default_map[fmt]]) || table_fmt[default_map[fmt]]; + if(sfmt == null) sfmt = default_str[fmt] || "General"; break; } if(isgeneral(sfmt,0)) return general_fmt(v, o); diff --git a/ssf.js b/ssf.js index 0e4c1c4..9f251a2 100644 --- a/ssf.js +++ b/ssf.js @@ -3,7 +3,7 @@ /*jshint -W041 */ var SSF = ({}); var make_ssf = function make_ssf(SSF){ -SSF.version = '0.11.0'; +SSF.version = '0.11.1'; 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;} @@ -67,11 +67,65 @@ function init_table(t) { t[48]= '##0.0E+0'; t[49]= '@'; t[56]= '"上午/下午 "hh"時"mm"分"ss"秒 "'; - t[65535]= 'General'; } var table_fmt = {}; init_table(table_fmt); +/* Defaults determined by systematically testing in Excel 2019 */ + +/* These formats appear to default to other formats in the table */ +var default_map = []; +var defi = 0; + +// 5 -> 37 ... 8 -> 40 +for(defi = 5; defi <= 8; ++defi) default_map[defi] = 32 + defi; + +// 23 -> 0 ... 26 -> 0 +for(defi = 23; defi <= 26; ++defi) default_map[defi] = 0; + +// 27 -> 14 ... 31 -> 14 +for(defi = 27; defi <= 31; ++defi) default_map[defi] = 14; +// 50 -> 14 ... 58 -> 14 +for(defi = 50; defi <= 58; ++defi) default_map[defi] = 14; + +// 59 -> 1 ... 62 -> 4 +for(defi = 59; defi <= 62; ++defi) default_map[defi] = defi - 58; +// 67 -> 9 ... 68 -> 10 +for(defi = 67; defi <= 68; ++defi) default_map[defi] = defi - 58; +// 72 -> 14 ... 75 -> 17 +for(defi = 72; defi <= 75; ++defi) default_map[defi] = defi - 58; + +// 69 -> 12 ... 71 -> 14 +for(defi = 67; defi <= 68; ++defi) default_map[defi] = defi - 57; + +// 76 -> 20 ... 78 -> 22 +for(defi = 76; defi <= 78; ++defi) default_map[defi] = defi - 56; + +// 79 -> 45 ... 81 -> 47 +for(defi = 79; defi <= 81; ++defi) default_map[defi] = defi - 34; + +// 82 -> 0 ... 65536 -> 0 (omitted) + +/* These formats technically refer to Accounting formats with no equivalent */ +var default_str = []; + +// 5 -- Currency, 0 decimal, black negative +default_str[5] = default_str[63] = '"$"#,##0_);\\("$"#,##0\\)'; +// 6 -- Currency, 0 decimal, red negative +default_str[6] = default_str[64] = '"$"#,##0_);[Red]\\("$"#,##0\\)'; +// 7 -- Currency, 2 decimal, black negative +default_str[7] = default_str[65] = '"$"#,##0.00_);\\("$"#,##0.00\\)'; +// 8 -- Currency, 2 decimal, red negative +default_str[8] = default_str[66] = '"$"#,##0.00_);[Red]\\("$"#,##0.00\\)'; + +// 41 -- Accounting, 0 decimal, No Symbol +default_str[41] = '_(* #,##0_);_(* \\(#,##0\\);_(* "-"_);_(@_)'; +// 42 -- Accounting, 0 decimal, $ Symbol +default_str[42] = '_("$"* #,##0_);_("$"* \\(#,##0\\);_("$"* "-"_);_(@_)'; +// 43 -- Accounting, 2 decimal, No Symbol +default_str[43] = '_(* #,##0.00_);_(* \\(#,##0.00\\);_(* "-"??_);_(@_)'; +// 44 -- Accounting, 2 decimal, $ Symbol +default_str[44] = '_("$"* #,##0.00_);_("$"* \\(#,##0.00\\);_("$"* "-"??_);_(@_)'; function frac(x, D, mixed) { var sgn = x < 0 ? -1 : 1; var B = x * sgn; @@ -176,7 +230,14 @@ function general_fmt(v, opts) { throw new Error("unsupported value in General format: " + v); } SSF._general = general_fmt; -function fix_hijri() { return 0; } +function fix_hijri(date, o) { + /* TODO: properly adjust y/m/d and */ + o[0] -= 581; + var dow = date.getDay(); + if(date < 60) dow = (dow + 6) % 7; + return dow; +} +var THAI_DIGITS = "\u0E50\u0E51\u0E52\u0E53\u0E54\u0E55\u0E56\u0E57\u0E58\u0E59".split(""); /*jshint -W086 */ function write_date(type, fmt, val, ss0) { var o="", ss=0, tt=0, y = val.y, out, outl = 0; @@ -320,7 +381,10 @@ function carry(val, d) { } return 0; } -function flr(val) { if(val < 2147483647 && val > -2147483648) return ""+(val >= 0 ? (val|0) : (val-1|0)); return ""+Math.floor(val); } +function flr(val) { + if(val < 2147483647 && val > -2147483648) return ""+(val >= 0 ? (val|0) : (val-1|0)); + return ""+Math.floor(val); +} function write_num_flt(type, fmt, val) { if(type.charCodeAt(0) === 40 && !fmt.match(closeparen)) { var ffmt = fmt.replace(/\( */,"").replace(/ \)/,"").replace(/\)/,""); @@ -541,7 +605,7 @@ function split_fmt(fmt) { return out; } SSF._split = split_fmt; -var abstime = /\[[HhMmSs]*\]/; +var abstime = /\[[HhMmSs\u0E0A\u0E19\u0E17]*\]/; function fmt_is_date(fmt) { var i = 0, /*cc = 0,*/ c = "", o = ""; while(i < fmt.length) { @@ -557,9 +621,10 @@ function fmt_is_date(fmt) { case 'M': case 'D': case 'Y': case 'H': case 'S': case 'E': /* falls through */ case 'm': case 'd': case 'y': case 'h': case 's': case 'e': case 'g': return true; - case 'A': case 'a': + case 'A': case 'a': case '上': if(fmt.substr(i, 3).toUpperCase() === "A/P") return true; if(fmt.substr(i, 5).toUpperCase() === "AM/PM") return true; + if(fmt.substr(i, 5).toUpperCase() === "上午/下午") return true; ++i; break; case '[': o = c; @@ -616,11 +681,12 @@ function eval_fmt(fmt, v, opts, flen) { 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': + case 'A': case 'a': case '上': 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'; } + else if(fmt.substr(i,5).toUpperCase() === "上午/下午") { if(dt!=null) q.v = dt.H >= 12 ? "下午" : "上午"; q.t = 'T'; i+=5; hr='h'; } else { q.t = "t"; ++i; } if(dt==null && q.t === 'T') return ""; out[out.length] = q; lst = c; break; @@ -656,12 +722,14 @@ function eval_fmt(fmt, v, opts, flen) { o = c; while(i < fmt.length && "0123456789".indexOf(fmt.charAt(++i)) > -1) o+=fmt.charAt(i); out[out.length] = {t:'D', v:o}; break; case ' ': out[out.length] = {t:c, v:c}; ++i; break; - case "$": out[out.length] = {t:'t', v:'$'}; ++i; break; + case '$': out[out.length] = {t:'t', v:'$'}; ++i; break; default: if(",$-+/():!^&'~{}<>=€acfijklopqrtuvwxzP".indexOf(c) === -1) throw new Error('unrecognized character ' + c + ' in ' + fmt); out[out.length] = {t:'t', v:c}; ++i; break; } } + + /* Scan for date/time parts */ var bt = 0, ss0 = 0, ssm; for(i=out.length-1, lst='t'; i >= 0; --i) { switch(out[i].t) { @@ -680,6 +748,7 @@ function eval_fmt(fmt, v, opts, flen) { if(bt < 3 && out[i].v.match(/[Ss]/)) bt = 3; } } + /* time rounding depends on presence of minute / second / usec fields */ switch(bt) { case 0: break; case 1: @@ -692,6 +761,7 @@ if(dt.u >= 0.5) { dt.u = 0; ++dt.S; } if(dt.S >= 60) { dt.S = 0; ++dt.M; } break; } + /* replace fields */ var nstr = "", jj; for(i=0; i < out.length; ++i) { @@ -830,6 +900,8 @@ function format(fmt,v,o) { case "number": if(fmt == 14 && o.dateNF) sfmt = o.dateNF; else sfmt = (o.table != null ? (o.table) : table_fmt)[fmt]; + if(sfmt == null) sfmt = (o.table && o.table[default_map[fmt]]) || table_fmt[default_map[fmt]]; + if(sfmt == null) sfmt = default_str[fmt] || "General"; break; } if(isgeneral(sfmt,0)) return general_fmt(v, o); diff --git a/test/dateNF.js b/test/dateNF.js index 17a9dd6..5b6685f 100644 --- a/test/dateNF.js +++ b/test/dateNF.js @@ -1,5 +1,6 @@ /* vim: set ts=2: */ /*jshint loopfunc:true, mocha:true, node:true */ +/*eslint-env mocha, node */ var SSF = require('../'); var assert = require('assert'); describe('dateNF override', function() { @@ -14,3 +15,36 @@ describe('dateNF override', function() { assert.equal(SSF.format('m/d/yy', 43880, {dateNF:"dd/mm/yyyy"}), "19/02/2020"); }); }); +describe('asian formats', function() { + it('上午/下午 (AM/PM)', function() { + assert.equal(SSF.format('上午/下午', 0), '上午'); + assert.equal(SSF.format('上午/下午', 0.25), '上午'); + assert.equal(SSF.format('上午/下午', 0.49), '上午'); + assert.equal(SSF.format('上午/下午', 0.5), '下午'); + assert.equal(SSF.format('上午/下午', 0.51), '下午'); + assert.equal(SSF.format('上午/下午', 0.99), '下午'); + assert.equal(SSF.format('上午/下午', 1), '上午'); + }); + it('bb (buddhist)', function() { + [ + [12345, + [ 'yyyy', '1933'], + [ 'eeee', '1933'], + [ 'bbbb', '2476'], + //[ 'ปปปป', '๒๔๗๖'], + [ 'b2yyyy', '1352'], + [ 'b2eeee', '1352'], + [ 'b2bbbb', '1895'], + //[ 'b2ปปปป', '๑๘๙๕'] + ] + ].forEach(function(row) { + row.slice(1).forEach(function(fmt) { + assert.equal(SSF.format(fmt[0], row[0]), fmt[1]); + }); + }); + }); + it.skip('thai fields', function() { + SSF.format('\u0E27/\u0E14/\u0E1B\u0E1B\u0E1B\u0E1B \u0E0A\u0E0A:\u0E19\u0E19:\u0E17\u0E17', 12345.67); + assert.equal(SSF.format('\u0E27/\u0E14/\u0E1B\u0E1B\u0E1B\u0E1B \u0E0A\u0E0A:\u0E19\u0E19:\u0E17\u0E17', 12345.67), "๑๘/๑๐/๒๔๗๖ ๑๖:๐๔:๔๘"); + }); +}); diff --git a/test/negative.js b/test/negative.js index c2c95c5..cde17ca 100644 --- a/test/negative.js +++ b/test/negative.js @@ -1,6 +1,6 @@ /* vim: set ts=2: */ /*jshint loopfunc:true, mocha:true, node:true */ -/*eslint-env mocha */ +/*eslint-env mocha, node */ var SSF = require('../'); var assert = require('assert');