diff options
author | Steve Harter <sharter@microsoft.com> | 2015-09-18 14:01:16 -0500 |
---|---|---|
committer | Matt Ellis <matell@microsoft.com> | 2015-09-22 11:50:40 -0700 |
commit | 7561e211e364255805314e27064ff62782a05b5e (patch) | |
tree | 00d454c29b4e3019e136b4f9b848ffa784982fb9 /src/corefx | |
parent | cf5669690fbd4fe08ecf264783380667ceb25c27 (diff) | |
download | coreclr-7561e211e364255805314e27064ff62782a05b5e.tar.gz coreclr-7561e211e364255805314e27064ff62782a05b5e.tar.bz2 coreclr-7561e211e364255805314e27064ff62782a05b5e.zip |
complete core implementation of CultureData for Unix
Diffstat (limited to 'src/corefx')
4 files changed, 335 insertions, 80 deletions
diff --git a/src/corefx/System.Globalization.Native/locale.cpp b/src/corefx/System.Globalization.Native/locale.cpp index d0675047a2..e02c32d680 100644 --- a/src/corefx/System.Globalization.Native/locale.cpp +++ b/src/corefx/System.Globalization.Native/locale.cpp @@ -14,7 +14,21 @@ #include "unicode/localpointer.h" #include "unicode/ulocdata.h" -inline int32_t UErrorCodeToBool(UErrorCode code) { return U_SUCCESS(code) ? 1 : 0; } +int32_t UErrorCodeToBool(UErrorCode status) +{ + if (U_SUCCESS(status)) + { + return 1; + } + + // assert errors that should never occur + assert(status != U_BUFFER_OVERFLOW_ERROR); + assert(status != U_INTERNAL_PROGRAM_ERROR); + + // add possible SetLastError support here + + return 0; +} Locale GetLocale(const UChar* localeName, bool canonize) { @@ -50,6 +64,22 @@ UErrorCode u_charsToUChars_safe(const char *str, UChar* value, int32_t valueLeng return U_ZERO_ERROR; } +void FixupLocaleName(UChar* value, int32_t valueLength) +{ + for (int i = 0; i < valueLength; i++) + { + if (value[i] == (UChar)'\0') + { + break; + } + else if (value[i] == (UChar)'_') + { + value[i] = (UChar)'-'; + } + } +} + + extern "C" int32_t GetLocaleName(const UChar* localeName, UChar* value, int32_t valueLength) { Locale locale = GetLocale(localeName, true); @@ -60,26 +90,13 @@ extern "C" int32_t GetLocaleName(const UChar* localeName, UChar* value, int32_t return UErrorCodeToBool(U_ILLEGAL_ARGUMENT_ERROR); } - if (strlen(locale.getISO3Language()) == 0) - { - // unknown language; language is required (script and country optional) - return UErrorCodeToBool(U_ILLEGAL_ARGUMENT_ERROR); - } + // other validation done on managed side UErrorCode status = u_charsToUChars_safe(locale.getName(), value, valueLength); if (U_SUCCESS(status)) { - // replace underscores with hyphens to interop with existing .NET code - for (UChar* ch = value; *ch != (UChar)'\0'; ch++) - { - if (*ch == (UChar)'_') - { - *ch = (UChar)'-'; - } - } + FixupLocaleName(value, valueLength); } - assert(status != U_BUFFER_OVERFLOW_ERROR); - return UErrorCodeToBool(status); } diff --git a/src/corefx/System.Globalization.Native/locale.hpp b/src/corefx/System.Globalization.Native/locale.hpp index 73d331df14..894ea90936 100644 --- a/src/corefx/System.Globalization.Native/locale.hpp +++ b/src/corefx/System.Globalization.Native/locale.hpp @@ -38,3 +38,11 @@ u_charsToUChars_safe Copies the given null terminated char* to UChar with error checking. Replacement for ICU u_charsToUChars */ UErrorCode u_charsToUChars_safe(const char *str, UChar* value, int32_t valueLength); + +/* +Function: +FixupLocaleName + +Replace underscores with hyphens to interop with existing .NET code +*/ +void FixupLocaleName(UChar* value, int32_t valueLength); diff --git a/src/corefx/System.Globalization.Native/localeNumberData.cpp b/src/corefx/System.Globalization.Native/localeNumberData.cpp index 8ba1f066a5..7ab62ca96f 100644 --- a/src/corefx/System.Globalization.Native/localeNumberData.cpp +++ b/src/corefx/System.Globalization.Native/localeNumberData.cpp @@ -8,20 +8,22 @@ #include "locale.hpp" +#include "unicode/calendar.h" #include "unicode/decimfmt.h" +#include "unicode/localpointer.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 UCHAR_CURRENCY ((UChar)0x00A4) // international currency +#define UCHAR_SPACE ((UChar)0x0020) // space +#define UCHAR_NBSPACE ((UChar)0x00A0) // space +#define UCHAR_DIGIT ((UChar)0x0023) // '#' +#define UCHAR_SEMICOLON ((UChar)0x003B) // ';' +#define UCHAR_MINUS ((UChar)0x002D) // '-' +#define UCHAR_PERCENT ((UChar)0x0025) // '%' +#define UCHAR_OPENPAREN ((UChar)0x0028) // '(' +#define UCHAR_CLOSEPAREN ((UChar)0x0029) // ')' #define ARRAY_LENGTH(array) (sizeof(array)/sizeof(array[0])) @@ -36,7 +38,7 @@ enum LocaleNumberData : int32_t MonetaryFractionalDigitsCount = 0x00000019, PositiveMonetaryNumberFormat = 0x0000001B, NegativeMonetaryNumberFormat = 0x0000001C, - CalendarType = 0x00001009, + FirstDayofWeek = 0x0000100C, FirstWeekOfYear = 0x0000100D, ReadingLayout = 0x00000070, NegativePercentFormat = 0x00000074, @@ -45,22 +47,30 @@ enum LocaleNumberData : int32_t Monetary = 0x00000018 }; +// Enum that corresponds to managed enum System.Globalization.CalendarWeekRule +enum CalendarWeekRule : int32_t +{ + FirstDay = 0, + FirstFullWeek = 1, + FirstFourDayWeek = 2 +}; + /* Function: -NormalizePattern +NormalizeNumericPattern Returns a numeric string pattern in a format that we can match against the appropriate managed pattern. */ -void NormalizePattern(const UnicodeString *srcPattern, UnicodeString *destPattern, bool isNegative) +void NormalizeNumericPattern(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) + // A srcPattern example: "#,##0.00 C;(#,##0.00 C)" but where C is the international currency symbol (UCHAR_CURRENCY) // 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 iNegativePatternStart = srcPattern->indexOf(chPatternSeparator); + int32_t iNegativePatternStart = srcPattern->indexOf(UCHAR_SEMICOLON); if (iNegativePatternStart >= 0) { if (isNegative) @@ -80,10 +90,10 @@ void NormalizePattern(const UnicodeString *srcPattern, UnicodeString *destPatter for (int i = iStart; i <= iEnd; i++) { - UChar ch = srcPattern->char32At(i); + UChar ch = srcPattern->charAt(i); switch (ch) { - case chPatternDigit: + case UCHAR_DIGIT: if (!digitAdded) { digitAdded = true; @@ -91,7 +101,7 @@ void NormalizePattern(const UnicodeString *srcPattern, UnicodeString *destPatter } break; - case chCurrencySign: + case UCHAR_CURRENCY: if (!currencyAdded) { currencyAdded = true; @@ -99,12 +109,12 @@ void NormalizePattern(const UnicodeString *srcPattern, UnicodeString *destPatter } break; - case chSpace: - case chSpace2: + case UCHAR_SPACE: + case UCHAR_NBSPACE: if (!spaceAdded) { spaceAdded = true; - destPattern->append(chSpace); + destPattern->append(UCHAR_SPACE); } else { @@ -112,34 +122,34 @@ void NormalizePattern(const UnicodeString *srcPattern, UnicodeString *destPatter } break; - case chPatternMinus: - case chOpenParen: - case chCloseParen: + case UCHAR_MINUS: + case UCHAR_OPENPAREN: + case UCHAR_CLOSEPAREN: minusAdded = true; destPattern->append(ch); break; - case chPercent: + case UCHAR_PERCENT: destPattern->append(ch); break; } } - // if there is no negative subpattern, the convention is to prefix the minus sign + // if there is no negative subpattern, the ICU convention is to prefix the minus sign if (isNegative && !minusAdded) { - destPattern->insert(0, chPatternMinus); + destPattern->insert(0, UCHAR_MINUS); } } /* Function: -GetPattern +GetNumericPattern 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) +int GetNumericPattern(DecimalFormat *decimalFormat, const char* patterns[], int patternsCount, bool isNegative) { const int INVALID_FORMAT = -1; const int MAX_DOTNET_NUMERIC_PATTERN_LENGTH = 6; // example: "(C n)" plus terminator @@ -149,7 +159,7 @@ int GetPattern(DecimalFormat *decimalFormat, const char* patterns[], int pattern decimalFormat->toPattern(icuPattern); UnicodeString normalizedPattern; - NormalizePattern(&icuPattern, &normalizedPattern, isNegative); + NormalizeNumericPattern(&icuPattern, &normalizedPattern, isNegative); assert(normalizedPattern.length() > 0); assert(normalizedPattern.length() < MAX_DOTNET_NUMERIC_PATTERN_LENGTH); @@ -186,14 +196,22 @@ int GetCurrencyNegativePattern(const Locale &locale) UErrorCode status = U_ZERO_ERROR; LocalPointer<NumberFormat> format(NumberFormat::createInstance(locale, UNUM_CURRENCY, status)); - if (U_FAILURE(status)) + assert(U_SUCCESS(status)); + if (U_SUCCESS(status)) { - assert(false); - return DEFAULT_VALUE; + DecimalFormat* decimalFormat = dynamic_cast<DecimalFormat*>(format.getAlias()); + assert(decimalFormat != NULL); + if (decimalFormat != NULL) + { + int value = GetNumericPattern(decimalFormat, Patterns, ARRAY_LENGTH(Patterns), true); + if (value >= 0) + { + return value; + } + } } - int value = GetPattern(static_cast<DecimalFormat*>(format.getAlias()), Patterns, ARRAY_LENGTH(Patterns), true); - return (value >= 0) ? value : DEFAULT_VALUE; + return DEFAULT_VALUE; } /* @@ -210,14 +228,22 @@ int GetCurrencyPositivePattern(const Locale &locale) UErrorCode status = U_ZERO_ERROR; LocalPointer<NumberFormat> format(NumberFormat::createInstance(locale, UNUM_CURRENCY, status)); - if (U_FAILURE(status)) + assert(U_SUCCESS(status)); + if (U_SUCCESS(status)) { - assert(false); - return DEFAULT_VALUE; + DecimalFormat* decimalFormat = dynamic_cast<DecimalFormat*>(format.getAlias()); + assert(decimalFormat != NULL); + if (decimalFormat != NULL) + { + int value = GetNumericPattern(decimalFormat, Patterns, ARRAY_LENGTH(Patterns), false); + if (value >= 0) + { + return value; + } + } } - int value = GetPattern(static_cast<DecimalFormat*>(format.getAlias()), Patterns, ARRAY_LENGTH(Patterns), false); - return (value >= 0) ? value : DEFAULT_VALUE; + return DEFAULT_VALUE; } /* @@ -234,14 +260,22 @@ int GetNumberNegativePattern(const Locale &locale) UErrorCode status = U_ZERO_ERROR; LocalPointer<NumberFormat> format(NumberFormat::createInstance(locale, UNUM_DECIMAL, status)); - if (U_FAILURE(status)) + assert(U_SUCCESS(status)); + if (U_SUCCESS(status)) { - assert(false); - return DEFAULT_VALUE; + DecimalFormat* decimalFormat = dynamic_cast<DecimalFormat*>(format.getAlias()); + assert(decimalFormat != NULL); + if (decimalFormat != NULL) + { + int value = GetNumericPattern(decimalFormat, Patterns, ARRAY_LENGTH(Patterns), true); + if (value >= 0) + { + return value; + } + } } - int value = GetPattern(static_cast<DecimalFormat*>(format.getAlias()), Patterns, ARRAY_LENGTH(Patterns), true); - return (value >= 0) ? value : DEFAULT_VALUE; + return DEFAULT_VALUE; } /* @@ -258,14 +292,22 @@ int GetPercentNegativePattern(const Locale &locale) UErrorCode status = U_ZERO_ERROR; LocalPointer<NumberFormat> format(NumberFormat::createInstance(locale, UNUM_PERCENT, status)); - if (U_FAILURE(status)) + assert(U_SUCCESS(status)); + if (U_SUCCESS(status)) { - assert(false); - return DEFAULT_VALUE; + DecimalFormat* decimalFormat = dynamic_cast<DecimalFormat*>(format.getAlias()); + assert(decimalFormat != NULL); + if (decimalFormat != NULL) + { + int value = GetNumericPattern(decimalFormat, Patterns, ARRAY_LENGTH(Patterns), true); + if (value >= 0) + { + return value; + } + } } - int value = GetPattern(static_cast<DecimalFormat*>(format.getAlias()), Patterns, ARRAY_LENGTH(Patterns), true); - return (value >= 0) ? value : DEFAULT_VALUE; + return DEFAULT_VALUE; } /* @@ -282,14 +324,22 @@ int GetPercentPositivePattern(const Locale &locale) UErrorCode status = U_ZERO_ERROR; LocalPointer<NumberFormat> format(NumberFormat::createInstance(locale, UNUM_PERCENT, status)); - if (U_FAILURE(status)) + assert(U_SUCCESS(status)); + if (U_SUCCESS(status)) { - assert(false); - return DEFAULT_VALUE; + DecimalFormat* decimalFormat = dynamic_cast<DecimalFormat*>(format.getAlias()); + assert(decimalFormat != NULL); + if (decimalFormat != NULL) + { + int value = GetNumericPattern(decimalFormat, Patterns, ARRAY_LENGTH(Patterns), false); + if (value >= 0) + { + return value; + } + } } - int value = GetPattern(static_cast<DecimalFormat*>(format.getAlias()), Patterns, ARRAY_LENGTH(Patterns), false); - return (value >= 0) ? value : DEFAULT_VALUE; + return DEFAULT_VALUE; } /* @@ -366,15 +416,55 @@ extern "C" int32_t GetLocaleInfoInt(const UChar* localeName, LocaleNumberData lo case NegativeMonetaryNumberFormat: *value = GetCurrencyNegativePattern(locale); break; - case CalendarType: - *value = 1; //TODO: implement - break; case FirstWeekOfYear: - *value = 0; //TODO: implement + { + // corresponds to DateTimeFormat.CalendarWeekRule + LocalPointer<Calendar> calendar(Calendar::createInstance(locale, status)); + if (U_SUCCESS(status)) + { + // values correspond to LOCALE_IFIRSTWEEKOFYEAR + int minDaysInWeek = calendar->getMinimalDaysInFirstWeek(); + if (minDaysInWeek == 1) + { + *value = CalendarWeekRule::FirstDay; + } + else if (minDaysInWeek == 7) + { + *value = CalendarWeekRule::FirstFullWeek; + } + else if (minDaysInWeek >= 4) + { + *value = CalendarWeekRule::FirstFourDayWeek; + } + else + { + status = U_UNSUPPORTED_ERROR; + } + } break; + } case ReadingLayout: - *value = 0; //todo: implement + { + // coresponds to values 0 and 1 in LOCALE_IREADINGLAYOUT (values 2 and 3 not used in coreclr) + // 0 - Left to right (such as en-US) + // 1 - Right to left (such as arabic locales) + ULayoutType orientation = uloc_getCharacterOrientation(locale.getName(), &status); + // alternative implementation in ICU 54+ is Locale.isRightToLeft() which also supports script tags in locale + if (U_SUCCESS(status)) + { + *value = (orientation == ULOC_LAYOUT_RTL) ? 1 : 0; + } break; + } + case FirstDayofWeek: + { + LocalPointer<Calendar> pcalendar(Calendar::createInstance(locale, status)); + if (U_SUCCESS(status)) + { + *value = pcalendar->getFirstDayOfWeek(status) - 1; // .NET is 0-based and ICU is 1-based + } + break; + } case NegativePercentFormat: *value = GetPercentNegativePattern(locale); break; @@ -387,7 +477,45 @@ extern "C" int32_t GetLocaleInfoInt(const UChar* localeName, LocaleNumberData lo break; } - assert(status != U_BUFFER_OVERFLOW_ERROR); + return UErrorCodeToBool(status); +} + +/* +PAL Function: +GetLocaleInfoGroupingSizes + +Obtains grouping sizes for decimal and currency +Returns 1 for success, 0 otherwise +*/ +extern "C" int32_t GetLocaleInfoGroupingSizes(const UChar* localeName, LocaleNumberData localeGroupingData, int32_t* primaryGroupSize, int32_t* secondaryGroupSize) +{ + Locale locale = GetLocale(localeName); + if (locale.isBogus()) + { + return UErrorCodeToBool(U_ILLEGAL_ARGUMENT_ERROR); + } + + UNumberFormatStyle style; + switch (localeGroupingData) + { + case Digit: + style = UNUM_DECIMAL; + break; + case Monetary: + style = UNUM_CURRENCY; + break; + default: + return UErrorCodeToBool(U_UNSUPPORTED_ERROR); + } + + UErrorCode status = U_ZERO_ERROR; + UNumberFormat* numformat = unum_open(style, NULL, 0, locale.getName(), NULL, &status); + if (U_SUCCESS(status)) + { + *primaryGroupSize = unum_getAttribute(numformat, UNUM_GROUPING_SIZE); + *secondaryGroupSize = unum_getAttribute(numformat, UNUM_SECONDARY_GROUPING_SIZE); + unum_close(numformat); + } return UErrorCodeToBool(status); } diff --git a/src/corefx/System.Globalization.Native/localeStringData.cpp b/src/corefx/System.Globalization.Native/localeStringData.cpp index 6c88211532..fc5250da1b 100644 --- a/src/corefx/System.Globalization.Native/localeStringData.cpp +++ b/src/corefx/System.Globalization.Native/localeStringData.cpp @@ -8,10 +8,14 @@ #include "locale.hpp" -#include "unicode/dcfmtsym.h" //decimal -#include "unicode/dtfmtsym.h" //date +#include "unicode/dcfmtsym.h" //decimal symbols +#include "unicode/dtfmtsym.h" //date symbols +#include "unicode/smpdtfmt.h" //date format #include "unicode/localpointer.h" +// invariant character definitions used by ICU +#define UCHAR_SPACE ((UChar)0x0020) // space +#define UCHAR_NBSPACE ((UChar)0x00A0) // space // Enum that corresponds to managed enum CultureData.LocaleStringData. // The numeric values of the enum members match their Win32 counterparts. @@ -41,6 +45,7 @@ enum LocaleStringData : int32_t Iso3166CountryName = 0x0000005A, NaNSymbol = 0x00000069, PositiveInfinitySymbol = 0x0000006a, + ParentName = 0x0000006d, PercentSymbol = 0x00000076, PerMilleSymbol = 0x00000077 }; @@ -224,6 +229,22 @@ extern "C" int32_t GetLocaleInfoString(const UChar* localeName, LocaleStringData case PositiveInfinitySymbol: status = GetLocaleInfoDecimalFormatSymbol(locale, DecimalFormatSymbols::kInfinitySymbol, value, valueLength); break; + case ParentName: + { + // ICU supports lang[-script][-region][-variant] so up to 4 parents including invariant locale + char localeNameTemp[ULOC_FULLNAME_CAPACITY]; + + uloc_getParent(locale.getName(), localeNameTemp, ULOC_FULLNAME_CAPACITY, &status); + if (U_SUCCESS(status)) + { + status = u_charsToUChars_safe(localeNameTemp, value, valueLength); + if (U_SUCCESS(status)) + { + FixupLocaleName(value, valueLength); + } + } + break; + } case PercentSymbol: status = GetLocaleInfoDecimalFormatSymbol(locale, DecimalFormatSymbols::kPercentSymbol, value, valueLength); break; @@ -232,11 +253,92 @@ extern "C" int32_t GetLocaleInfoString(const UChar* localeName, LocaleStringData break; default: status = U_UNSUPPORTED_ERROR; - assert(false); break; }; - assert(status != U_BUFFER_OVERFLOW_ERROR); + return UErrorCodeToBool(status); +} + +/* +Function: +NormalizeTimePattern + +Convert an ICU non-localized time pattern to .NET format +*/ +void NormalizeTimePattern(const UnicodeString *srcPattern, UnicodeString *destPattern) +{ + // An srcPattern example: "h:mm:ss a" + // A destPattern example: "h:mm:ss tt" + destPattern->remove(); + + bool amPmAdded = false; + for (int i = 0; i <= srcPattern->length() - 1; i++) + { + UChar ch = srcPattern->charAt(i); + switch (ch) + { + case ':': + case '.': + case 'H': + case 'h': + case 'm': + case 's': + destPattern->append(ch); + break; + + case UCHAR_SPACE: + case UCHAR_NBSPACE: + destPattern->append(UCHAR_SPACE); + break; + + case 'a': // AM/PM + if (!amPmAdded) + { + amPmAdded = true; + destPattern->append("tt"); + } + break; + } + } +} + +/* +PAL Function: +GetLocaleTimeFormat + +Obtains time format information. +Returns 1 for success, 0 otherwise +*/ +extern "C" int32_t GetLocaleTimeFormat(const UChar* localeName, int shortFormat, UChar* value, int32_t valueLength) +{ + Locale locale = GetLocale(localeName); + if (locale.isBogus()) + { + return UErrorCodeToBool(U_ILLEGAL_ARGUMENT_ERROR); + } + + DateFormat::EStyle style = (shortFormat != 0) ? DateFormat::kShort : DateFormat::kMedium; + LocalPointer<DateFormat> dateFormat(DateFormat::createTimeInstance(style, locale)); + if (dateFormat == NULL || !dateFormat.isValid()) + { + return UErrorCodeToBool(U_MEMORY_ALLOCATION_ERROR); + } + + // cast to SimpleDateFormat so we can call toPattern() + SimpleDateFormat* sdf = dynamic_cast<SimpleDateFormat*>(dateFormat.getAlias()); + if (sdf == NULL) + { + return UErrorCodeToBool(U_INTERNAL_PROGRAM_ERROR); + } + + UnicodeString icuPattern; + sdf->toPattern(icuPattern); + + UnicodeString dotnetPattern; + NormalizeTimePattern(&icuPattern, &dotnetPattern); + + UErrorCode status = U_ZERO_ERROR; + dotnetPattern.extract(value, valueLength, status); return UErrorCodeToBool(status); } |