diff --git a/src/libraries/Common/src/Interop/Interop.Locale.OSX.cs b/src/libraries/Common/src/Interop/Interop.Locale.OSX.cs index 52882346b63222..0f7b1763d58509 100644 --- a/src/libraries/Common/src/Interop/Interop.Locale.OSX.cs +++ b/src/libraries/Common/src/Interop/Interop.Locale.OSX.cs @@ -8,9 +8,21 @@ internal static partial class Interop internal static partial class Globalization { [LibraryImport(Libraries.GlobalizationNative, EntryPoint = "GlobalizationNative_GetLocaleNameNative", StringMarshalling = StringMarshalling.Utf8)] - internal static unsafe partial string GetLocaleNameNative(string localeName); + internal static partial string GetLocaleNameNative(string localeName); [LibraryImport(Libraries.GlobalizationNative, EntryPoint = "GlobalizationNative_GetLocaleInfoStringNative", StringMarshalling = StringMarshalling.Utf8)] - internal static unsafe partial string GetLocaleInfoStringNative(string localeName, uint localeStringData); + internal static partial string GetLocaleInfoStringNative(string localeName, uint localeStringData); + + [LibraryImport(Libraries.GlobalizationNative, EntryPoint = "GlobalizationNative_GetLocaleInfoIntNative", StringMarshalling = StringMarshalling.Utf8)] + internal static partial int GetLocaleInfoIntNative(string localeName, uint localeNumberData); + + [LibraryImport(Libraries.GlobalizationNative, EntryPoint = "GlobalizationNative_GetLocaleInfoPrimaryGroupingSizeNative", StringMarshalling = StringMarshalling.Utf8)] + internal static partial int GetLocaleInfoPrimaryGroupingSizeNative(string localeName, uint localeGroupingData); + + [LibraryImport(Libraries.GlobalizationNative, EntryPoint = "GlobalizationNative_GetLocaleInfoSecondaryGroupingSizeNative", StringMarshalling = StringMarshalling.Utf8)] + internal static partial int GetLocaleInfoSecondaryGroupingSizeNative(string localeName, uint localeGroupingData); + + [LibraryImport(Libraries.GlobalizationNative, EntryPoint = "GlobalizationNative_GetLocaleTimeFormatNative", StringMarshalling = StringMarshalling.Utf8)] + internal static partial string GetLocaleTimeFormatNative(string localeName, [MarshalAs(UnmanagedType.Bool)] bool shortFormat); } } diff --git a/src/libraries/System.Globalization/tests/Hybrid/Hybrid.IOS.Tests.csproj b/src/libraries/System.Globalization/tests/Hybrid/Hybrid.IOS.Tests.csproj index b2622d1a6afb95..b9887c1021aaf4 100644 --- a/src/libraries/System.Globalization/tests/Hybrid/Hybrid.IOS.Tests.csproj +++ b/src/libraries/System.Globalization/tests/Hybrid/Hybrid.IOS.Tests.csproj @@ -1,10 +1,36 @@ - $(NetCoreAppCurrent)-ios;$(NetCoreAppCurrent)-tvos;$(NetCoreAppCurrent)-maccatalyst;$(NetCoreAppCurrent)-osx + $(NetCoreAppCurrent)-ios;$(NetCoreAppCurrent)-tvos;$(NetCoreAppCurrent)-maccatalyst true true + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.OSX.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.OSX.cs index 870d8504fb9678..cdbdd65a81426f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.OSX.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.OSX.cs @@ -7,21 +7,23 @@ namespace System.Globalization { internal sealed partial class CultureData { + private const int LOC_FULLNAME_CAPACITY = 157; // max size of locale name + /// /// This method uses the sRealName field (which is initialized by the constructor before this is called) to /// initialize the rest of the state of CultureData based on the underlying OS globalization library. /// - private bool InitNativeCultureDataCore() + private bool InitAppleCultureDataCore() { Debug.Assert(_sRealName != null); Debug.Assert(!GlobalizationMode.Invariant); string realNameBuffer = _sRealName; - _sWindowsName = GetLocaleNameNative(realNameBuffer); + _sWindowsName = _sName = _sRealName = GetLocaleNameNative(realNameBuffer); return true; } - internal static unsafe string GetLocaleNameNative(string localeName) + internal static string GetLocaleNameNative(string localeName) { return Interop.Globalization.GetLocaleNameNative(localeName); } @@ -36,11 +38,111 @@ private string GetLocaleInfoNative(LocaleStringData type) // For LOCALE_SPARENT we need the option of using the "real" name (forcing neutral names) instead of the // "windows" name, which can be specific for downlevel (< windows 7) os's. - private static unsafe string GetLocaleInfoNative(string localeName, LocaleStringData type) + private static string GetLocaleInfoNative(string localeName, LocaleStringData type) { Debug.Assert(localeName != null, "[CultureData.GetLocaleInfoNative] Expected localeName to be not be null"); return Interop.Globalization.GetLocaleInfoStringNative(localeName, (uint)type); } + + private int GetLocaleInfoNative(LocaleNumberData type) + { + Debug.Assert(_sWindowsName != null, "[CultureData.GetLocaleInfoNative(LocaleNumberData)] Expected _sWindowsName to be populated already"); + + // returning 0 will cause the first supported calendar to be returned, which is the preferred calendar + if (type == LocaleNumberData.CalendarType) + return 0; + + int value = Interop.Globalization.GetLocaleInfoIntNative(_sWindowsName, (uint)type); + const int DEFAULT_VALUE = 0; + if (value < 0) + { + Debug.Fail("[CultureData.GetLocaleInfoNative(LocaleNumberData)] failed"); + return DEFAULT_VALUE; + } + + return value; + } + + private int[] GetLocaleInfoNative(LocaleGroupingData type) + { + Debug.Assert(_sWindowsName != null, "[CultureData.GetLocaleInfoNative(LocaleGroupingData)] Expected _sWindowsName to be populated already"); + + int primaryGroupingSize = Interop.Globalization.GetLocaleInfoPrimaryGroupingSizeNative(_sWindowsName, (uint)type); + int secondaryGroupingSize = Interop.Globalization.GetLocaleInfoSecondaryGroupingSizeNative(_sWindowsName, (uint)type); + + if (secondaryGroupingSize == 0) + { + return new int[] { primaryGroupingSize }; + } + + return new int[] { primaryGroupingSize, secondaryGroupingSize }; + } + + private string GetTimeFormatStringNative() => GetTimeFormatStringNative(shortFormat: false); + + private string GetTimeFormatStringNative(bool shortFormat) + { + Debug.Assert(_sWindowsName != null, "[CultureData.GetTimeFormatStringNative(bool shortFormat)] Expected _sWindowsName to be populated already"); + + string result = Interop.Globalization.GetLocaleTimeFormatNative(_sWindowsName, shortFormat); + + return ConvertNativeTimeFormatString(result); + } + + private static string ConvertNativeTimeFormatString(string nativeFormatString) + { + Span result = stackalloc char[LOC_FULLNAME_CAPACITY]; + + bool amPmAdded = false; + int resultPos = 0; + + for (int i = 0; i < nativeFormatString.Length; i++) + { + switch (nativeFormatString[i]) + { + case '\'': + result[resultPos++] = nativeFormatString[i++]; + while (i < nativeFormatString.Length) + { + char current = nativeFormatString[i]; + result[resultPos++] = current; + if (current == '\'') + { + break; + } + i++; + } + break; + + case ':': + case '.': + case 'H': + case 'h': + case 'm': + case 's': + result[resultPos++] = nativeFormatString[i]; + break; + + case ' ': + case '\u00A0': + // Convert nonbreaking spaces into regular spaces + result[resultPos++] = ' '; + break; + + case 'a': // AM/PM + if (!amPmAdded) + { + amPmAdded = true; + result[resultPos++] = 't'; + result[resultPos++] = 't'; + } + break; + + } + } + + return result.Slice(0, resultPos).ToString(); + } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.Unix.cs index 2a253d5367be30..76c009beb6466f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.Unix.cs @@ -7,7 +7,12 @@ namespace System.Globalization { internal sealed partial class CultureData { - private bool InitCultureDataCore() => InitIcuCultureDataCore(); + private bool InitCultureDataCore() => +#if TARGET_OSX || TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS + GlobalizationMode.Hybrid ? InitAppleCultureDataCore() : InitIcuCultureDataCore(); +#else + InitIcuCultureDataCore(); +#endif // Unix doesn't support user overrides partial void InitUserOverride(bool useUserOverride); @@ -20,7 +25,11 @@ internal sealed partial class CultureData private string[]? GetTimeFormatsCore(bool shortFormat) { +#if TARGET_OSX || TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS + string format = GlobalizationMode.Hybrid ? GetTimeFormatStringNative(shortFormat) : IcuGetTimeFormatString(shortFormat); +#else string format = IcuGetTimeFormatString(shortFormat); +#endif return new string[] { format }; } diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.cs index 92037366b63891..12ea4ded8beedd 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.cs @@ -1541,7 +1541,11 @@ internal int FirstDayOfWeek { if (_iFirstDayOfWeek == undef && !GlobalizationMode.Invariant) { +#if TARGET_OSX || TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS + _iFirstDayOfWeek = GlobalizationMode.Hybrid ? GetLocaleInfoNative(LocaleNumberData.FirstDayOfWeek) : IcuGetLocaleInfo(LocaleNumberData.FirstDayOfWeek); +#else _iFirstDayOfWeek = ShouldUseUserOverrideNlsData ? NlsGetFirstDayOfWeek() : IcuGetLocaleInfo(LocaleNumberData.FirstDayOfWeek); +#endif } return _iFirstDayOfWeek; } @@ -1949,7 +1953,11 @@ internal string TimeSeparator } else { +#if TARGET_OSX || TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS + string? longTimeFormat = GlobalizationMode.Hybrid ? GetTimeFormatStringNative() : IcuGetTimeFormatString(); +#else string? longTimeFormat = ShouldUseUserOverrideNlsData ? NlsGetTimeFormatString() : IcuGetTimeFormatString(); +#endif if (string.IsNullOrEmpty(longTimeFormat)) { longTimeFormat = LongTimes[0]; @@ -2285,8 +2293,11 @@ private int GetLocaleInfoCore(LocaleNumberData type) // This is never reached but helps illinker statically remove dependencies if (GlobalizationMode.Invariant) return 0; - +#if TARGET_OSX || TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS + return GlobalizationMode.Hybrid ? GetLocaleInfoNative(type) : IcuGetLocaleInfo(type); +#else return GlobalizationMode.UseNls ? NlsGetLocaleInfo(type) : IcuGetLocaleInfo(type); +#endif } private int GetLocaleInfoCoreUserOverride(LocaleNumberData type) @@ -2294,8 +2305,11 @@ private int GetLocaleInfoCoreUserOverride(LocaleNumberData type) // This is never reached but helps illinker statically remove dependencies if (GlobalizationMode.Invariant) return 0; - +#if TARGET_OSX || TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS + return GlobalizationMode.Hybrid ? GetLocaleInfoNative(type) : IcuGetLocaleInfo(type); +#else return ShouldUseUserOverrideNlsData ? NlsGetLocaleInfo(type) : IcuGetLocaleInfo(type); +#endif } private string GetLocaleInfoCoreUserOverride(LocaleStringData type) @@ -2343,7 +2357,11 @@ private int[] GetLocaleInfoCoreUserOverride(LocaleGroupingData type) if (GlobalizationMode.Invariant) return null!; +#if TARGET_OSX || TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS + return GlobalizationMode.Hybrid ? GetLocaleInfoNative(type) : IcuGetLocaleInfo(type); +#else return ShouldUseUserOverrideNlsData ? NlsGetLocaleInfo(type) : IcuGetLocaleInfo(type); +#endif } /// diff --git a/src/native/libs/System.Globalization.Native/entrypoints.c b/src/native/libs/System.Globalization.Native/entrypoints.c index 10254d6256db4a..c3c65b2196cde9 100644 --- a/src/native/libs/System.Globalization.Native/entrypoints.c +++ b/src/native/libs/System.Globalization.Native/entrypoints.c @@ -61,6 +61,10 @@ static const Entry s_globalizationNative[] = #ifdef __APPLE__ DllImportEntry(GlobalizationNative_GetLocaleNameNative) DllImportEntry(GlobalizationNative_GetLocaleInfoStringNative) + DllImportEntry(GlobalizationNative_GetLocaleInfoIntNative) + DllImportEntry(GlobalizationNative_GetLocaleInfoPrimaryGroupingSizeNative) + DllImportEntry(GlobalizationNative_GetLocaleInfoSecondaryGroupingSizeNative) + DllImportEntry(GlobalizationNative_GetLocaleTimeFormatNative) #endif }; diff --git a/src/native/libs/System.Globalization.Native/pal_locale.c b/src/native/libs/System.Globalization.Native/pal_locale.c index eada9f12ddd39f..ccae3e0c6d989f 100644 --- a/src/native/libs/System.Globalization.Native/pal_locale.c +++ b/src/native/libs/System.Globalization.Native/pal_locale.c @@ -277,3 +277,25 @@ int32_t GlobalizationNative_IsPredefinedLocale(const UChar* localeName) return err == U_ZERO_ERROR; } + +/* +PAL Function: +GetLocaleTimeFormat + +Obtains time format information (in ICU format, it needs to be converted to .NET's format). +Returns 1 for success, 0 otherwise +*/ +int32_t GlobalizationNative_GetLocaleTimeFormat(const UChar* localeName, + int shortFormat, + UChar* value, + int32_t valueLength) +{ + UErrorCode err = U_ZERO_ERROR; + char locale[ULOC_FULLNAME_CAPACITY]; + GetLocale(localeName, locale, ULOC_FULLNAME_CAPACITY, false, &err); + UDateFormatStyle style = (shortFormat != 0) ? UDAT_SHORT : UDAT_MEDIUM; + UDateFormat* pFormat = udat_open(style, UDAT_NONE, locale, NULL, 0, NULL, 0, &err); + udat_toPattern(pFormat, false, value, valueLength, &err); + udat_close(pFormat); + return UErrorCodeToBool(err); +} diff --git a/src/native/libs/System.Globalization.Native/pal_locale.h b/src/native/libs/System.Globalization.Native/pal_locale.h index 7ee6d1e7ee56e1..7fe89f667f2132 100644 --- a/src/native/libs/System.Globalization.Native/pal_locale.h +++ b/src/native/libs/System.Globalization.Native/pal_locale.h @@ -9,10 +9,16 @@ PALEXPORT int32_t GlobalizationNative_GetLocales(UChar *value, int32_t valueLeng PALEXPORT int32_t GlobalizationNative_GetLocaleName(const UChar* localeName, UChar* value, int32_t valueLength); -#ifdef __APPLE__ -PALEXPORT const char* GlobalizationNative_GetLocaleNameNative(const char* localeName); -#endif - PALEXPORT int32_t GlobalizationNative_GetDefaultLocaleName(UChar* value, int32_t valueLength); PALEXPORT int32_t GlobalizationNative_IsPredefinedLocale(const UChar* localeName); + +PALEXPORT int32_t GlobalizationNative_GetLocaleTimeFormat(const UChar* localeName, + int shortFormat, UChar* value, + int32_t valueLength); + +#ifdef __APPLE__ +PALEXPORT const char* GlobalizationNative_GetLocaleNameNative(const char* localeName); + +PALEXPORT const char* GlobalizationNative_GetLocaleTimeFormatNative(const char* localeName, int shortFormat); +#endif diff --git a/src/native/libs/System.Globalization.Native/pal_locale.m b/src/native/libs/System.Globalization.Native/pal_locale.m index 7400c48b1c9ba6..66b951db76125c 100644 --- a/src/native/libs/System.Globalization.Native/pal_locale.m +++ b/src/native/libs/System.Globalization.Native/pal_locale.m @@ -4,6 +4,7 @@ #include #include "pal_locale_internal.h" #include "pal_localeStringData.h" +#include "pal_localeNumberData.h" #import #import @@ -34,10 +35,10 @@ const char* GlobalizationNative_GetLocaleNameNative(const char* localeName) { - NSString *locName = [NSString stringWithFormat:@"%s", localeName]; - NSLocale *currentLocale = [[NSLocale alloc] initWithLocaleIdentifier:locName]; - const char* value = [currentLocale.localeIdentifier UTF8String]; - return strdup(value); + NSString *locName = [NSString stringWithFormat:@"%s", localeName]; + NSLocale *currentLocale = [[NSLocale alloc] initWithLocaleIdentifier:locName]; + const char* value = [currentLocale.localeIdentifier UTF8String]; + return strdup(value); } const char* GlobalizationNative_GetLocaleInfoStringNative(const char* localeName, LocaleStringData localeStringData) @@ -51,7 +52,6 @@ [dateFormatter setLocale:currentLocale]; NSLocale *gbLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_GB"]; - switch (localeStringData) { ///// localized name of locale, eg "German (Germany)" in UI language (corresponds to LOCALE_SLOCALIZEDDISPLAYNAME) @@ -148,9 +148,426 @@ value = ""; break; } - return strdup(value); } + +// invariant character definitions +#define CHAR_CURRENCY ((char)0x00A4) // international currency +#define CHAR_SPACE ((char)0x0020) // space +#define CHAR_NBSPACE ((char)0x00A0) // no-break space +#define CHAR_DIGIT ((char)0x0023) // '#' +#define CHAR_MINUS ((char)0x002D) // '-' +#define CHAR_PERCENT ((char)0x0025) // '%' +#define CHAR_OPENPAREN ((char)0x0028) // '(' +#define CHAR_CLOSEPAREN ((char)0x0029) // ')' +#define CHAR_ZERO ((char)0x0030) // '0' + +/* +Function: +NormalizeNumericPattern + +Returns a numeric string pattern in a format that we can match against the +appropriate managed pattern. Examples: +For PositiveMonetaryNumberFormat "ยค#,##0.00" becomes "Cn" +For NegativeNumberFormat "#,##0.00;(#,##0.00)" becomes "(n)" +*/ +static char* NormalizeNumericPattern(const char* srcPattern, int isNegative) +{ + int iStart = 0; + int iEnd = strlen(srcPattern); + + // ';' separates positive and negative subpatterns. + // When there is no explicit negative subpattern, + // an implicit negative subpattern is formed from the positive pattern with a prefixed '-'. + char * ptrNegativePattern = strrchr(srcPattern,';'); + if (ptrNegativePattern) + { + int32_t iNegativePatternStart = ptrNegativePattern - srcPattern; + if (isNegative) + { + iStart = iNegativePatternStart + 1; + } + else + { + iEnd = iNegativePatternStart - 1; + } + } + + int minusAdded = false; + + for (int i = iStart; i <= iEnd; i++) + { + switch (srcPattern[i]) + { + case CHAR_MINUS: + case CHAR_OPENPAREN: + case CHAR_CLOSEPAREN: + minusAdded = true; + break; + } + + if (minusAdded) + break; + } + + // international currency symbol (CHAR_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 + char* destPattern; + int index = 0; + + // if there is no negative subpattern, prefix the minus sign + if (isNegative && !minusAdded) + { + int length = (iEnd - iStart) + 2; + destPattern = (char*)calloc((size_t)length, sizeof(char)); + if (!destPattern) + { + return NULL; + } + destPattern[index++] = '-'; + } + else + { + int length = (iEnd - iStart) + 1; + destPattern = (char*)calloc((size_t)length, sizeof(char)); + if (!destPattern) + { + return NULL; + } + } + + int digitAdded = false; + int currencyAdded = false; + int spaceAdded = false; + + for (int i = iStart; i <= iEnd; i++) + { + char ch = srcPattern[i]; + switch (ch) + { + case CHAR_DIGIT: + case CHAR_ZERO: + if (!digitAdded) + { + digitAdded = true; + destPattern[index++] = 'n'; + } + break; + + case CHAR_CURRENCY: + if (!currencyAdded) + { + currencyAdded = true; + destPattern[index++] = 'C'; + } + break; + + case CHAR_SPACE: + case CHAR_NBSPACE: + if (!spaceAdded) + { + spaceAdded = true; + destPattern[index++] = ' '; + } + break; + + case CHAR_MINUS: + case CHAR_OPENPAREN: + case CHAR_CLOSEPAREN: + case CHAR_PERCENT: + destPattern[index++] = ch; + break; + } + } + + const int MAX_DOTNET_NUMERIC_PATTERN_LENGTH = 6; // example: "(C n)" plus terminator + + if (destPattern[0] == '\0' || strlen (destPattern) >= MAX_DOTNET_NUMERIC_PATTERN_LENGTH) + { + free (destPattern); + return NULL; + } + + return destPattern; +} + +/* +Function: +GetNumericPattern + +Determines the pattern from the decimalFormat and returns the matching pattern's +index from patterns[]. +Returns index -1 if no pattern is found. +*/ +static int GetPatternIndex(char* normalizedPattern,const char* patterns[], int patternsCount) +{ + const int INVALID_FORMAT = -1; + + if (!normalizedPattern) + { + return INVALID_FORMAT; + } + + for (int i = 0; i < patternsCount; i++) + { + if (strcmp(normalizedPattern, patterns[i]) == 0) + { + free(normalizedPattern); + return i; + } + } + + assert(false); // should have found a valid pattern + + free(normalizedPattern); + return INVALID_FORMAT; +} + +static int32_t GetValueForNumberFormat(NSLocale *currentLocale, LocaleNumberData localeNumberData) +{ + NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init]; + numberFormatter.locale = currentLocale; + const char *pFormat; + int32_t value; + + switch(localeNumberData) + { + case LocaleNumber_PositiveMonetaryNumberFormat: + { + numberFormatter.numberStyle = NSNumberFormatterCurrencyStyle; + static const char* Patterns[] = {"Cn", "nC", "C n", "n C"}; + pFormat = [[numberFormatter positiveFormat] UTF8String]; + char* normalizedPattern = NormalizeNumericPattern(pFormat, false); + value = GetPatternIndex(normalizedPattern, Patterns, sizeof(Patterns)/sizeof(Patterns[0])); + break; + } + case LocaleNumber_NegativeMonetaryNumberFormat: + { + numberFormatter.numberStyle = NSNumberFormatterCurrencyStyle; + 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)", "C- n" }; + pFormat = [[numberFormatter negativeFormat] UTF8String]; + char* normalizedPattern = NormalizeNumericPattern(pFormat, true); + value = GetPatternIndex(normalizedPattern, Patterns, sizeof(Patterns)/sizeof(Patterns[0])); + break; + } + case LocaleNumber_NegativeNumberFormat: + { + numberFormatter.numberStyle = NSNumberFormatterDecimalStyle; + static const char* Patterns[] = {"(n)", "-n", "- n", "n-", "n -"}; + pFormat = [[numberFormatter negativeFormat] UTF8String]; + char* normalizedPattern = NormalizeNumericPattern(pFormat, true); + value = GetPatternIndex(normalizedPattern, Patterns, sizeof(Patterns)/sizeof(Patterns[0])); + break; + } + case LocaleNumber_NegativePercentFormat: + { + numberFormatter.numberStyle = NSNumberFormatterPercentStyle; + static const char* Patterns[] = {"-n %", "-n%", "-%n", "%-n", "%n-", "n-%", "n%-", "-% n", "n %-", "% n-", "% -n", "n- %"}; + pFormat = [[numberFormatter negativeFormat] UTF8String]; + char* normalizedPattern = NormalizeNumericPattern(pFormat, true); + value = GetPatternIndex(normalizedPattern, Patterns, sizeof(Patterns)/sizeof(Patterns[0])); + break; + } + case LocaleNumber_PositivePercentFormat: + { + numberFormatter.numberStyle = NSNumberFormatterPercentStyle; + static const char* Patterns[] = {"n %", "n%", "%n", "% n"}; + pFormat = [[numberFormatter positiveFormat] UTF8String]; + char* normalizedPattern = NormalizeNumericPattern(pFormat, false); + value = GetPatternIndex(normalizedPattern, Patterns, sizeof(Patterns)/sizeof(Patterns[0])); + break; + } + default: + return -1; + } + + return value; +} + +int32_t GlobalizationNative_GetLocaleInfoIntNative(const char* localeName, LocaleNumberData localeNumberData) +{ + bool isSuccess = true; + int32_t value; + NSString *locName = [NSString stringWithFormat:@"%s", localeName]; + NSLocale *currentLocale = [[NSLocale alloc] initWithLocaleIdentifier:locName]; + + switch (localeNumberData) + { + case LocaleNumber_MeasurementSystem: + { + const char *measurementSystem = [[currentLocale objectForKey:NSLocaleMeasurementSystem] UTF8String]; + NSLocale *usLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"]; + const char *us_measurementSystem = [[usLocale objectForKey:NSLocaleMeasurementSystem] UTF8String]; + value = (measurementSystem == us_measurementSystem) ? 1 : 0; + break; + } + case LocaleNumber_FractionalDigitsCount: + { + NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init]; + numberFormatter.locale = currentLocale; + numberFormatter.numberStyle = NSNumberFormatterDecimalStyle; + value = (int32_t)numberFormatter.maximumFractionDigits; + break; + } + case LocaleNumber_MonetaryFractionalDigitsCount: + { + NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init]; + numberFormatter.locale = currentLocale; + numberFormatter.numberStyle = NSNumberFormatterCurrencyStyle; + value = (int32_t)numberFormatter.maximumFractionDigits; + break; + } + case LocaleNumber_PositiveMonetaryNumberFormat: + case LocaleNumber_NegativeMonetaryNumberFormat: + case LocaleNumber_NegativeNumberFormat: + case LocaleNumber_NegativePercentFormat: + case LocaleNumber_PositivePercentFormat: + { + value = GetValueForNumberFormat(currentLocale, localeNumberData); + if (value < 0) + { + isSuccess = false; + } + break; + } + case LocaleNumber_FirstWeekOfYear: + { + NSCalendar *calendar = [currentLocale objectForKey:NSLocaleCalendar]; + int minDaysInWeek = (int32_t)[calendar minimumDaysInFirstWeek]; + if (minDaysInWeek == 1) + { + value = WeekRule_FirstDay; + } + else if (minDaysInWeek == 7) + { + value = WeekRule_FirstFullWeek; + } + else if (minDaysInWeek >= 4) + { + value = WeekRule_FirstFourDayWeek; + } + else + { + value = -1; + isSuccess = false; + } + break; + } + case LocaleNumber_ReadingLayout: + { + NSLocaleLanguageDirection langDir = [NSLocale characterDirectionForLanguage:[[NSLocale currentLocale] objectForKey:NSLocaleLanguageCode]]; + // 0 - Left to right (such as en-US) + // 1 - Right to left (such as arabic locales) + value = NSLocaleLanguageDirectionRightToLeft == langDir ? 1 : 0; + break; + } + case LocaleNumber_FirstDayofWeek: + { + NSCalendar *calendar = [currentLocale objectForKey:NSLocaleCalendar]; + value = [calendar firstWeekday] - 1; // .NET is 0-based and in Apple is 1-based; + break; + } + default: + value = -1; + isSuccess = false; + break; + } + + assert(isSuccess); + + return value; +} + +/* +PAL Function: +GlobalizationNative_GetLocaleInfoPrimaryGroupingSizeNative + +Returns primary grouping size for decimal and currency +*/ +int32_t GlobalizationNative_GetLocaleInfoPrimaryGroupingSizeNative(const char* localeName, LocaleNumberData localeGroupingData) +{ + bool isSuccess = true; + NSString *locName = [NSString stringWithFormat:@"%s", localeName]; + NSLocale *currentLocale = [[NSLocale alloc] initWithLocaleIdentifier:locName]; + NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init]; + numberFormatter.locale = currentLocale; + + switch (localeGroupingData) + { + case LocaleNumber_Digit: + numberFormatter.numberStyle = NSNumberFormatterDecimalStyle; + break; + case LocaleNumber_Monetary: + numberFormatter.numberStyle = NSNumberFormatterCurrencyStyle; + break; + default: + isSuccess = false; + assert(isSuccess); + break; + } + return [numberFormatter groupingSize]; +} + +/* +PAL Function: +GlobalizationNative_GetLocaleInfoSecondaryGroupingSizeNative + +Returns secondary grouping size for decimal and currency +*/ +int32_t GlobalizationNative_GetLocaleInfoSecondaryGroupingSizeNative(const char* localeName, LocaleNumberData localeGroupingData) +{ + bool isSuccess = true; + NSString *locName = [NSString stringWithFormat:@"%s", localeName]; + NSLocale *currentLocale = [[NSLocale alloc] initWithLocaleIdentifier:locName]; + NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init]; + numberFormatter.locale = currentLocale; + + switch (localeGroupingData) + { + case LocaleNumber_Digit: + numberFormatter.numberStyle = NSNumberFormatterDecimalStyle; + break; + case LocaleNumber_Monetary: + numberFormatter.numberStyle = NSNumberFormatterCurrencyStyle; + break; + default: + isSuccess = false; + assert(isSuccess); + break; + } + + return [numberFormatter secondaryGroupingSize]; +} + +/* +PAL Function: +GlobalizationNative_GetLocaleTimeFormatNative + +Returns time format information (in native format, it needs to be converted to .NET's format). +*/ +const char* GlobalizationNative_GetLocaleTimeFormatNative(const char* localeName, int shortFormat) +{ + NSString *locName = [NSString stringWithFormat:@"%s", localeName]; + NSLocale *currentLocale = [[NSLocale alloc] initWithLocaleIdentifier:locName]; + NSDateFormatter* dateFormatter = [[NSDateFormatter alloc] init]; + [dateFormatter setLocale:currentLocale]; + + if (shortFormat != 0) + { + [dateFormatter setTimeStyle:NSDateFormatterShortStyle]; + } + else + { + [dateFormatter setTimeStyle:NSDateFormatterMediumStyle]; + } + + return strdup([[dateFormatter dateFormat] UTF8String]); +} + #endif #if defined(TARGET_MACCATALYST) || defined(TARGET_IOS) || defined(TARGET_TVOS) diff --git a/src/native/libs/System.Globalization.Native/pal_localeNumberData.h b/src/native/libs/System.Globalization.Native/pal_localeNumberData.h index db5c401b8b7b60..a68f1e32e334ab 100644 --- a/src/native/libs/System.Globalization.Native/pal_localeNumberData.h +++ b/src/native/libs/System.Globalization.Native/pal_localeNumberData.h @@ -43,3 +43,14 @@ PALEXPORT int32_t GlobalizationNative_GetLocaleInfoGroupingSizes(const UChar* lo LocaleNumberData localeGroupingData, int32_t* primaryGroupSize, int32_t* secondaryGroupSize); + +#ifdef __APPLE__ +PALEXPORT int32_t GlobalizationNative_GetLocaleInfoIntNative(const char* localeName, + LocaleNumberData localeNumberData); + +PALEXPORT int32_t GlobalizationNative_GetLocaleInfoPrimaryGroupingSizeNative(const char* localeName, + LocaleNumberData localeGroupingData); + +PALEXPORT int32_t GlobalizationNative_GetLocaleInfoSecondaryGroupingSizeNative(const char* localeName, + LocaleNumberData localeGroupingData); +#endif diff --git a/src/native/libs/System.Globalization.Native/pal_localeStringData.c b/src/native/libs/System.Globalization.Native/pal_localeStringData.c index 9120b89bd329bd..488cbc8cb35f2a 100644 --- a/src/native/libs/System.Globalization.Native/pal_localeStringData.c +++ b/src/native/libs/System.Globalization.Native/pal_localeStringData.c @@ -401,24 +401,3 @@ int32_t GlobalizationNative_GetLocaleInfoString(const UChar* localeName, return UErrorCodeToBool(status); } -/* -PAL Function: -GetLocaleTimeFormat - -Obtains time format information (in ICU format, it needs to be converted to .NET's format). -Returns 1 for success, 0 otherwise -*/ -int32_t GlobalizationNative_GetLocaleTimeFormat(const UChar* localeName, - int shortFormat, - UChar* value, - int32_t valueLength) -{ - UErrorCode err = U_ZERO_ERROR; - char locale[ULOC_FULLNAME_CAPACITY]; - GetLocale(localeName, locale, ULOC_FULLNAME_CAPACITY, false, &err); - UDateFormatStyle style = (shortFormat != 0) ? UDAT_SHORT : UDAT_MEDIUM; - UDateFormat* pFormat = udat_open(style, UDAT_NONE, locale, NULL, 0, NULL, 0, &err); - udat_toPattern(pFormat, false, value, valueLength, &err); - udat_close(pFormat); - return UErrorCodeToBool(err); -} diff --git a/src/native/libs/System.Globalization.Native/pal_localeStringData.h b/src/native/libs/System.Globalization.Native/pal_localeStringData.h index e6030d8a4cf86e..a6961c39761bfd 100644 --- a/src/native/libs/System.Globalization.Native/pal_localeStringData.h +++ b/src/native/libs/System.Globalization.Native/pal_localeStringData.h @@ -52,9 +52,5 @@ PALEXPORT int32_t GlobalizationNative_GetLocaleInfoString(const UChar* localeNam #ifdef __APPLE__ PALEXPORT const char* GlobalizationNative_GetLocaleInfoStringNative(const char* localeName, LocaleStringData localeStringData); -#endif - -PALEXPORT int32_t GlobalizationNative_GetLocaleTimeFormat(const UChar* localeName, - int shortFormat, UChar* value, - int32_t valueLength); +#endif diff --git a/src/native/libs/System.Globalization.Native/pal_locale_internal.h b/src/native/libs/System.Globalization.Native/pal_locale_internal.h index 1f9edb7640cbfb..c754554bbfdd55 100644 --- a/src/native/libs/System.Globalization.Native/pal_locale_internal.h +++ b/src/native/libs/System.Globalization.Native/pal_locale_internal.h @@ -4,7 +4,6 @@ #pragma once #include "pal_icushim_internal.h" -#include "pal_localeStringData.h" /* Function: @@ -61,21 +60,4 @@ DetectDefaultSystemLocaleName Detects the default locale string for Apple platforms */ char* DetectDefaultAppleLocaleName(void); - -/* -Function: -GlobalizationNative_GetLocaleNameNative - -Returns native locale name for Apple platforms -*/ -const char* GlobalizationNative_GetLocaleNameNative(const char* localeName); - -/* -Function: -GlobalizationNative_GetLocaleInfoStringNative - -Returns string locale information if found for the specified locale name for Apple platforms. -Returns empty string if not found. -*/ -const char* GlobalizationNative_GetLocaleInfoStringNative(const char* localeName, LocaleStringData localeStringData); #endif