From 3a769f77f6e73baa8fcc66a6cd6ebdb4611c3d55 Mon Sep 17 00:00:00 2001 From: Tarek Mahmoud Sayed Date: Fri, 20 Mar 2020 17:49:24 -0700 Subject: [PATCH] Update RegionInfo data and Fix RegionInfo.CurrentRegion on Windows (#33834) * Update RegionInfo data and Fix RegionInfo.CurrentRegion on Windows * Address the feedback * feedback * More Feedback addressing --- .../PinvokeAnalyzerExceptionList.analyzerdata | 3 + .../Windows/Kernel32/Interop.Globalization.cs | 10 ++ .../System/PlatformDetection.cs | 3 +- .../tests/CultureInfo/CultureInfoAll.cs | 12 +- .../tests/System/Globalization/RegionInfoTests.cs | 17 ++- .../src/System/Globalization/CultureData.Unix.cs | 2 + .../System/Globalization/CultureData.Windows.cs | 29 +++++ .../src/System/Globalization/CultureData.cs | 129 ++++++++++++++++++++- .../src/System/Globalization/RegionInfo.cs | 2 +- 9 files changed, 199 insertions(+), 8 deletions(-) diff --git a/src/coreclr/src/System.Private.CoreLib/PinvokeAnalyzerExceptionList.analyzerdata b/src/coreclr/src/System.Private.CoreLib/PinvokeAnalyzerExceptionList.analyzerdata index 5d45345..141a5c2 100644 --- a/src/coreclr/src/System.Private.CoreLib/PinvokeAnalyzerExceptionList.analyzerdata +++ b/src/coreclr/src/System.Private.CoreLib/PinvokeAnalyzerExceptionList.analyzerdata @@ -5,3 +5,6 @@ normaliz.dll!NormalizeString user32.dll!GetProcessWindowStation user32.dll!GetUserObjectInformationW + + +kernel32.dll!GetGeoInfo \ No newline at end of file diff --git a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.Globalization.cs b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.Globalization.cs index 13c56f1..608073d 100644 --- a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.Globalization.cs +++ b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.Globalization.cs @@ -40,6 +40,10 @@ internal static partial class Interop internal const uint TIME_NOSECONDS = 0x00000002; + internal const int GEOCLASS_NATION = 16; + internal const int GEO_ISO2 = 4; + internal const int GEOID_NOT_AVAILABLE = -1; + internal const string LOCALE_NAME_USER_DEFAULT = null; internal const string LOCALE_NAME_SYSTEM_DEFAULT = "!x-sys-default-locale"; @@ -133,6 +137,12 @@ internal static partial class Interop [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] internal static extern int GetCalendarInfoEx(string? lpLocaleName, uint Calendar, IntPtr lpReserved, uint CalType, IntPtr lpCalData, int cchData, IntPtr lpValue); + [DllImport("kernel32.dll")] + internal static extern int GetUserGeoID(int geoClass); + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] + internal static extern int GetGeoInfo(int location, int geoType, char* lpGeoData, int cchData, int LangId); + [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] internal static extern bool EnumCalendarInfoExEx(EnumCalendarInfoProcExEx pCalInfoEnumProcExEx, string lpLocaleName, uint Calendar, string? lpReserved, uint CalType, void* lParam); diff --git a/src/libraries/Common/tests/CoreFx.Private.TestUtilities/System/PlatformDetection.cs b/src/libraries/Common/tests/CoreFx.Private.TestUtilities/System/PlatformDetection.cs index 088f7ee..3f021d3 100644 --- a/src/libraries/Common/tests/CoreFx.Private.TestUtilities/System/PlatformDetection.cs +++ b/src/libraries/Common/tests/CoreFx.Private.TestUtilities/System/PlatformDetection.cs @@ -33,6 +33,7 @@ namespace System public static bool IsArgIteratorSupported => IsMonoRuntime || (IsWindows && IsNotArmProcess); public static bool IsArgIteratorNotSupported => !IsArgIteratorSupported; public static bool Is32BitProcess => IntPtr.Size == 4; + public static bool IsNotWindows => !IsWindows; // Please make sure that you have the libgdiplus dependency installed. // For details, see https://docs.microsoft.com/dotnet/core/install/dependencies?pivots=os-macos&tabs=netcore31#libgdiplus @@ -206,7 +207,7 @@ namespace System private static bool GetIsRunningOnMonoInterpreter() { // This is a temporary solution because mono does not support interpreter detection - // within the runtime. + // within the runtime. var val = Environment.GetEnvironmentVariable("MONO_ENV_OPTIONS"); return (val != null && val.Contains("--interpreter")); } diff --git a/src/libraries/System.Globalization/tests/CultureInfo/CultureInfoAll.cs b/src/libraries/System.Globalization/tests/CultureInfo/CultureInfoAll.cs index f20cecc..028cf71 100644 --- a/src/libraries/System.Globalization/tests/CultureInfo/CultureInfoAll.cs +++ b/src/libraries/System.Globalization/tests/CultureInfo/CultureInfoAll.cs @@ -6,6 +6,7 @@ using System.ComponentModel; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; +using Microsoft.DotNet.RemoteExecutor; using System.Text; using Xunit; @@ -631,10 +632,13 @@ namespace System.Globalization.Tests [Fact] public void ClearCachedDataTest() { - CultureInfo ci = CultureInfo.GetCultureInfo("ja-JP"); - Assert.True((object) ci == (object) CultureInfo.GetCultureInfo("ja-JP"), "Expected getting same object reference"); - ci.ClearCachedData(); - Assert.False((object) ci == (object) CultureInfo.GetCultureInfo("ja-JP"), "expected to get a new object reference"); + RemoteExecutor.Invoke(() => + { + CultureInfo ci = CultureInfo.GetCultureInfo("ja-JP"); + Assert.True((object) ci == (object) CultureInfo.GetCultureInfo("ja-JP"), "Expected getting same object reference"); + ci.ClearCachedData(); + Assert.False((object) ci == (object) CultureInfo.GetCultureInfo("ja-JP"), "expected to get a new object reference"); + }).Dispose(); } [Fact] diff --git a/src/libraries/System.Globalization/tests/System/Globalization/RegionInfoTests.cs b/src/libraries/System.Globalization/tests/System/Globalization/RegionInfoTests.cs index 204365e..f729526 100644 --- a/src/libraries/System.Globalization/tests/System/Globalization/RegionInfoTests.cs +++ b/src/libraries/System.Globalization/tests/System/Globalization/RegionInfoTests.cs @@ -52,7 +52,7 @@ namespace System.Globalization.Tests AssertExtensions.Throws("name", () => new RegionInfo(name)); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows))] public void CurrentRegion() { using (new ThreadCultureChange("en-US")) @@ -63,6 +63,21 @@ namespace System.Globalization.Tests } } + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsWindows))] + public void TestCurrentRegion() + { + RemoteExecutor.Invoke(() => + { + RegionInfo ri = RegionInfo.CurrentRegion; + CultureInfo.CurrentCulture.ClearCachedData(); // clear the current region cached data + + CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo("ja-JP"); + + // Changing the current culture shouldn't affect the default current region as we get it from Windows settings. + Assert.Equal(ri.TwoLetterISORegionName, RegionInfo.CurrentRegion.TwoLetterISORegionName); + }).Dispose(); + } + [Theory] [InlineData("en-US", "United States")] [OuterLoop("May fail on machines with multiple language packs installed")] // see https://github.com/dotnet/runtime/issues/30132 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 e429353..f3c3e5d 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 @@ -418,5 +418,7 @@ namespace System.Globalization internal bool IsWin32Installed => false; internal bool IsReplacementCulture => false; + + internal static CultureData GetCurrentRegionData() => CultureInfo.CurrentCulture._cultureData; } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.Windows.cs index b8ae408..48e4552 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.Windows.cs @@ -7,6 +7,7 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Text; using Internal.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace System.Globalization { @@ -714,5 +715,33 @@ namespace System.Globalization return false; } } + + internal static unsafe CultureData GetCurrentRegionData() + { + Span geoIso2Letters = stackalloc char[10]; + + int geoId = Interop.Kernel32.GetUserGeoID(Interop.Kernel32.GEOCLASS_NATION); + if (geoId != Interop.Kernel32.GEOID_NOT_AVAILABLE) + { + int geoIsoIdLength; + fixed (char* pGeoIsoId = geoIso2Letters) + { + geoIsoIdLength = Interop.Kernel32.GetGeoInfo(geoId, Interop.Kernel32.GEO_ISO2, pGeoIsoId, geoIso2Letters.Length, 0); + } + + if (geoIsoIdLength != 0) + { + geoIsoIdLength -= geoIso2Letters[geoIsoIdLength - 1] == 0 ? 1 : 0; // handle null termination and exclude it. + CultureData? cd = GetCultureDataForRegion(geoIso2Letters.Slice(0, geoIsoIdLength).ToString(), true); + if (cd != null) + { + return cd; + } + } + } + + // Fallback to current locale data. + return CultureInfo.CurrentCulture._cultureData; + } } } 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 90a502b..70fc788 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.cs @@ -152,135 +152,262 @@ namespace System.Globalization /// private static Dictionary RegionNames => s_regionNames ??= - new Dictionary(211 /* prime */) + new Dictionary(257 /* prime */) { + { "001", "en-001" }, { "029", "en-029" }, + { "150", "en-150" }, + { "419", "es-419" }, + { "AD", "ca-AD" }, { "AE", "ar-AE" }, { "AF", "prs-AF" }, + { "AG", "en-AG" }, + { "AI", "en-AI" }, { "AL", "sq-AL" }, { "AM", "hy-AM" }, + { "AO", "pt-AO" }, + { "AQ", "en-A" }, { "AR", "es-AR" }, + { "AS", "en-AS" }, { "AT", "de-AT" }, { "AU", "en-AU" }, + { "AW", "nl-AW" }, + { "AX", "sv-AX" }, { "AZ", "az-Cyrl-AZ" }, { "BA", "bs-Latn-BA" }, + { "BB", "en-BB" }, { "BD", "bn-BD" }, { "BE", "nl-BE" }, + { "BF", "fr-BF" }, { "BG", "bg-BG" }, { "BH", "ar-BH" }, + { "BI", "rn-BI" }, + { "BJ", "fr-BJ" }, + { "BL", "fr-BL" }, + { "BM", "en-BM" }, { "BN", "ms-BN" }, { "BO", "es-BO" }, + { "BQ", "nl-BQ" }, { "BR", "pt-BR" }, + { "BS", "en-BS" }, + { "BT", "dz-BT" }, + { "BV", "nb-B" }, + { "BW", "en-BW" }, { "BY", "be-BY" }, { "BZ", "en-BZ" }, { "CA", "en-CA" }, + { "CC", "en-CC" }, + { "CD", "fr-CD" }, + { "CF", "sg-CF" }, + { "CG", "fr-CG" }, { "CH", "it-CH" }, + { "CI", "fr-CI" }, + { "CK", "en-CK" }, { "CL", "es-CL" }, + { "CM", "fr-C" }, { "CN", "zh-CN" }, { "CO", "es-CO" }, { "CR", "es-CR" }, { "CS", "sr-Cyrl-CS" }, + { "CU", "es-CU" }, + { "CV", "pt-CV" }, + { "CW", "nl-CW" }, + { "CX", "en-CX" }, + { "CY", "el-CY" }, { "CZ", "cs-CZ" }, { "DE", "de-DE" }, + { "DJ", "fr-DJ" }, { "DK", "da-DK" }, + { "DM", "en-DM" }, { "DO", "es-DO" }, { "DZ", "ar-DZ" }, { "EC", "es-EC" }, { "EE", "et-EE" }, { "EG", "ar-EG" }, + { "ER", "tig-ER" }, { "ES", "es-ES" }, { "ET", "am-ET" }, { "FI", "fi-FI" }, + { "FJ", "en-FJ" }, + { "FK", "en-FK" }, + { "FM", "en-FM" }, { "FO", "fo-FO" }, { "FR", "fr-FR" }, + { "GA", "fr-GA" }, { "GB", "en-GB" }, + { "GD", "en-GD" }, { "GE", "ka-GE" }, + { "GF", "fr-GF" }, + { "GG", "en-GG" }, + { "GH", "en-GH" }, + { "GI", "en-GI" }, { "GL", "kl-GL" }, + { "GM", "en-GM" }, + { "GN", "fr-GN" }, + { "GP", "fr-GP" }, + { "GQ", "es-GQ" }, { "GR", "el-GR" }, + { "GS", "en-G" }, { "GT", "es-GT" }, + { "GU", "en-GU" }, + { "GW", "pt-GW" }, + { "GY", "en-GY" }, { "HK", "zh-HK" }, + { "HM", "en-H" }, { "HN", "es-HN" }, { "HR", "hr-HR" }, + { "HT", "fr-HT" }, { "HU", "hu-HU" }, { "ID", "id-ID" }, { "IE", "en-IE" }, { "IL", "he-IL" }, + { "IM", "gv-IM" }, { "IN", "hi-IN" }, + { "IO", "en-IO" }, { "IQ", "ar-IQ" }, { "IR", "fa-IR" }, { "IS", "is-IS" }, { "IT", "it-IT" }, { "IV", "" }, + { "JE", "en-JE" }, { "JM", "en-JM" }, { "JO", "ar-JO" }, { "JP", "ja-JP" }, { "KE", "sw-KE" }, { "KG", "ky-KG" }, { "KH", "km-KH" }, + { "KI", "en-KI" }, + { "KM", "ar-KM" }, + { "KN", "en-KN" }, + { "KP", "ko-KP" }, { "KR", "ko-KR" }, { "KW", "ar-KW" }, + { "KY", "en-KY" }, { "KZ", "kk-KZ" }, { "LA", "lo-LA" }, { "LB", "ar-LB" }, + { "LC", "en-LC" }, { "LI", "de-LI" }, { "LK", "si-LK" }, + { "LR", "en-LR" }, + { "LS", "st-LS" }, { "LT", "lt-LT" }, { "LU", "lb-LU" }, { "LV", "lv-LV" }, { "LY", "ar-LY" }, { "MA", "ar-MA" }, { "MC", "fr-MC" }, + { "MD", "ro-MD" }, { "ME", "sr-Latn-ME" }, + { "MF", "fr-MF" }, + { "MG", "mg-MG" }, + { "MH", "en-MH" }, { "MK", "mk-MK" }, + { "ML", "fr-ML" }, + { "MM", "my-MM" }, { "MN", "mn-MN" }, { "MO", "zh-MO" }, + { "MP", "en-MP" }, + { "MQ", "fr-MQ" }, + { "MR", "ar-MR" }, + { "MS", "en-MS" }, { "MT", "mt-MT" }, + { "MU", "en-MU" }, { "MV", "dv-MV" }, + { "MW", "en-MW" }, { "MX", "es-MX" }, { "MY", "ms-MY" }, + { "MZ", "pt-MZ" }, + { "NA", "en-NA" }, + { "NC", "fr-NC" }, + { "NE", "fr-NE" }, + { "NF", "en-NF" }, { "NG", "ig-NG" }, { "NI", "es-NI" }, { "NL", "nl-NL" }, { "NO", "nn-NO" }, { "NP", "ne-NP" }, + { "NR", "en-NR" }, + { "NU", "en-NU" }, { "NZ", "en-NZ" }, { "OM", "ar-OM" }, { "PA", "es-PA" }, { "PE", "es-PE" }, + { "PF", "fr-PF" }, + { "PG", "en-PG" }, { "PH", "en-PH" }, { "PK", "ur-PK" }, { "PL", "pl-PL" }, + { "PM", "fr-PM" }, + { "PN", "en-PN" }, { "PR", "es-PR" }, + { "PS", "ar-PS" }, { "PT", "pt-PT" }, + { "PW", "en-PW" }, { "PY", "es-PY" }, { "QA", "ar-QA" }, + { "RE", "fr-RE" }, { "RO", "ro-RO" }, { "RS", "sr-Latn-RS" }, { "RU", "ru-RU" }, { "RW", "rw-RW" }, { "SA", "ar-SA" }, + { "SB", "en-SB" }, + { "SC", "fr-SC" }, + { "SD", "ar-SD" }, { "SE", "sv-SE" }, { "SG", "zh-SG" }, + { "SH", "en-SH" }, { "SI", "sl-SI" }, + { "SJ", "nb-SJ" }, { "SK", "sk-SK" }, + { "SL", "en-SL" }, + { "SM", "it-SM" }, { "SN", "wo-SN" }, + { "SO", "so-SO" }, + { "SR", "nl-SR" }, + { "SS", "en-SS" }, + { "ST", "pt-ST" }, { "SV", "es-SV" }, + { "SX", "nl-SX" }, { "SY", "ar-SY" }, + { "SZ", "ss-SZ" }, + { "TC", "en-TC" }, + { "TD", "fr-TD" }, + { "TF", "fr-T" }, + { "TG", "fr-TG" }, { "TH", "th-TH" }, { "TJ", "tg-Cyrl-TJ" }, + { "TK", "en-TK" }, + { "TL", "pt-TL" }, { "TM", "tk-TM" }, { "TN", "ar-TN" }, + { "TO", "to-TO" }, { "TR", "tr-TR" }, { "TT", "en-TT" }, + { "TV", "en-TV" }, { "TW", "zh-TW" }, + { "TZ", "sw-TZ" }, { "UA", "uk-UA" }, + { "UG", "sw-UG" }, + { "UM", "en-UM" }, { "US", "en-US" }, { "UY", "es-UY" }, { "UZ", "uz-Cyrl-UZ" }, + { "VA", "it-VA" }, + { "VC", "en-VC" }, { "VE", "es-VE" }, + { "VG", "en-VG" }, + { "VI", "en-VI" }, { "VN", "vi-VN" }, + { "VU", "fr-VU" }, + { "WF", "fr-WF" }, + { "WS", "en-WS" }, + { "XK", "sq-XK" }, { "YE", "ar-YE" }, + { "YT", "fr-YT" }, { "ZA", "af-ZA" }, + { "ZM", "en-ZM" }, { "ZW", "en-ZW" } }; diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/RegionInfo.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/RegionInfo.cs index ccaa806..47894cc 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/RegionInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/RegionInfo.cs @@ -96,7 +96,7 @@ namespace System.Globalization RegionInfo? temp = s_currentRegionInfo; if (temp == null) { - temp = new RegionInfo(CultureInfo.CurrentCulture._cultureData); + temp = new RegionInfo(CultureData.GetCurrentRegionData()); // Need full name for custom cultures temp._name = temp._cultureData.RegionName; -- 2.7.4