├── .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 └── format.min.js ├── package.json ├── rollup.config.js ├── src └── format.js └── test └── test.js /.cjsescache: -------------------------------------------------------------------------------- 1 | { 2 | "src/format.js": null 3 | } -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es6": true 5 | }, 6 | "parserOptions": { 7 | "ecmaVersion": 2017, 8 | "sourceType": "module" 9 | }, 10 | "rules": { 11 | "curly": 1, 12 | "dot-location": [2,"property"], 13 | "eqeqeq": 1, 14 | "linebreak-style": [2, "unix"], 15 | "no-else-return": 1, 16 | "no-eval": 2, 17 | "no-octal": 2, 18 | "no-with": 2, 19 | "radix": 2, 20 | "brace-style": 1, 21 | "camelcase": 2, 22 | "indent": [2, "tab"], 23 | "no-array-constructor": 2, 24 | "quotes": [2, "double", { 25 | "allowTemplateLiterals": true, 26 | "avoidEscape": true 27 | }], 28 | "quote-props": 0, 29 | "spaced-comment": 2, 30 | "arrow-spacing": 2, 31 | "no-var": 2, 32 | "no-unused-vars": 1 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # lockfiles 2 | package-lock.json 3 | yarn.lock 4 | 5 | # temp stuff 6 | tmp/ 7 | *.tmp 8 | *.bak 9 | 10 | # logs 11 | *.stackdump 12 | *.log 13 | 14 | # Build 15 | node_modules/ 16 | 17 | # Windows crap 18 | Thumbs.db 19 | Desktop.ini 20 | 21 | # Mac crap 22 | .DS_Store 23 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "bitwise" : true, 3 | "camelcase" : true, 4 | "curly" : true, 5 | "eqeqeq" : true, 6 | "es3" : true, 7 | "eqnull" : true, 8 | "forin" : true, 9 | "freeze" : true, 10 | "immed" : true, 11 | "indent" : 2, 12 | "jquery" : true, 13 | "latedef" : true, 14 | "newcap" : true, 15 | "noarg" : true, 16 | "noempty" : true, 17 | "nonbsp" : true, 18 | "nonew" : true, 19 | "quotmark" : true, 20 | "sub" : true, 21 | "trailing" : true, 22 | "undef" : true, 23 | "unused" : true 24 | } -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "12" 4 | - "10" 5 | - "8" 6 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Please do not submit pull requests to the main branch. 4 | 5 | * Fork the library. 6 | * Install [grunt](http://gruntjs.com/getting-started#installing-the-cli). 7 | * Run `npm install` to install dependencies. 8 | * Make your changes to the `src/format.js` file. 9 | * Add unit tests to the `/test/test.js` file; your contribution may not be merged without unit tests. 10 | * To test your changes, run `grunt`. 11 | * When all your tests are passing, commit your changes to your fork. 12 | * Submit a pull request to a branch of the repository. 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2016 ecava (author KPL) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to use, 6 | copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the 7 | Software, and to permit persons to whom the Software is furnished to do so, 8 | subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Javascript Number Formatter 2 | 3 | Lightweight & Fast JavaScript Number Formatter 4 | 5 | [![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] 6 | 7 | ## Introduction 8 | 9 | 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. 10 | 11 | ```js 12 | format( "#,##0.####", 1234567.890 ); // output: "1,234,567.89" 13 | format( "$ #,###.00", -1234567.890 ); // output: "$ -1,234,567.89" 14 | 15 | // Added in v2.0.0 16 | format( "$ #,###.00", -1234567.890, {enforceMaskSign: true}); // output: "$ 1,234,567.89" 17 | format( "$ -#,###.00", -1234567.890, {enforceMaskSign: true}); // output: "$ -1,234,567.89" 18 | format( "$ +#,###.00", -1234567.890, {enforceMaskSign: true}); // output: "$ -1,234,567.89" 19 | format( "$ +#,###.00", 1234567.890, {enforceMaskSign: true}); // output: "$ +1,234,567.89" 20 | ``` 21 | 22 | † Initial development release of this code was written by KPL and hosted at [Google Code](https://code.google.com/p/javascript-number-formatter/). 23 | 24 | ## Features 25 | 26 | * Short, fast, flexible yet standalone. 27 | * Accept standard number formatting like `#,##0.00` or with negation `-000.####`. 28 | * Accept any country format like `# ##0,00`, `#,###.##`, `#'###.##` or any type of non-numbering symbol. 29 | * Accept any numbers of digit grouping. `#,##,#0.000` or `#,###0.##` are all valid. 30 | * Accept any redundant/fool-proof formatting. `##,###,##.#` or `0#,#00#.###0#` are all OK. 31 | * Auto number rounding. 32 | * Simple interface, just supply mask & value like this: `format( "0.0000", 3.141592)`. 33 | * Include a prefix & suffix with the mask. 34 | 35 | ## Limitations 36 | 37 | * No scientific/engineering formatting. 38 | * Not for date or phone formation. 39 | * No color control. 40 | * 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'` 41 | * The prefix or suffix *can not* include any numbers (`0-9`), dashes (`-`), or plus signs (`+`). 42 | 43 | ## Format Symbols 44 | 45 | | Description | Symbol | Summary | 46 | |---------------|--------|---------| 47 | | Mask symbols | #0123456789+- | Mask symbols used for formatting the value. | 48 | | Placeholders | #123456789 | Un-forced digit*; this optional digit will only show if it is required as a placeholder. | 49 | | Zero | 0 | Forced digit; the digit will be shown whether or not the digit is relevant to the value. | 50 | | Signs | +- | Indicates a positive or negative value; visible depending on the value sign and the `enforceMaskSign` setting. | 51 | | Leftmost | | _Any_ non-mask symbol† inside the mask will be set to represent a thousands separator. | 52 | | Rightmost | | _Any_ non-mask symbol† inside the mask‡ will be set to represent the decimal separator. | 53 | | Prefix/Suffix | | _Any_ non-mask symbol† outside the mask. | 54 | 55 | \* Non-zero mask digits (`1` through `9`) behave the same as the `#`.
56 | † Anything not a digit, and not a `+`, `-` or `#`.
57 | ‡ In the case where there is a trailing decimal or comma, it will be included in the mask, e.g. `#.` or `0,`. 58 | 59 | ## Note 60 | 61 | 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`. 62 | 63 | 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`. 64 | 65 | ## Installation 66 | 67 | ### npm package 68 | 69 | npm install --save number-format.js 70 | 71 | ## Demos 72 | 73 | A demo/sample page with few examples is provided ([demo](http://mottie.github.io/javascript-number-formatter/)). 74 | 75 | And a jsFiddle was created to aid in testing: https://jsfiddle.net/Mottie/t2etyodx/ 76 | 77 | [build-url]: https://travis-ci.org/Mottie/javascript-number-formatter 78 | [build-image]: https://travis-ci.org/Mottie/javascript-number-formatter.png?branch=master 79 | [npm-url]: https://www.npmjs.com/package/number-format.js 80 | [npm-image]: https://img.shields.io/npm/v/number-format.js.svg 81 | [david-dev-url]: https://david-dm.org/Mottie/javascript-number-formatter?type=dev 82 | [david-dev-image]: https://david-dm.org/Mottie/javascript-number-formatter/dev-status.svg 83 | [license-url]: https://github.com/Mottie/javascript-number-formatter/blob/master/LICENSE 84 | [license-image]: https://img.shields.io/badge/license-MIT-blue.svg 85 | 86 | ## Recent Changes 87 | 88 | View the [complete change log here](https://github.com/Mottie/javascript-number-formatter/wiki). 89 | 90 | ### v2.0.7 (2018-11-13) 91 | 92 | * Update typescript binding. See [issue #20](https://github.com/Mottie/javascript-number-formatter/issues/20). 93 | * Fix improper placeholder behavior. Updated Readme with format symbols table. Closes [issue #19](https://github.com/Mottie/javascript-number-formatter/issues/19). 94 | * Add more tests. 95 | * Meta: 96 | * Update dependencies. 97 | * Improve code readability. 98 | * Include version in min.js. 99 | 100 | ### v2.0.6 (2018-11-06) 101 | 102 | * Trim trailing zeros in mask. Fixes [issue #18](https://github.com/Mottie/javascript-number-formatter/issues/18). 103 | 104 | ### v2.0.0 – 2.0.5 (2018-10-26) 105 | 106 | * Add `ignoreSign` option (modified to `enforeceMaskSign`!). 107 | * Switch to XO, AVA & rollup. 108 | * Meta: Update dot files & remove bower support. 109 | * Code cleanup & convert to ES2015. 110 | * Rename `ignoreSign` to `enforceMaskSign` (default `false`). 111 | * Reduce code complexity. 112 | * Export as node module. 113 | * Update TS with options. 114 | * Switch demo to use lib file & highlight valid results. 115 | * Switch from Grunt to rollup. 116 | * Switch from IIFE to UMD output. 117 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | declare const format : (mask: string, value: number, options?: object) => string; 2 | export = format; 3 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | JS Number Formatter • Test/Sample Page 6 | 19 | 20 | 21 |

JavaScript Number Formatter
Test/Sample Page

22 | 23 |

Usage

24 |
25 |
// format( mask, value );
 26 | // result "1,234,567.89"
 27 | format( "#,##0.####", 1234567.890 );
28 |
29 | 30 |

Examples

31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 |
#DescriptionInputFormatOutput
Most common world wide
Simple123456.789#,##0.00
Random20110628.15001234#,##0.###0
Random0#,###.##0
Long number1234567890.1234567890#,###.##0
Negative value-0.1#
Negative value-0.10
Negative value-0.130.#
Negative value-5000.123456789#,##0.######
Localization format
US, UK and many more1234567.890#,##0.00
Estonia, France-128983833.4560022### ###,##
Germany, Italy-1234560.10002920##.000,00
Japan963852741.001###,####.00
Switzerland33445566.778899#'###'#00.00
Any format
-1234.5678##.000,00
4651321.841##^000*00
3411.498-##¿000$00
465456456.8798721200!00@00
Force comma as separator1112341.4348712###,###.
Force space as separator2344441.4348712### ###.
Force dot as separator2345341.4348712###.###
Prefix & Suffix
No spaces123456789.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 comma123456789.9876a, b c? #.00 yep!
Prefix with a periods123456789.9876cost... #,##0.00 yep!
Suffix with comma & period123456789.9876$# ###,00 USD, or euros.
Suffix with period123456789.9876It costs $# ###,00 euros.
Hanging decimal123456789.9876test:### ###. ing
Masks that don't work
No "#" outside of mask123456789.9876item #abc $#,###.00
No numbers outside of mask123456789.987699 items = $#,###.00
No dashes outside of mask123456789.9876cost -- $#,###.00 -- value
No plus sign in prefix123456789.9876++ value! $#,###.00
No plus sign in suffix123456789.9876$#,###.00 ++ value!
322 |
323 | 324 |
325 |

See also...

326 | 327 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 355 | 356 | 357 | -------------------------------------------------------------------------------- /lib/format.es5.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Javascript-number-formatter 3 | * Lightweight & Fast JavaScript Number Formatter 4 | * 5 | * @preserve IntegraXor Web SCADA - JavaScript Number Formatter (http://www.integraxor.com/) 6 | * @author KPL 7 | * @maintainer Rob Garrison 8 | * @copyright 2019 ecava 9 | * @license MIT 10 | * @link http://mottie.github.com/javascript-number-formatter/ 11 | * @version 2.0.9 12 | */ 13 | (function (global, factory) { 14 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : 15 | typeof define === 'function' && define.amd ? define(factory) : 16 | (global = global || self, global.format = factory()); 17 | }(this, function () { 'use strict'; 18 | 19 | function _slicedToArray(arr, i) { 20 | return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); 21 | } 22 | 23 | function _arrayWithHoles(arr) { 24 | if (Array.isArray(arr)) return arr; 25 | } 26 | 27 | function _iterableToArrayLimit(arr, i) { 28 | var _arr = []; 29 | var _n = true; 30 | var _d = false; 31 | var _e = undefined; 32 | 33 | try { 34 | for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { 35 | _arr.push(_s.value); 36 | 37 | if (i && _arr.length === i) break; 38 | } 39 | } catch (err) { 40 | _d = true; 41 | _e = err; 42 | } finally { 43 | try { 44 | if (!_n && _i["return"] != null) _i["return"](); 45 | } finally { 46 | if (_d) throw _e; 47 | } 48 | } 49 | 50 | return _arr; 51 | } 52 | 53 | function _nonIterableRest() { 54 | throw new TypeError("Invalid attempt to destructure non-iterable instance"); 55 | } 56 | 57 | var maskRegex = /[0-9\-+#]/; 58 | var notMaskRegex = /[^\d\-+#]/g; 59 | 60 | function getIndex(mask) { 61 | return mask.search(maskRegex); 62 | } 63 | 64 | function processMask() { 65 | var mask = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : "#.##"; 66 | var maskObj = {}; 67 | var len = mask.length; 68 | var start = getIndex(mask); 69 | maskObj.prefix = start > 0 ? mask.substring(0, start) : ""; // Reverse string: not an ideal method if there are surrogate pairs 70 | 71 | var end = getIndex(mask.split("").reverse().join("")); 72 | var offset = len - end; 73 | var substr = mask.substring(offset, offset + 1); // Add 1 to offset if mask has a trailing decimal/comma 74 | 75 | var indx = offset + (substr === "." || substr === "," ? 1 : 0); 76 | maskObj.suffix = end > 0 ? mask.substring(indx, len) : ""; 77 | maskObj.mask = mask.substring(start, indx); 78 | maskObj.maskHasNegativeSign = maskObj.mask.charAt(0) === "-"; 79 | maskObj.maskHasPositiveSign = maskObj.mask.charAt(0) === "+"; // Search for group separator & decimal; anything not digit, 80 | // not +/- sign, and not # 81 | 82 | var result = maskObj.mask.match(notMaskRegex); // Treat the right most symbol as decimal 83 | 84 | maskObj.decimal = result && result[result.length - 1] || "."; // Treat the left most symbol as group separator 85 | 86 | maskObj.separator = result && result[1] && result[0] || ","; // Split the decimal for the format string if any 87 | 88 | result = maskObj.mask.split(maskObj.decimal); 89 | maskObj.integer = result[0]; 90 | maskObj.fraction = result[1]; 91 | return maskObj; 92 | } 93 | 94 | function processValue(value, maskObj, options) { 95 | var isNegative = false; 96 | var valObj = { 97 | value: value 98 | }; 99 | 100 | if (value < 0) { 101 | isNegative = true; // Process only abs(), and turn on flag. 102 | 103 | valObj.value = -valObj.value; 104 | } 105 | 106 | valObj.sign = isNegative ? "-" : ""; // Fix the decimal first, toFixed will auto fill trailing zero. 107 | 108 | valObj.value = Number(valObj.value).toFixed(maskObj.fraction && maskObj.fraction.length); // Convert number to string to trim off *all* trailing decimal zero(es) 109 | 110 | valObj.value = Number(valObj.value).toString(); // Fill back any trailing zero according to format 111 | // look for last zero in format 112 | 113 | var posTrailZero = maskObj.fraction && maskObj.fraction.lastIndexOf("0"); 114 | 115 | var _valObj$value$split = valObj.value.split("."), 116 | _valObj$value$split2 = _slicedToArray(_valObj$value$split, 2), 117 | _valObj$value$split2$ = _valObj$value$split2[0], 118 | valInteger = _valObj$value$split2$ === void 0 ? "0" : _valObj$value$split2$, 119 | _valObj$value$split2$2 = _valObj$value$split2[1], 120 | valFraction = _valObj$value$split2$2 === void 0 ? "" : _valObj$value$split2$2; 121 | 122 | if (!valFraction || valFraction && valFraction.length <= posTrailZero) { 123 | valFraction = posTrailZero < 0 ? "" : Number("0." + valFraction).toFixed(posTrailZero + 1).replace("0.", ""); 124 | } 125 | 126 | valObj.integer = valInteger; 127 | valObj.fraction = valFraction; 128 | addSeparators(valObj, maskObj); // Remove negative sign if result is zero 129 | 130 | if (valObj.result === "0" || valObj.result === "") { 131 | // Remove negative sign if result is zero 132 | isNegative = false; 133 | valObj.sign = ""; 134 | } 135 | 136 | if (!isNegative && maskObj.maskHasPositiveSign) { 137 | valObj.sign = "+"; 138 | } else if (isNegative && maskObj.maskHasPositiveSign) { 139 | valObj.sign = "-"; 140 | } else if (isNegative) { 141 | valObj.sign = options && options.enforceMaskSign && !maskObj.maskHasNegativeSign ? "" : "-"; 142 | } 143 | 144 | return valObj; 145 | } 146 | 147 | function addSeparators(valObj, maskObj) { 148 | valObj.result = ""; // Look for separator 149 | 150 | var szSep = maskObj.integer.split(maskObj.separator); // Join back without separator for counting the pos of any leading 0 151 | 152 | var maskInteger = szSep.join(""); 153 | var posLeadZero = maskInteger && maskInteger.indexOf("0"); 154 | 155 | if (posLeadZero > -1) { 156 | while (valObj.integer.length < maskInteger.length - posLeadZero) { 157 | valObj.integer = "0" + valObj.integer; 158 | } 159 | } else if (Number(valObj.integer) === 0) { 160 | valObj.integer = ""; 161 | } // Process the first group separator from decimal (.) only, the rest ignore. 162 | // get the length of the last slice of split result. 163 | 164 | 165 | var posSeparator = szSep[1] && szSep[szSep.length - 1].length; 166 | 167 | if (posSeparator) { 168 | var len = valObj.integer.length; 169 | var offset = len % posSeparator; 170 | 171 | for (var indx = 0; indx < len; indx++) { 172 | valObj.result += valObj.integer.charAt(indx); // -posSeparator so that won't trail separator on full length 173 | 174 | if (!((indx - offset + 1) % posSeparator) && indx < len - posSeparator) { 175 | valObj.result += maskObj.separator; 176 | } 177 | } 178 | } else { 179 | valObj.result = valObj.integer; 180 | } 181 | 182 | valObj.result += maskObj.fraction && valObj.fraction ? maskObj.decimal + valObj.fraction : ""; 183 | return valObj; 184 | } 185 | 186 | var format = (function (mask, value) { 187 | var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; 188 | 189 | if (!mask || isNaN(Number(value))) { 190 | // Invalid inputs 191 | return value; 192 | } 193 | 194 | var maskObj = processMask(mask); 195 | var valObj = processValue(value, maskObj, options); 196 | return maskObj.prefix + valObj.sign + valObj.result + maskObj.suffix; 197 | }); 198 | 199 | return format; 200 | 201 | })); 202 | -------------------------------------------------------------------------------- /lib/format.esm.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Javascript-number-formatter 3 | * Lightweight & Fast JavaScript Number Formatter 4 | * 5 | * @preserve IntegraXor Web SCADA - JavaScript Number Formatter (http://www.integraxor.com/) 6 | * @author KPL 7 | * @maintainer Rob Garrison 8 | * @copyright 2019 ecava 9 | * @license MIT 10 | * @link http://mottie.github.com/javascript-number-formatter/ 11 | * @version 2.0.9 12 | */ 13 | const maskRegex = /[0-9\-+#]/; 14 | const notMaskRegex = /[^\d\-+#]/g; 15 | 16 | function getIndex(mask) { 17 | return mask.search(maskRegex); 18 | } 19 | 20 | function processMask(mask = "#.##") { 21 | const maskObj = {}; 22 | const len = mask.length; 23 | const start = getIndex(mask); 24 | maskObj.prefix = start > 0 ? mask.substring(0, start) : ""; 25 | 26 | // Reverse string: not an ideal method if there are surrogate pairs 27 | const end = getIndex(mask.split("").reverse().join("")); 28 | const offset = len - end; 29 | const substr = mask.substring(offset, offset + 1); 30 | // Add 1 to offset if mask has a trailing decimal/comma 31 | const indx = offset + ((substr === "." || (substr === ",")) ? 1 : 0); 32 | maskObj.suffix = end > 0 ? mask.substring(indx, len) : ""; 33 | 34 | maskObj.mask = mask.substring(start, indx); 35 | maskObj.maskHasNegativeSign = maskObj.mask.charAt(0) === "-"; 36 | maskObj.maskHasPositiveSign = maskObj.mask.charAt(0) === "+"; 37 | 38 | // Search for group separator & decimal; anything not digit, 39 | // not +/- sign, and not # 40 | let result = maskObj.mask.match(notMaskRegex); 41 | // Treat the right most symbol as decimal 42 | maskObj.decimal = (result && result[result.length - 1]) || "."; 43 | // Treat the left most symbol as group separator 44 | maskObj.separator = (result && result[1] && result[0]) || ","; 45 | 46 | // Split the decimal for the format string if any 47 | result = maskObj.mask.split(maskObj.decimal); 48 | maskObj.integer = result[0]; 49 | maskObj.fraction = result[1]; 50 | return maskObj; 51 | } 52 | 53 | function processValue(value, maskObj, options) { 54 | let isNegative = false; 55 | const valObj = { 56 | value 57 | }; 58 | if (value < 0) { 59 | isNegative = true; 60 | // Process only abs(), and turn on flag. 61 | valObj.value = -valObj.value; 62 | } 63 | 64 | valObj.sign = isNegative ? "-" : ""; 65 | 66 | // Fix the decimal first, toFixed will auto fill trailing zero. 67 | valObj.value = Number(valObj.value).toFixed(maskObj.fraction && maskObj.fraction.length); 68 | // Convert number to string to trim off *all* trailing decimal zero(es) 69 | valObj.value = Number(valObj.value).toString(); 70 | 71 | // Fill back any trailing zero according to format 72 | // look for last zero in format 73 | const posTrailZero = maskObj.fraction && maskObj.fraction.lastIndexOf("0"); 74 | let [valInteger = "0", valFraction = ""] = valObj.value.split("."); 75 | if (!valFraction || (valFraction && valFraction.length <= posTrailZero)) { 76 | valFraction = posTrailZero < 0 77 | ? "" 78 | : (Number("0." + valFraction).toFixed(posTrailZero + 1)).replace("0.", ""); 79 | } 80 | 81 | valObj.integer = valInteger; 82 | valObj.fraction = valFraction; 83 | addSeparators(valObj, maskObj); 84 | 85 | // Remove negative sign if result is zero 86 | if (valObj.result === "0" || valObj.result === "") { 87 | // Remove negative sign if result is zero 88 | isNegative = false; 89 | valObj.sign = ""; 90 | } 91 | 92 | if (!isNegative && maskObj.maskHasPositiveSign) { 93 | valObj.sign = "+"; 94 | } else if (isNegative && maskObj.maskHasPositiveSign) { 95 | valObj.sign = "-"; 96 | } else if (isNegative) { 97 | valObj.sign = options && options.enforceMaskSign && !maskObj.maskHasNegativeSign 98 | ? "" 99 | : "-"; 100 | } 101 | 102 | return valObj; 103 | } 104 | 105 | function addSeparators(valObj, maskObj) { 106 | valObj.result = ""; 107 | // Look for separator 108 | const szSep = maskObj.integer.split(maskObj.separator); 109 | // Join back without separator for counting the pos of any leading 0 110 | const maskInteger = szSep.join(""); 111 | 112 | const posLeadZero = maskInteger && maskInteger.indexOf("0"); 113 | if (posLeadZero > -1) { 114 | while (valObj.integer.length < (maskInteger.length - posLeadZero)) { 115 | valObj.integer = "0" + valObj.integer; 116 | } 117 | } else if (Number(valObj.integer) === 0) { 118 | valObj.integer = ""; 119 | } 120 | 121 | // Process the first group separator from decimal (.) only, the rest ignore. 122 | // get the length of the last slice of split result. 123 | const posSeparator = (szSep[1] && szSep[szSep.length - 1].length); 124 | if (posSeparator) { 125 | const len = valObj.integer.length; 126 | const offset = len % posSeparator; 127 | for (let indx = 0; indx < len; indx++) { 128 | valObj.result += valObj.integer.charAt(indx); 129 | // -posSeparator so that won't trail separator on full length 130 | if (!((indx - offset + 1) % posSeparator) && indx < len - posSeparator) { 131 | valObj.result += maskObj.separator; 132 | } 133 | } 134 | } else { 135 | valObj.result = valObj.integer; 136 | } 137 | 138 | valObj.result += (maskObj.fraction && valObj.fraction) 139 | ? maskObj.decimal + valObj.fraction 140 | : ""; 141 | return valObj; 142 | } 143 | 144 | var format = (mask, value, options = {}) => { 145 | if (!mask || isNaN(Number(value))) { 146 | // Invalid inputs 147 | return value; 148 | } 149 | 150 | const maskObj = processMask(mask); 151 | const valObj = processValue(value, maskObj, options); 152 | return maskObj.prefix + valObj.sign + valObj.result + maskObj.suffix; 153 | }; 154 | 155 | export default format; 156 | -------------------------------------------------------------------------------- /lib/format.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Javascript-number-formatter 3 | * Lightweight & Fast JavaScript Number Formatter 4 | * 5 | * @preserve IntegraXor Web SCADA - JavaScript Number Formatter (http://www.integraxor.com/) 6 | * @author KPL 7 | * @maintainer Rob Garrison 8 | * @copyright 2019 ecava 9 | * @license MIT 10 | * @link http://mottie.github.com/javascript-number-formatter/ 11 | * @version 2.0.9 12 | */ 13 | (function (global, factory) { 14 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : 15 | typeof define === 'function' && define.amd ? define(factory) : 16 | (global = global || self, global.format = factory()); 17 | }(this, function () { 'use strict'; 18 | 19 | const maskRegex = /[0-9\-+#]/; 20 | const notMaskRegex = /[^\d\-+#]/g; 21 | 22 | function getIndex(mask) { 23 | return mask.search(maskRegex); 24 | } 25 | 26 | function processMask(mask = "#.##") { 27 | const maskObj = {}; 28 | const len = mask.length; 29 | const start = getIndex(mask); 30 | maskObj.prefix = start > 0 ? mask.substring(0, start) : ""; 31 | 32 | // Reverse string: not an ideal method if there are surrogate pairs 33 | const end = getIndex(mask.split("").reverse().join("")); 34 | const offset = len - end; 35 | const substr = mask.substring(offset, offset + 1); 36 | // Add 1 to offset if mask has a trailing decimal/comma 37 | const indx = offset + ((substr === "." || (substr === ",")) ? 1 : 0); 38 | maskObj.suffix = end > 0 ? mask.substring(indx, len) : ""; 39 | 40 | maskObj.mask = mask.substring(start, indx); 41 | maskObj.maskHasNegativeSign = maskObj.mask.charAt(0) === "-"; 42 | maskObj.maskHasPositiveSign = maskObj.mask.charAt(0) === "+"; 43 | 44 | // Search for group separator & decimal; anything not digit, 45 | // not +/- sign, and not # 46 | let result = maskObj.mask.match(notMaskRegex); 47 | // Treat the right most symbol as decimal 48 | maskObj.decimal = (result && result[result.length - 1]) || "."; 49 | // Treat the left most symbol as group separator 50 | maskObj.separator = (result && result[1] && result[0]) || ","; 51 | 52 | // Split the decimal for the format string if any 53 | result = maskObj.mask.split(maskObj.decimal); 54 | maskObj.integer = result[0]; 55 | maskObj.fraction = result[1]; 56 | return maskObj; 57 | } 58 | 59 | function processValue(value, maskObj, options) { 60 | let isNegative = false; 61 | const valObj = { 62 | value 63 | }; 64 | if (value < 0) { 65 | isNegative = true; 66 | // Process only abs(), and turn on flag. 67 | valObj.value = -valObj.value; 68 | } 69 | 70 | valObj.sign = isNegative ? "-" : ""; 71 | 72 | // Fix the decimal first, toFixed will auto fill trailing zero. 73 | valObj.value = Number(valObj.value).toFixed(maskObj.fraction && maskObj.fraction.length); 74 | // Convert number to string to trim off *all* trailing decimal zero(es) 75 | valObj.value = Number(valObj.value).toString(); 76 | 77 | // Fill back any trailing zero according to format 78 | // look for last zero in format 79 | const posTrailZero = maskObj.fraction && maskObj.fraction.lastIndexOf("0"); 80 | let [valInteger = "0", valFraction = ""] = valObj.value.split("."); 81 | if (!valFraction || (valFraction && valFraction.length <= posTrailZero)) { 82 | valFraction = posTrailZero < 0 83 | ? "" 84 | : (Number("0." + valFraction).toFixed(posTrailZero + 1)).replace("0.", ""); 85 | } 86 | 87 | valObj.integer = valInteger; 88 | valObj.fraction = valFraction; 89 | addSeparators(valObj, maskObj); 90 | 91 | // Remove negative sign if result is zero 92 | if (valObj.result === "0" || valObj.result === "") { 93 | // Remove negative sign if result is zero 94 | isNegative = false; 95 | valObj.sign = ""; 96 | } 97 | 98 | if (!isNegative && maskObj.maskHasPositiveSign) { 99 | valObj.sign = "+"; 100 | } else if (isNegative && maskObj.maskHasPositiveSign) { 101 | valObj.sign = "-"; 102 | } else if (isNegative) { 103 | valObj.sign = options && options.enforceMaskSign && !maskObj.maskHasNegativeSign 104 | ? "" 105 | : "-"; 106 | } 107 | 108 | return valObj; 109 | } 110 | 111 | function addSeparators(valObj, maskObj) { 112 | valObj.result = ""; 113 | // Look for separator 114 | const szSep = maskObj.integer.split(maskObj.separator); 115 | // Join back without separator for counting the pos of any leading 0 116 | const maskInteger = szSep.join(""); 117 | 118 | const posLeadZero = maskInteger && maskInteger.indexOf("0"); 119 | if (posLeadZero > -1) { 120 | while (valObj.integer.length < (maskInteger.length - posLeadZero)) { 121 | valObj.integer = "0" + valObj.integer; 122 | } 123 | } else if (Number(valObj.integer) === 0) { 124 | valObj.integer = ""; 125 | } 126 | 127 | // Process the first group separator from decimal (.) only, the rest ignore. 128 | // get the length of the last slice of split result. 129 | const posSeparator = (szSep[1] && szSep[szSep.length - 1].length); 130 | if (posSeparator) { 131 | const len = valObj.integer.length; 132 | const offset = len % posSeparator; 133 | for (let indx = 0; indx < len; indx++) { 134 | valObj.result += valObj.integer.charAt(indx); 135 | // -posSeparator so that won't trail separator on full length 136 | if (!((indx - offset + 1) % posSeparator) && indx < len - posSeparator) { 137 | valObj.result += maskObj.separator; 138 | } 139 | } 140 | } else { 141 | valObj.result = valObj.integer; 142 | } 143 | 144 | valObj.result += (maskObj.fraction && valObj.fraction) 145 | ? maskObj.decimal + valObj.fraction 146 | : ""; 147 | return valObj; 148 | } 149 | 150 | var format = (mask, value, options = {}) => { 151 | if (!mask || isNaN(Number(value))) { 152 | // Invalid inputs 153 | return value; 154 | } 155 | 156 | const maskObj = processMask(mask); 157 | const valObj = processValue(value, maskObj, options); 158 | return maskObj.prefix + valObj.sign + valObj.result + maskObj.suffix; 159 | }; 160 | 161 | return format; 162 | 163 | })); 164 | -------------------------------------------------------------------------------- /lib/format.min.js: -------------------------------------------------------------------------------- 1 | /*! Javascript-number-formatter v2.0.9 */ 2 | !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e=e||self).format=t()}(this,function(){"use strict";const e=/[0-9\-+#]/,t=/[^\d\-+#]/g;function n(t){return t.search(e)}return(e,i,r={})=>{if(!e||isNaN(Number(i)))return i;const s=function(e="#.##"){const i={},r=e.length,s=n(e);i.prefix=s>0?e.substring(0,s):"";const a=n(e.split("").reverse().join("")),o=r-a,u=e.substring(o,o+1),l=o+("."===u||","===u?1:0);i.suffix=a>0?e.substring(l,r):"",i.mask=e.substring(s,l),i.maskHasNegativeSign="-"===i.mask.charAt(0),i.maskHasPositiveSign="+"===i.mask.charAt(0);let g=i.mask.match(t);return i.decimal=g&&g[g.length-1]||".",i.separator=g&&g[1]&&g[0]||",",g=i.mask.split(i.decimal),i.integer=g[0],i.fraction=g[1],i}(e),a=function(e,t,n){let i=!1;const r={value:e};e<0&&(i=!0,r.value=-r.value),r.sign=i?"-":"",r.value=Number(r.value).toFixed(t.fraction&&t.fraction.length),r.value=Number(r.value).toString();const s=t.fraction&&t.fraction.lastIndexOf("0");let[a="0",o=""]=r.value.split(".");return(!o||o&&o.length<=s)&&(o=s<0?"":Number("0."+o).toFixed(s+1).replace("0.","")),r.integer=a,r.fraction=o,function(e,t){e.result="";const n=t.integer.split(t.separator),i=n.join(""),r=i&&i.indexOf("0");if(r>-1)for(;e.integer.length=8" 57 | }, 58 | "scripts": { 59 | "build": "rollup -c", 60 | "test": "xo && ava", 61 | "updater": "npx updates -cu && npm install" 62 | }, 63 | "typings": "./index.d.ts", 64 | "devDependencies": { 65 | "@babel/core": "^7.2.2", 66 | "@babel/plugin-transform-object-assign": "^7.2.0", 67 | "@babel/preset-env": "^7.3.1", 68 | "ava": "*", 69 | "rollup": "^1.1.2", 70 | "rollup-plugin-babel": "^4.3.2", 71 | "rollup-plugin-cjs-es": "^0.7.0", 72 | "rollup-plugin-node-resolve": "^4.0.0", 73 | "rollup-plugin-terser": "^4.0.3", 74 | "updates": "^6.2.1", 75 | "xo": "*" 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import cjs from "rollup-plugin-cjs-es"; 2 | import resolve from "rollup-plugin-node-resolve"; 3 | import babel from "rollup-plugin-babel"; 4 | import {terser} from "rollup-plugin-terser"; 5 | 6 | import pkg from "./package.json"; 7 | 8 | const banner = `/** 9 | * Javascript-number-formatter 10 | * Lightweight & Fast JavaScript Number Formatter 11 | * 12 | * @preserve IntegraXor Web SCADA - JavaScript Number Formatter (http://www.integraxor.com/) 13 | * @author KPL 14 | * @maintainer Rob Garrison 15 | * @copyright ${new Date().getFullYear()} ecava 16 | * @license MIT 17 | * @link http://mottie.github.com/javascript-number-formatter/ 18 | * @version ${pkg.version} 19 | */`; 20 | 21 | export default [{ 22 | input: "src/format.js", 23 | output: [{ 24 | file: "lib/format.js", 25 | name: "format", 26 | format: "umd", 27 | sourceMap: false, 28 | banner, 29 | },{ 30 | file: "lib/format.esm.js", 31 | name: "format", 32 | format: "esm", 33 | sourceMap: false, 34 | banner, 35 | }], 36 | plugins: [ 37 | resolve(), 38 | cjs({ 39 | nested: true 40 | }) 41 | ] 42 | }, { 43 | input: "src/format.js", 44 | output: [{ 45 | file: "lib/format.es5.js", 46 | name: "format", 47 | format: "umd", 48 | sourceMap: false, 49 | banner, 50 | }], 51 | plugins: [ 52 | resolve(), 53 | cjs({ 54 | nested: true 55 | }), 56 | babel({ 57 | exclude: "node_modules/**", 58 | presets: [ 59 | ["@babel/preset-env", { 60 | modules: false 61 | }] 62 | ], 63 | plugins: [ 64 | "@babel/plugin-transform-object-assign" 65 | ] 66 | }), 67 | ] 68 | }, 69 | 70 | { 71 | input: "src/format.js", 72 | output: { 73 | file: "lib/format.min.js", 74 | name: "format", 75 | format: "umd", 76 | sourceMap: false, 77 | banner: `/*! Javascript-number-formatter v${pkg.version} */`, 78 | }, 79 | plugins: [ 80 | resolve(), 81 | cjs({ 82 | nested: true 83 | }), 84 | terser({ 85 | compress: { 86 | passes: 3 87 | }, 88 | output: { 89 | comments: /^!/ 90 | } 91 | }) 92 | ] 93 | }]; 94 | -------------------------------------------------------------------------------- /src/format.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const maskRegex = /[0-9\-+#]/; 4 | const notMaskRegex = /[^\d\-+#]/g; 5 | 6 | function getIndex(mask) { 7 | return mask.search(maskRegex); 8 | } 9 | 10 | function processMask(mask = "#.##") { 11 | const maskObj = {}; 12 | const len = mask.length; 13 | const start = getIndex(mask); 14 | maskObj.prefix = start > 0 ? mask.substring(0, start) : ""; 15 | 16 | // Reverse string: not an ideal method if there are surrogate pairs 17 | const end = getIndex(mask.split("").reverse().join("")); 18 | const offset = len - end; 19 | const substr = mask.substring(offset, offset + 1); 20 | // Add 1 to offset if mask has a trailing decimal/comma 21 | const indx = offset + ((substr === "." || (substr === ",")) ? 1 : 0); 22 | maskObj.suffix = end > 0 ? mask.substring(indx, len) : ""; 23 | 24 | maskObj.mask = mask.substring(start, indx); 25 | maskObj.maskHasNegativeSign = maskObj.mask.charAt(0) === "-"; 26 | maskObj.maskHasPositiveSign = maskObj.mask.charAt(0) === "+"; 27 | 28 | // Search for group separator & decimal; anything not digit, 29 | // not +/- sign, and not # 30 | let result = maskObj.mask.match(notMaskRegex); 31 | // Treat the right most symbol as decimal 32 | maskObj.decimal = (result && result[result.length - 1]) || "."; 33 | // Treat the left most symbol as group separator 34 | maskObj.separator = (result && result[1] && result[0]) || ","; 35 | 36 | // Split the decimal for the format string if any 37 | result = maskObj.mask.split(maskObj.decimal); 38 | maskObj.integer = result[0]; 39 | maskObj.fraction = result[1]; 40 | return maskObj; 41 | } 42 | 43 | function processValue(value, maskObj, options) { 44 | let isNegative = false; 45 | const valObj = { 46 | value 47 | }; 48 | if (value < 0) { 49 | isNegative = true; 50 | // Process only abs(), and turn on flag. 51 | valObj.value = -valObj.value; 52 | } 53 | 54 | valObj.sign = isNegative ? "-" : ""; 55 | 56 | // Fix the decimal first, toFixed will auto fill trailing zero. 57 | valObj.value = Number(valObj.value).toFixed(maskObj.fraction && maskObj.fraction.length); 58 | // Convert number to string to trim off *all* trailing decimal zero(es) 59 | valObj.value = Number(valObj.value).toString(); 60 | 61 | // Fill back any trailing zero according to format 62 | // look for last zero in format 63 | const posTrailZero = maskObj.fraction && maskObj.fraction.lastIndexOf("0"); 64 | let [valInteger = "0", valFraction = ""] = valObj.value.split("."); 65 | if (!valFraction || (valFraction && valFraction.length <= posTrailZero)) { 66 | valFraction = posTrailZero < 0 67 | ? "" 68 | : (Number("0." + valFraction).toFixed(posTrailZero + 1)).replace("0.", ""); 69 | } 70 | 71 | valObj.integer = valInteger; 72 | valObj.fraction = valFraction; 73 | addSeparators(valObj, maskObj); 74 | 75 | // Remove negative sign if result is zero 76 | if (valObj.result === "0" || valObj.result === "") { 77 | // Remove negative sign if result is zero 78 | isNegative = false; 79 | valObj.sign = ""; 80 | } 81 | 82 | if (!isNegative && maskObj.maskHasPositiveSign) { 83 | valObj.sign = "+"; 84 | } else if (isNegative && maskObj.maskHasPositiveSign) { 85 | valObj.sign = "-"; 86 | } else if (isNegative) { 87 | valObj.sign = options && options.enforceMaskSign && !maskObj.maskHasNegativeSign 88 | ? "" 89 | : "-"; 90 | } 91 | 92 | return valObj; 93 | } 94 | 95 | function addSeparators(valObj, maskObj) { 96 | valObj.result = ""; 97 | // Look for separator 98 | const szSep = maskObj.integer.split(maskObj.separator); 99 | // Join back without separator for counting the pos of any leading 0 100 | const maskInteger = szSep.join(""); 101 | 102 | const posLeadZero = maskInteger && maskInteger.indexOf("0"); 103 | if (posLeadZero > -1) { 104 | while (valObj.integer.length < (maskInteger.length - posLeadZero)) { 105 | valObj.integer = "0" + valObj.integer; 106 | } 107 | } else if (Number(valObj.integer) === 0) { 108 | valObj.integer = ""; 109 | } 110 | 111 | // Process the first group separator from decimal (.) only, the rest ignore. 112 | // get the length of the last slice of split result. 113 | const posSeparator = (szSep[1] && szSep[szSep.length - 1].length); 114 | if (posSeparator) { 115 | const len = valObj.integer.length; 116 | const offset = len % posSeparator; 117 | for (let indx = 0; indx < len; indx++) { 118 | valObj.result += valObj.integer.charAt(indx); 119 | // -posSeparator so that won't trail separator on full length 120 | if (!((indx - offset + 1) % posSeparator) && indx < len - posSeparator) { 121 | valObj.result += maskObj.separator; 122 | } 123 | } 124 | } else { 125 | valObj.result = valObj.integer; 126 | } 127 | 128 | valObj.result += (maskObj.fraction && valObj.fraction) 129 | ? maskObj.decimal + valObj.fraction 130 | : ""; 131 | return valObj; 132 | } 133 | 134 | module.exports = (mask, value, options = {}) => { 135 | if (!mask || isNaN(Number(value))) { 136 | // Invalid inputs 137 | return value; 138 | } 139 | 140 | const maskObj = processMask(mask); 141 | const valObj = processValue(value, maskObj, options); 142 | return maskObj.prefix + valObj.sign + valObj.result + maskObj.suffix; 143 | }; 144 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | import test from "ava"; 2 | import format from "../src/format"; 3 | 4 | /* Simple mask */ 5 | test("basic masks", t => { 6 | t.is(format("#,##0.00", 123456789.123), "123,456,789.12", "Mask: \"#,##0.00\""); 7 | t.is(format("#,##0.00", "123456.789"), "123,456.79"); 8 | t.is(format("#,##0.00", 123456.789), "123,456.79"); 9 | t.is(format("#,##0.00", 123456.7), "123,456.70"); 10 | t.is(format("#,##0.00", 123456), "123,456.00"); 11 | t.is(format("#,##0.00", 0), "0.00"); 12 | t.is(format("#", -0.1), ""); 13 | t.is(format("0", -0.1), "0"); 14 | t.is(format("0.#", -0.13), "-0.1"); 15 | t.is(format("#,##0.00", -123), "-123.00"); 16 | t.is(format("#,##0.00", -123456.789), "-123,456.79"); 17 | 18 | t.is(format("#,##0.0", 123456789.123), "123,456,789.1", "Mask: \"#,##0.0\""); 19 | t.is(format("#,##0.0", 123456.789), "123,456.8"); 20 | t.is(format("#,##0.0", 123456.7), "123,456.7"); 21 | t.is(format("#,##0.0", 123456), "123,456.0"); 22 | t.is(format("#,##0.0", 0), "0.0"); 23 | t.is(format("#,##0.0", -123), "-123.0"); 24 | t.is(format("#,##0.0", -123456.789), "-123,456.8"); 25 | 26 | t.is(format("#,##0.", 123456789.123), "123,456,789", "Mask: \"#,##0.\""); 27 | t.is(format("#,##0.", 123456.789), "123,457"); 28 | t.is(format("#,##0.", 123456.7), "123,457"); 29 | t.is(format("#,##0.", 123456), "123,456"); 30 | t.is(format("#,##0.", 0), "0"); 31 | t.is(format("#,##0.", -123), "-123"); 32 | t.is(format("#,##0.", -123456.789), "-123,457"); 33 | 34 | t.is(format("#.##0,", 123456789.123), "123.456.789", "Mask: \"#.##0,\""); 35 | t.is(format("#.##0,", 123456.789), "123.457"); 36 | t.is(format("#.##0,", 123456.7), "123.457"); 37 | t.is(format("#.##0,", 123456), "123.456"); 38 | t.is(format("#.##0,", 0), "0"); 39 | t.is(format("#.##0,", -123), "-123"); 40 | t.is(format("#.##0,", -123456.789), "-123.457"); 41 | 42 | t.is(format("#,##0.###0", 12345678.98765432), "12,345,678.9877", "Mask: \"#,##0.###0\""); 43 | }); 44 | 45 | /* Localizations */ 46 | test("Localizations", t => { 47 | t.is(format("### ###,##", 123456789.987654321), "123 456 789,99", "Estonia, France: ### ###,##"); 48 | t.is(format("##.000,00", 123456789.987654321), "123.456.789,99", "Germany, Italy: ##.000,00"); 49 | t.is(format("###,####.00", 123456789.987654321), "1,2345,6789.99", "Japan: ###,####.00"); 50 | t.is(format("#'###'#00.00", 123456789.987654321), "123'456'789.99", "Switzerland: #'###'#00.00"); 51 | }); 52 | 53 | /* Made-up-izations */ 54 | test("Any format", t => { 55 | t.is(format("#,##0 00", 123456789.987654321), "123,456,789 99"); 56 | t.is(format("#x##0 00", 123456789.987654321), "123x456x789 99"); 57 | t.is(format("##^000*00", 123456789.987654321), "123^456^789*99"); 58 | t.is(format("##¿000$00", 123456789.987654321), "123¿456¿789$99"); 59 | t.is(format("00!00@00", 123456789.987654321), "1!23!45!67!89@99"); 60 | }); 61 | 62 | /* Any non-zero digit in mask behaves like # */ 63 | test("Non-zero digits", t => { 64 | t.is(format("999.999", 123.0), "123"); 65 | t.is(format("123,456789", 123.0), "123"); 66 | }); 67 | 68 | /* Precision */ 69 | test("Precision", t => { 70 | t.is(format("### ###,", 123456789.987654321), "123 456 790"); 71 | t.is(format("###.###,", 123456789.987654321), "123.456.790"); 72 | t.is(format("##,000.", 123456789.987654321), "123,456,790"); 73 | t.is(format("###,####.", 123456789.187654321), "1,2345,6789"); 74 | t.is(format("#'###'#00,", 123456789.087654321), "123'456'789"); 75 | t.is(format("#,##0.####", 1234567.890), "1,234,567.89"); 76 | t.is(format("#,##0.###0", 1234567.890), "1,234,567.8900"); 77 | t.is(format("#,##0.##0#", 1234567.890), "1,234,567.890"); 78 | t.is(format("#,##0.#0##", 1234567.890), "1,234,567.89"); 79 | t.is(format("#,##0.#", 1234567.890), "1,234,567.9"); 80 | t.is(format("#,###.", 1234567.890), "1,234,568"); 81 | 82 | t.is(format("#,###.", 1234567), "1,234,567"); 83 | t.is(format("#,###.#", 1234567), "1,234,567"); 84 | t.is(format("#,###.##", 1234567), "1,234,567"); 85 | t.is(format("#,###.0", 1234567), "1,234,567.0"); 86 | t.is(format("#,###.00", 1234567), "1,234,567.00"); 87 | 88 | t.is(format("#.00", 0.78), ".78"); 89 | t.is(format("#.000", 0.78), ".780"); 90 | t.is(format("#.0000", 0.78), ".7800"); 91 | t.is(format("0.00", 0.78), "0.78"); 92 | t.is(format("0.000", 0.78), "0.780"); 93 | t.is(format("0.0000", 0.78), "0.7800"); 94 | t.is(format("00.00", 0.78), "00.78"); 95 | 96 | t.is(format("#.##", 0), ""); 97 | t.is(format("0.00", 0), "0.00"); 98 | t.is(format("0", 0), "0"); 99 | }); 100 | 101 | /* Mask with prefix and/or suffix */ 102 | test("Prefix & Suffix", t => { 103 | // Usage 104 | t.is(format("$#,##0.00USD", 123456789.123), "$123,456,789.12USD", "$#,##0.00USD"); 105 | t.is(format("$ #,##0.00 USD", 123456789.123), "$ 123,456,789.12 USD", "$ #,##0.00 USD"); 106 | t.is(format("##.000,00 €", 123456789.123), "123.456.789,12 €", "##.000,00 €"); 107 | t.is(format("###,####.00 ¥", 123456789.123), "1,2345,6789.12 ¥", "###,####.00 ¥"); 108 | 109 | t.is(format("### ###,### ¢ and stuff", 123456789.123), "123 456 789,123 ¢ and stuff", "### ###,### ¢ and stuff"); 110 | t.is(format(" #,##0.00 a b c ", 123456789.123), " 123,456,789.12 a b c ", "leading & trailing spaces"); 111 | 112 | t.is(format("$ (#,###.00) Money", 123456789.123), "$ (123,456,789.12) Money", "spaces & mask wrapped in parenthesis"); 113 | 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"); 114 | t.is(format("$# ###,00 USD, or euros.", 123456789.123), "$123 456 789,12 USD, or euros.", "suffix with comma & period"); 115 | t.is(format("prefix with a periods?... #.00 yep!", 123456789.123), "prefix with a periods?... 123456789.12 yep!", "prefix with a periods"); 116 | t.is(format("It costs $# ###,00 euros.", 123456789.123), "It costs $123 456 789,12 euros.", "suffix with period"); 117 | t.is(format("test:### ###. ing", 123456789.123), "test:123 456 789 ing", "Hanging decimals"); 118 | }); 119 | 120 | test("Masks that don't work", t => { 121 | // Not allowed 122 | 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"); 123 | t.is(format("99 items = $#,###.00", 123456789.123) !== "99 items = $123,456,789.12", true, "BROKEN: numbers outside of mask"); 124 | t.is(format("cost -- $#,###.00 -- value", 123456789.123) !== "cost -- $123,456,789.12 -- value", true, "BROKEN: dashes outside of mask"); 125 | t.is(format("++ value! $#,###.00 ++ value!", 123456789.123) !== "++ value! $123,456,789.12 ++ value!", true, "BROKEN: plus signs outside of mask"); 126 | }); 127 | 128 | test("Numbers with negative sign", t => { 129 | t.is(format("-#,##0.######", -5000.123456789), "-5,000.123457"); 130 | t.is(format("-#,##0.######", 5000.123456789), "5,000.123457"); 131 | t.is(format("#,##0.######", -5000.123456789), "-5,000.123457"); 132 | t.is(format("$ #,###.00", -1234567.890), "$ -1,234,567.89"); 133 | t.is(format("$ -#,###.00", -1234567.890), "$ -1,234,567.89"); 134 | 135 | t.is(format("-#,##0.######", -5000.123456789, {enforceMaskSign: true}), "-5,000.123457"); 136 | t.is(format("-#,##0.######", 5000.123456789, {enforceMaskSign: true}), "5,000.123457"); 137 | t.is(format("#,##0.######", -5000.123456789, {enforceMaskSign: true}), "5,000.123457"); 138 | t.is(format("$ #,###.00", -1234567.890, {enforceMaskSign: true}), "$ 1,234,567.89"); 139 | t.is(format("$ -#,###.00", -1234567.890, {enforceMaskSign: true}), "$ -1,234,567.89"); 140 | }); 141 | 142 | test("Numbers with positive sign", t => { 143 | t.is(format("+#,##0.######", +5000.123456789), "+5,000.123457"); 144 | t.is(format("+#,##0.######", 5000.123456789), "+5,000.123457"); 145 | t.is(format("#,##0.######", +5000.123456789), "5,000.123457"); 146 | t.is(format("+#,##0.######", -5000.123456789), "-5,000.123457"); 147 | 148 | t.is(format("+#,##0.######", +5000.123456789, {enforceMaskSign: true}), "+5,000.123457"); 149 | t.is(format("+#,##0.######", 5000.123456789, {enforceMaskSign: true}), "+5,000.123457"); 150 | t.is(format("#,##0.######", +5000.123456789, {enforceMaskSign: true}), "5,000.123457"); 151 | t.is(format("+#,##0.######", -5000.123456789, {enforceMaskSign: true}), "-5,000.123457"); 152 | t.is(format("$ +#,###.00", -1234567.890, {enforceMaskSign: true}), "$ -1,234,567.89"); 153 | t.is(format("$ +#,###.00", 1234567.890, {enforceMaskSign: true}), "$ +1,234,567.89"); 154 | }); 155 | --------------------------------------------------------------------------------