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; }