Skip to content
This repository has been archived by the owner on May 18, 2020. It is now read-only.

Commit

Permalink
Completely rewritten the code that handles mixed text/format - now wo…
Browse files Browse the repository at this point in the history
…rking almost properly. Fixed some unit tests
  • Loading branch information
therazor committed May 30, 2012
1 parent 562c88c commit 15f0638
Show file tree
Hide file tree
Showing 2 changed files with 112 additions and 97 deletions.
191 changes: 101 additions & 90 deletions lib/globalize.js
Expand Up @@ -764,18 +764,36 @@ formatDate = function( value, format, culture ) {
return expandInt( number, 0, formatInfo ) + right;
},

// handles formats such as #,#.00
// http://msdn.microsoft.com/en-us/library/0c899ak8.aspx#SectionSeparator
/* handles formats such as #,#.00
* -----------------------------------
* The functionality and results from this method are meant to closely resemble
* C# custom number formatting: http://msdn.microsoft.com/en-us/library/0c899ak8.aspx
* Notable exceptions are:
* 1) '' are not used to pass literal content. Use \ instead.
* (e.g. "'#'" should be "\\#" instead)
* 2) when using ; section separators, do not fall back to the positive
* section with nonpositive numbers, if the nonpositive section doesn't have
* any formatting token.
* (e.g. "class=positive;class=negative" becomes "class=negative" with a number < 0.)
* 3) negative signs are always placed at the beginning of the
* leftmost formatting token.
* (e.g. "<li>#.#</li>" becomes "<li>-12.43</li>")
*/
// FIXME: implement number scaling
customNumberFormat = function( value, format, formatInfo ) {
if ( format === '' )
return value.toString();

function notEscapedSplit( stringToSplit, delimiter, limit ) {
return stringToSplit.split(delimiter, limit);
}

// FIXME ignore escaped ; first
// Now this would be nice and easy with javascript's
// negative lookbehind regex... o wait
var patterns = format.split(';', 3),
//discardIfRoundedIsZero = !!value,
// variable length negative lookbehind regex... o wait
// var patterns = format.split(/(?<!([\\]{2})*\\);/, 3),
// var patterns = format.split(';', 3),
var patterns = notEscapedSplit( format, ';', 3 ),
zeroPattern,
pattern;

Expand All @@ -797,8 +815,10 @@ formatDate = function( value, format, culture ) {
if ( value === 0 )
patterns = patterns.slice( 2 );
case 2:
if ( value < 0 )
if ( value < 0 ) {
patterns = patterns.slice( 1 );
value = Math.abs( value );
}
case 1:
pattern = patterns[0];
}
Expand All @@ -814,54 +834,61 @@ formatDate = function( value, format, culture ) {
subpatternContainer,
subpattern = '',
decimalsSeparatorIndex = -1,
uniqueCharsFound = [];
uniqueCharsFound = [],
hasThousandsSeparator = false;

for (i = 0; i < len; i++) {
var isEscaped;

if ( pattern.charAt(i) == '\\' ) {
if ( i + 1 === len )
break;

isEscaped = true;
i++;
} else
isEscaped = false;

// TODO /[.,#%‰E0-9]/i
if ( isEscaped || /[^.,#0]/.test( pattern.charAt(i) ) ) {
subpatternContainer = textualSubpatterns;
} else {
subpatternContainer = subpatterns;

// TODO /[.,E]/i also %‰ maybe?
// FIXME Number scaling specifier: If one or more commas are
// specified immediately to the left of the explicit or
// implicit decimal point, the number to be formatted is divided
// by 1000 for each comma. For example, if the string "0,,"
// is used to format the number 100 million, the output is "100".
if ( /[.,]/.test( pattern.charAt(i) ) ) {
if ( ~arrayIndexOf( uniqueCharsFound, pattern.charAt(i) ) ||
( pattern.charAt(i) == ',' && ~arrayIndexOf( uniqueCharsFound, '.' ) ) )
continue;
else if ( pattern.charAt(i) == '.' )
decimalsSeparatorIndex = subpatterns.length;

uniqueCharsFound.push( pattern.charAt(i) );
if ( !isEscaped && pattern.charAt(i) === ',' ) {
if ( !~arrayIndexOf( uniqueCharsFound, '.' ) &&
( subpatterns.length ||
( subpatternContainer === subpatterns && subpattern.length )
)
)
hasThousandsSeparator = true;
} else if ( i !== len ) {
// TODO /[.,#%‰E0-9]/i
if ( isEscaped || /[^.,#0]/.test( pattern.charAt(i) ) ) {
subpatternContainer = textualSubpatterns;
} else {
subpatternContainer = subpatterns;

// TODO /[.,E]/i also %‰ maybe?
// FIXME Number scaling specifier: If one or more commas are
// specified immediately to the left of the explicit or
// implicit decimal point, the number to be formatted is divided
// by 1000 for each comma. For example, if the string "0,,"
// is used to format the number 100 million, the output is "100".
if ( /[.]/.test( pattern.charAt(i) ) ) {
if ( ~arrayIndexOf( uniqueCharsFound, pattern.charAt(i) ) ||
( pattern.charAt(i) == ',' && ~arrayIndexOf( uniqueCharsFound, '.' ) ) )
continue;
else if ( pattern.charAt(i) == '.' )
decimalsSeparatorIndex = subpatterns.length;

uniqueCharsFound.push( pattern.charAt(i) );
}
}
}

if ( i === 0 )
firstSubpatternContainer = lastSubpatternContainer = subpatternContainer;
if ( !firstSubpatternContainer )
firstSubpatternContainer = lastSubpatternContainer = subpatternContainer;

if ( subpatternContainer === lastSubpatternContainer )
subpattern += pattern.charAt(i);
else {
lastSubpatternContainer.push( subpattern );
subpattern = pattern.charAt(i);
}
if ( subpatternContainer === lastSubpatternContainer )
subpattern += pattern.charAt(i);
else {
lastSubpatternContainer.push( subpattern );
subpattern = pattern.charAt(i);
}

lastSubpatternContainer = subpatternContainer;
lastSubpatternContainer = subpatternContainer;
}
};

subpatternContainer.push( subpattern );
Expand All @@ -870,8 +897,7 @@ formatDate = function( value, format, culture ) {
return textualSubpatterns[0];

var fullPattern = subpatterns.join( '' ),
intDecPatterns = fullPattern.replace( ',', '' ).split( '.' ),
hasThousandsSeparator = !!~fullPattern.split( '.' ).shift().search(/[^,]+,/),
intDecPatterns = fullPattern.split( '.' ),
intMinLength,
decimalsLengthRange; // [ min, max ]

Expand All @@ -894,21 +920,19 @@ formatDate = function( value, format, culture ) {
: 0,
rounded = roundNumber( value, precision );

// TODO: since we have a pattern !== zeroPattern already, isn't
// discardIfRoundedIsZero redundant?
if ( rounded === 0 && /*discardIfRoundedIsZero &&*/ pattern !== zeroPattern )
if ( rounded === 0 && pattern !== zeroPattern )
return customNumberFormat.call( this, 0, zeroPattern, formatInfo );

var formattedValue = [],
var formattedIntDec = [],
roundedString = Math.abs( rounded ).toString(),
curSubpatterns = firstSubpatternContainer;

if ( hasThousandsSeparator )
formattedValue.push( expandInt( rounded, intMinLength, formatInfo ) );
formattedIntDec.push( expandInt( rounded, intMinLength, formatInfo ) );
else {
formattedValue.push( zeroPad( roundedString.split('.').shift(), intMinLength, true ) );
formattedIntDec.push( zeroPad( roundedString.split('.').shift(), intMinLength, true ) );
if ( rounded < 0 )
formattedValue[0] = '-' + formattedValue[0];
formattedIntDec[0] = '-' + formattedIntDec[0];
}

if ( ~decimalsSeparatorIndex && intDecPatterns.length > 1 && intDecPatterns[1] !== '' ) {
Expand All @@ -923,57 +947,44 @@ formatDate = function( value, format, culture ) {
else
decimalPlaces = roundedDecimals.length;

formattedValue.push( zeroPad( roundedDecimals, decimalPlaces, false ) );
formattedIntDec.push( zeroPad( roundedDecimals, decimalPlaces, false ) );
}

var curFragment,
curDecimalPlaces = decimalsLengthRange[1],
minDecimalPlaces = decimalPlaces;

formattedValue = formattedValue.join( formatInfo[ "." ] );

/*
for ( i = subpatterns.length - 1; i > 0; i-- ) {
if ( i > decimalsSeparatorIndex || )
;
else
if ( i > decimalsSeparatorIndex && formattedIntDec.length > 1 ) { // decimal part
var sliceLength = subpatterns[i].length - ( subpatterns[i].lastIndexOf( '.' ) + 1 );

subpatterns[i] = currentFragmentValue;
}*/

var currentFragmentValue,
intSeparators = 0
curDecimalPlaces = decimalPlaces;
curFragment = formattedIntDec[1].substring( formattedIntDec[1].length +
curDecimalPlaces - minDecimalPlaces - 1 );
formattedIntDec[1] = formattedIntDec[1].slice( 0,
formattedIntDec[1].length - curFragment.length );

for ( i = subpatterns.length - 1; i > 0; i-- ) {
/*if ( i < decimalsSeparatorIndex && curDecimalPlaces < decimalsLengthRange[1] && false )
currentFragmentValue = formattedValue.slice(
-Math.min( decimalsLengthRange[1] - curDecimalPlaces, subpatterns[i].length ) );
curDecimalPlaces -= currentFragmentValue;
else */if ( !hasThousandsSeparator || i > decimalsSeparatorIndex )
currentFragmentValue = formattedValue.slice( -subpatterns[i].length );
else {
if ( decimalsSeparatorIndex === i ) {
var separatorPosition = formattedValue.lastIndexOf( formatInfo["."] );
currentFragmentValue = formattedValue.slice( separatorPosition );
formattedValue = formattedValue.slice( 0, separatorPosition );
subpatterns[i] = subpatterns[i].slice( 0, separatorPosition );
} else // FIXME what is this for?
currentFragmentValue = '';

var j = subpatterns[i].length - 1;
curDecimalPlaces -= sliceLength;
minDecimalPlaces -= curFragment.length;
}
if ( i <= decimalsSeparatorIndex || !~decimalsSeparatorIndex ) { // int part
var sliceLength = subpatterns[i].indexOf( '.' );
if ( !~sliceLength )
sliceLength = subpatterns[i].length;
if ( hasThousandsSeparator )
sliceLength += 0;

curFragment = formattedIntDec[0].slice( formattedIntDec[0].length - sliceLength );
if ( i === decimalsSeparatorIndex && formattedIntDec.length > 1 )
curFragment += formatInfo['.'] + formattedIntDec.pop();

while ( j >= 0 ) {
currentFragmentValue = formattedValue[j + intSeparators] + currentFragmentValue;

if ( formattedValue[j + intSeparators] === formatInfo[","] )
intSeparators++;
else
j--;
}
formattedIntDec[0] = formattedIntDec[0].substring( 0, formattedIntDec[0].length - sliceLength );
}

subpatterns[i] = currentFragmentValue;
formattedValue = formattedValue.slice( 0, formattedValue.length - currentFragmentValue.length );
subpatterns[i] = curFragment;
}

subpatterns[i] = formattedValue;
subpatterns[0] = formattedIntDec.join( formatInfo["."] );
// subpatterns[0] = formattedIntDec[0] + subpatterns[0];

var formattedString = '';

Expand Down
18 changes: 11 additions & 7 deletions test/customFormat.js
Expand Up @@ -64,7 +64,7 @@ test("Number Formatting - custom w/ pattern for +/-", function() {
equal( Globalize.format(-1234.567, "Positive: 0;Negative: 0;zero;ignored"), "Negative: 1235" );
equal( Globalize.format(0, "Positive: 0;Negative: 0;zero;ignored"), "zero" );
equal( Globalize.format(0.00001, "Positive: 0;Negative: 0;zero;ignored"), "zero" );
equal( Globalize.format(0.00001, "Positive: 0;Negative: 0.0000#;zero;ignored"), "Negative: 0.00001" );
equal( Globalize.format(-0.00001, "Positive: 0;Negative: 0.0000#;zero;ignored"), "Negative: 0.00001" );
equal( Globalize.format(0.00001, "Positive: 0;Negative: 0;;ignored"), "Positive: 0" );
equal( Globalize.format(-0.00001, "Positive: 0;Negative: 0;;ignored"), "Positive: 0" );
// the test below gives a different result (zero) in C#.
Expand All @@ -76,11 +76,15 @@ test("Number Formatting - custom w/ pattern for +/-", function() {
});

test("Number Formatting - custom w/ non-formatting text", function() {
equal( Globalize.format(1234.567, "# 0 # # 0 0|0.0|0 0 0 # #"), " 0 0 1 2 3|4.5|6 7 0" );
equal( Globalize.format(-1234.567, "# 0 # # 0 0|0.0|0 0 0 # #"), "- 0 0 1 2 3|4.5|6 7 0" );
equal( Globalize.format(-1234.567, "0 # # 0 0|0.0|0 0 0 # #"), "-0 0 1 2 3|4.5|6 7 0" );
equal( Globalize.format(-1234.567, "0 # # 0 \\0|0.0|0 0 0 # #"), "-0 1 2 3 0|4.5|6 7 0" );
equal( Globalize.format(1234.567, "<a href=\"\\#go\">#</a>"), "<a href=\"#go\">1235</a>" );
equal( Globalize.format(5417543010, "(000) 000-0000"), "(541) 754-3010" );
equal( Globalize.format(123123, "0000|,|000"), "0,123,||123" );
equal( Globalize.format(1234.567, "# 0 # # 0 0|0.0|0 0 0 # #"), " 0 0 1 2 3|4.5|6 7 0 " );
equal( Globalize.format(1234.567, "# 0 # #,000.0|0 0 0 # #"), " 0 0 1,234.5|6 7 0 " );
equal( Globalize.format(1234.567, "# 0 # #,000|.|0|0 0 0 # #"), " 0 0 1,234|.|5|6 7 0 " );
equal( Globalize.format(-1234.567, "# 0 # # 0 0|0.0|0 0 0 # #"), "- 0 0 1 2 3|4.5|6 7 0 " );
equal( Globalize.format(-1234.567, "0 # # 0 0|0.0|0 0 0 # #"), "-0 0 1 2 3|4.5|6 7 0 " );
equal( Globalize.format(-1234.567, "0 # # 0 \\0|0.0|0 0 0 # #"), "-0 1 2 3 0|4.5|6 7 0 " );
equal( Globalize.format(-1234.567, "<a href=\"\\#go\">#</a>"), "<a href=\"#go\">-1235</a>" );
equal( Globalize.format(1234.567, '\\\\00000'), "\\01235" );
});

Expand All @@ -97,7 +101,7 @@ test("Number Formatting - custom w/ various edge cases", function() {
equal( Globalize.format(1234.567, "000, ,000, ,000.00"), "000, 001, 234.57" );
equal( Globalize.format(1234.567, "00 00, ,00, ,00.00"), "00, 00 1,2 34.57" );
equal( Globalize.format(1234567890, "##0bill ##0mill ##0thousands and ##0"), "1bill 234mill 567thousands and 890" );
equal( Globalize.format(12,23, "##0bill ##0mill ##0thousands and ##0"), "0bill 000mill 000thousands and 012" );
equal( Globalize.format(12.23, "##0bill ##0mill ##0thousands and ##0"), "0bill 000mill 000thousands and 012" );
equal( Globalize.format(0.1234, ".####"), ".1234" );
equal( Globalize.format(0.1234, ".## ##"), ".12 34" );
equal( Globalize.format(0.1234, "."), "" );
Expand Down

0 comments on commit 15f0638

Please sign in to comment.