ssf v0.11.3

This commit is contained in:
SheetJS 2023-10-23 21:22:11 -04:00
parent c8e6d5c20f
commit cd5fafda32
10 changed files with 119 additions and 71 deletions

View File

@ -59,7 +59,7 @@ ctest: ## Build browser test fixtures
.PHONY: ctestserv
ctestserv: ## Start a test server on port 8000
@cd ctest && python -mSimpleHTTPServer
@cd ctest && python -mSimpleHTTPServer || python3 -mhttp.server || npx -y http-server -p 8000 .
## Code Checking

View File

@ -8,16 +8,14 @@ features like international support as well as dedicated support.
## Installation
With [npm](https://www.npmjs.org/package/ssf):
```bash
$ npm install ssf
$ npm install https://cdn.sheetjs.com/ssf-0.11.3/ssf-0.11.3.tgz
```
In the browser:
```html
<script src="ssf.js"></script>
<script src="https://cdn.sheetjs.com/ssf-0.11.3/ssf.js"></script>
```
The browser exposes a variable `SSF`
@ -107,13 +105,3 @@ granted by the Apache 2.0 license are reserved by the Original Author.
## Badges
[![Sauce Test Status](https://saucelabs.com/browser-matrix/ssfjs.svg)](https://saucelabs.com/u/ssfjs)
[![Build Status](https://travis-ci.org/SheetJS/ssf.svg?branch=master)](https://travis-ci.org/SheetJS/ssf)
[![Coverage Status](http://img.shields.io/coveralls/SheetJS/ssf/master.svg)](https://coveralls.io/r/SheetJS/ssf?branch=master)
[![NPM Downloads](https://img.shields.io/npm/dt/ssf.svg)](https://npmjs.org/package/ssf)
[![Dependencies Status](https://david-dm.org/sheetjs/ssf/status.svg)](https://david-dm.org/sheetjs/ssf)
[![Analytics](https://ga-beacon.appspot.com/UA-36810333-1/SheetJS/ssf?pixel)](https://github.com/SheetJS/ssf)

View File

@ -1 +1 @@
SSF.version = '0.11.2';
SSF.version = '0.11.3';

View File

@ -1,5 +1,17 @@
function normalize_xl_unsafe(v/*:number*/)/*:number*/ {
var s = v.toPrecision(16);
if(s.indexOf("e") > -1) {
var m = s.slice(0, s.indexOf("e"));
m = m.indexOf(".") > -1 ? m.slice(0, (m.slice(0,2) == "0." ? 17 : 16)) : (m.slice(0,15) + fill("0", m.length - 15));
return m + s.slice(s.indexOf("e"));
}
var n = s.indexOf(".") > -1 ? s.slice(0, (s.slice(0,2) == "0." ? 17 : 16)) : (s.slice(0,15) + fill("0", s.length - 15));
return Number(n);
}
function parse_date_code(v/*:number*/,opts/*:?any*/,b2/*:?boolean*/) {
if(v > 2958465 || v < 0) return null;
v = normalize_xl_unsafe(v);
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};

View File

@ -54,7 +54,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 == 0 ? 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 */

View File

@ -85,10 +85,11 @@ function eval_fmt(fmt/*:string*/, v/*:any*/, opts/*:any*/, flen/*:number*/) {
switch(out[i].t) {
case 'h': case 'H': out[i].t = hr; lst='h'; if(bt < 1) bt = 1; break;
case 's':
if((ssm=out[i].v.match(/\.0+$/))) ss0=Math.max(ss0,ssm[0].length-1);
if((ssm=out[i].v.match(/\.0+$/))) { ss0=Math.max(ss0,ssm[0].length-1); bt = 4;}
if(bt < 3) bt = 3;
/* falls through */
case 'd': case 'y': case 'M': case 'e': lst=out[i].t; break;
case 'd': case 'y': case 'e': lst=out[i].t; break;
case 'M': lst=out[i].t; if(bt < 2) bt = 2; break;
case 'm': if(lst === 's') { out[i].t = 'M'; if(bt < 2) bt = 2; } break;
case 'X': /*if(out[i].v === "B2");*/
break;
@ -98,19 +99,29 @@ 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 */
var _dt;
switch(bt) {
case 0: break;
case 1:
/*::if(!dt) break;*/
case 2:
case 3:
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; }
if(dt.H >= 24) { dt.H = 0; ++dt.D; _dt = parse_date_code(dt.D); _dt.u = dt.u; _dt.S = dt.S; _dt.M = dt.M; _dt.H = dt.H; dt = _dt; }
break;
case 2:
/*::if(!dt) break;*/
if(dt.u >= 0.5) { dt.u = 0; ++dt.S; }
case 4:
switch(ss0) {
case 1: dt.u = Math.round(dt.u * 10)/10; break;
case 2: dt.u = Math.round(dt.u * 100)/100; break;
case 3: dt.u = Math.round(dt.u * 1000)/1000; break;
}
if(dt.u >= 1) { dt.u = 0; ++dt.S; }
if(dt.S >= 60) { dt.S = 0; ++dt.M; }
if(dt.M >= 60) { dt.M = 0; ++dt.H; }
if(dt.H >= 24) { dt.H = 0; ++dt.D; _dt = parse_date_code(dt.D); _dt.u = dt.u; _dt.S = dt.S; _dt.M = dt.M; _dt.H = dt.H; dt = _dt; }
break;
}

View File

@ -1,6 +1,6 @@
{
"name": "ssf",
"version": "0.11.2",
"version": "0.11.3",
"author": "sheetjs",
"description": "Format data using ECMA-376 spreadsheet Format Codes",
"keywords": [
@ -23,7 +23,7 @@
},
"repository": {
"type": "git",
"url": "git://github.com/SheetJS/sheetjs.git",
"url": "https://git.sheetjs.com/SheetJS/sheetjs",
"directory": "packages/ssf"
},
"scripts": {
@ -48,9 +48,9 @@
"holes"
]
},
"homepage": "http://sheetjs.com/",
"homepage": "https://sheetjs.com/",
"bugs": {
"url": "https://github.com/SheetJS/sheetjs/issues"
"url": "https://git.sheetjs.com/SheetJS/sheetjs/issues"
},
"license": "Apache-2.0",
"engines": {

View File

@ -3,8 +3,8 @@
/*jshint -W041 */
/*:: declare var DO_NOT_EXPORT_SSF: any; */
var SSF/*:SSFModule*/ = ({}/*:any*/);
var make_ssf = function make_ssf(SSF/*:SSFModule*/){
SSF.version = '0.11.2';
function make_ssf(SSF/*:SSFModule*/){
SSF.version = '0.11.3';
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;}
@ -161,8 +161,20 @@ 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 normalize_xl_unsafe(v/*:number*/)/*:number*/ {
var s = v.toPrecision(16);
if(s.indexOf("e") > -1) {
var m = s.slice(0, s.indexOf("e"));
m = m.indexOf(".") > -1 ? m.slice(0, (m.slice(0,2) == "0." ? 17 : 16)) : (m.slice(0,15) + fill("0", m.length - 15));
return m + s.slice(s.indexOf("e"));
}
var n = s.indexOf(".") > -1 ? s.slice(0, (s.slice(0,2) == "0." ? 17 : 16)) : (s.slice(0,15) + fill("0", s.length - 15));
return Number(n);
}
function parse_date_code(v/*:number*/,opts/*:?any*/,b2/*:?boolean*/) {
if(v > 2958465 || v < 0) return null;
v = normalize_xl_unsafe(v);
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 +213,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 +265,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*/) {
@ -336,7 +345,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 == 0 ? 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 */
@ -780,10 +789,11 @@ function eval_fmt(fmt/*:string*/, v/*:any*/, opts/*:any*/, flen/*:number*/) {
switch(out[i].t) {
case 'h': case 'H': out[i].t = hr; lst='h'; if(bt < 1) bt = 1; break;
case 's':
if((ssm=out[i].v.match(/\.0+$/))) ss0=Math.max(ss0,ssm[0].length-1);
if((ssm=out[i].v.match(/\.0+$/))) { ss0=Math.max(ss0,ssm[0].length-1); bt = 4;}
if(bt < 3) bt = 3;
/* falls through */
case 'd': case 'y': case 'M': case 'e': lst=out[i].t; break;
case 'd': case 'y': case 'e': lst=out[i].t; break;
case 'M': lst=out[i].t; if(bt < 2) bt = 2; break;
case 'm': if(lst === 's') { out[i].t = 'M'; if(bt < 2) bt = 2; } break;
case 'X': /*if(out[i].v === "B2");*/
break;
@ -793,19 +803,29 @@ 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 */
var _dt;
switch(bt) {
case 0: break;
case 1:
/*::if(!dt) break;*/
case 2:
case 3:
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; }
if(dt.H >= 24) { dt.H = 0; ++dt.D; _dt = parse_date_code(dt.D); _dt.u = dt.u; _dt.S = dt.S; _dt.M = dt.M; _dt.H = dt.H; dt = _dt; }
break;
case 2:
/*::if(!dt) break;*/
if(dt.u >= 0.5) { dt.u = 0; ++dt.S; }
case 4:
switch(ss0) {
case 1: dt.u = Math.round(dt.u * 10)/10; break;
case 2: dt.u = Math.round(dt.u * 100)/100; break;
case 3: dt.u = Math.round(dt.u * 1000)/1000; break;
}
if(dt.u >= 1) { dt.u = 0; ++dt.S; }
if(dt.S >= 60) { dt.S = 0; ++dt.M; }
if(dt.M >= 60) { dt.M = 0; ++dt.H; }
if(dt.H >= 24) { dt.H = 0; ++dt.D; _dt = parse_date_code(dt.D); _dt.u = dt.u; _dt.S = dt.S; _dt.M = dt.M; _dt.H = dt.H; dt = _dt; }
break;
}
@ -985,7 +1005,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;

View File

@ -2,8 +2,8 @@
/* vim: set ts=2: */
/*jshint -W041 */
var SSF = ({});
var make_ssf = function make_ssf(SSF){
SSF.version = '0.11.2';
function make_ssf(SSF){
SSF.version = '0.11.3';
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;}
@ -157,8 +157,20 @@ function frac(x, D, mixed) {
var q = Math.floor(sgn * P/Q);
return [q, sgn*P - q*Q, Q];
}
function normalize_xl_unsafe(v) {
var s = v.toPrecision(16);
if(s.indexOf("e") > -1) {
var m = s.slice(0, s.indexOf("e"));
m = m.indexOf(".") > -1 ? m.slice(0, (m.slice(0,2) == "0." ? 17 : 16)) : (m.slice(0,15) + fill("0", m.length - 15));
return m + s.slice(s.indexOf("e"));
}
var n = s.indexOf(".") > -1 ? s.slice(0, (s.slice(0,2) == "0." ? 17 : 16)) : (s.slice(0,15) + fill("0", s.length - 15));
return Number(n);
}
function parse_date_code(v,opts,b2) {
if(v > 2958465 || v < 0) return null;
v = normalize_xl_unsafe(v);
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 +209,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 +261,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) {
@ -331,7 +340,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 == 0 ? 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 */
@ -687,7 +696,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', dt, j, cc, sec = false, usec = false;
var out = [], o = "", i = 0, c = "", lst='t', dt, j, cc;
var hr='H';
/* Tokenize */
while(i < fmt.length) {
@ -716,7 +725,6 @@ function eval_fmt(fmt, v, opts, flen) {
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;
if(c === 's') sec = true;
if(c === 'm' && lst.toLowerCase() === 'h') c = 'M';
if(c === 'h') c = hr;
out[out.length] = {t:c, v:o}; lst = c; break;
@ -746,7 +754,7 @@ function eval_fmt(fmt, v, opts, flen) {
case '.':
if(dt != null) {
o = c; while(++i < fmt.length && (c=fmt.charAt(i)) === "0") o += c;
out[out.length] = {t:'s', v:o}; usec=true; break;
out[out.length] = {t:'s', v:o}; break;
}
/* falls through */
case '0': case '#':
@ -768,21 +776,17 @@ function eval_fmt(fmt, v, opts, flen) {
}
}
/* Round number up if displaying seconds with no microseconds */
if (dt && (dt.S + dt.u >= 59.5) && sec && !usec) {
dt=parse_date_code(Math.round((v - Math.floor(v))*86400)/86400, opts)
}
/* 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) {
case 'h': case 'H': out[i].t = hr; lst='h'; if(bt < 1) bt = 1; break;
case 's':
if((ssm=out[i].v.match(/\.0+$/))) ss0=Math.max(ss0,ssm[0].length-1);
if((ssm=out[i].v.match(/\.0+$/))) { ss0=Math.max(ss0,ssm[0].length-1); bt = 4;}
if(bt < 3) bt = 3;
/* falls through */
case 'd': case 'y': case 'M': case 'e': lst=out[i].t; break;
case 'd': case 'y': case 'e': lst=out[i].t; break;
case 'M': lst=out[i].t; if(bt < 2) bt = 2; break;
case 'm': if(lst === 's') { out[i].t = 'M'; if(bt < 2) bt = 2; } break;
case 'X': /*if(out[i].v === "B2");*/
break;
@ -792,18 +796,29 @@ 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 */
var _dt;
switch(bt) {
case 0: break;
case 1:
if(dt.u >= 0.5) { dt.u = 0; ++dt.S; }
case 2:
case 3:
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; }
if(dt.H >= 24) { dt.H = 0; ++dt.d; }
if(dt.H >= 24) { dt.H = 0; ++dt.D; _dt = parse_date_code(dt.D); _dt.u = dt.u; _dt.S = dt.S; _dt.M = dt.M; _dt.H = dt.H; dt = _dt; }
break;
case 2:
if(dt.u >= 0.5) { dt.u = 0; ++dt.S; }
case 4:
switch(ss0) {
case 1: dt.u = Math.round(dt.u * 10)/10; break;
case 2: dt.u = Math.round(dt.u * 100)/100; break;
case 3: dt.u = Math.round(dt.u * 1000)/1000; break;
}
if(dt.u >= 1) { dt.u = 0; ++dt.S; }
if(dt.S >= 60) { dt.S = 0; ++dt.M; }
if(dt.M >= 60) { dt.M = 0; ++dt.H; }
if(dt.H >= 24) { dt.H = 0; ++dt.D; _dt = parse_date_code(dt.D); _dt.u = dt.u; _dt.S = dt.S; _dt.M = dt.M; _dt.H = dt.H; dt = _dt; }
break;
}
@ -978,7 +993,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;

View File

@ -59,17 +59,17 @@ 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: "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 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: "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"}
];