-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
=== - Added new converter @opuscapita/i18n/NumberConverter - Added method for `I18nManager.formatBigNumber(<string>) <string>` - Added method for `I18nManager.parseBigNumber(<string>) <string>` - Added method for `I18nManager.formatBigDecimalNumber(<string>) <string>` - Added method for `I18nManager.parseBigDecimalNumber(<string>) <string>` - Added method for `I18nManager.formatBigDecimalNumberWithPattern(<string>) <string>`
- Loading branch information
Showing
8 changed files
with
453 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,193 @@ | ||
import Converter from './Converter'; | ||
import ParseError from './ParseError'; | ||
const BigNumber = require('bignumber.js'); | ||
|
||
export const ERROR_CODE = 'error.parse.number'; | ||
|
||
const floatNumberReg = /^-?\d+\.?\d*$/; | ||
const intNumberReg = /^-?\d+$/; | ||
|
||
export default class BigNumberConverter extends Converter { | ||
constructor(format, groupSep, decSep, decSepUseAlways) { | ||
super(); | ||
|
||
this._format = format; | ||
this._groupSep = groupSep; | ||
this._decSep = decSep; | ||
this._decSepUseAlways = decSepUseAlways || false; | ||
|
||
if (format.lastIndexOf('.') !== -1) { | ||
this._integerFormat = format.substring(0, format.indexOf('.')); | ||
this._decimalFormat = format.substring(format.indexOf('.') + 1); | ||
} else { | ||
this._integerFormat = format; | ||
this._decimalFormat = ''; | ||
} | ||
} | ||
|
||
_validateStringIfItIsANumber(value) { | ||
let stringValue = value; | ||
if (this._groupSep) { | ||
stringValue = stringValue.replace(new RegExp('\\' + this._groupSep, 'g'), ''); | ||
} | ||
|
||
if (this._decSep !== undefined) { | ||
stringValue = stringValue.replace(this._decSep, '.'); | ||
} | ||
|
||
if (this._format.indexOf('.') !== -1) { | ||
if (!floatNumberReg.test(stringValue)) { | ||
throw new ParseError(ERROR_CODE, { value }); | ||
} | ||
} else { | ||
if (!intNumberReg.test(stringValue)) { | ||
throw new ParseError(ERROR_CODE, { value }); | ||
} | ||
} | ||
} | ||
|
||
_parseFractionalPart(bigNumber) { | ||
// nothing to format | ||
if (this._decimalFormat === '') { | ||
return ''; | ||
} | ||
|
||
const fractionalPartString = bigNumber.toFixed().split('.')[1] || ''; | ||
|
||
let result = ''; | ||
for (let i = 0; i < this._decimalFormat.length; i++) { | ||
const currentDigit = fractionalPartString.charAt(i); | ||
if (this._decimalFormat.charAt(i) === '0') { | ||
// char does not exist | ||
if (currentDigit === '') { | ||
// add 0 anyway | ||
result = `${result}0`; | ||
} else { | ||
result = `${result}${currentDigit}`; | ||
} | ||
} else { | ||
// # is found in the pattern | ||
const leftOptionalDigitsAmount = this._decimalFormat.length - i; | ||
// take all left digits statring from i index but not more that amount of characters left in format | ||
result = `${result}${fractionalPartString.substr(i, leftOptionalDigitsAmount)}`; | ||
break; | ||
} | ||
} | ||
|
||
return result; | ||
} | ||
|
||
_parseIntegerPart(bigNumber) { | ||
let integerNumber = bigNumber; | ||
// if there is not decimal separator in the format, then we round the value | ||
// like if it done in DecimalFormat, see https://docs.oracle.com/javase/7/docs/api/java/text/DecimalFormat.html | ||
if (this._format.indexOf('.') === -1) { | ||
integerNumber = integerNumber.integerValue(BigNumber.ROUND_HALF_UP); | ||
} | ||
|
||
// cut fractional part | ||
if (bigNumber.isNegative()) { | ||
integerNumber = integerNumber.integerValue(BigNumber.ROUND_CEIL); | ||
} else { | ||
integerNumber = integerNumber.integerValue(BigNumber.ROUND_FLOOR); | ||
} | ||
|
||
if (this._integerFormat.charAt(this._integerFormat.length - 1) === '#' && integerNumber.isZero() === 0) { | ||
return 0; | ||
} | ||
|
||
let result = ''; | ||
|
||
// convert number ot a string and cut - sign if any | ||
const integerPartWithoutSign = integerNumber.abs().toFixed(); | ||
|
||
// find how many digits are in the group | ||
let groupLength = 9999; | ||
const groupSeparatorIndexInFormat = this._integerFormat.lastIndexOf(','); | ||
if (groupSeparatorIndexInFormat !== -1) { | ||
groupLength = this._integerFormat.length - groupSeparatorIndexInFormat - 1; | ||
} | ||
|
||
let groupCount = 0; | ||
for (let k = integerPartWithoutSign.length - 1; k >= 0; k--) { | ||
result = integerPartWithoutSign.charAt(k) + result; | ||
groupCount++; | ||
if (groupCount === groupLength && k !== 0) { | ||
result = (this._groupSep || '') + result; | ||
groupCount = 0; | ||
} | ||
} | ||
|
||
// account for any pre-data 0's | ||
if (this._integerFormat.length > result.length) { | ||
const padStart = this._integerFormat.indexOf('0'); | ||
if (padStart !== -1) { | ||
const padLen = this._integerFormat.length - padStart; | ||
|
||
// pad to left with 0's | ||
while (result.length < padLen) { | ||
result = '0' + result; | ||
} | ||
} | ||
} | ||
|
||
return result; | ||
} | ||
|
||
valueToString(numberAsString) { | ||
// null -> null is returned | ||
if (numberAsString === null) { | ||
return null; | ||
} | ||
|
||
// throw TypeError if value is not a string | ||
if (typeof numberAsString !== 'string') { | ||
throw TypeError(`'${numberAsString}' is not a String!`); | ||
} | ||
|
||
const bigNumber = new BigNumber(numberAsString); | ||
|
||
if (bigNumber.isNaN()) { | ||
throw TypeError(`'${numberAsString}' is not a Number!`); | ||
} | ||
|
||
// validate integer number | ||
// parse integrer and fractional part separately | ||
const integerPartString = this._parseIntegerPart(bigNumber); | ||
const fractionalPartString = this._parseFractionalPart(bigNumber); | ||
|
||
// setup decimal separator if it is needed | ||
let decimalSeparator = ''; | ||
if (fractionalPartString !== '' || this._decSepUseAlways) { | ||
decimalSeparator = this._decSep; | ||
} | ||
// setup negative sign | ||
let minusSign = ''; | ||
if (bigNumber.isNegative()) { | ||
minusSign = '-'; | ||
} | ||
|
||
return minusSign + integerPartString + decimalSeparator + fractionalPartString; | ||
} | ||
|
||
stringToValue(string) { | ||
if (string === null) { | ||
return null; | ||
} | ||
|
||
if (typeof string !== 'string') { | ||
throw TypeError(`'${string}' is not a String!`); | ||
} | ||
|
||
this._validateStringIfItIsANumber(string); | ||
|
||
// removing decimal and grouping separator | ||
let stringValue = string; | ||
if (this._groupSep) { | ||
while (stringValue.indexOf(this._groupSep) > -1) { | ||
stringValue = stringValue.replace(this._groupSep, ''); | ||
} | ||
} | ||
return new BigNumber(stringValue.replace(this._decSep, '.')).toFixed().toString(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
import { assert } from 'chai'; | ||
|
||
import BigNumberConverter from './BigNumberConverter'; | ||
import ParseError from './ParseError'; | ||
|
||
describe('BigNumberConverter', () => { | ||
it('should decimal format with group separator', () => { | ||
let dc = new BigNumberConverter('#,##0.00', ',', '.'); | ||
|
||
assert.strictEqual(dc.valueToString(null), null); | ||
assert.strictEqual(dc.valueToString('10000000'), '10,000,000.00'); | ||
assert.strictEqual(dc.valueToString('-10000'), '-10,000.00'); | ||
assert.strictEqual(dc.valueToString('1100.99'), '1,100.99'); | ||
assert.throws(() => {return dc.valueToString(12345)}, TypeError, `'12345' is not a String!`); | ||
assert.throws(() => {return dc.valueToString('werwe')}, TypeError, `'werwe' is not a Number!`); | ||
|
||
assert.strictEqual(dc.stringToValue(null), null); | ||
assert.strictEqual(dc.stringToValue('10,000.00'), '10000'); | ||
assert.strictEqual(dc.stringToValue('-10,000.00'), '-10000'); | ||
assert.strictEqual(dc.stringToValue('1,100.99'), '1100.99'); | ||
assert.throws(() => {return dc.stringToValue(12345)}, TypeError, `'12345' is not a String!`); | ||
}); | ||
|
||
it('should decimal format', () => { | ||
let dc = new BigNumberConverter('#,##0.00', null, '.'); | ||
|
||
assert.strictEqual(dc.valueToString('10000'), '10000.00'); | ||
assert.strictEqual(dc.valueToString('-10000'), '-10000.00'); | ||
assert.strictEqual(dc.valueToString('1100.99'), '1100.99'); | ||
|
||
assert.strictEqual(dc.stringToValue('10000.00'), '10000'); | ||
assert.strictEqual(dc.stringToValue('-10000.00'), '-10000'); | ||
assert.strictEqual(dc.stringToValue('1100.99'), '1100.99'); | ||
|
||
const badValue = '10,000.00'; | ||
assert.throws(() => { | ||
dc.stringToValue(badValue); | ||
}, ParseError, `invalid parsed value [${badValue}]`); | ||
}); | ||
|
||
it('should decimal format with space group separator', () => { | ||
let dc = new BigNumberConverter('#,##0.00', ' ', ','); | ||
|
||
assert.strictEqual(dc.valueToString('10000'), '10 000,00'); | ||
assert.strictEqual(dc.valueToString('-10000'), '-10 000,00'); | ||
assert.strictEqual(dc.valueToString('1100.55'), '1 100,55'); | ||
assert.strictEqual(dc.valueToString('-1.1'), '-1,10'); | ||
assert.strictEqual(dc.valueToString('-1.9'), '-1,90'); | ||
|
||
assert.strictEqual(dc.stringToValue('10 000,00'), '10000'); | ||
assert.strictEqual(dc.stringToValue('-10 000,00'), '-10000'); | ||
assert.strictEqual(dc.stringToValue('1 100,99'), '1100.99'); | ||
|
||
const invalidValues = ['test', 'test1321321', '10,000.00', '5435432test']; | ||
|
||
let convertToNumber = (value) => { | ||
return () => { | ||
return dc.stringToValue(value); | ||
} | ||
}; | ||
for (const value of invalidValues) { | ||
assert.throws(convertToNumber(value), ParseError, `invalid parsed value [${value}]`); | ||
} | ||
}); | ||
|
||
it('should integer format with ` group separator', () => { | ||
// formatting integer values | ||
let dc = new BigNumberConverter('#,##0', '`'); | ||
|
||
assert.strictEqual(dc.valueToString('10000'), '10`000'); | ||
assert.strictEqual(dc.stringToValue('10`000'), '10000'); | ||
|
||
assert.strictEqual(dc.valueToString('10000.99'), '10`001'); | ||
|
||
assert.throws(() => { | ||
dc.stringToValue('10`000.99'); | ||
}, ParseError, 'invalid parsed value [10`000.99]'); | ||
}); | ||
|
||
it('should decimal format with custom decimal separator', () => { | ||
let dc = new BigNumberConverter('#.##', null, ','); | ||
|
||
assert.strictEqual(dc.valueToString('100'), '100'); | ||
|
||
assert.strictEqual(dc.stringToValue('1000'), '1000'); | ||
assert.strictEqual(dc.stringToValue('100000,1'), '100000.1'); | ||
|
||
assert.strictEqual(dc.valueToString('100.01'), '100,01'); | ||
|
||
assert.strictEqual(dc.valueToString('0.0'), '0'); | ||
assert.strictEqual(dc.valueToString('0'), '0'); | ||
}); | ||
|
||
it('should decimal format with always decimal separator', () => { | ||
let dc = new BigNumberConverter('#.##', null, ',', true); | ||
|
||
assert.strictEqual(dc.valueToString('100'), '100,'); | ||
|
||
assert.strictEqual(dc.stringToValue('1000'), '1000'); | ||
assert.strictEqual(dc.stringToValue('100000,1'), '100000.1'); | ||
|
||
assert.strictEqual(dc.valueToString('100.01'), '100,01'); | ||
|
||
assert.strictEqual(dc.valueToString('0.0'), '0,'); | ||
assert.strictEqual(dc.valueToString('0.1'), '0,1'); | ||
}); | ||
|
||
it('should zero first for integer format', () => { | ||
let dc = new BigNumberConverter('00', null, '.'); | ||
assert.strictEqual(dc.valueToString('9'), '09'); | ||
}); | ||
|
||
it('should decimal format with custom format', () => { | ||
let dc = new BigNumberConverter('#.##########', null, ',', false); | ||
assert.strictEqual(dc.valueToString('1000000'), '1000000'); | ||
}); | ||
|
||
it('should decimal format with custom format and group separator', () => { | ||
let dc = new BigNumberConverter('#,##0.0#########', ',', '.'); | ||
assert.strictEqual(dc.valueToString('123456789.12'), '123,456,789.12'); | ||
}); | ||
|
||
it('should format big number', () => { | ||
let dc = new BigNumberConverter('#,##0.00', ',', '.'); | ||
assert.strictEqual(dc.valueToString('9007199254740989.07'), '9,007,199,254,740,989.07'); | ||
assert.strictEqual(dc.stringToValue('9,007,199,254,740,989.07'), '9007199254740989.07'); | ||
|
||
dc = new BigNumberConverter('#.00', null, '.'); | ||
assert.strictEqual(dc.valueToString('21321312312312344'), '21321312312312344.00'); | ||
assert.strictEqual(dc.stringToValue('21321312312312344.00'), '21321312312312344'); | ||
assert.strictEqual(dc.valueToString('99999999999999999.99'), '99999999999999999.99'); | ||
assert.strictEqual(dc.stringToValue('99999999999999999.99'), '99999999999999999.99'); | ||
}); | ||
}); |
Oops, something went wrong.