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

JavaScript Number Formatter
Test/Sample Page

Usage

// format( mask, value );
// result "1,234,567.89"
format( "#,##0.####", 1234567.890 );

Examples

#DescriptionInputFormatOutput
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"); });