Skip to content

Commit 275b5ec

Browse files
LeoFusojknack
authored andcommitted
fix(issue-1160): support named 'locale' and 'format' parameters
This change enhances the numberFormat helper to support both 'format' and 'locale' as named parameters (via options hash), in addition to their existing support as positional arguments. The implementation now follows the same flexible pattern used by the dateFormat helper. The Javadoc has been updated accordingly. Includes tests for: - format as a named parameter - locale as a named parameter - mixed positional + named usage - support for locale strings like 'pt-BR'
1 parent 65f8980 commit 275b5ec

File tree

2 files changed

+62
-36
lines changed

2 files changed

+62
-36
lines changed

handlebars/src/main/java/com/github/jknack/handlebars/helper/StringHelpers.java

Lines changed: 40 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -569,34 +569,36 @@ protected CharSequence safeApply(final Object value, final Options options) {
569569
* Usage:
570570
*
571571
* <pre>
572-
* {{numberFormat number ["format"] [locale=default]}}
572+
* {{numberFormat number ["format"] [format="format"] [locale="locale"]
573+
* [groupingUsed=true|false] [maximumFractionDigits=...]
574+
* [maximumIntegerDigits=...] [minimumFractionDigits=...]
575+
* [minimumIntegerDigits=...] [parseIntegerOnly=true|false]
576+
* [roundingMode="HALF_UP"]}}
573577
* </pre>
574578
*
575-
* Format parameters is one of:
579+
* Format parameters can be:
576580
*
577581
* <ul>
578-
* <li>"integer": the integer number format
579-
* <li>"percent": the percent number format
580-
* <li>"currency": the decimal number format
581-
* <li>"pattern": a decimal pattern.
582+
* <li>"integer": formats the number as an integer
583+
* <li>"percent": formats the number as a percentage
584+
* <li>"currency": formats the number as currency
585+
* <li>"pattern": a {@link java.text.DecimalFormat} pattern, e.g. "#,###.00"
582586
* </ul>
583587
*
584-
* Otherwise, the default formatter will be used.
588+
* If not specified, the default number format will be used.
589+
* The format option can be specified either as a positional parameter or as a named hash parameter.
585590
*
586-
* <p>More options:
591+
* <p>Additional options:
587592
*
588593
* <ul>
589-
* <li>groupingUsed: Set whether or not grouping will be used in this format.
590-
* <li>maximumFractionDigits: Sets the maximum number of digits allowed in the fraction portion
591-
* of a number.
592-
* <li>maximumIntegerDigits: Sets the maximum number of digits allowed in the integer portion of
593-
* a number
594-
* <li>minimumFractionDigits: Sets the minimum number of digits allowed in the fraction portion
595-
* of a number
596-
* <li>minimumIntegerDigits: Sets the minimum number of digits allowed in the integer portion of
597-
* a number.
598-
* <li>parseIntegerOnly: Sets whether or not numbers should be parsed as integers only.
599-
* <li>roundingMode: Sets the {@link java.math.RoundingMode} used in this NumberFormat.
594+
* <li><strong>locale</strong>: the locale to use, e.g. "en_US" or "fr". Defaults to the system locale.
595+
* <li><strong>groupingUsed</strong>: whether grouping (e.g. thousands separators) should be used.
596+
* <li><strong>maximumFractionDigits</strong>: maximum number of digits in the fractional part.
597+
* <li><strong>maximumIntegerDigits</strong>: maximum number of digits in the integer part.
598+
* <li><strong>minimumFractionDigits</strong>: minimum number of digits in the fractional part.
599+
* <li><strong>minimumIntegerDigits</strong>: minimum number of digits in the integer part.
600+
* <li><strong>parseIntegerOnly</strong>: whether numbers should be parsed as integers only.
601+
* <li><strong>roundingMode</strong>: the {@link java.math.RoundingMode} to apply, e.g. "HALF_UP".
600602
* </ul>
601603
*
602604
* @see NumberFormat
@@ -663,23 +665,31 @@ protected CharSequence safeApply(final Object value, final Options options) {
663665
* @return The number format to use.
664666
*/
665667
private NumberFormat build(final Options options) {
666-
if (options.params.length == 0) {
667-
return NumberStyle.DEFAULT.numberFormat(Locale.getDefault());
668-
}
669-
isTrue(
670-
options.params[0] instanceof String, "found '%s', expected 'string'", options.params[0]);
671-
String format = options.param(0);
672-
String localeStr = options.param(1, Locale.getDefault().toString());
673-
Locale locale = LocaleUtils.toLocale(localeStr);
668+
final String format = getFormatArgument(options);
669+
final Locale locale = getLocaleArgument(options);
674670
try {
675-
NumberStyle style = NumberStyle.valueOf(format.toUpperCase().trim());
671+
NumberStyle style = NumberStyle.valueOf(format);
676672
return style.numberFormat(locale);
677-
} catch (ArrayIndexOutOfBoundsException ex) {
678-
return NumberStyle.DEFAULT.numberFormat(locale);
679673
} catch (IllegalArgumentException ex) {
680674
return new DecimalFormat(format, new DecimalFormatSymbols(locale));
681675
}
682676
}
677+
678+
private String getFormatArgument(final Options options) {
679+
final String stringifiedFormat = options.param(0, options.hash("format"));
680+
if (stringifiedFormat == null || stringifiedFormat.isBlank()) {
681+
return NumberStyle.DEFAULT.name();
682+
}
683+
return stringifiedFormat.toUpperCase().trim();
684+
}
685+
686+
private Locale getLocaleArgument(final Options options) {
687+
final String stringifiedLocale = options.param(1, options.hash("locale"));
688+
if (stringifiedLocale != null && !stringifiedLocale.isBlank()) {
689+
return LocaleUtils.toLocale(stringifiedLocale);
690+
}
691+
return Locale.getDefault();
692+
}
683693
},
684694

685695
/**

handlebars/src/test/java/com/github/jknack/handlebars/NumberFormatTest.java

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -72,11 +72,27 @@ public void frLocale() throws IOException {
7272
shouldCompileTo("{{numberFormat this \"" + pattern + "\" \"fr\"}}", number, expected);
7373
}
7474

75-
public static Date date(final int day, final int month, final int year) {
76-
Calendar calendar = Calendar.getInstance();
77-
calendar.set(Calendar.DATE, day);
78-
calendar.set(Calendar.MONTH, month - 1);
79-
calendar.set(Calendar.YEAR, year);
80-
return calendar.getTime();
75+
@Test
76+
public void namedFormat() throws IOException {
77+
final Number number = Math.PI;
78+
final Locale defaultLocale = Locale.getDefault();
79+
final String expected = NumberFormat
80+
.getPercentInstance(defaultLocale)
81+
.format(number);
82+
83+
shouldCompileTo("{{numberFormat this format=\"percent\"}}", number, expected);
84+
}
85+
86+
@Test
87+
public void namedBrLocale() throws IOException {
88+
final Number number = Math.PI;
89+
final String pattern = "currency";
90+
final Locale portuguese = Locale.forLanguageTag("pt-BR");
91+
92+
final String expected = NumberFormat
93+
.getCurrencyInstance(portuguese)
94+
.format(number);
95+
96+
shouldCompileTo("{{numberFormat this \"" + pattern + "\" locale=\"pt-BR\"}}", number, expected);
8197
}
8298
}

0 commit comments

Comments
 (0)