diff --git a/packages/ssf/bits/50_date.js b/packages/ssf/bits/50_date.js index 3c66dba..3627376 100644 --- a/packages/ssf/bits/50_date.js +++ b/packages/ssf/bits/50_date.js @@ -55,7 +55,7 @@ function write_date(type/*:number*/, fmt/*:string*/, val, ss0/*:?number*/)/*:str switch(fmt) { case '[h]': case '[hh]': out = val.D*24+val.H; break; case '[m]': case '[mm]': out = (val.D*24+val.H)*60+val.M; break; - case '[s]': case '[ss]': out = ((val.D*24+val.H)*60+val.M)*60+Math.round(val.S+val.u); break; + case '[s]': case '[ss]': out = ((val.D*24+val.H)*60+val.M)*60+(ss0 < 1 ? Math.round(val.S+val.u) : val.S); break; default: throw 'bad abstime format: ' + fmt; } outl = fmt.length === 3 ? 1 : 2; break; case 101: /* 'e' era */ diff --git a/packages/ssf/bits/82_eval.js b/packages/ssf/bits/82_eval.js index 9d87f9f..feace2f 100644 --- a/packages/ssf/bits/82_eval.js +++ b/packages/ssf/bits/82_eval.js @@ -99,23 +99,14 @@ function eval_fmt(fmt/*:string*/, v/*:any*/, opts/*:any*/, flen/*:number*/) { } } /* time rounding depends on presence of minute / second / usec fields */ - switch(bt) { - case 0: break; - case 1: - /*::if(!dt) break;*/ - if(dt.u >= 0.5) { dt.u = 0; ++dt.S; } - if(dt.S >= 60) { dt.S = 0; ++dt.M; } - if(dt.M >= 60) { dt.M = 0; ++dt.H; } - break; - case 2: - /*::if(!dt) break;*/ - if(dt.u >= 0.5) { dt.u = 0; ++dt.S; } - if(dt.S >= 60) { dt.S = 0; ++dt.M; } - break; + if (bt > 0 && bt < 3 && dt.u >= 0.5) { + round_up_date(dt, opts); } /* replace fields */ - var {nstr,out} = replace_fields(out, dt, ss0, v, opts); + var replaced = replace_fields(out, dt, ss0, v, opts); + var nstr = replaced.nstr; + out = replaced.out; var vv = "", myv, ostr; if(nstr.length > 0) { if(nstr.charCodeAt(0) == 40) /* '(' */ { @@ -183,7 +174,7 @@ function eval_fmt(fmt/*:string*/, v/*:any*/, opts/*:any*/, flen/*:number*/) { } 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}} + 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) { diff --git a/packages/ssf/ssf.flow.js b/packages/ssf/ssf.flow.js index 03d8809..511ac1f 100644 --- a/packages/ssf/ssf.flow.js +++ b/packages/ssf/ssf.flow.js @@ -335,7 +335,7 @@ function write_date(type/*:number*/, fmt/*:string*/, val, ss0/*:?number*/)/*:str switch(fmt) { case '[h]': case '[hh]': out = val.D*24+val.H; break; case '[m]': case '[mm]': out = (val.D*24+val.H)*60+val.M; break; - case '[s]': case '[ss]': out = ((val.D*24+val.H)*60+val.M)*60+Math.round(val.S+val.u); break; + case '[s]': case '[ss]': out = ((val.D*24+val.H)*60+val.M)*60+(ss0 < 1 ? Math.round(val.S+val.u) : val.S); break; default: throw 'bad abstime format: ' + fmt; } outl = fmt.length === 3 ? 1 : 2; break; case 101: /* 'e' era */ @@ -793,23 +793,14 @@ function eval_fmt(fmt/*:string*/, v/*:any*/, opts/*:any*/, flen/*:number*/) { } } /* time rounding depends on presence of minute / second / usec fields */ - switch(bt) { - case 0: break; - case 1: - /*::if(!dt) break;*/ - if(dt.u >= 0.5) { dt.u = 0; ++dt.S; } - if(dt.S >= 60) { dt.S = 0; ++dt.M; } - if(dt.M >= 60) { dt.M = 0; ++dt.H; } - break; - case 2: - /*::if(!dt) break;*/ - if(dt.u >= 0.5) { dt.u = 0; ++dt.S; } - if(dt.S >= 60) { dt.S = 0; ++dt.M; } - break; + if (bt > 0 && bt < 3 && dt.u >= 0.5) { + round_up_date(dt, opts); } /* replace fields */ - var {nstr,out} = replace_fields(out, dt, ss0, v, opts); + var replaced = replace_fields(out, dt, ss0, v, opts); + var nstr = replaced.nstr; + out = replaced.out; var vv = "", myv, ostr; if(nstr.length > 0) { if(nstr.charCodeAt(0) == 40) /* '(' */ { @@ -877,7 +868,7 @@ function eval_fmt(fmt/*:string*/, v/*:any*/, opts/*:any*/, flen/*:number*/) { } 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}} + 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) { diff --git a/packages/ssf/ssf.js b/packages/ssf/ssf.js index 5189668..e35ca57 100644 --- a/packages/ssf/ssf.js +++ b/packages/ssf/ssf.js @@ -330,7 +330,7 @@ if(ss0 >= 2) tt = ss0 === 3 ? 1000 : 100; switch(fmt) { case '[h]': case '[hh]': out = val.D*24+val.H; break; case '[m]': case '[mm]': out = (val.D*24+val.H)*60+val.M; break; - case '[s]': case '[ss]': out = ((val.D*24+val.H)*60+val.M)*60+Math.round(val.S+val.u); break; + case '[s]': case '[ss]': out = ((val.D*24+val.H)*60+val.M)*60+(ss0 < 1 ? Math.round(val.S+val.u) : val.S); break; default: throw 'bad abstime format: ' + fmt; } outl = fmt.length === 3 ? 1 : 2; break; case 101: /* 'e' era */ @@ -786,21 +786,14 @@ function eval_fmt(fmt, v, opts, flen) { } } /* time rounding depends on presence of minute / second / usec fields */ - switch(bt) { - case 0: break; - case 1: -if(dt.u >= 0.5) { dt.u = 0; ++dt.S; } - if(dt.S >= 60) { dt.S = 0; ++dt.M; } - if(dt.M >= 60) { dt.M = 0; ++dt.H; } - break; - case 2: -if(dt.u >= 0.5) { dt.u = 0; ++dt.S; } - if(dt.S >= 60) { dt.S = 0; ++dt.M; } - break; + if (bt > 0 && bt < 3 && dt.u >= 0.5) { + round_up_date(dt, opts); } /* replace fields */ - var {nstr,out} = replace_fields(out, dt, ss0, v, opts); + var replaced = replace_fields(out, dt, ss0, v, opts); + var nstr = replaced.nstr; + out = replaced.out; var vv = "", myv, ostr; if(nstr.length > 0) { if(nstr.charCodeAt(0) == 40) /* '(' */ { @@ -868,7 +861,7 @@ if(dt.u >= 0.5) { dt.u = 0; ++dt.S; } } 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}} + 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) { diff --git a/packages/ssf/test/date.js b/packages/ssf/test/date.js index c12c274..5b5aaa3 100644 --- a/packages/ssf/test/date.js +++ b/packages/ssf/test/date.js @@ -49,10 +49,34 @@ describe('time format rounding', function() { [{date1904: true}, {date1904: false}].forEach(opts => { testCases.forEach(testCase => { it(testCase.desc + ` (1904: ${opts.date1904})`, - () => testRow([testCase.value, testCase.date1904[`${opts.date1904}`]], headers, opts)) - }) - }) -}) + () => testRow([testCase.value, testCase.date1904[`${opts.date1904}`]], headers, opts)); + }); + }); +}); + +describe('time format precision rounding', function() { + var value = "4018.99999998843"; + var testCases = [ + {desc: "end-of-year thousandths rounding", format: "mm/dd/yyyy hh:mm:ss.000", expected: "12/31/1910 23:59:59.999"}, + {desc: "end-of-year hundredths round up", format: "mm/dd/yyyy hh:mm:ss.00", expected: "01/01/1911 00:00:00.00"}, + {desc: "end-of-year minutes round up", format: "mm/dd/yyyy hh:mm", expected: "01/01/1911 00:00"}, + {desc: "hour duration thousandths rounding", format: "[hh]:mm:ss.000", expected: "96455:59:59.999"}, + {desc: "hour duration hundredths round up", format: "[hh]:mm:ss.00", expected: "96456:00:00.00"}, + {desc: "hour duration minute round up (w/ ss)", format: "[hh]:mm:ss", expected: "96456:00:00"}, + {desc: "hour duration minute round up", format: "[hh]:mm", expected: "96456:00"}, + {desc: "hour duration round up", format: "[hh]", expected: "96456"}, + {desc: "minute duration thousandths rounding", format: "[mm]:ss.000", expected: "5787359:59.999"}, + {desc: "minute duration hundredths round up", format: "[mm]:ss.00", expected: "5787360:00.00"}, + {desc: "minute duration round up", format: "[mm]:ss", expected: "5787360:00"}, + {desc: "second duration thousandths rounding", format: "[ss].000", expected: "347241599.999"}, + {desc: "second duration hundredths round up", format: "[ss].00", expected: "347241600.00"}, + {desc: "second duration round up", format: "[ss]", expected: "347241600"}, + ]; + testCases.forEach(testCase => { + var headers = ["value", testCase.format]; + it(testCase.desc, () => {testRow([value, testCase.expected], headers, {})}); + }); +}); describe('date formats', function() { doit(process.env.MINTEST ? dates.slice(0,4000) : dates);