Repository: Mottie/javascript-number-formatter
Branch: master
Commit: c252291e0c95
Files: 19
Total size: 51.8 KB
Directory structure:
gitextract_yoaizzfr/
├── .cjsescache
├── .eslintrc
├── .gitattributes
├── .gitignore
├── .jshintrc
├── .npmrc
├── .travis.yml
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── index.d.ts
├── index.html
├── lib/
│ ├── format.es5.js
│ ├── format.esm.js
│ └── format.js
├── package.json
├── rollup.config.js
├── src/
│ └── format.js
└── test/
└── test.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .cjsescache
================================================
{
"src/format.js": null
}
================================================
FILE: .eslintrc
================================================
{
"env": {
"browser": true,
"es6": true
},
"parserOptions": {
"ecmaVersion": 2017,
"sourceType": "module"
},
"rules": {
"curly": 1,
"dot-location": [2,"property"],
"eqeqeq": 1,
"linebreak-style": [2, "unix"],
"no-else-return": 1,
"no-eval": 2,
"no-octal": 2,
"no-with": 2,
"radix": 2,
"brace-style": 1,
"camelcase": 2,
"indent": [2, "tab"],
"no-array-constructor": 2,
"quotes": [2, "double", {
"allowTemplateLiterals": true,
"avoidEscape": true
}],
"quote-props": 0,
"spaced-comment": 2,
"arrow-spacing": 2,
"no-var": 2,
"no-unused-vars": 1
}
}
================================================
FILE: .gitattributes
================================================
* text=auto
================================================
FILE: .gitignore
================================================
# lockfiles
package-lock.json
yarn.lock
# temp stuff
tmp/
*.tmp
*.bak
# logs
*.stackdump
*.log
# Build
node_modules/
# Windows crap
Thumbs.db
Desktop.ini
# Mac crap
.DS_Store
================================================
FILE: .jshintrc
================================================
{
"bitwise" : true,
"camelcase" : true,
"curly" : true,
"eqeqeq" : true,
"es3" : true,
"eqnull" : true,
"forin" : true,
"freeze" : true,
"immed" : true,
"indent" : 2,
"jquery" : true,
"latedef" : true,
"newcap" : true,
"noarg" : true,
"noempty" : true,
"nonbsp" : true,
"nonew" : true,
"quotmark" : true,
"sub" : true,
"trailing" : true,
"undef" : true,
"unused" : true
}
================================================
FILE: .npmrc
================================================
package-lock=false
================================================
FILE: .travis.yml
================================================
language: node_js
node_js:
- "12"
- "10"
- "8"
================================================
FILE: CONTRIBUTING.md
================================================
# Contributing
Please do not submit pull requests to the main branch.
* Fork the library.
* Install [grunt](http://gruntjs.com/getting-started#installing-the-cli).
* Run `npm install` to install dependencies.
* Make your changes to the `src/format.js` file.
* Add unit tests to the `/test/test.js` file; your contribution may not be merged without unit tests.
* To test your changes, run `grunt`.
* When all your tests are passing, commit your changes to your fork.
* Submit a pull request to a branch of the repository.
================================================
FILE: LICENSE
================================================
Copyright 2016 ecava (author KPL)
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================================================
FILE: README.md
================================================
# Javascript Number Formatter
Lightweight & Fast JavaScript Number Formatter
[![Build Status][build-image]][build-url] [![NPM Version][npm-image]][npm-url] [![devDependency Status][david-dev-image]][david-dev-url] [![MIT][license-image]][license-url]
## Introduction
This standalone number formatter† is intended to be short and fast. As they are the main factors for a high performance JavaScript app. Development release is around 150 lines including license info, blank lines and comments. And production release is less than 2,000 bytes.
```js
format( "#,##0.####", 1234567.890 ); // output: "1,234,567.89"
format( "$ #,###.00", -1234567.890 ); // output: "$ -1,234,567.89"
// Added in v2.0.0
format( "$ #,###.00", -1234567.890, {enforceMaskSign: true}); // output: "$ 1,234,567.89"
format( "$ -#,###.00", -1234567.890, {enforceMaskSign: true}); // output: "$ -1,234,567.89"
format( "$ +#,###.00", -1234567.890, {enforceMaskSign: true}); // output: "$ -1,234,567.89"
format( "$ +#,###.00", 1234567.890, {enforceMaskSign: true}); // output: "$ +1,234,567.89"
```
† Initial development release of this code was written by KPL and hosted at [Google Code](https://code.google.com/p/javascript-number-formatter/).
## Features
* Short, fast, flexible yet standalone.
* Accept standard number formatting like `#,##0.00` or with negation `-000.####`.
* Accept any country format like `# ##0,00`, `#,###.##`, `#'###.##` or any type of non-numbering symbol.
* Accept any numbers of digit grouping. `#,##,#0.000` or `#,###0.##` are all valid.
* Accept any redundant/fool-proof formatting. `##,###,##.#` or `0#,#00#.###0#` are all OK.
* Auto number rounding.
* Simple interface, just supply mask & value like this: `format( "0.0000", 3.141592)`.
* Include a prefix & suffix with the mask.
## Limitations
* No scientific/engineering formatting.
* Not for date or phone formation.
* No color control.
* No prefix or suffix is allowed except leading negation symbol. So `$#,##0.00` or `#,###.##USD` will not yield expected outcome. Use `'$'+format('#,##0.00', 123.45)` or `format('#,##0.00', 456.789) + 'USD'`
* The prefix or suffix *can not* include any numbers (`0-9`), dashes (`-`), or plus signs (`+`).
## Format Symbols
| Description | Symbol | Summary |
|---------------|--------|---------|
| Mask symbols | #0123456789+- | Mask symbols used for formatting the value. |
| Placeholders | #123456789 | Un-forced digit*; this optional digit will only show if it is required as a placeholder. |
| Zero | 0 | Forced digit; the digit will be shown whether or not the digit is relevant to the value. |
| Signs | +- | Indicates a positive or negative value; visible depending on the value sign and the `enforceMaskSign` setting. |
| Leftmost | | _Any_ non-mask symbol† inside the mask will be set to represent a thousands separator. |
| Rightmost | | _Any_ non-mask symbol† inside the mask‡ will be set to represent the decimal separator. |
| Prefix/Suffix | | _Any_ non-mask symbol† outside the mask. |
\* Non-zero mask digits (`1` through `9`) behave the same as the `#`.
† Anything not a digit, and not a `+`, `-` or `#`.
‡ In the case where there is a trailing decimal or comma, it will be included in the mask, e.g. `#.` or `0,`.
## Note
When only one symbol is supplied, the library will always treat that symbol as a decimal. For example, `format( '#,###', 1234567.890)` will output `1234567,890`.
To force a single symbol to be used as a separator, add a trailing symbol. In this example, a period is added to the end of the mask - `format( '#,###.', 1234567.890)` - resulting in it being used as a decimal and forcing the first symbol to be the separator and return this output: `1,234,567`.
## Installation
### npm package
npm install --save number-format.js
## Demos
A demo/sample page with few examples is provided ([demo](http://mottie.github.io/javascript-number-formatter/)).
And a jsFiddle was created to aid in testing: https://jsfiddle.net/Mottie/t2etyodx/
[build-url]: https://travis-ci.org/Mottie/javascript-number-formatter
[build-image]: https://travis-ci.org/Mottie/javascript-number-formatter.png?branch=master
[npm-url]: https://www.npmjs.com/package/number-format.js
[npm-image]: https://img.shields.io/npm/v/number-format.js.svg
[david-dev-url]: https://david-dm.org/Mottie/javascript-number-formatter?type=dev
[david-dev-image]: https://david-dm.org/Mottie/javascript-number-formatter/dev-status.svg
[license-url]: https://github.com/Mottie/javascript-number-formatter/blob/master/LICENSE
[license-image]: https://img.shields.io/badge/license-MIT-blue.svg
## Recent Changes
View the [complete change log here](https://github.com/Mottie/javascript-number-formatter/wiki).
### v2.0.7 (2018-11-13)
* Update typescript binding. See [issue #20](https://github.com/Mottie/javascript-number-formatter/issues/20).
* Fix improper placeholder behavior. Updated Readme with format symbols table. Closes [issue #19](https://github.com/Mottie/javascript-number-formatter/issues/19).
* Add more tests.
* Meta:
* Update dependencies.
* Improve code readability.
* Include version in min.js.
### v2.0.6 (2018-11-06)
* Trim trailing zeros in mask. Fixes [issue #18](https://github.com/Mottie/javascript-number-formatter/issues/18).
### v2.0.0 – 2.0.5 (2018-10-26)
* Add `ignoreSign` option (modified to `enforeceMaskSign`!).
* Switch to XO, AVA & rollup.
* Meta: Update dot files & remove bower support.
* Code cleanup & convert to ES2015.
* Rename `ignoreSign` to `enforceMaskSign` (default `false`).
* Reduce code complexity.
* Export as node module.
* Update TS with options.
* Switch demo to use lib file & highlight valid results.
* Switch from Grunt to rollup.
* Switch from IIFE to UMD output.
================================================
FILE: index.d.ts
================================================
declare const format : (mask: string, value: number, options?: object) => string;
export = format;
================================================
FILE: index.html
================================================
JS Number Formatter • Test/Sample Page
Usage
// format( mask, value );
// result "1,234,567.89"
format( "#,##0.####", 1234567.890 );
Examples
# Description Input Format Output
Most common world wide
Simple
123456.789
#,##0.00
Random
20110628.15001234
#,##0.###0
Random
0
#,###.##0
Long number
1234567890.1234567890
#,###.##0
Negative value
-0.1
#
Negative value
-0.1
0
Negative value
-0.13
0.#
Negative value
-5000.123456789
#,##0.######
Localization format
US, UK and many more
1234567.890
#,##0.00
Estonia, France
-128983833.4560022
### ###,##
Germany, Italy
-1234560.10002920
##.000,00
Japan
963852741.001
###,####.00
Switzerland
33445566.778899
#'###'#00.00
Any format
-1234.5678
##.000,00
4651321.841
##^000*00
3411.498
-##¿000$00
465456456.87987212
00!00@00
Force comma as separator
1112341.4348712
###,###.
Force space as separator
2344441.4348712
### ###.
Force dot as separator
2345341.4348712
###.###
Prefix & Suffix
No spaces
123456789.9876
$#,##0.00USD
Extra spaces (set white-space: pre; in cells)
123456789.9876
$ #,##0.00 USD
123456789.9876
##.000,00 €
123456789.9876
###,####.00 ¥
123456789.9876
### ###,### ¢ and stuff
123456789.9876
#,##0.00 a b c
Spaces & parenthesis (indicates a negative value, but the input is positive)
123456789.9876
$ (#,###.00) Money
Spaces & parenthesis (negative; not converted!)
-123456789.9876
$ (#,###.00) Money
Prefix with comma
123456789.9876
a, b c? #.00 yep!
Prefix with a periods
123456789.9876
cost... #,##0.00 yep!
Suffix with comma & period
123456789.9876
$# ###,00 USD, or euros.
Suffix with period
123456789.9876
It costs $# ###,00 euros.
Hanging decimal
123456789.9876
test:### ###. ing
Masks that don't work
No "#" outside of mask
123456789.9876
item #abc $#,###.00
No numbers outside of mask
123456789.9876
99 items = $#,###.00
No dashes outside of mask
123456789.9876
cost -- $#,###.00 -- value
No plus sign in prefix
123456789.9876
++ value! $#,###.00
No plus sign in suffix
123456789.9876
$#,###.00 ++ value!
See also...
================================================
FILE: lib/format.es5.js
================================================
/**
* Javascript-number-formatter
* Lightweight & Fast JavaScript Number Formatter
*
* @preserve IntegraXor Web SCADA - JavaScript Number Formatter (http://www.integraxor.com/)
* @author KPL
* @maintainer Rob Garrison
* @copyright 2019 ecava
* @license MIT
* @link http://mottie.github.com/javascript-number-formatter/
* @version 2.0.9
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global = global || self, global.format = factory());
}(this, function () { 'use strict';
function _slicedToArray(arr, i) {
return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest();
}
function _arrayWithHoles(arr) {
if (Array.isArray(arr)) return arr;
}
function _iterableToArrayLimit(arr, i) {
var _arr = [];
var _n = true;
var _d = false;
var _e = undefined;
try {
for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) {
_arr.push(_s.value);
if (i && _arr.length === i) break;
}
} catch (err) {
_d = true;
_e = err;
} finally {
try {
if (!_n && _i["return"] != null) _i["return"]();
} finally {
if (_d) throw _e;
}
}
return _arr;
}
function _nonIterableRest() {
throw new TypeError("Invalid attempt to destructure non-iterable instance");
}
var maskRegex = /[0-9\-+#]/;
var notMaskRegex = /[^\d\-+#]/g;
function getIndex(mask) {
return mask.search(maskRegex);
}
function processMask() {
var mask = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : "#.##";
var maskObj = {};
var len = mask.length;
var start = getIndex(mask);
maskObj.prefix = start > 0 ? mask.substring(0, start) : ""; // Reverse string: not an ideal method if there are surrogate pairs
var end = getIndex(mask.split("").reverse().join(""));
var offset = len - end;
var substr = mask.substring(offset, offset + 1); // Add 1 to offset if mask has a trailing decimal/comma
var indx = offset + (substr === "." || substr === "," ? 1 : 0);
maskObj.suffix = end > 0 ? mask.substring(indx, len) : "";
maskObj.mask = mask.substring(start, indx);
maskObj.maskHasNegativeSign = maskObj.mask.charAt(0) === "-";
maskObj.maskHasPositiveSign = maskObj.mask.charAt(0) === "+"; // Search for group separator & decimal; anything not digit,
// not +/- sign, and not #
var result = maskObj.mask.match(notMaskRegex); // Treat the right most symbol as decimal
maskObj.decimal = result && result[result.length - 1] || "."; // Treat the left most symbol as group separator
maskObj.separator = result && result[1] && result[0] || ","; // Split the decimal for the format string if any
result = maskObj.mask.split(maskObj.decimal);
maskObj.integer = result[0];
maskObj.fraction = result[1];
return maskObj;
}
function processValue(value, maskObj, options) {
var isNegative = false;
var valObj = {
value: value
};
if (value < 0) {
isNegative = true; // Process only abs(), and turn on flag.
valObj.value = -valObj.value;
}
valObj.sign = isNegative ? "-" : ""; // Fix the decimal first, toFixed will auto fill trailing zero.
valObj.value = Number(valObj.value).toFixed(maskObj.fraction && maskObj.fraction.length); // Convert number to string to trim off *all* trailing decimal zero(es)
valObj.value = Number(valObj.value).toString(); // Fill back any trailing zero according to format
// look for last zero in format
var posTrailZero = maskObj.fraction && maskObj.fraction.lastIndexOf("0");
var _valObj$value$split = valObj.value.split("."),
_valObj$value$split2 = _slicedToArray(_valObj$value$split, 2),
_valObj$value$split2$ = _valObj$value$split2[0],
valInteger = _valObj$value$split2$ === void 0 ? "0" : _valObj$value$split2$,
_valObj$value$split2$2 = _valObj$value$split2[1],
valFraction = _valObj$value$split2$2 === void 0 ? "" : _valObj$value$split2$2;
if (!valFraction || valFraction && valFraction.length <= posTrailZero) {
valFraction = posTrailZero < 0 ? "" : Number("0." + valFraction).toFixed(posTrailZero + 1).replace("0.", "");
}
valObj.integer = valInteger;
valObj.fraction = valFraction;
addSeparators(valObj, maskObj); // Remove negative sign if result is zero
if (valObj.result === "0" || valObj.result === "") {
// Remove negative sign if result is zero
isNegative = false;
valObj.sign = "";
}
if (!isNegative && maskObj.maskHasPositiveSign) {
valObj.sign = "+";
} else if (isNegative && maskObj.maskHasPositiveSign) {
valObj.sign = "-";
} else if (isNegative) {
valObj.sign = options && options.enforceMaskSign && !maskObj.maskHasNegativeSign ? "" : "-";
}
return valObj;
}
function addSeparators(valObj, maskObj) {
valObj.result = ""; // Look for separator
var szSep = maskObj.integer.split(maskObj.separator); // Join back without separator for counting the pos of any leading 0
var maskInteger = szSep.join("");
var posLeadZero = maskInteger && maskInteger.indexOf("0");
if (posLeadZero > -1) {
while (valObj.integer.length < maskInteger.length - posLeadZero) {
valObj.integer = "0" + valObj.integer;
}
} else if (Number(valObj.integer) === 0) {
valObj.integer = "";
} // Process the first group separator from decimal (.) only, the rest ignore.
// get the length of the last slice of split result.
var posSeparator = szSep[1] && szSep[szSep.length - 1].length;
if (posSeparator) {
var len = valObj.integer.length;
var offset = len % posSeparator;
for (var indx = 0; indx < len; indx++) {
valObj.result += valObj.integer.charAt(indx); // -posSeparator so that won't trail separator on full length
if (!((indx - offset + 1) % posSeparator) && indx < len - posSeparator) {
valObj.result += maskObj.separator;
}
}
} else {
valObj.result = valObj.integer;
}
valObj.result += maskObj.fraction && valObj.fraction ? maskObj.decimal + valObj.fraction : "";
return valObj;
}
var format = (function (mask, value) {
var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
if (!mask || isNaN(Number(value))) {
// Invalid inputs
return value;
}
var maskObj = processMask(mask);
var valObj = processValue(value, maskObj, options);
return maskObj.prefix + valObj.sign + valObj.result + maskObj.suffix;
});
return format;
}));
================================================
FILE: lib/format.esm.js
================================================
/**
* Javascript-number-formatter
* Lightweight & Fast JavaScript Number Formatter
*
* @preserve IntegraXor Web SCADA - JavaScript Number Formatter (http://www.integraxor.com/)
* @author KPL
* @maintainer Rob Garrison
* @copyright 2019 ecava
* @license MIT
* @link http://mottie.github.com/javascript-number-formatter/
* @version 2.0.9
*/
const maskRegex = /[0-9\-+#]/;
const notMaskRegex = /[^\d\-+#]/g;
function getIndex(mask) {
return mask.search(maskRegex);
}
function processMask(mask = "#.##") {
const maskObj = {};
const len = mask.length;
const start = getIndex(mask);
maskObj.prefix = start > 0 ? mask.substring(0, start) : "";
// Reverse string: not an ideal method if there are surrogate pairs
const end = getIndex(mask.split("").reverse().join(""));
const offset = len - end;
const substr = mask.substring(offset, offset + 1);
// Add 1 to offset if mask has a trailing decimal/comma
const indx = offset + ((substr === "." || (substr === ",")) ? 1 : 0);
maskObj.suffix = end > 0 ? mask.substring(indx, len) : "";
maskObj.mask = mask.substring(start, indx);
maskObj.maskHasNegativeSign = maskObj.mask.charAt(0) === "-";
maskObj.maskHasPositiveSign = maskObj.mask.charAt(0) === "+";
// Search for group separator & decimal; anything not digit,
// not +/- sign, and not #
let result = maskObj.mask.match(notMaskRegex);
// Treat the right most symbol as decimal
maskObj.decimal = (result && result[result.length - 1]) || ".";
// Treat the left most symbol as group separator
maskObj.separator = (result && result[1] && result[0]) || ",";
// Split the decimal for the format string if any
result = maskObj.mask.split(maskObj.decimal);
maskObj.integer = result[0];
maskObj.fraction = result[1];
return maskObj;
}
function processValue(value, maskObj, options) {
let isNegative = false;
const valObj = {
value
};
if (value < 0) {
isNegative = true;
// Process only abs(), and turn on flag.
valObj.value = -valObj.value;
}
valObj.sign = isNegative ? "-" : "";
// Fix the decimal first, toFixed will auto fill trailing zero.
valObj.value = Number(valObj.value).toFixed(maskObj.fraction && maskObj.fraction.length);
// Convert number to string to trim off *all* trailing decimal zero(es)
valObj.value = Number(valObj.value).toString();
// Fill back any trailing zero according to format
// look for last zero in format
const posTrailZero = maskObj.fraction && maskObj.fraction.lastIndexOf("0");
let [valInteger = "0", valFraction = ""] = valObj.value.split(".");
if (!valFraction || (valFraction && valFraction.length <= posTrailZero)) {
valFraction = posTrailZero < 0
? ""
: (Number("0." + valFraction).toFixed(posTrailZero + 1)).replace("0.", "");
}
valObj.integer = valInteger;
valObj.fraction = valFraction;
addSeparators(valObj, maskObj);
// Remove negative sign if result is zero
if (valObj.result === "0" || valObj.result === "") {
// Remove negative sign if result is zero
isNegative = false;
valObj.sign = "";
}
if (!isNegative && maskObj.maskHasPositiveSign) {
valObj.sign = "+";
} else if (isNegative && maskObj.maskHasPositiveSign) {
valObj.sign = "-";
} else if (isNegative) {
valObj.sign = options && options.enforceMaskSign && !maskObj.maskHasNegativeSign
? ""
: "-";
}
return valObj;
}
function addSeparators(valObj, maskObj) {
valObj.result = "";
// Look for separator
const szSep = maskObj.integer.split(maskObj.separator);
// Join back without separator for counting the pos of any leading 0
const maskInteger = szSep.join("");
const posLeadZero = maskInteger && maskInteger.indexOf("0");
if (posLeadZero > -1) {
while (valObj.integer.length < (maskInteger.length - posLeadZero)) {
valObj.integer = "0" + valObj.integer;
}
} else if (Number(valObj.integer) === 0) {
valObj.integer = "";
}
// Process the first group separator from decimal (.) only, the rest ignore.
// get the length of the last slice of split result.
const posSeparator = (szSep[1] && szSep[szSep.length - 1].length);
if (posSeparator) {
const len = valObj.integer.length;
const offset = len % posSeparator;
for (let indx = 0; indx < len; indx++) {
valObj.result += valObj.integer.charAt(indx);
// -posSeparator so that won't trail separator on full length
if (!((indx - offset + 1) % posSeparator) && indx < len - posSeparator) {
valObj.result += maskObj.separator;
}
}
} else {
valObj.result = valObj.integer;
}
valObj.result += (maskObj.fraction && valObj.fraction)
? maskObj.decimal + valObj.fraction
: "";
return valObj;
}
var format = (mask, value, options = {}) => {
if (!mask || isNaN(Number(value))) {
// Invalid inputs
return value;
}
const maskObj = processMask(mask);
const valObj = processValue(value, maskObj, options);
return maskObj.prefix + valObj.sign + valObj.result + maskObj.suffix;
};
export default format;
================================================
FILE: lib/format.js
================================================
/**
* Javascript-number-formatter
* Lightweight & Fast JavaScript Number Formatter
*
* @preserve IntegraXor Web SCADA - JavaScript Number Formatter (http://www.integraxor.com/)
* @author KPL
* @maintainer Rob Garrison
* @copyright 2019 ecava
* @license MIT
* @link http://mottie.github.com/javascript-number-formatter/
* @version 2.0.9
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global = global || self, global.format = factory());
}(this, function () { 'use strict';
const maskRegex = /[0-9\-+#]/;
const notMaskRegex = /[^\d\-+#]/g;
function getIndex(mask) {
return mask.search(maskRegex);
}
function processMask(mask = "#.##") {
const maskObj = {};
const len = mask.length;
const start = getIndex(mask);
maskObj.prefix = start > 0 ? mask.substring(0, start) : "";
// Reverse string: not an ideal method if there are surrogate pairs
const end = getIndex(mask.split("").reverse().join(""));
const offset = len - end;
const substr = mask.substring(offset, offset + 1);
// Add 1 to offset if mask has a trailing decimal/comma
const indx = offset + ((substr === "." || (substr === ",")) ? 1 : 0);
maskObj.suffix = end > 0 ? mask.substring(indx, len) : "";
maskObj.mask = mask.substring(start, indx);
maskObj.maskHasNegativeSign = maskObj.mask.charAt(0) === "-";
maskObj.maskHasPositiveSign = maskObj.mask.charAt(0) === "+";
// Search for group separator & decimal; anything not digit,
// not +/- sign, and not #
let result = maskObj.mask.match(notMaskRegex);
// Treat the right most symbol as decimal
maskObj.decimal = (result && result[result.length - 1]) || ".";
// Treat the left most symbol as group separator
maskObj.separator = (result && result[1] && result[0]) || ",";
// Split the decimal for the format string if any
result = maskObj.mask.split(maskObj.decimal);
maskObj.integer = result[0];
maskObj.fraction = result[1];
return maskObj;
}
function processValue(value, maskObj, options) {
let isNegative = false;
const valObj = {
value
};
if (value < 0) {
isNegative = true;
// Process only abs(), and turn on flag.
valObj.value = -valObj.value;
}
valObj.sign = isNegative ? "-" : "";
// Fix the decimal first, toFixed will auto fill trailing zero.
valObj.value = Number(valObj.value).toFixed(maskObj.fraction && maskObj.fraction.length);
// Convert number to string to trim off *all* trailing decimal zero(es)
valObj.value = Number(valObj.value).toString();
// Fill back any trailing zero according to format
// look for last zero in format
const posTrailZero = maskObj.fraction && maskObj.fraction.lastIndexOf("0");
let [valInteger = "0", valFraction = ""] = valObj.value.split(".");
if (!valFraction || (valFraction && valFraction.length <= posTrailZero)) {
valFraction = posTrailZero < 0
? ""
: (Number("0." + valFraction).toFixed(posTrailZero + 1)).replace("0.", "");
}
valObj.integer = valInteger;
valObj.fraction = valFraction;
addSeparators(valObj, maskObj);
// Remove negative sign if result is zero
if (valObj.result === "0" || valObj.result === "") {
// Remove negative sign if result is zero
isNegative = false;
valObj.sign = "";
}
if (!isNegative && maskObj.maskHasPositiveSign) {
valObj.sign = "+";
} else if (isNegative && maskObj.maskHasPositiveSign) {
valObj.sign = "-";
} else if (isNegative) {
valObj.sign = options && options.enforceMaskSign && !maskObj.maskHasNegativeSign
? ""
: "-";
}
return valObj;
}
function addSeparators(valObj, maskObj) {
valObj.result = "";
// Look for separator
const szSep = maskObj.integer.split(maskObj.separator);
// Join back without separator for counting the pos of any leading 0
const maskInteger = szSep.join("");
const posLeadZero = maskInteger && maskInteger.indexOf("0");
if (posLeadZero > -1) {
while (valObj.integer.length < (maskInteger.length - posLeadZero)) {
valObj.integer = "0" + valObj.integer;
}
} else if (Number(valObj.integer) === 0) {
valObj.integer = "";
}
// Process the first group separator from decimal (.) only, the rest ignore.
// get the length of the last slice of split result.
const posSeparator = (szSep[1] && szSep[szSep.length - 1].length);
if (posSeparator) {
const len = valObj.integer.length;
const offset = len % posSeparator;
for (let indx = 0; indx < len; indx++) {
valObj.result += valObj.integer.charAt(indx);
// -posSeparator so that won't trail separator on full length
if (!((indx - offset + 1) % posSeparator) && indx < len - posSeparator) {
valObj.result += maskObj.separator;
}
}
} else {
valObj.result = valObj.integer;
}
valObj.result += (maskObj.fraction && valObj.fraction)
? maskObj.decimal + valObj.fraction
: "";
return valObj;
}
var format = (mask, value, options = {}) => {
if (!mask || isNaN(Number(value))) {
// Invalid inputs
return value;
}
const maskObj = processMask(mask);
const valObj = processValue(value, maskObj, options);
return maskObj.prefix + valObj.sign + valObj.result + maskObj.suffix;
};
return format;
}));
================================================
FILE: package.json
================================================
{
"name": "number-format.js",
"nick": "javascript-number-formatter",
"description": "Lightweight & Fast JavaScript Number Formatter",
"version": "2.0.9",
"homepage": "http://mottie.github.com/javascript-number-formatter/",
"main": "lib/format.min.js",
"demo": "https://github.com/Mottie/javascript-number-formatter/index.html",
"repository": {
"type": "git",
"url": "git://github.com/Mottie/javascript-number-formatter.git"
},
"author": {
"name": "KPL",
"url": "https://code.google.com/u/100789773228459308870/"
},
"maintainers": [
{
"name": "Rob Garrison",
"url": "https://github.com/Mottie",
"email": "wowmotty@gmail.com"
}
],
"license": "MIT",
"bugs": "https://github.com/Mottie/javascript-number-formatter/issues",
"docs": "http://mottie.github.com/javascript-number-formatter/index.html",
"keywords": [
"number",
"format",
"formatter",
"currency"
],
"files": [
"lib/",
"index.d.ts"
],
"xo": {
"space": false,
"ignore": [
"rollup.config.js",
"lib"
],
"envs": [
"node"
],
"rules": {
"no-sparse-arrays": 0,
"quotes": [
"error",
"double"
],
"operator-linebreak": 0
}
},
"engines": {
"node": ">=8"
},
"scripts": {
"build": "rollup -c",
"test": "xo && ava",
"updater": "npx updates -cu && npm install"
},
"typings": "./index.d.ts",
"devDependencies": {
"@babel/core": "^7.2.2",
"@babel/plugin-transform-object-assign": "^7.2.0",
"@babel/preset-env": "^7.3.1",
"ava": "*",
"rollup": "^1.1.2",
"rollup-plugin-babel": "^4.3.2",
"rollup-plugin-cjs-es": "^0.7.0",
"rollup-plugin-node-resolve": "^4.0.0",
"rollup-plugin-terser": "^4.0.3",
"updates": "^6.2.1",
"xo": "*"
}
}
================================================
FILE: rollup.config.js
================================================
import cjs from "rollup-plugin-cjs-es";
import resolve from "rollup-plugin-node-resolve";
import babel from "rollup-plugin-babel";
import {terser} from "rollup-plugin-terser";
import pkg from "./package.json";
const banner = `/**
* Javascript-number-formatter
* Lightweight & Fast JavaScript Number Formatter
*
* @preserve IntegraXor Web SCADA - JavaScript Number Formatter (http://www.integraxor.com/)
* @author KPL
* @maintainer Rob Garrison
* @copyright ${new Date().getFullYear()} ecava
* @license MIT
* @link http://mottie.github.com/javascript-number-formatter/
* @version ${pkg.version}
*/`;
export default [{
input: "src/format.js",
output: [{
file: "lib/format.js",
name: "format",
format: "umd",
sourceMap: false,
banner,
},{
file: "lib/format.esm.js",
name: "format",
format: "esm",
sourceMap: false,
banner,
}],
plugins: [
resolve(),
cjs({
nested: true
})
]
}, {
input: "src/format.js",
output: [{
file: "lib/format.es5.js",
name: "format",
format: "umd",
sourceMap: false,
banner,
}],
plugins: [
resolve(),
cjs({
nested: true
}),
babel({
exclude: "node_modules/**",
presets: [
["@babel/preset-env", {
modules: false
}]
],
plugins: [
"@babel/plugin-transform-object-assign"
]
}),
]
},
{
input: "src/format.js",
output: {
file: "lib/format.min.js",
name: "format",
format: "umd",
sourceMap: false,
banner: `/*! Javascript-number-formatter v${pkg.version} */`,
},
plugins: [
resolve(),
cjs({
nested: true
}),
terser({
compress: {
passes: 3
},
output: {
comments: /^!/
}
})
]
}];
================================================
FILE: src/format.js
================================================
"use strict";
const maskRegex = /[0-9\-+#]/;
const notMaskRegex = /[^\d\-+#]/g;
function getIndex(mask) {
return mask.search(maskRegex);
}
function processMask(mask = "#.##") {
const maskObj = {};
const len = mask.length;
const start = getIndex(mask);
maskObj.prefix = start > 0 ? mask.substring(0, start) : "";
// Reverse string: not an ideal method if there are surrogate pairs
const end = getIndex(mask.split("").reverse().join(""));
const offset = len - end;
const substr = mask.substring(offset, offset + 1);
// Add 1 to offset if mask has a trailing decimal/comma
const indx = offset + ((substr === "." || (substr === ",")) ? 1 : 0);
maskObj.suffix = end > 0 ? mask.substring(indx, len) : "";
maskObj.mask = mask.substring(start, indx);
maskObj.maskHasNegativeSign = maskObj.mask.charAt(0) === "-";
maskObj.maskHasPositiveSign = maskObj.mask.charAt(0) === "+";
// Search for group separator & decimal; anything not digit,
// not +/- sign, and not #
let result = maskObj.mask.match(notMaskRegex);
// Treat the right most symbol as decimal
maskObj.decimal = (result && result[result.length - 1]) || ".";
// Treat the left most symbol as group separator
maskObj.separator = (result && result[1] && result[0]) || ",";
// Split the decimal for the format string if any
result = maskObj.mask.split(maskObj.decimal);
maskObj.integer = result[0];
maskObj.fraction = result[1];
return maskObj;
}
function processValue(value, maskObj, options) {
let isNegative = false;
const valObj = {
value
};
if (value < 0) {
isNegative = true;
// Process only abs(), and turn on flag.
valObj.value = -valObj.value;
}
valObj.sign = isNegative ? "-" : "";
// Fix the decimal first, toFixed will auto fill trailing zero.
valObj.value = Number(valObj.value).toFixed(maskObj.fraction && maskObj.fraction.length);
// Convert number to string to trim off *all* trailing decimal zero(es)
valObj.value = Number(valObj.value).toString();
// Fill back any trailing zero according to format
// look for last zero in format
const posTrailZero = maskObj.fraction && maskObj.fraction.lastIndexOf("0");
let [valInteger = "0", valFraction = ""] = valObj.value.split(".");
if (!valFraction || (valFraction && valFraction.length <= posTrailZero)) {
valFraction = posTrailZero < 0
? ""
: (Number("0." + valFraction).toFixed(posTrailZero + 1)).replace("0.", "");
}
valObj.integer = valInteger;
valObj.fraction = valFraction;
addSeparators(valObj, maskObj);
// Remove negative sign if result is zero
if (valObj.result === "0" || valObj.result === "") {
// Remove negative sign if result is zero
isNegative = false;
valObj.sign = "";
}
if (!isNegative && maskObj.maskHasPositiveSign) {
valObj.sign = "+";
} else if (isNegative && maskObj.maskHasPositiveSign) {
valObj.sign = "-";
} else if (isNegative) {
valObj.sign = options && options.enforceMaskSign && !maskObj.maskHasNegativeSign
? ""
: "-";
}
return valObj;
}
function addSeparators(valObj, maskObj) {
valObj.result = "";
// Look for separator
const szSep = maskObj.integer.split(maskObj.separator);
// Join back without separator for counting the pos of any leading 0
const maskInteger = szSep.join("");
const posLeadZero = maskInteger && maskInteger.indexOf("0");
if (posLeadZero > -1) {
while (valObj.integer.length < (maskInteger.length - posLeadZero)) {
valObj.integer = "0" + valObj.integer;
}
} else if (Number(valObj.integer) === 0) {
valObj.integer = "";
}
// Process the first group separator from decimal (.) only, the rest ignore.
// get the length of the last slice of split result.
const posSeparator = (szSep[1] && szSep[szSep.length - 1].length);
if (posSeparator) {
const len = valObj.integer.length;
const offset = len % posSeparator;
for (let indx = 0; indx < len; indx++) {
valObj.result += valObj.integer.charAt(indx);
// -posSeparator so that won't trail separator on full length
if (!((indx - offset + 1) % posSeparator) && indx < len - posSeparator) {
valObj.result += maskObj.separator;
}
}
} else {
valObj.result = valObj.integer;
}
valObj.result += (maskObj.fraction && valObj.fraction)
? maskObj.decimal + valObj.fraction
: "";
return valObj;
}
module.exports = (mask, value, options = {}) => {
if (!mask || isNaN(Number(value))) {
// Invalid inputs
return value;
}
const maskObj = processMask(mask);
const valObj = processValue(value, maskObj, options);
return maskObj.prefix + valObj.sign + valObj.result + maskObj.suffix;
};
================================================
FILE: test/test.js
================================================
import test from "ava";
import format from "../src/format";
/* Simple mask */
test("basic masks", t => {
t.is(format("#,##0.00", 123456789.123), "123,456,789.12", "Mask: \"#,##0.00\"");
t.is(format("#,##0.00", "123456.789"), "123,456.79");
t.is(format("#,##0.00", 123456.789), "123,456.79");
t.is(format("#,##0.00", 123456.7), "123,456.70");
t.is(format("#,##0.00", 123456), "123,456.00");
t.is(format("#,##0.00", 0), "0.00");
t.is(format("#", -0.1), "");
t.is(format("0", -0.1), "0");
t.is(format("0.#", -0.13), "-0.1");
t.is(format("#,##0.00", -123), "-123.00");
t.is(format("#,##0.00", -123456.789), "-123,456.79");
t.is(format("#,##0.0", 123456789.123), "123,456,789.1", "Mask: \"#,##0.0\"");
t.is(format("#,##0.0", 123456.789), "123,456.8");
t.is(format("#,##0.0", 123456.7), "123,456.7");
t.is(format("#,##0.0", 123456), "123,456.0");
t.is(format("#,##0.0", 0), "0.0");
t.is(format("#,##0.0", -123), "-123.0");
t.is(format("#,##0.0", -123456.789), "-123,456.8");
t.is(format("#,##0.", 123456789.123), "123,456,789", "Mask: \"#,##0.\"");
t.is(format("#,##0.", 123456.789), "123,457");
t.is(format("#,##0.", 123456.7), "123,457");
t.is(format("#,##0.", 123456), "123,456");
t.is(format("#,##0.", 0), "0");
t.is(format("#,##0.", -123), "-123");
t.is(format("#,##0.", -123456.789), "-123,457");
t.is(format("#.##0,", 123456789.123), "123.456.789", "Mask: \"#.##0,\"");
t.is(format("#.##0,", 123456.789), "123.457");
t.is(format("#.##0,", 123456.7), "123.457");
t.is(format("#.##0,", 123456), "123.456");
t.is(format("#.##0,", 0), "0");
t.is(format("#.##0,", -123), "-123");
t.is(format("#.##0,", -123456.789), "-123.457");
t.is(format("#,##0.###0", 12345678.98765432), "12,345,678.9877", "Mask: \"#,##0.###0\"");
});
/* Localizations */
test("Localizations", t => {
t.is(format("### ###,##", 123456789.987654321), "123 456 789,99", "Estonia, France: ### ###,##");
t.is(format("##.000,00", 123456789.987654321), "123.456.789,99", "Germany, Italy: ##.000,00");
t.is(format("###,####.00", 123456789.987654321), "1,2345,6789.99", "Japan: ###,####.00");
t.is(format("#'###'#00.00", 123456789.987654321), "123'456'789.99", "Switzerland: #'###'#00.00");
});
/* Made-up-izations */
test("Any format", t => {
t.is(format("#,##0 00", 123456789.987654321), "123,456,789 99");
t.is(format("#x##0 00", 123456789.987654321), "123x456x789 99");
t.is(format("##^000*00", 123456789.987654321), "123^456^789*99");
t.is(format("##¿000$00", 123456789.987654321), "123¿456¿789$99");
t.is(format("00!00@00", 123456789.987654321), "1!23!45!67!89@99");
});
/* Any non-zero digit in mask behaves like # */
test("Non-zero digits", t => {
t.is(format("999.999", 123.0), "123");
t.is(format("123,456789", 123.0), "123");
});
/* Precision */
test("Precision", t => {
t.is(format("### ###,", 123456789.987654321), "123 456 790");
t.is(format("###.###,", 123456789.987654321), "123.456.790");
t.is(format("##,000.", 123456789.987654321), "123,456,790");
t.is(format("###,####.", 123456789.187654321), "1,2345,6789");
t.is(format("#'###'#00,", 123456789.087654321), "123'456'789");
t.is(format("#,##0.####", 1234567.890), "1,234,567.89");
t.is(format("#,##0.###0", 1234567.890), "1,234,567.8900");
t.is(format("#,##0.##0#", 1234567.890), "1,234,567.890");
t.is(format("#,##0.#0##", 1234567.890), "1,234,567.89");
t.is(format("#,##0.#", 1234567.890), "1,234,567.9");
t.is(format("#,###.", 1234567.890), "1,234,568");
t.is(format("#,###.", 1234567), "1,234,567");
t.is(format("#,###.#", 1234567), "1,234,567");
t.is(format("#,###.##", 1234567), "1,234,567");
t.is(format("#,###.0", 1234567), "1,234,567.0");
t.is(format("#,###.00", 1234567), "1,234,567.00");
t.is(format("#.00", 0.78), ".78");
t.is(format("#.000", 0.78), ".780");
t.is(format("#.0000", 0.78), ".7800");
t.is(format("0.00", 0.78), "0.78");
t.is(format("0.000", 0.78), "0.780");
t.is(format("0.0000", 0.78), "0.7800");
t.is(format("00.00", 0.78), "00.78");
t.is(format("#.##", 0), "");
t.is(format("0.00", 0), "0.00");
t.is(format("0", 0), "0");
});
/* Mask with prefix and/or suffix */
test("Prefix & Suffix", t => {
// Usage
t.is(format("$#,##0.00USD", 123456789.123), "$123,456,789.12USD", "$#,##0.00USD");
t.is(format("$ #,##0.00 USD", 123456789.123), "$ 123,456,789.12 USD", "$ #,##0.00 USD");
t.is(format("##.000,00 €", 123456789.123), "123.456.789,12 €", "##.000,00 €");
t.is(format("###,####.00 ¥", 123456789.123), "1,2345,6789.12 ¥", "###,####.00 ¥");
t.is(format("### ###,### ¢ and stuff", 123456789.123), "123 456 789,123 ¢ and stuff", "### ###,### ¢ and stuff");
t.is(format(" #,##0.00 a b c ", 123456789.123), " 123,456,789.12 a b c ", "leading & trailing spaces");
t.is(format("$ (#,###.00) Money", 123456789.123), "$ (123,456,789.12) Money", "spaces & mask wrapped in parenthesis");
t.is(format("prefix with a comma, includes everything? #.00 yep!", 123456789.123), "prefix with a comma, includes everything? 123456789.12 yep!", "prefix with a comma");
t.is(format("$# ###,00 USD, or euros.", 123456789.123), "$123 456 789,12 USD, or euros.", "suffix with comma & period");
t.is(format("prefix with a periods?... #.00 yep!", 123456789.123), "prefix with a periods?... 123456789.12 yep!", "prefix with a periods");
t.is(format("It costs $# ###,00 euros.", 123456789.123), "It costs $123 456 789,12 euros.", "suffix with period");
t.is(format("test:### ###. ing", 123456789.123), "test:123 456 789 ing", "Hanging decimals");
});
test("Masks that don't work", t => {
// Not allowed
t.is(format("No # outside of the mask $#,###.00", 123456789.123) !== "No # outside of the mask $123,456,789.12", true, "BROKEN: # outside of the mask");
t.is(format("99 items = $#,###.00", 123456789.123) !== "99 items = $123,456,789.12", true, "BROKEN: numbers outside of mask");
t.is(format("cost -- $#,###.00 -- value", 123456789.123) !== "cost -- $123,456,789.12 -- value", true, "BROKEN: dashes outside of mask");
t.is(format("++ value! $#,###.00 ++ value!", 123456789.123) !== "++ value! $123,456,789.12 ++ value!", true, "BROKEN: plus signs outside of mask");
});
test("Numbers with negative sign", t => {
t.is(format("-#,##0.######", -5000.123456789), "-5,000.123457");
t.is(format("-#,##0.######", 5000.123456789), "5,000.123457");
t.is(format("#,##0.######", -5000.123456789), "-5,000.123457");
t.is(format("$ #,###.00", -1234567.890), "$ -1,234,567.89");
t.is(format("$ -#,###.00", -1234567.890), "$ -1,234,567.89");
t.is(format("-#,##0.######", -5000.123456789, {enforceMaskSign: true}), "-5,000.123457");
t.is(format("-#,##0.######", 5000.123456789, {enforceMaskSign: true}), "5,000.123457");
t.is(format("#,##0.######", -5000.123456789, {enforceMaskSign: true}), "5,000.123457");
t.is(format("$ #,###.00", -1234567.890, {enforceMaskSign: true}), "$ 1,234,567.89");
t.is(format("$ -#,###.00", -1234567.890, {enforceMaskSign: true}), "$ -1,234,567.89");
});
test("Numbers with positive sign", t => {
t.is(format("+#,##0.######", +5000.123456789), "+5,000.123457");
t.is(format("+#,##0.######", 5000.123456789), "+5,000.123457");
t.is(format("#,##0.######", +5000.123456789), "5,000.123457");
t.is(format("+#,##0.######", -5000.123456789), "-5,000.123457");
t.is(format("+#,##0.######", +5000.123456789, {enforceMaskSign: true}), "+5,000.123457");
t.is(format("+#,##0.######", 5000.123456789, {enforceMaskSign: true}), "+5,000.123457");
t.is(format("#,##0.######", +5000.123456789, {enforceMaskSign: true}), "5,000.123457");
t.is(format("+#,##0.######", -5000.123456789, {enforceMaskSign: true}), "-5,000.123457");
t.is(format("$ +#,###.00", -1234567.890, {enforceMaskSign: true}), "$ -1,234,567.89");
t.is(format("$ +#,###.00", 1234567.890, {enforceMaskSign: true}), "$ +1,234,567.89");
});