├── .gitignore ├── .travis.yml ├── .verbrc.md ├── Gulpfile.coffee ├── LICENSE ├── README.md ├── dist ├── parse-decimal-number.js └── parse-decimal-number.min.js ├── docs ├── about.md ├── related.md ├── usage-api.md └── usage-numeral.md ├── gulpfile.js ├── package.json ├── renovate.json ├── src └── parse-decimal-number.coffee └── test └── parse-decimal-number-test.coffee /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | # Logs 3 | logs 4 | *.log 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # Compiled binary addons (http://nodejs.org/api/addons.html) 21 | build/Release 22 | 23 | # Dependency directory 24 | # Commenting this out is preferred by some people, see 25 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 26 | node_modules 27 | 28 | # Users Environment Variables 29 | .lock-wscript 30 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "stable" 4 | -------------------------------------------------------------------------------- /.verbrc.md: -------------------------------------------------------------------------------- 1 | # {%= name %} ![Travis](https://img.shields.io/travis/AndreasPizsa/{%= name %}.svg?style=flat-square) [![Coverage Status](https://img.shields.io/coveralls/AndreasPizsa/{%= name %}.svg?style=flat-square)](https://coveralls.io/github/AndreasPizsa/{%= name %}?branch=master) ![Downloads](https://img.shields.io/npm/dm/{%= name %}.svg?style=flat-square) 2 | > {%= description %} 3 | 4 | ## About 5 | {%= docs('about') %} 6 | 7 | ## Install 8 | {%= include("install-npm", {save: "--save"}) %} 9 | 10 | ## Usage 11 | {%= docs('usage-api') %} 12 | {%= docs('usage-numeral') %} 13 | 14 | ## Related Projects 15 | {%= docs('related') %} 16 | 17 | ## Running tests 18 | {%%= include("tests") %} 19 | 20 | ## Contributing 21 | {%= include("contributing") %} 22 | 23 | ## Author 24 | {%= include("author") %} 25 | 26 | ## License 27 | {%= copyright() %} 28 | {%= license() %} 29 | 30 | *** 31 | 32 | {%= include("footer") %} 33 | -------------------------------------------------------------------------------- /Gulpfile.coffee: -------------------------------------------------------------------------------- 1 | coffee = require 'gulp-coffee' 2 | coveralls = require 'gulp-coveralls' 3 | gulp = require 'gulp' 4 | gutil = require 'gulp-util' 5 | istanbul = require 'gulp-istanbul' 6 | mocha = require 'gulp-mocha' 7 | path = require 'path' 8 | uglify = require 'gulp-uglifyjs' 9 | verb = require 'gulp-verb' 10 | 11 | destDir = path.dirname require('./package.json').main 12 | 13 | gulp.task 'docs', -> 14 | # sadly, verb is broken 15 | gulp.src ['.verbrc.md'] 16 | .pipe verb dest:'README.md' 17 | .pipe gulp.dest './' 18 | 19 | gulp.task 'test', -> 20 | gulp.src './src/*.js' 21 | .pipe istanbul() 22 | .on 'finish',-> 23 | gulp.src './test/*.{js,coffee,litcoffee}', read:false 24 | .pipe mocha 25 | reporter: 'spec' 26 | compilers: 'coffee:coffee-script/register' 27 | .pipe istanbul.writeReports() 28 | 29 | gulp.task 'coveralls', ['test'], -> 30 | gulp.src 'coverage/**/lcov.info' 31 | .pipe coveralls() 32 | 33 | gulp.task 'compile',-> 34 | gulp.src './src/*.{coffee,litcoffee}' 35 | .pipe coffee bare:false 36 | .on 'error', gutil.log 37 | .pipe gulp.dest destDir 38 | .pipe uglify(path.basename(require('./package.json').main).replace('.js','.min.js')) 39 | .pipe gulp.dest destDir 40 | 41 | gulp.task 'default', ['compile','test', 'coveralls'] #, 'docs' 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Andreas Pizsa 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # parse-decimal-number ![Travis](https://img.shields.io/travis/AndreasPizsa/parse-decimal-number.svg?style=flat-square) [![Coverage Status](https://img.shields.io/coveralls/AndreasPizsa/parse-decimal-number.svg?style=flat-square)](https://coveralls.io/github/AndreasPizsa/parse-decimal-number?branch=master) ![Downloads](https://img.shields.io/npm/dm/parse-decimal-number.svg?style=flat-square) 2 | [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2FAndreasPizsa%2Fparse-decimal-number.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2FAndreasPizsa%2Fparse-decimal-number?ref=badge_shield) 3 | > Parse a decimal number with i18n format support (localized decimal points and thousands separators) 4 | 5 | ## About 6 | OK, let’s fix international numbers **parsing** and **validation** once and forever. I got the inspiration for this in a UI project because somehow the libraries we used didn’t do a great job, so I wrote my own parser, and this is a more polished version of it. 7 | 8 | These are the design goals: 9 | 10 | * **Simple.** String in, float out, done. ✓ 11 | * **Accurate.** Parses numbers and returns `NaN` for non-numbers. (=good for input validation) ✓ 12 | * **Lightweight.** (<1k minified) ✓ 13 | * **Complete.** No external dependencies ✓ 14 | * **Solid.** 100% Code Coverage ✓ 15 | * **CLDR Support.** Supports `cldr` data ✓ 16 | 17 | In it’s simplest form, you just use it as a `parseFloat` replacement. 18 | 19 | ## Install 20 | #### Install with [npm](https://npmjs.org) 21 | 22 | ```bash 23 | npm i parse-decimal-number --save 24 | ``` 25 | 26 | ## Usage 27 | ```javascript 28 | parseDecimalNumber = require('parse-decimal-number'); 29 | console.log(parseDecimalNumber('12,345,678.90')); 30 | // -> 12345678.90 31 | 32 | console.log(parseDecimalNumber('12.345.678,90','.,')); 33 | // -> 12345678.90 34 | ``` 35 | 36 | ### parseDecimalNumber(string _[,options]_) 37 | Returns a `float` representation of _string_ or `NaN` if _string_ is not a parseable number. Use the optional `options` parameter to specify the thousands and decimal point characters. 38 | 39 | #### Parameters 40 | **string** A String that is supposed to contain a number. 41 | 42 | **options** _optional_ A string, array or hash with thousands and decimal separators. 43 | 44 | * _String_ 45 | a two-character string consisting of the thousands character followed by the decimal point character, e.g. `',.'` 46 | 47 | * _Array_ 48 | An array of two elements, the first being the thousands character, the second being the decimal point character, e.g. `['.',',']` 49 | 50 | * _Hash_ with the following elements (this is compatible with NumeralJS) 51 | * **`thousands`** thousands separator character. _Default:_ `,` 52 | * **`decimal`** decimal point character. _Default:_ `.` 53 | 54 | **enforceGroupSize** A boolean indicating whether to support that individual groups between the thousands character are exactly 3 digits 55 | 56 | #### Examples 57 | 58 | ```javascript 59 | console.log(parseDecimalNumber('12.345.678,90')); 60 | // -> 12345678.90 61 | ``` 62 | 63 | ###### String `options` 64 | ```javascript 65 | console.log(parseDecimalNumber('12.345.678,90','.,')); 66 | // -> 12345678.90 67 | ``` 68 | 69 | ###### Array `options` 70 | ```javascript 71 | console.log(parseDecimalNumber('12.345.678,90',['.',','])); 72 | // -> 12345678.90 73 | ``` 74 | 75 | ###### Hash `options` 76 | ```javascript 77 | var customSeparators = {thousands:'.',decimal:','}; 78 | console.log(parseDecimalNumber('12.345.678,90',customSeparators)); 79 | // -> 12345678.90 80 | ``` 81 | 82 | ### parseDecimalNumber.withOptions(options) 83 | Returns a _function_ that will take a _string_ as an argument and return a `float` or `NaN`, just like `parseDecimalNumber`. 84 | 85 | #### Example 86 | 87 | ```javascript 88 | const cldr = require('cldr'); 89 | 90 | const locale = 'de_DE'; 91 | const options = cldr.extractNumberSymbols(locale); 92 | 93 | const parse = parseDecimalNumber.withOptions(options); 94 | 95 | parse('123.456.789,0123'); // -> 123456789.0123 96 | ``` 97 | 98 | 99 | ### Setting and Resetting Default Options 100 | 101 | ##### parseDecimalNumber.setOptions 102 | Set the default thousands and decimal characters that are used when no options are passed to `parseDecimalNumber`. 103 | 104 | ```javascript 105 | var defaultSeparators = {thousands:'.',decimal:','}; 106 | parseDecimalNumber.setOptions(defaultSeparators); 107 | 108 | console.log(parseDecimalNumber('12.345.678,90')); 109 | // -> 12345678.90 110 | ``` 111 | 112 | 113 | ##### parseDecimalNumber.factorySettings 114 | has the same effect as `parseDecimalNumber.setOptions({thousands:',',decimal:'.'};)` 115 | 116 | ## Using with `cldr` 117 | 118 | You can easily apply CLDR data using the [`cldr`](https://www.npmjs.com/package/cldr) package: 119 | 120 | ```javascript 121 | const cldr = require('cldr'); 122 | 123 | parseDecimalNumber( 124 | '12.345.678,90', 125 | cldr.extractNumberSymbols('de_DE') 126 | ); 127 | ``` 128 | 129 | 130 | ## Using with Numeral.js 131 | [Numeral.js](http://numeraljs.com/) is good at formatting numbers and comes with an extensive set of locale data that you can use with `parse-decimal-number`. 132 | 133 | If you use `numeral` in your project, you can use their locale data as follows: 134 | 135 | ```javascript 136 | parseDecimalNumber('12.345.678,90', numeral.localeData('de').delimiters); 137 | // -> 12345678.9 138 | ``` 139 | 140 | You can of course use the same data to set the default values for `parse-decimal-number`: 141 | 142 | ```javascript 143 | parseDecimalNumber.setOptions(numeral.localeData('de').delimiters); 144 | parseDecimalNumber('12.345.678,90'); 145 | // -> 12345678.9 146 | ``` 147 | 148 | Done :relaxed: 149 | 150 | 151 | ## Related Projects 152 | To keep this project as small and modular as possible, the locale data itself has been left out of this library. If you need locale date, other projects might be helpful: 153 | 154 | * [cldr](https://www.npmjs.com/package/cldr) 155 | * [Numeral](http://numeraljs.com) 156 | * [jsi18n](https://github.com/marcoscaceres/jsi18n) 157 | * [Unicode Common Locale Data Repository](http://cldr.unicode.org/index/downloads/latest) 158 | * [Wikipedia](http://en.wikipedia.org/wiki/Decimal_mark#Examples_of_use) 159 | 160 | 161 | ## Running tests 162 | {%= include("tests") %} 163 | 164 | ## Contributing 165 | In lieu of a formal styleguide, take care to maintain the existing coding style. Add unit tests for any new or changed functionality, and please re-build the documentation with [gulp-verb](https://github.com/assemble/gulp-verb) before submitting a pull request. 166 | 167 | 168 | ## Author 169 | 170 | **Andreas Pizsa (http://github.com/AndreasPizsa)** 171 | 172 | + [github/AndreasPizsa](https://github.com/AndreasPizsa) 173 | + [twitter/AndreasPizsa](http://twitter.com/AndreasPizsa) 174 | 175 | ## License 176 | Copyright (c) 2017 Andreas Pizsa (http://github.com/AndreasPizsa), contributors. 177 | 178 | [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2FAndreasPizsa%2Fparse-decimal-number.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2FAndreasPizsa%2Fparse-decimal-number?ref=badge_large) [![Greenkeeper badge](https://badges.greenkeeper.io/AndreasPizsa/parse-decimal-number.svg)](https://greenkeeper.io/) 179 | -------------------------------------------------------------------------------- /dist/parse-decimal-number.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var options, parseDecimalNumber, patterns; 3 | 4 | patterns = []; 5 | 6 | options = {}; 7 | 8 | module.exports = parseDecimalNumber = function(value, inOptions, enforceGroupSize) { 9 | var decimal, fractionPart, groupMinSize, integerPart, number, pattern, patternIndex, result, thousands; 10 | if (enforceGroupSize == null) { 11 | enforceGroupSize = true; 12 | } 13 | if (typeof inOptions === 'string') { 14 | if (inOptions.length !== 2) { 15 | throw { 16 | name: 'ArgumentException', 17 | message: 'The format for string options is \'\' (exactly two characters)' 18 | }; 19 | } 20 | thousands = inOptions[0], decimal = inOptions[1]; 21 | } else if (Array.isArray(inOptions)) { 22 | if (inOptions.length !== 2) { 23 | throw { 24 | name: 'ArgumentException', 25 | message: 'The format for array options is [\'\',\'[\'] (exactly two elements)' 26 | }; 27 | } 28 | thousands = inOptions[0], decimal = inOptions[1]; 29 | } else { 30 | thousands = (inOptions != null ? inOptions.thousands : void 0) || (inOptions != null ? inOptions.group : void 0) || options.thousands; 31 | decimal = (inOptions != null ? inOptions.decimal : void 0) || options.decimal; 32 | } 33 | patternIndex = "" + thousands + decimal + enforceGroupSize; 34 | pattern = patterns[patternIndex]; 35 | if (pattern == null) { 36 | groupMinSize = enforceGroupSize ? 3 : 1; 37 | pattern = patterns[patternIndex] = new RegExp("^\\s*([+\-]?(?:(?:\\d{1,3}(?:\\" + thousands + "\\d{" + groupMinSize + ",3})+)|\\d*))(?:\\" + decimal + "(\\d*))?\\s*$"); 38 | } 39 | result = value.match(pattern); 40 | if (!((result != null) && result.length === 3)) { 41 | return 0/0; 42 | } 43 | integerPart = result[1].replace(new RegExp("\\" + thousands, 'g'), ''); 44 | fractionPart = result[2]; 45 | number = parseFloat(integerPart + "." + fractionPart); 46 | return number; 47 | }; 48 | 49 | module.exports.setOptions = function(newOptions) { 50 | var key, value; 51 | for (key in newOptions) { 52 | value = newOptions[key]; 53 | options[key] = value; 54 | } 55 | }; 56 | 57 | module.exports.factoryReset = function() { 58 | options = { 59 | thousands: ',', 60 | decimal: '.' 61 | }; 62 | }; 63 | 64 | module.exports.withOptions = function(options, enforceGroupSize) { 65 | if (enforceGroupSize == null) { 66 | enforceGroupSize = true; 67 | } 68 | return function(value) { 69 | return parseDecimalNumber(value, options, enforceGroupSize); 70 | }; 71 | }; 72 | 73 | module.exports.factoryReset(); 74 | 75 | }).call(this); 76 | -------------------------------------------------------------------------------- /dist/parse-decimal-number.min.js: -------------------------------------------------------------------------------- 1 | (function(){var a,b,c;c=[],a={},module.exports=b=function(b,d,e){var f,g,h,i,j,k,l,m;if(null==e&&(e=!0),"string"==typeof d){if(2!==d.length)throw{name:"ArgumentException",message:"The format for string options is '' (exactly two characters)"};m=d[0],f=d[1]}else if(Array.isArray(d)){if(2!==d.length)throw{name:"ArgumentException",message:"The format for array options is ['','['] (exactly two elements)"};m=d[0],f=d[1]}else m=(null!=d?d.thousands:void 0)||(null!=d?d.group:void 0)||a.thousands,f=(null!=d?d.decimal:void 0)||a.decimal;return k=""+m+f+e,j=c[k],null==j&&(h=e?3:1,j=c[k]=new RegExp("^\\s*([+-]?(?:(?:\\d{1,3}(?:\\"+m+"\\d{"+h+",3})+)|\\d*))(?:\\"+f+"(\\d*))?\\s*$")),null==(l=b.match(j))||3!==l.length?NaN:(i=l[1].replace(new RegExp("\\"+m,"g"),""),g=l[2],parseFloat(i+"."+g))},module.exports.setOptions=function(b){var c,d;for(c in b)d=b[c],a[c]=d},module.exports.factoryReset=function(){a={thousands:",",decimal:"."}},module.exports.withOptions=function(a,c){return null==c&&(c=!0),function(d){return b(d,a,c)}},module.exports.factoryReset()}).call(this); -------------------------------------------------------------------------------- /docs/about.md: -------------------------------------------------------------------------------- 1 | OK, let’s fix international numbers **parsing** and **validation** once and forever. I got the inspiration for this in a UI project because somehow the libraries we used didn’t do a great job, so I wrote my own parser, and this is a more polished version of it. 2 | 3 | These are the design goals: 4 | 5 | * **Simple.** String in, float out, done. ✓ 6 | * **Accurate.** Parses numbers and returns `NaN` for non-numbers. (=good for input validation) ✓ 7 | * **Lightweight.** (<1k minified) ✓ 8 | * **Complete.** No external dependencies ✓ 9 | * **Solid.** 100% Code Coverage ✓ 10 | 11 | In it’s simplest form, you just use it as a `parseFloat` replacement. 12 | -------------------------------------------------------------------------------- /docs/related.md: -------------------------------------------------------------------------------- 1 | To keep this project as small and modular as possible, the locale data itself has been left out of this library. If you need locale date, other projects might be helpful: 2 | 3 | * [Numeral](http://numeraljs.com) 4 | * [jsi18n](https://github.com/marcoscaceres/jsi18n) 5 | * [Unicode Common Locale Data Repository](http://cldr.unicode.org/index/downloads/latest) 6 | * [Wikipedia](http://en.wikipedia.org/wiki/Decimal_mark#Examples_of_use) 7 | -------------------------------------------------------------------------------- /docs/usage-api.md: -------------------------------------------------------------------------------- 1 | ```javascript 2 | parseDecimalNumber = require('parse-decimal-number'); 3 | console.log(parseDecimalNumber('12,345,678.90')); 4 | // -> 12345678.90 5 | 6 | console.log(parseDecimalNumber('12.345.678,90','.,')); 7 | // -> 12345678.90 8 | ``` 9 | 10 | ## parseDecimalNumber(string _[,options]_) 11 | Returns a `float` representation of _string_ or `NaN` if _string_ is not a parseable number. Use the optional `options` parameter to specify the thousands and decimal point characters. 12 | 13 | ### Parameters 14 | **string** A String that is supposed to contain a number. 15 | 16 | **options** _optional_ A string, array or hash with thousands and decimal separators. 17 | 18 | * _String_ 19 | a two-character string consisting of the thousands character followed by the decimal point character, e.g. `',.'` 20 | 21 | * _Array_ 22 | An array of two elements, the first being the thousands character, the second being the decimal point character, e.g. `['.',',']` 23 | 24 | * _Hash_ with the following elements (this is compatible with NumeralJS) 25 | * **`thousands`** thousands separator character. _Default:_ `,` 26 | * **`decimal`** decimal point character. _Default:_ `.` 27 | 28 | **enforceGroupSize** A boolean indicating whether to support that individual groups between the thousands character are exactly 3 digits 29 | 30 | ### Examples 31 | 32 | ```javascript 33 | console.log(parseDecimalNumber('12.345.678,90')); 34 | // -> 12345678.90 35 | ``` 36 | 37 | ##### String `options` 38 | ```javascript 39 | console.log(parseDecimalNumber('12.345.678,90','.,')); 40 | // -> 12345678.90 41 | ``` 42 | 43 | ##### Array `options` 44 | ```javascript 45 | console.log(parseDecimalNumber('12.345.678,90',['.',','])); 46 | // -> 12345678.90 47 | ``` 48 | 49 | ##### Hash `options` 50 | ```javascript 51 | var customSeparators = {thousands:'.',decimal:','}; 52 | console.log(parseDecimalNumber('12.345.678,90',customSeparators)); 53 | // -> 12345678.90 54 | ``` 55 | 56 | ## Setting and Resetting Default Options 57 | 58 | #### parseDecimalNumber.setOptions 59 | Set the default thousands and decimal characters that are used when no options are passed to `parseDecimalNumber`. 60 | 61 | ```javascript 62 | var defaultSeparators = {thousands:'.',decimal:','}; 63 | parseDecimalNumber.setOptions(defaultSeparators); 64 | 65 | console.log(parseDecimalNumber('12.345.678,90')); 66 | // -> 12345678.90 67 | ``` 68 | 69 | 70 | #### parseDecimalNumber.factorySettings 71 | has the same effect as `parseDecimalNumber.setOptions({thousands:',',decimal:'.'};)` 72 | -------------------------------------------------------------------------------- /docs/usage-numeral.md: -------------------------------------------------------------------------------- 1 | # Using with Numeral.js 2 | [Numeral.js](http://numeraljs.com/) is good at formatting numbers and comes with an extensive set of locale data that you can use with `parse-decimal-number`. 3 | 4 | If you use `numeral` in your project, you can use their locale data as follows: 5 | 6 | ```javascript 7 | parseDecimalNumber('12.345.678,90', numeral.localeData('de').delimiters); 8 | // -> 12345678.9 9 | ``` 10 | 11 | You can of course use the same data to set the default values for `parse-decimal-number`: 12 | 13 | ```javascript 14 | parseDecimalNumber.setOptions(numeral.localeData('de').delimiters); 15 | parseDecimalNumber('12.345.678,90'); 16 | // -> 12345678.9 17 | ``` 18 | 19 | Done :relaxed: 20 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | require('coffee-script/register'); 2 | require('./Gulpfile.coffee'); 3 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "parse-decimal-number", 3 | "version": "1.0.0", 4 | "description": "Parse a decimal number with i18n format support (localized decimal points and thousands separators)", 5 | "main": "./dist/parse-decimal-number.js", 6 | "scripts": { 7 | "test": "gulp coveralls" 8 | }, 9 | "files": [ 10 | "dist", 11 | "README.md", 12 | "LICENSE" 13 | ], 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/AndreasPizsa/parse-decimal-number.git" 17 | }, 18 | "publishConfig": { 19 | "registry": "https://registry.npmjs.org/" 20 | }, 21 | "keywords": [ 22 | "parse", 23 | "number", 24 | "float", 25 | "decimal", 26 | "i18n", 27 | "l11n", 28 | "localized", 29 | "international", 30 | "format", 31 | "convert", 32 | "integer", 33 | "localized", 34 | "points", 35 | "comma", 36 | "separators" 37 | ], 38 | "author": "Andreas Pizsa (http://github.com/AndreasPizsa)", 39 | "license": "MIT", 40 | "bugs": { 41 | "url": "https://github.com/AndreasPizsa/parse-decimal-number/issues" 42 | }, 43 | "homepage": "https://github.com/AndreasPizsa/parse-decimal-number", 44 | "devDependencies": { 45 | "cldr": "4.13.0", 46 | "coffee-script": "1.12.7", 47 | "gulp": "3.9.1", 48 | "gulp-blanket-mocha": "0.0.4", 49 | "gulp-coffee": "3.0.3", 50 | "gulp-coveralls": "0.1.4", 51 | "gulp-istanbul": "1.1.3", 52 | "gulp-mocha": "6.0.0", 53 | "gulp-uglifyjs": "0.6.2", 54 | "gulp-util": "3.0.8", 55 | "gulp-verb": "0.3.0", 56 | "lodash": "4.17.11", 57 | "verb": "0.8.10" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | {"pinVersions": true, "dependencies": {"pinVersions": false}} 2 | -------------------------------------------------------------------------------- /src/parse-decimal-number.coffee: -------------------------------------------------------------------------------- 1 | patterns=[] 2 | options ={} 3 | 4 | module.exports = parseDecimalNumber = (value,inOptions,enforceGroupSize=true)-> 5 | 6 | if typeof inOptions is 'string' 7 | if inOptions.length isnt 2 then throw {name:'ArgumentException',message:'The format for string options is \'\' (exactly two characters)'} 8 | [thousands, decimal] = inOptions 9 | else if Array.isArray inOptions 10 | if inOptions.length isnt 2 then throw {name:'ArgumentException',message:'The format for array options is [\'\',\'[\'] (exactly two elements)'} 11 | [thousands, decimal] = inOptions 12 | else 13 | thousands = inOptions?.thousands or inOptions?.group or options.thousands 14 | decimal = inOptions?.decimal or options.decimal 15 | 16 | patternIndex = "#{thousands}#{decimal}#{enforceGroupSize}" 17 | pattern = patterns[patternIndex] 18 | unless pattern? 19 | groupMinSize = if enforceGroupSize then 3 else 1 20 | pattern = patterns[patternIndex] = new RegExp "^\\s*([+\-]?(?:(?:\\d{1,3}(?:\\#{thousands}\\d{#{groupMinSize},3})+)|\\d*))(?:\\#{decimal}(\\d*))?\\s*$" 21 | 22 | result = value.match pattern 23 | return NaN unless result? and result.length is 3 24 | 25 | integerPart = result[1].replace new RegExp("\\#{thousands}",'g'),'' 26 | fractionPart = result[2] 27 | number = parseFloat "#{integerPart}.#{fractionPart}" 28 | return number 29 | 30 | module.exports.setOptions = (newOptions)-> 31 | options[key] = value for key, value of newOptions 32 | return 33 | 34 | module.exports.factoryReset = -> 35 | options = 36 | thousands : ',' 37 | decimal : '.' 38 | return 39 | 40 | module.exports.withOptions = (options, enforceGroupSize=true)-> 41 | (value) -> parseDecimalNumber value, options, enforceGroupSize 42 | 43 | module.exports.factoryReset() 44 | -------------------------------------------------------------------------------- /test/parse-decimal-number-test.coffee: -------------------------------------------------------------------------------- 1 | assert = require 'assert' 2 | _=require 'lodash' 3 | 4 | parseDecimalNumber = require '../' 5 | 6 | localSeparators = [ 7 | {thousands:',', decimal:'.'} # 1,234.56 -- United States 8 | {thousands:'.', decimal:','} # 1.234,56 -- German 9 | {thousands:'\'', decimal:'.'} # 1'234.56 -- Switzerland 10 | {thousands:' ', decimal:','} # 1 234,56 -- French 11 | {thousands:',', decimal:'/'} # 1,234/56 -- Persian (Iran) 12 | {thousands:' ', decimal:'-'} # 1 234-56 -- Kazakhstan 13 | {thousands:' ', decimal:'.'} # 1 234.56 -- Estonia 14 | ] 15 | 16 | buildNumber = (max,doFraction,separators)-> 17 | int = originalValue = Math.ceil(Math.random() * max) 18 | 19 | string = '' 20 | while int > 999 21 | remainder = int % 1000 22 | zeros = if remainder < 100 then '0' else '' 23 | zeros += if remainder< 10 then '0' else '' 24 | string = separators.thousands + zeros + remainder + string 25 | int-=remainder 26 | int/=1000 27 | string = int + string 28 | if doFraction 29 | fraction = Math.ceil(Math.random() * 99999999) 30 | string += separators.decimal + fraction 31 | return text:string,number:parseFloat(originalValue+'.'+fraction) 32 | 33 | return text:string, number:originalValue 34 | 35 | testAllNumbers = (maxInt,toFractions)-> 36 | for separators in localSeparators 37 | number = buildNumber maxInt,toFractions,separators 38 | it "correctly parses #{number.text} (#{number.number})", -> 39 | assert.strictEqual parseDecimalNumber(number.text, separators),number.number 40 | 41 | describe 'parse-decimal-number',-> 42 | beforeEach -> 43 | parseDecimalNumber.factoryReset() 44 | 45 | it 'correctly parses decimal formats with default options',-> 46 | assert.strictEqual parseDecimalNumber('12,345,679.90'),12345679.90 47 | 48 | it 'correctly parses integer formats with default options',-> 49 | assert.strictEqual parseDecimalNumber('12,345,679'),12345679 50 | 51 | it 'correctly parses normal integers with default options',-> 52 | assert.strictEqual parseDecimalNumber('12345679'),12345679 53 | 54 | it 'can set other options and then parse correctly',-> 55 | parseDecimalNumber.setOptions thousands:' ', decimal:':' 56 | assert.strictEqual parseDecimalNumber('123 456 789:98765432'),123456789.98765432 57 | 58 | it 'correctly parses normal floats with default options',-> 59 | assert.strictEqual parseDecimalNumber('12345679.09'),12345679.09 60 | 61 | it 'correctly parses negative floats with default options',-> 62 | assert.strictEqual parseDecimalNumber('-12345679.09'),-12345679.09 63 | 64 | it 'correctly parses positive floats beginning with +', -> 65 | assert.strictEqual parseDecimalNumber('+12345679.09'),12345679.09 66 | 67 | describe 'correctly parses all decimal formats',-> 68 | testAllNumbers 999999999,true 69 | 70 | describe 'correctly parses all integer formats',-> 71 | testAllNumbers 999999999,false 72 | 73 | describe 'correctly parses all decimal formats < 1000',-> 74 | testAllNumbers 1000,true 75 | 76 | describe 'correctly parses all integer formats < 1000',-> 77 | testAllNumbers 1000,false 78 | 79 | it 'returns NaN if a value can\'t be parsed', -> 80 | assert.strictEqual _.isNaN(parseDecimalNumber('whatever', {thousands:',',decimal:'.'})), true 81 | 82 | it 'accepts a two-character string as options argument', -> 83 | assert.strictEqual parseDecimalNumber('123,456.78', ',.'), 123456.78 84 | 85 | it 'throws an error if a string with a length other than two is used as options argument', -> 86 | assert.throws -> parseDecimalNumber('123,456.78', 'ABC') 87 | assert.throws -> parseDecimalNumber('123,456.78', 'D') 88 | 89 | it 'accepts a two-element array as options', -> 90 | assert.strictEqual parseDecimalNumber('123,456.78', [',','.']), 123456.78 91 | 92 | it 'returns NaN if group size is not three', -> 93 | assert.strictEqual _.isNaN(parseDecimalNumber('12,34,567.22', [',','.'])), true 94 | 95 | it 'does not NaN if group size is not three but enforceGroupSize is false', -> 96 | assert.strictEqual parseDecimalNumber('12,34,567.22', [',','.'], false), 1234567.22 97 | 98 | it 'throws an error if an array with a length other than two is used as options argument', -> 99 | assert.throws -> parseDecimalNumber('123,456.78', [',','.','#']) 100 | assert.throws -> parseDecimalNumber('123,456.78', [',']) 101 | 102 | describe '`.withOptions`', -> 103 | [ 104 | ['.,', '1.234.567,89'] 105 | [',.', '1,234,567.89'] 106 | ].forEach ([options, expect]) -> 107 | it "can parse with #{options}", -> 108 | parse = parseDecimalNumber.withOptions options 109 | assert.strictEqual parse(expect), 1234567.89 110 | 111 | describe 'cldr support', -> 112 | cldr = require 'cldr' 113 | [ 114 | ['de_DE', '1.234.567,89'] 115 | ['en_US', '1,234,567.89'] 116 | ].forEach ([locale, expect]) -> 117 | 118 | it "parses #{locale}", -> 119 | assert.strictEqual parseDecimalNumber(expect, cldr.extractNumberSymbols(locale)), 1234567.89 120 | --------------------------------------------------------------------------------