diff --git a/src/coreclr/src/System.Private.CoreLib/Resources/Strings.resx b/src/coreclr/src/System.Private.CoreLib/Resources/Strings.resx index 37b49a1cd9983..add5d7aa933ff 100644 --- a/src/coreclr/src/System.Private.CoreLib/Resources/Strings.resx +++ b/src/coreclr/src/System.Private.CoreLib/Resources/Strings.resx @@ -1129,6 +1129,9 @@ Culture name '{0}' is not supported. + + Culture name '{0}' is not a predefined culture. + Invalid DateTimeKind value. diff --git a/src/libraries/Common/src/Interop/Unix/System.Globalization.Native/Interop.Locale.cs b/src/libraries/Common/src/Interop/Unix/System.Globalization.Native/Interop.Locale.cs index b563752bc00d2..d9a300fd156dc 100644 --- a/src/libraries/Common/src/Interop/Unix/System.Globalization.Native/Interop.Locale.cs +++ b/src/libraries/Common/src/Interop/Unix/System.Globalization.Native/Interop.Locale.cs @@ -21,6 +21,10 @@ internal static partial class Globalization [return: MarshalAs(UnmanagedType.Bool)] internal static extern unsafe bool GetDefaultLocaleName(char* value, int valueLength); + [DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_IsPredefinedLocale")] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern unsafe bool IsPredefinedLocale(string localeName); + [DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_GetLocaleTimeFormat")] [return: MarshalAs(UnmanagedType.Bool)] internal static extern unsafe bool GetLocaleTimeFormat(string localeName, bool shortFormat, char* value, int valueLength); diff --git a/src/libraries/Native/Unix/System.Globalization.Native/pal_locale.c b/src/libraries/Native/Unix/System.Globalization.Native/pal_locale.c index 9508f97c8bd62..ffbeef3d15f12 100644 --- a/src/libraries/Native/Unix/System.Globalization.Native/pal_locale.c +++ b/src/libraries/Native/Unix/System.Globalization.Native/pal_locale.c @@ -233,3 +233,24 @@ int32_t GlobalizationNative_GetDefaultLocaleName(UChar* value, int32_t valueLeng return UErrorCodeToBool(status); } + +// GlobalizationNative_IsPredefinedLocale returns TRUE if ICU has a real data for the locale. +// Otherwise it returns FALSE; + +int32_t GlobalizationNative_IsPredefinedLocale(const UChar* localeName) +{ + UErrorCode err = U_ZERO_ERROR; + char locale[ULOC_FULLNAME_CAPACITY]; + GetLocale(localeName, locale, ULOC_FULLNAME_CAPACITY, FALSE, &err); + + if (U_FAILURE(err)) + return FALSE; + + // ures_open returns err = U_ZERO_ERROR when ICU has data for localeName. + // If it is fake locale, it will return err = U_USING_FALLBACK_WARNING || err = U_USING_DEFAULT_WARNING. + // Other err values would be just a failure. + UResourceBundle* uresb = ures_open(NULL, locale, &err); + ures_close(uresb); + + return err == U_ZERO_ERROR; +} diff --git a/src/libraries/Native/Unix/System.Globalization.Native/pal_locale.h b/src/libraries/Native/Unix/System.Globalization.Native/pal_locale.h index 7d3992d34ba92..f771954d65f64 100644 --- a/src/libraries/Native/Unix/System.Globalization.Native/pal_locale.h +++ b/src/libraries/Native/Unix/System.Globalization.Native/pal_locale.h @@ -56,3 +56,5 @@ DLLEXPORT int32_t GlobalizationNative_GetLocales(UChar *value, int32_t valueLeng DLLEXPORT int32_t GlobalizationNative_GetLocaleName(const UChar* localeName, UChar* value, int32_t valueLength); DLLEXPORT int32_t GlobalizationNative_GetDefaultLocaleName(UChar* value, int32_t valueLength); + +DLLEXPORT int32_t GlobalizationNative_IsPredefinedLocale(const UChar* localeName); diff --git a/src/libraries/System.Globalization/tests/CultureInfo/GetCultureInfo.cs b/src/libraries/System.Globalization/tests/CultureInfo/GetCultureInfo.cs new file mode 100644 index 0000000000000..985e03f7bab18 --- /dev/null +++ b/src/libraries/System.Globalization/tests/CultureInfo/GetCultureInfo.cs @@ -0,0 +1,47 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using Xunit; + +namespace System.Globalization.Tests +{ + public class GetCultureInfoTests + { + public static bool PlatformSupportsFakeCulture => !PlatformDetection.IsWindows || (PlatformDetection.WindowsVersion >= 10 && !PlatformDetection.IsFullFramework); + + [ConditionalTheory(nameof(PlatformSupportsFakeCulture))] + [InlineData("en")] + [InlineData("en-US")] + [InlineData("ja-JP")] + [InlineData("ar-SA")] + [InlineData("xx-XX")] + public void GetCultureInfo(string name) + { + Assert.Equal(name, CultureInfo.GetCultureInfo(name).Name); + Assert.Equal(name, CultureInfo.GetCultureInfo(name, predefinedOnly: false).Name); + } + + [ConditionalTheory(nameof(PlatformSupportsFakeCulture))] + [InlineData("en@US")] + [InlineData("\uFFFF")] + public void TestInvalidCultureNames(string name) + { + Assert.Throws(() => CultureInfo.GetCultureInfo(name)); + Assert.Throws(() => CultureInfo.GetCultureInfo(name, predefinedOnly: false)); + Assert.Throws(() => CultureInfo.GetCultureInfo(name, predefinedOnly: true)); + } + + [ConditionalTheory(nameof(PlatformSupportsFakeCulture))] + [InlineData("xx")] + [InlineData("xx-XX")] + [InlineData("xx-YY")] + public void TestFakeCultureNames(string name) + { + Assert.Equal(name, CultureInfo.GetCultureInfo(name).Name); + Assert.Equal(name, CultureInfo.GetCultureInfo(name, predefinedOnly: false).Name); + Assert.Throws(() => CultureInfo.GetCultureInfo(name, predefinedOnly: true)); + } + } +} diff --git a/src/libraries/System.Globalization/tests/System.Globalization.Tests.csproj b/src/libraries/System.Globalization/tests/System.Globalization.Tests.csproj index d5dcfcdcffeb9..888ebb272e303 100644 --- a/src/libraries/System.Globalization/tests/System.Globalization.Tests.csproj +++ b/src/libraries/System.Globalization/tests/System.Globalization.Tests.csproj @@ -52,6 +52,7 @@ + diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureInfo.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureInfo.Unix.cs index 2a16ab6111f04..da4c00af2f487 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureInfo.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureInfo.Unix.cs @@ -28,6 +28,16 @@ internal static CultureInfo GetUserDefaultCulture() return cultureInfo; } + private static CultureInfo GetPredefinedCultureInfo(string name) + { + if (!Interop.Globalization.IsPredefinedLocale(name)) + { + throw new CultureNotFoundException(nameof(name), SR.Format(SR.Argument_InvalidPredefinedCultureName, name)); + } + + return GetCultureInfo(name); + } + private static CultureInfo GetUserDefaultUICulture() { return InitializeUserDefaultCulture(); diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureInfo.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureInfo.Windows.cs index 1a8e8e81cb504..dd4f34f58c2c2 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureInfo.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureInfo.Windows.cs @@ -26,6 +26,24 @@ internal static CultureInfo GetUserDefaultCulture() return GetCultureByName(strDefault); } + private static CultureInfo GetPredefinedCultureInfo(string name) + { + CultureInfo culture = GetCultureInfo(name); + string englishName = culture.EnglishName; + + // Check if the English Name starts with "Unknown Locale" or "Unknown Language" terms. + const int SecondTermIndex = 8; + + if (englishName.StartsWith("Unknown ", StringComparison.Ordinal) && englishName.Length > SecondTermIndex && + (englishName.IndexOf("Locale", SecondTermIndex, StringComparison.Ordinal) == SecondTermIndex || + englishName.IndexOf("Language", SecondTermIndex, StringComparison.Ordinal) == SecondTermIndex)) + { + throw new CultureNotFoundException(nameof(name), SR.Format(SR.Argument_InvalidPredefinedCultureName, name)); + } + + return culture; + } + private static unsafe CultureInfo GetUserDefaultUICulture() { if (GlobalizationMode.Invariant) diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureInfo.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureInfo.cs index bc276e2bae052..e853316861459 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureInfo.cs @@ -1114,6 +1114,21 @@ public static CultureInfo GetCultureInfo(string name, string altName) return result; } + public static CultureInfo GetCultureInfo(string name, bool predefinedOnly) + { + if (name is null) + { + throw new ArgumentNullException(nameof(name)); + } + + if (predefinedOnly) + { + return GetPredefinedCultureInfo(name); + } + + return GetCultureInfo(name); + } + private static Dictionary CachedCulturesByName { get diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 302b853906dab..457b513d0e788 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -4432,6 +4432,7 @@ public void ClearCachedData() { } public System.Globalization.CultureInfo GetConsoleFallbackUICulture() { throw null; } public static System.Globalization.CultureInfo GetCultureInfo(int culture) { throw null; } public static System.Globalization.CultureInfo GetCultureInfo(string name) { throw null; } + public static System.Globalization.CultureInfo GetCultureInfo(string name, bool predefinedOnly) { throw null; } public static System.Globalization.CultureInfo GetCultureInfo(string name, string altName) { throw null; } public static System.Globalization.CultureInfo GetCultureInfoByIetfLanguageTag(string name) { throw null; } public static System.Globalization.CultureInfo[] GetCultures(System.Globalization.CultureTypes types) { throw null; }