CultureInfo and CultureData use OS information to initialize culture routines on...
authorSantiago Fernandez Madero <safern@microsoft.com>
Mon, 4 May 2020 22:17:14 +0000 (15:17 -0700)
committerGitHub <noreply@github.com>
Mon, 4 May 2020 22:17:14 +0000 (15:17 -0700)
* CultureInfo and CultureData use OS information to initialize culture routines on Windows

* PR Feedback

* Bring back removed assert

* Remove non meaninful asserts

* PR Feedback

12 files changed:
src/libraries/System.Globalization/tests/System/Globalization/RegionInfoTests.cs
src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems
src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.Icu.cs
src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.Nls.cs
src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.Unix.cs [new file with mode: 0644]
src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.Windows.cs [new file with mode: 0644]
src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.cs
src/libraries/System.Private.CoreLib/src/System/Globalization/CultureInfo.Icu.cs
src/libraries/System.Private.CoreLib/src/System/Globalization/CultureInfo.Nls.cs
src/libraries/System.Private.CoreLib/src/System/Globalization/CultureInfo.Unix.cs [new file with mode: 0644]
src/libraries/System.Private.CoreLib/src/System/Globalization/CultureInfo.Windows.cs [new file with mode: 0644]
src/libraries/System.Private.CoreLib/src/System/Globalization/CultureInfo.cs

index 68f4324..be61f29 100644 (file)
@@ -52,8 +52,9 @@ namespace System.Globalization.Tests
             AssertExtensions.Throws<ArgumentException>("name", () => new RegionInfo(name));
         }
 
-        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsIcuGlobalization))]
-        public void CurrentRegion_Icu()
+        [Fact]
+        [PlatformSpecific(TestPlatforms.AnyUnix)]
+        public void CurrentRegion_Unix()
         {
             using (new ThreadCultureChange("en-US"))
             {
@@ -63,8 +64,9 @@ namespace System.Globalization.Tests
             }
         }
 
-        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNlsGlobalization))]
-        public void TestCurrentRegion_Nls()
+        [Fact]
+        [PlatformSpecific(TestPlatforms.Windows)]
+        public void CurrentRegion_Windows()
         {
             RemoteExecutor.Invoke(() =>
             {
index 638aa0c..951552c 100644 (file)
     <Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\DebugProvider.Windows.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Stopwatch.Windows.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Tracing\RuntimeEventSourceHelper.Windows.cs" Condition="'$(FeaturePerfTracing)' == 'true'" />
+    <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\CultureData.Windows.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\CultureInfo.Windows.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\HijriCalendar.Win32.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\Guid.Windows.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\IO\DriveInfoInternal.Windows.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\Environment.NoRegistry.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\Environment.Unix.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\Environment.Unix.GetFolderPathCore.cs" Condition="'$(TargetsiOS)' != 'true' and '$(TargetstvOS)' != 'true'" />
+    <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\CultureData.Unix.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\CultureInfo.Unix.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\HijriCalendar.Unix.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\Guid.Unix.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\IO\DriveInfoInternal.Unix.cs" />
index 452a75f..2936e25 100644 (file)
@@ -4,6 +4,7 @@
 
 using System.Collections.Generic;
 using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
 
 namespace System.Globalization
 {
@@ -12,77 +13,6 @@ namespace System.Globalization
         // ICU constants
         private const int ICU_ULOC_KEYWORD_AND_VALUES_CAPACITY = 100; // max size of keyword or value
         private const int ICU_ULOC_FULLNAME_CAPACITY = 157;           // max size of locale name
-        private const string ICU_COLLATION_KEYWORD = "@collation=";
-
-        /// <summary>
-        /// 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.
-        /// </summary>
-        private unsafe bool IcuInitCultureData()
-        {
-            Debug.Assert(_sRealName != null);
-
-            Debug.Assert(!GlobalizationMode.Invariant);
-            Debug.Assert(!GlobalizationMode.UseNls);
-
-            string realNameBuffer = _sRealName;
-
-            // Basic validation
-            if (realNameBuffer.Contains('@'))
-            {
-                return false; // don't allow ICU variants to come in directly
-            }
-
-            // Replace _ (alternate sort) with @collation= for ICU
-            ReadOnlySpan<char> alternateSortName = default;
-            int index = realNameBuffer.IndexOf('_');
-            if (index > 0)
-            {
-                if (index >= (realNameBuffer.Length - 1) // must have characters after _
-                    || realNameBuffer.IndexOf('_', index + 1) >= 0) // only one _ allowed
-                {
-                    return false; // fail
-                }
-                alternateSortName = realNameBuffer.AsSpan(index + 1);
-                realNameBuffer = string.Concat(realNameBuffer.AsSpan(0, index), ICU_COLLATION_KEYWORD, alternateSortName);
-            }
-
-            // Get the locale name from ICU
-            if (!GetLocaleName(realNameBuffer, out _sWindowsName))
-            {
-                return false; // fail
-            }
-
-            // Replace the ICU collation keyword with an _
-            Debug.Assert(_sWindowsName != null);
-            index = _sWindowsName.IndexOf(ICU_COLLATION_KEYWORD, StringComparison.Ordinal);
-            if (index >= 0)
-            {
-                _sName = string.Concat(_sWindowsName.AsSpan(0, index), "_", alternateSortName);
-            }
-            else
-            {
-                _sName = _sWindowsName;
-            }
-            _sRealName = _sName;
-
-            _iLanguage = LCID;
-            if (_iLanguage == 0)
-            {
-                _iLanguage = CultureInfo.LOCALE_CUSTOM_UNSPECIFIED;
-            }
-
-            _bNeutral = TwoLetterISOCountryName.Length == 0;
-
-            _sSpecificCulture = _bNeutral ? IcuLocaleData.GetSpecificCultureName(_sRealName) : _sRealName;
-
-            // Remove the sort from sName unless custom culture
-            if (index > 0 && !_bNeutral && !IsCustomCultureId(_iLanguage))
-            {
-                _sName = _sWindowsName.Substring(0, index);
-            }
-            return true;
-        }
 
         internal static unsafe bool GetLocaleName(string localeName, out string? windowsName)
         {
@@ -99,7 +29,7 @@ namespace System.Globalization
             return true;
         }
 
-        internal static unsafe bool GetDefaultLocaleName(out string? windowsName)
+        internal static unsafe bool GetDefaultLocaleName([NotNullWhen(true)] out string? windowsName)
         {
             // Get the default (system) locale name from ICU
             char* buffer = stackalloc char[ICU_ULOC_FULLNAME_CAPACITY];
index ed07e20..e8d4d05 100644 (file)
@@ -13,141 +13,6 @@ namespace System.Globalization
 {
     internal partial class CultureData
     {
-        /// <summary>
-        /// Check with the OS to see if this is a valid culture.
-        /// If so we populate a limited number of fields.  If its not valid we return false.
-        ///
-        /// The fields we populate:
-        ///
-        /// sWindowsName -- The name that windows thinks this culture is, ie:
-        ///                            en-US if you pass in en-US
-        ///                            de-DE_phoneb if you pass in de-DE_phoneb
-        ///                            fj-FJ if you pass in fj (neutral, on a pre-Windows 7 machine)
-        ///                            fj if you pass in fj (neutral, post-Windows 7 machine)
-        ///
-        /// sRealName -- The name you used to construct the culture, in pretty form
-        ///                       en-US if you pass in EN-us
-        ///                       en if you pass in en
-        ///                       de-DE_phoneb if you pass in de-DE_phoneb
-        ///
-        /// sSpecificCulture -- The specific culture for this culture
-        ///                             en-US for en-US
-        ///                             en-US for en
-        ///                             de-DE_phoneb for alt sort
-        ///                             fj-FJ for fj (neutral)
-        ///
-        /// sName -- The IETF name of this culture (ie: no sort info, could be neutral)
-        ///                en-US if you pass in en-US
-        ///                en if you pass in en
-        ///                de-DE if you pass in de-DE_phoneb
-        ///
-        /// bNeutral -- TRUE if it is a neutral locale
-        ///
-        /// For a neutral we just populate the neutral name, but we leave the windows name pointing to the
-        /// windows locale that's going to provide data for us.
-        /// </summary>
-        private unsafe bool NlsInitCultureData()
-        {
-            Debug.Assert(!GlobalizationMode.Invariant);
-            Debug.Assert(GlobalizationMode.UseNls);
-
-            int result;
-            string realNameBuffer = _sRealName;
-            char* pBuffer = stackalloc char[Interop.Kernel32.LOCALE_NAME_MAX_LENGTH];
-
-            result = GetLocaleInfoEx(realNameBuffer, Interop.Kernel32.LOCALE_SNAME, pBuffer, Interop.Kernel32.LOCALE_NAME_MAX_LENGTH);
-
-            // Did it fail?
-            if (result == 0)
-            {
-                return false;
-            }
-
-            // It worked, note that the name is the locale name, so use that (even for neutrals)
-            // We need to clean up our "real" name, which should look like the windows name right now
-            // so overwrite the input with the cleaned up name
-            _sRealName = new string(pBuffer, 0, result - 1);
-            realNameBuffer = _sRealName;
-
-            // Check for neutrality, don't expect to fail
-            // (buffer has our name in it, so we don't have to do the gc. stuff)
-
-            result = GetLocaleInfoEx(realNameBuffer, Interop.Kernel32.LOCALE_INEUTRAL | Interop.Kernel32.LOCALE_RETURN_NUMBER, pBuffer, sizeof(int) / sizeof(char));
-            if (result == 0)
-            {
-                return false;
-            }
-
-            // Remember our neutrality
-            _bNeutral = *((uint*)pBuffer) != 0;
-
-            // Note: Parents will be set dynamically
-
-            // Start by assuming the windows name will be the same as the specific name since windows knows
-            // about specifics on all versions. Only for downlevel Neutral locales does this have to change.
-            _sWindowsName = realNameBuffer;
-
-            // Neutrals and non-neutrals are slightly different
-            if (_bNeutral)
-            {
-                // Neutral Locale
-
-                // IETF name looks like neutral name
-                _sName = realNameBuffer;
-
-                // Specific locale name is whatever ResolveLocaleName (win7+) returns.
-                // (Buffer has our name in it, and we can recycle that because windows resolves it before writing to the buffer)
-                result = Interop.Kernel32.ResolveLocaleName(realNameBuffer, pBuffer, Interop.Kernel32.LOCALE_NAME_MAX_LENGTH);
-
-                // 0 is failure, 1 is invariant (""), which we expect
-                if (result < 1)
-                {
-                    return false;
-                }
-                // We found a locale name, so use it.
-                // In vista this should look like a sort name (de-DE_phoneb) or a specific culture (en-US) and be in the "pretty" form
-                _sSpecificCulture = new string(pBuffer, 0, result - 1);
-            }
-            else
-            {
-                // Specific Locale
-
-                // Specific culture's the same as the locale name since we know its not neutral
-                // On mac we'll use this as well, even for neutrals. There's no obvious specific
-                // culture to use and this isn't exposed, but behaviorally this is correct on mac.
-                // Note that specifics include the sort name (de-DE_phoneb)
-                _sSpecificCulture = realNameBuffer;
-
-                _sName = realNameBuffer;
-
-                // We need the IETF name (sname)
-                // If we aren't an alt sort locale then this is the same as the windows name.
-                // If we are an alt sort locale then this is the same as the part before the _ in the windows name
-                // This is for like de-DE_phoneb and es-ES_tradnl that hsouldn't have the _ part
-
-                result = GetLocaleInfoEx(realNameBuffer, Interop.Kernel32.LOCALE_ILANGUAGE | Interop.Kernel32.LOCALE_RETURN_NUMBER, pBuffer, sizeof(int) / sizeof(char));
-                if (result == 0)
-                {
-                    return false;
-                }
-
-                _iLanguage = *((int*)pBuffer);
-
-                if (!IsCustomCultureId(_iLanguage))
-                {
-                    // not custom locale
-                    int index = realNameBuffer.IndexOf('_');
-                    if (index > 0 && index < realNameBuffer.Length)
-                    {
-                        _sName = realNameBuffer.Substring(0, index);
-                    }
-                }
-            }
-
-            // It succeeded.
-            return true;
-        }
-
         // Wrappers around the GetLocaleInfoEx APIs which handle marshalling the returned
         // data as either and Int or string.
         internal static unsafe string? GetLocaleInfoEx(string localeName, uint field)
@@ -294,7 +159,7 @@ namespace System.Globalization
             CultureInfo? ci;
 
             if (CultureInfo.DefaultThreadCurrentUICulture != null &&
-                ((ci = CultureInfo.NlsGetUserDefaultCulture()) != null) &&
+                ((ci = CultureInfo.GetUserDefaultCulture()) != null) &&
                 !CultureInfo.DefaultThreadCurrentUICulture.Name.Equals(ci.Name))
             {
                 return NativeName;
@@ -738,34 +603,5 @@ namespace System.Globalization
                 return false;
             }
         }
-
-        internal static unsafe CultureData NlsGetCurrentRegionData()
-        {
-            Debug.Assert(GlobalizationMode.UseNls);
-            Span<char> 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.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.Unix.cs
new file mode 100644 (file)
index 0000000..99b07c4
--- /dev/null
@@ -0,0 +1,85 @@
+// 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.Diagnostics;
+
+namespace System.Globalization
+{
+    internal partial class CultureData
+    {
+        private const string ICU_COLLATION_KEYWORD = "@collation=";
+
+        /// <summary>
+        /// 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.
+        /// </summary>
+        private bool InitCultureDataCore()
+        {
+            Debug.Assert(_sRealName != null);
+            Debug.Assert(!GlobalizationMode.Invariant);
+
+            string realNameBuffer = _sRealName;
+
+            // Basic validation
+            if (realNameBuffer.Contains('@'))
+            {
+                return false; // don't allow ICU variants to come in directly
+            }
+
+            // Replace _ (alternate sort) with @collation= for ICU
+            ReadOnlySpan<char> alternateSortName = default;
+            int index = realNameBuffer.IndexOf('_');
+            if (index > 0)
+            {
+                if (index >= (realNameBuffer.Length - 1) // must have characters after _
+                    || realNameBuffer.IndexOf('_', index + 1) >= 0) // only one _ allowed
+                {
+                    return false; // fail
+                }
+                alternateSortName = realNameBuffer.AsSpan(index + 1);
+                realNameBuffer = string.Concat(realNameBuffer.AsSpan(0, index), ICU_COLLATION_KEYWORD, alternateSortName);
+            }
+
+            // Get the locale name from ICU
+            if (!GetLocaleName(realNameBuffer, out _sWindowsName))
+            {
+                return false; // fail
+            }
+
+            // Replace the ICU collation keyword with an _
+            Debug.Assert(_sWindowsName != null);
+            index = _sWindowsName.IndexOf(ICU_COLLATION_KEYWORD, StringComparison.Ordinal);
+            if (index >= 0)
+            {
+                _sName = string.Concat(_sWindowsName.AsSpan(0, index), "_", alternateSortName);
+            }
+            else
+            {
+                _sName = _sWindowsName;
+            }
+            _sRealName = _sName;
+
+            _iLanguage = LCID;
+            if (_iLanguage == 0)
+            {
+                _iLanguage = CultureInfo.LOCALE_CUSTOM_UNSPECIFIED;
+            }
+
+            _bNeutral = TwoLetterISOCountryName.Length == 0;
+
+            _sSpecificCulture = _bNeutral ? IcuLocaleData.GetSpecificCultureName(_sRealName) : _sRealName;
+
+            // Remove the sort from sName unless custom culture
+            if (index > 0 && !_bNeutral && !IsCustomCultureId(_iLanguage))
+            {
+                _sName = _sWindowsName.Substring(0, index);
+            }
+            return true;
+        }
+
+        internal bool IsWin32Installed => false;
+
+        internal static unsafe 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
new file mode 100644 (file)
index 0000000..e67747b
--- /dev/null
@@ -0,0 +1,176 @@
+// 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.Diagnostics;
+
+namespace System.Globalization
+{
+    internal partial class CultureData
+    {
+        /// <summary>
+        /// Check with the OS to see if this is a valid culture.
+        /// If so we populate a limited number of fields.  If its not valid we return false.
+        ///
+        /// The fields we populate:
+        ///
+        /// sWindowsName -- The name that windows thinks this culture is, ie:
+        ///                            en-US if you pass in en-US
+        ///                            de-DE_phoneb if you pass in de-DE_phoneb
+        ///                            fj-FJ if you pass in fj (neutral, on a pre-Windows 7 machine)
+        ///                            fj if you pass in fj (neutral, post-Windows 7 machine)
+        ///
+        /// sRealName -- The name you used to construct the culture, in pretty form
+        ///                       en-US if you pass in EN-us
+        ///                       en if you pass in en
+        ///                       de-DE_phoneb if you pass in de-DE_phoneb
+        ///
+        /// sSpecificCulture -- The specific culture for this culture
+        ///                             en-US for en-US
+        ///                             en-US for en
+        ///                             de-DE_phoneb for alt sort
+        ///                             fj-FJ for fj (neutral)
+        ///
+        /// sName -- The IETF name of this culture (ie: no sort info, could be neutral)
+        ///                en-US if you pass in en-US
+        ///                en if you pass in en
+        ///                de-DE if you pass in de-DE_phoneb
+        ///
+        /// bNeutral -- TRUE if it is a neutral locale
+        ///
+        /// For a neutral we just populate the neutral name, but we leave the windows name pointing to the
+        /// windows locale that's going to provide data for us.
+        /// </summary>
+        private unsafe bool InitCultureDataCore()
+        {
+            Debug.Assert(!GlobalizationMode.Invariant);
+
+            int result;
+            string realNameBuffer = _sRealName;
+            char* pBuffer = stackalloc char[Interop.Kernel32.LOCALE_NAME_MAX_LENGTH];
+
+            result = GetLocaleInfoEx(realNameBuffer, Interop.Kernel32.LOCALE_SNAME, pBuffer, Interop.Kernel32.LOCALE_NAME_MAX_LENGTH);
+
+            // Did it fail?
+            if (result == 0)
+            {
+                return false;
+            }
+
+            // It worked, note that the name is the locale name, so use that (even for neutrals)
+            // We need to clean up our "real" name, which should look like the windows name right now
+            // so overwrite the input with the cleaned up name
+            _sRealName = new string(pBuffer, 0, result - 1);
+            realNameBuffer = _sRealName;
+
+            // Check for neutrality, don't expect to fail
+            // (buffer has our name in it, so we don't have to do the gc. stuff)
+
+            result = GetLocaleInfoEx(realNameBuffer, Interop.Kernel32.LOCALE_INEUTRAL | Interop.Kernel32.LOCALE_RETURN_NUMBER, pBuffer, sizeof(int) / sizeof(char));
+            if (result == 0)
+            {
+                return false;
+            }
+
+            // Remember our neutrality
+            _bNeutral = *((uint*)pBuffer) != 0;
+
+            // Note: Parents will be set dynamically
+
+            // Start by assuming the windows name will be the same as the specific name since windows knows
+            // about specifics on all versions. Only for downlevel Neutral locales does this have to change.
+            _sWindowsName = realNameBuffer;
+
+            // Neutrals and non-neutrals are slightly different
+            if (_bNeutral)
+            {
+                // Neutral Locale
+
+                // IETF name looks like neutral name
+                _sName = realNameBuffer;
+
+                // Specific locale name is whatever ResolveLocaleName (win7+) returns.
+                // (Buffer has our name in it, and we can recycle that because windows resolves it before writing to the buffer)
+                result = Interop.Kernel32.ResolveLocaleName(realNameBuffer, pBuffer, Interop.Kernel32.LOCALE_NAME_MAX_LENGTH);
+
+                // 0 is failure, 1 is invariant (""), which we expect
+                if (result < 1)
+                {
+                    return false;
+                }
+
+                // We found a locale name, so use it.
+                // In vista this should look like a sort name (de-DE_phoneb) or a specific culture (en-US) and be in the "pretty" form
+                _sSpecificCulture = new string(pBuffer, 0, result - 1);
+            }
+            else
+            {
+                // Specific Locale
+
+                // Specific culture's the same as the locale name since we know its not neutral
+                // On mac we'll use this as well, even for neutrals. There's no obvious specific
+                // culture to use and this isn't exposed, but behaviorally this is correct on mac.
+                // Note that specifics include the sort name (de-DE_phoneb)
+                _sSpecificCulture = realNameBuffer;
+
+                _sName = realNameBuffer;
+
+                // We need the IETF name (sname)
+                // If we aren't an alt sort locale then this is the same as the windows name.
+                // If we are an alt sort locale then this is the same as the part before the _ in the windows name
+                // This is for like de-DE_phoneb and es-ES_tradnl that hsouldn't have the _ part
+
+                result = GetLocaleInfoEx(realNameBuffer, Interop.Kernel32.LOCALE_ILANGUAGE | Interop.Kernel32.LOCALE_RETURN_NUMBER, pBuffer, sizeof(int) / sizeof(char));
+                if (result == 0)
+                {
+                    return false;
+                }
+
+                _iLanguage = *((int*)pBuffer);
+
+                if (!IsCustomCultureId(_iLanguage))
+                {
+                    // not custom locale
+                    int index = realNameBuffer.IndexOf('_');
+                    if (index > 0)
+                    {
+                        _sName = realNameBuffer.Substring(0, index);
+                    }
+                }
+            }
+
+            // It succeeded.
+            return true;
+        }
+
+        internal bool IsWin32Installed => true;
+
+        internal static unsafe CultureData GetCurrentRegionData()
+        {
+            Span<char> 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;
+        }
+    }
+}
index 011befa..de5f78b 100644 (file)
@@ -844,10 +844,6 @@ namespace System.Globalization
             return true;
         }
 
-        private bool InitCultureDataCore() => GlobalizationMode.UseNls ?
-                                                NlsInitCultureData() :
-                                                IcuInitCultureData();
-
         /// We'd rather people use the named version since this doesn't allow custom locales
         internal static CultureData GetCultureData(int culture, bool bUseUserOverride)
         {
@@ -1920,14 +1916,8 @@ namespace System.Globalization
 
         internal bool IsInvariantCulture => string.IsNullOrEmpty(Name);
 
-        internal bool IsWin32Installed => GlobalizationMode.UseNls;
-
         internal bool IsReplacementCulture => GlobalizationMode.UseNls ? NlsIsReplacementCulture : false;
 
-        internal static unsafe CultureData GetCurrentRegionData() => GlobalizationMode.UseNls ?
-                                                                        NlsGetCurrentRegionData() :
-                                                                        CultureInfo.CurrentCulture._cultureData;
-
         /// <summary>
         /// Get an instance of our default calendar
         /// </summary>
index 6fb99d3..4d6c415 100644 (file)
@@ -8,28 +8,6 @@ namespace System.Globalization
 {
     public partial class CultureInfo : IFormatProvider
     {
-        internal static CultureInfo IcuGetUserDefaultCulture()
-        {
-            Debug.Assert(!GlobalizationMode.UseNls);
-
-            if (GlobalizationMode.Invariant)
-                return CultureInfo.InvariantCulture;
-
-            CultureInfo cultureInfo;
-            string? localeName;
-            if (CultureData.GetDefaultLocaleName(out localeName))
-            {
-                Debug.Assert(localeName != null);
-                cultureInfo = GetCultureByName(localeName);
-            }
-            else
-            {
-                cultureInfo = CultureInfo.InvariantCulture;
-            }
-
-            return cultureInfo;
-        }
-
         private static CultureInfo IcuGetPredefinedCultureInfo(string name)
         {
             Debug.Assert(!GlobalizationMode.UseNls);
@@ -41,12 +19,5 @@ namespace System.Globalization
 
             return GetCultureInfo(name);
         }
-
-        private static CultureInfo IcuGetUserDefaultUICulture()
-        {
-            Debug.Assert(!GlobalizationMode.UseNls);
-
-            return InitializeUserDefaultCulture();
-        }
     }
 }
index 6265fc8..1cc13d7 100644 (file)
@@ -8,28 +8,6 @@ namespace System.Globalization
 {
     public partial class CultureInfo : IFormatProvider
     {
-        internal static CultureInfo NlsGetUserDefaultCulture()
-        {
-            Debug.Assert(GlobalizationMode.UseNls);
-
-            if (GlobalizationMode.Invariant)
-                return CultureInfo.InvariantCulture;
-
-            string? strDefault = CultureData.GetLocaleInfoEx(Interop.Kernel32.LOCALE_NAME_USER_DEFAULT, Interop.Kernel32.LOCALE_SNAME);
-            if (strDefault == null)
-            {
-                strDefault = CultureData.GetLocaleInfoEx(Interop.Kernel32.LOCALE_NAME_SYSTEM_DEFAULT, Interop.Kernel32.LOCALE_SNAME);
-
-                if (strDefault == null)
-                {
-                    // If system default doesn't work, use invariant
-                    return CultureInfo.InvariantCulture;
-                }
-            }
-
-            return GetCultureByName(strDefault);
-        }
-
         private static CultureInfo NlsGetPredefinedCultureInfo(string name)
         {
             Debug.Assert(GlobalizationMode.UseNls);
@@ -49,37 +27,5 @@ namespace System.Globalization
 
             return culture;
         }
-
-        private static unsafe CultureInfo NlsGetUserDefaultUICulture()
-        {
-            Debug.Assert(GlobalizationMode.UseNls);
-
-            if (GlobalizationMode.Invariant)
-                return CultureInfo.InvariantCulture;
-
-            const uint MUI_LANGUAGE_NAME = 0x8;    // Use ISO language (culture) name convention
-            uint langCount = 0;
-            uint bufLen = 0;
-
-            if (Interop.Kernel32.GetUserPreferredUILanguages(MUI_LANGUAGE_NAME, &langCount, null, &bufLen) != Interop.BOOL.FALSE)
-            {
-                char[] languages = new char[bufLen];
-                fixed (char* pLanguages = languages)
-                {
-                    if (Interop.Kernel32.GetUserPreferredUILanguages(MUI_LANGUAGE_NAME, &langCount, pLanguages, &bufLen) != Interop.BOOL.FALSE)
-                    {
-                        int index = 0;
-                        while (languages[index] != (char)0 && index < languages.Length)
-                        {
-                            index++;
-                        }
-
-                        return GetCultureByName(new string(languages, 0, index));
-                    }
-                }
-            }
-
-            return InitializeUserDefaultCulture();
-        }
     }
 }
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
new file mode 100644 (file)
index 0000000..a81b62e
--- /dev/null
@@ -0,0 +1,35 @@
+// 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.Diagnostics;
+
+namespace System.Globalization
+{
+    public partial class CultureInfo : IFormatProvider
+    {
+        internal static CultureInfo GetUserDefaultCulture()
+        {
+            if (GlobalizationMode.Invariant)
+                return CultureInfo.InvariantCulture;
+
+            CultureInfo cultureInfo;
+            if (CultureData.GetDefaultLocaleName(out string? localeName))
+            {
+                Debug.Assert(localeName != null);
+                cultureInfo = GetCultureByName(localeName);
+            }
+            else
+            {
+                cultureInfo = CultureInfo.InvariantCulture;
+            }
+
+            return cultureInfo;
+        }
+
+        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
new file mode 100644 (file)
index 0000000..99e093a
--- /dev/null
@@ -0,0 +1,46 @@
+// 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.
+
+namespace System.Globalization
+{
+    public partial class CultureInfo : IFormatProvider
+    {
+        internal static CultureInfo GetUserDefaultCulture()
+        {
+            if (GlobalizationMode.Invariant)
+                return CultureInfo.InvariantCulture;
+
+            string? strDefault = CultureData.GetLocaleInfoEx(Interop.Kernel32.LOCALE_NAME_USER_DEFAULT, Interop.Kernel32.LOCALE_SNAME) ??
+                                 CultureData.GetLocaleInfoEx(Interop.Kernel32.LOCALE_NAME_SYSTEM_DEFAULT, Interop.Kernel32.LOCALE_SNAME);
+
+            return strDefault != null ?
+                GetCultureByName(strDefault) :
+                CultureInfo.InvariantCulture;
+        }
+
+        private static unsafe CultureInfo GetUserDefaultUICulture()
+        {
+            if (GlobalizationMode.Invariant)
+                return CultureInfo.InvariantCulture;
+
+            const uint MUI_LANGUAGE_NAME = 0x8;    // Use ISO language (culture) name convention
+            uint langCount = 0;
+            uint bufLen = 0;
+
+            if (Interop.Kernel32.GetUserPreferredUILanguages(MUI_LANGUAGE_NAME, &langCount, null, &bufLen) != Interop.BOOL.FALSE)
+            {
+                Span<char> languages = bufLen <= 256 ? stackalloc char[(int)bufLen] : new char[bufLen];
+                fixed (char* pLanguages = languages)
+                {
+                    if (Interop.Kernel32.GetUserPreferredUILanguages(MUI_LANGUAGE_NAME, &langCount, pLanguages, &bufLen) != Interop.BOOL.FALSE)
+                    {
+                        return GetCultureByName(languages.ToString());
+                    }
+                }
+            }
+
+            return InitializeUserDefaultCulture();
+        }
+    }
+}
index f4daf5c..0075e69 100644 (file)
@@ -819,14 +819,6 @@ namespace System.Globalization
             return new GregorianCalendar();
         }
 
-        internal static CultureInfo GetUserDefaultCulture() => GlobalizationMode.UseNls ?
-                                                                   NlsGetUserDefaultCulture() :
-                                                                   IcuGetUserDefaultCulture();
-
-        private static CultureInfo GetUserDefaultUICulture() => GlobalizationMode.UseNls ?
-                                                                    NlsGetUserDefaultUICulture() :
-                                                                    IcuGetUserDefaultUICulture();
-
         /// <summary>
         /// Return/set the default calendar used by this culture.
         /// This value can be overridden by regional option if this is a current culture.