summaryrefslogtreecommitdiff
path: root/src/corefx
diff options
context:
space:
mode:
authorSteve Harter <sharter@microsoft.com>2015-08-27 12:31:25 -0500
committerMatt Ellis <matell@microsoft.com>2015-09-22 11:50:33 -0700
commit88b91180979f5089f76fae7e94d579eccd1a39e7 (patch)
treeedc565672276f5cd141657418a5b341599a55c36 /src/corefx
parent0d3a7b3b472e1d181aca666198b05884b54458b7 (diff)
downloadcoreclr-88b91180979f5089f76fae7e94d579eccd1a39e7.tar.gz
coreclr-88b91180979f5089f76fae7e94d579eccd1a39e7.tar.bz2
coreclr-88b91180979f5089f76fae7e94d579eccd1a39e7.zip
add number formatting
Diffstat (limited to 'src/corefx')
-rw-r--r--src/corefx/System.Globalization.Native/localeNumberData.cpp361
1 files changed, 321 insertions, 40 deletions
diff --git a/src/corefx/System.Globalization.Native/localeNumberData.cpp b/src/corefx/System.Globalization.Native/localeNumberData.cpp
index 10d1eaf03d..20faa0eb7a 100644
--- a/src/corefx/System.Globalization.Native/localeNumberData.cpp
+++ b/src/corefx/System.Globalization.Native/localeNumberData.cpp
@@ -4,11 +4,27 @@
//
#include <assert.h>
+#include <string.h>
#include "locale.hpp"
+#include "unicode/decimfmt.h"
+#include "unicode/numfmt.h"
#include "unicode/ulocdata.h"
+// invariant character definitions used by ICU
+#define chCurrencySign ((UChar)0x00A4) // international currency
+#define chSpace ((UChar)0x0020) // space
+#define chSpace2 ((UChar)0x00A0) // space
+#define chPatternDigit ((UChar)0x0023) // '#'
+#define chPatternSeparator ((UChar)0x003B) // ';'
+#define chPatternMinus ((UChar)0x002D) // '-'
+#define chPercent ((UChar)0x0025) // '%'
+#define chOpenParen ((UChar)0x0028) // '('
+#define chCloseParen ((UChar)0x0029) // ')'
+
+#define ARRAY_LENGTH(array) (sizeof(array)/sizeof(array[0]))
+
// Enum that corresponds to managed enum CultureData.LocaleNumberData.
// The numeric values of the enum members match their Win32 counterparts.
enum LocaleNumberData : int32_t
@@ -31,9 +47,259 @@ enum LocaleNumberData : int32_t
/*
Function:
+NormalizePattern
+
+Returns a numeric string pattern in a format that we can match against the appropriate managed pattern.
+*/
+UnicodeString* NormalizePattern(const UnicodeString *srcPattern, UnicodeString *destPattern, bool isNegative)
+{
+ // A srcPattern example: "#,##0.00 C;(#,##0.00 C)" but where C is the international currency symbol (chCurrencySign)
+ // The positive pattern comes first, then an optional negative pattern separated by a semicolon
+ // A destPattern example: "(C n)" where C represents the currency symbol, and n is the number
+ destPattern->remove();
+
+ int iStart = 0;
+ int iEnd = srcPattern->length() - 1;
+ int32_t iNegative = srcPattern->indexOf(chPatternSeparator);
+ if (iNegative >= 0)
+ {
+ if (isNegative)
+ {
+ iStart = iNegative + 1;
+ }
+ else
+ {
+ iEnd = iNegative - 1;
+ }
+ }
+
+ bool minusAdded = false;
+ bool digitAdded = false;
+ bool currencyAdded = false;
+ bool spaceAdded = false;
+
+ for (int i = iStart; i <= iEnd; i++)
+ {
+ UChar32 ch = srcPattern->char32At(i);
+ switch (ch)
+ {
+ case chPatternDigit:
+ if (!digitAdded)
+ {
+ digitAdded = true;
+ destPattern->append('n');
+ }
+ break;
+
+ case chCurrencySign:
+ if (!currencyAdded)
+ {
+ currencyAdded = true;
+ destPattern->append('C');
+ }
+ break;
+
+ case chSpace:
+ case chSpace2:
+ if (!spaceAdded)
+ {
+ spaceAdded = true;
+ destPattern->append(chSpace);
+ }
+ else
+ {
+ assert(false);
+ }
+ break;
+
+ case chPatternMinus:
+ case chOpenParen:
+ case chCloseParen:
+ minusAdded = true;
+ destPattern->append(ch);
+ break;
+
+ case chPercent:
+ destPattern->append(ch);
+ break;
+ }
+ }
+
+ // if there is no segative subpattern, the convention is to prefix the minus sign
+ if (isNegative && !minusAdded)
+ {
+ destPattern->insert(0, chPatternMinus);
+ }
+
+ return destPattern;
+}
+
+/*
+Function:
+GetPattern
+
+Determines the pattern from the decimalFormat and returns the matching pattern's index from patterns[].
+Returns index -1 if no pattern is found.
+*/
+int GetPattern(DecimalFormat *decimalFormat, const char* patterns[], int patternsCount, bool isNegative)
+{
+ const int MAX_DOTNET_NUMERIC_PATTERN_LENGTH = 6; // example: "(C n)" plus terminator
+ char charPattern[MAX_DOTNET_NUMERIC_PATTERN_LENGTH] = { 0 };
+
+ UnicodeString icuPattern;
+ icuPattern.remove();
+ decimalFormat->toPattern(icuPattern);
+
+ UnicodeString normalizedPattern;
+ NormalizePattern(&icuPattern, &normalizedPattern, isNegative);
+
+ assert(normalizedPattern.length() > 0);
+ assert(normalizedPattern.length() < MAX_DOTNET_NUMERIC_PATTERN_LENGTH);
+ if (normalizedPattern.length() == 0 || normalizedPattern.length() >= MAX_DOTNET_NUMERIC_PATTERN_LENGTH)
+ {
+ return -1;
+ }
+
+ u_UCharsToChars(normalizedPattern.getTerminatedBuffer(), charPattern, normalizedPattern.length() + 1);
+
+ for (int i = 0; i < patternsCount; i++)
+ {
+ if (strcmp(charPattern, patterns[i]) == 0)
+ {
+ return i;
+ }
+ };
+
+ assert(false); // should have found a valid pattern
+ return -1;
+}
+
+/*
+Function:
+GetCurrencyNegativePattern
+
+Implementation of NumberFormatInfo.CurrencyNegativePattern.
+Returns the pattern index.
+*/
+int GetCurrencyNegativePattern(const Locale &locale)
+{
+ const int DEFAULT_VALUE = 0;
+ static const char* Patterns[] = { "(Cn)", "-Cn", "C-n", "Cn-", "(nC)", "-nC", "n-C", "nC-", "-n C", "-C n", "n C-", "C n-", "C -n", "n- C", "(C n)", "(n C)" };
+ UErrorCode status = U_ZERO_ERROR;
+
+ LocalPointer<NumberFormat> format(NumberFormat::createInstance(locale, UNUM_CURRENCY, status));
+ if (U_FAILURE(status))
+ {
+ assert(false);
+ return DEFAULT_VALUE;
+ }
+
+ int value = GetPattern(static_cast<DecimalFormat*>(format.getAlias()), Patterns, ARRAY_LENGTH(Patterns), true);
+ return (value >= 0) ? value : DEFAULT_VALUE;
+}
+
+/*
+Function:
+GetCurrencyPositivePattern
+
+Implementation of NumberFormatInfo.CurrencyPositivePattern.
+Returns the pattern index.
+*/
+int GetCurrencyPositivePattern(const Locale &locale)
+{
+ const int DEFAULT_VALUE = 0;
+ static const char* Patterns[] = { "Cn", "nC", "C n", "n C" };
+ UErrorCode status = U_ZERO_ERROR;
+
+ LocalPointer<NumberFormat> format(NumberFormat::createInstance(locale, UNUM_CURRENCY, status));
+ if (U_FAILURE(status))
+ {
+ assert(false);
+ return DEFAULT_VALUE;
+ }
+
+ int value = GetPattern(static_cast<DecimalFormat*>(format.getAlias()), Patterns, ARRAY_LENGTH(Patterns), false);
+ return (value >= 0) ? value : DEFAULT_VALUE;
+}
+
+/*
+Function:
+GetNumberNegativePattern
+
+Implementation of NumberFormatInfo.NumberNegativePattern.
+Returns the pattern index.
+*/
+int GetNumberNegativePattern(const Locale &locale)
+{
+ const int DEFAULT_VALUE = 1;
+ static const char* Patterns[] = { "(n)", "-n", "- n", "n-", "n -" };
+ UErrorCode status = U_ZERO_ERROR;
+
+ LocalPointer<NumberFormat> format(NumberFormat::createInstance(locale, UNUM_DECIMAL, status));
+ if (U_FAILURE(status))
+ {
+ assert(false);
+ return DEFAULT_VALUE;
+ }
+
+ int value = GetPattern(static_cast<DecimalFormat*>(format.getAlias()), Patterns, ARRAY_LENGTH(Patterns), true);
+ return (value >= 0) ? value : DEFAULT_VALUE;
+}
+
+/*
+Function:
+GetPercentNegativePattern
+
+Implementation of NumberFormatInfo.PercentNegativePattern.
+Returns the pattern index.
+*/
+int GetPercentNegativePattern(const Locale &locale)
+{
+ const int DEFAULT_VALUE = 0;
+ static const char* Patterns[] = { "-n %", "-n%", "-%n", "%-n", "%n-", "n-%", "n%-", "-% n", "n %-", "% n-", "% -n", "n- %" };
+ UErrorCode status = U_ZERO_ERROR;
+
+ LocalPointer<NumberFormat> format(NumberFormat::createInstance(locale, UNUM_PERCENT, status));
+ if (U_FAILURE(status))
+ {
+ assert(false);
+ return DEFAULT_VALUE;
+ }
+
+ int value = GetPattern(static_cast<DecimalFormat*>(format.getAlias()), Patterns, ARRAY_LENGTH(Patterns), true);
+ return (value >= 0) ? value : DEFAULT_VALUE;
+}
+
+/*
+Function:
+GetPercentPositivePattern
+
+Implementation of NumberFormatInfo.PercentPositivePattern.
+Returns the pattern index.
+*/
+int GetPercentPositivePattern(const Locale &locale)
+{
+ const int DEFAULT_VALUE = 0;
+ static const char* Patterns[] = { "n %", "n%", "%n", "% n" };
+ UErrorCode status = U_ZERO_ERROR;
+
+ LocalPointer<NumberFormat> format(NumberFormat::createInstance(locale, UNUM_PERCENT, status));
+ if (U_FAILURE(status))
+ {
+ assert(false);
+ return DEFAULT_VALUE;
+ }
+
+ int value = GetPattern(static_cast<DecimalFormat*>(format.getAlias()), Patterns, ARRAY_LENGTH(Patterns), false);
+ return (value >= 0) ? value : DEFAULT_VALUE;
+}
+
+/*
+Function:
GetMeasurementSystem
-Obtains the measurement system for the local, determining if US or metric
+Obtains the measurement system for the local, determining if US or metric.
+Returns 1 for US, 0 otherwise.
*/
UErrorCode GetMeasurementSystem(const char *localeId, int32_t *value)
{
@@ -64,48 +330,63 @@ extern "C" int32_t GetLocaleInfoInt(const UChar* localeName, LocaleNumberData lo
}
UErrorCode status = U_ZERO_ERROR;
+ UNumberFormat* numformat = NULL;
switch (localeNumberData)
{
- case LanguageId:
- *value = locale.getLCID();
- break;
- case MeasurementSystem:
- status = GetMeasurementSystem(locale.getName(), value);
- break;
- case FractionalDigitsCount:
- *value = 2; //TODO: implement
- break;
- case NegativeNumberFormat:
- *value = 1; //TODO: implement
- break;
- case MonetaryFractionalDigitsCount:
- *value = 2; //TODO: implement
- break;
- case PositiveMonetaryNumberFormat:
- *value = 0; //TODO: implement
- break;
- case NegativeMonetaryNumberFormat:
- *value = 0; //TODO: implement
- break;
- case CalendarType:
- *value = 1; //TODO: implement
- break;
- case FirstWeekOfYear:
- *value = 0; //TODO: implement
- break;
- case ReadingLayout:
- *value = 0; //TODO: implement
- break;
- case NegativePercentFormat:
- *value = 0; //TODO: implement
- break;
- case PositivePercentFormat:
- *value = 0; //TODO: implement
- break;
- default:
- status = U_UNSUPPORTED_ERROR;
+ case LanguageId:
+ *value = locale.getLCID();
+ break;
+ case MeasurementSystem:
+ status = GetMeasurementSystem(locale.getName(), value);
+ break;
+ case FractionalDigitsCount:
+ numformat = unum_open(UNUM_DECIMAL, NULL, 0, locale.getName(), NULL, &status);
+ if (U_FAILURE(status))
+ {
assert(false);
- break;
+ return 0;
+ }
+ *value = unum_getAttribute(numformat, UNUM_MAX_FRACTION_DIGITS);
+ unum_close(numformat);
+ break;
+ case NegativeNumberFormat:
+ *value = GetNumberNegativePattern(locale);
+ break;
+ case MonetaryFractionalDigitsCount:
+ numformat = unum_open(UNUM_CURRENCY, NULL, 0, locale.getName(), NULL, &status);
+ if (U_FAILURE(status))
+ {
+ assert(false);
+ return 0;
+ }
+ *value = unum_getAttribute(numformat, UNUM_MAX_FRACTION_DIGITS);
+ unum_close(numformat);
+ break;
+ case PositiveMonetaryNumberFormat:
+ *value = GetCurrencyPositivePattern(locale);
+ break;
+ case NegativeMonetaryNumberFormat:
+ *value = GetCurrencyNegativePattern(locale);
+ break;
+ case CalendarType:
+ *value = 1; //TODO: implement
+ break;
+ case FirstWeekOfYear:
+ *value = 0; //TODO: implement
+ break;
+ case ReadingLayout:
+ *value = 0; //todo: implement
+ break;
+ case NegativePercentFormat:
+ *value = GetPercentNegativePattern(locale);
+ break;
+ case PositivePercentFormat:
+ *value = GetPercentPositivePattern(locale);
+ break;
+ default:
+ status = U_UNSUPPORTED_ERROR;
+ assert(false);
+ break;
}
assert(status != U_BUFFER_OVERFLOW_ERROR);