Permalink
Browse files

Completely rewritten the code that handles mixed text/format - now wo…

…rking almost properly. Fixed some unit tests
  • Loading branch information...
1 parent 562c88c commit 15f0638180cea1f249afd267fed982be72b76215 @therazor committed May 30, 2012
Showing with 112 additions and 97 deletions.
  1. +101 −90 lib/globalize.js
  2. +11 −7 test/customFormat.js
View
@@ -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;
@@ -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];
}
@@ -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 );
@@ -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 ]
@@ -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] !== '' ) {
@@ -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 = '';
View
@@ -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#.
@@ -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" );
});
@@ -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, "."), "" );

0 comments on commit 15f0638

Please sign in to comment.