Fix getting time zone names with Invariant Culture (#33318)
authorTarek Mahmoud Sayed <tarekms@microsoft.com>
Tue, 10 Mar 2020 16:38:34 +0000 (09:38 -0700)
committerGitHub <noreply@github.com>
Tue, 10 Mar 2020 16:38:34 +0000 (09:38 -0700)
* Fix getting time zone names with Invariant Culture

* Address the feedback

* Restrict the test to English languages only.

* Fix misspelling

* Remove un-needed line

src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.cs
src/libraries/System.Runtime/tests/System/TimeZoneInfoTests.cs

index 3a5ecca..92d3130 100644 (file)
@@ -24,6 +24,7 @@ namespace System
         private const string ZoneTabFileName = "zone.tab";
         private const string TimeZoneEnvironmentVariable = "TZ";
         private const string TimeZoneDirectoryEnvironmentVariable = "TZDIR";
+        private const string FallbackCultureName = "en-US";
 
         private TimeZoneInfo(byte[] data, string id, bool dstDisabled)
         {
@@ -80,9 +81,10 @@ namespace System
             }
             _displayName = _standardDisplayName;
 
-            GetDisplayName(Interop.Globalization.TimeZoneDisplayNameType.Generic, ref _displayName);
-            GetDisplayName(Interop.Globalization.TimeZoneDisplayNameType.Standard, ref _standardDisplayName);
-            GetDisplayName(Interop.Globalization.TimeZoneDisplayNameType.DaylightSavings, ref _daylightDisplayName);
+            string uiCulture = CultureInfo.CurrentUICulture.Name.Length == 0 ? FallbackCultureName : CultureInfo.CurrentUICulture.Name; // ICU doesn't work nicely with Invariant
+            GetDisplayName(Interop.Globalization.TimeZoneDisplayNameType.Generic, uiCulture, ref _displayName);
+            GetDisplayName(Interop.Globalization.TimeZoneDisplayNameType.Standard, uiCulture, ref _standardDisplayName);
+            GetDisplayName(Interop.Globalization.TimeZoneDisplayNameType.DaylightSavings, uiCulture, ref _daylightDisplayName);
 
             if (_standardDisplayName == _displayName)
             {
@@ -108,7 +110,7 @@ namespace System
             ValidateTimeZoneInfo(_id, _baseUtcOffset, _adjustmentRules, out _supportsDaylightSavingTime);
         }
 
-        private unsafe void GetDisplayName(Interop.Globalization.TimeZoneDisplayNameType nameType, ref string? displayName)
+        private unsafe void GetDisplayName(Interop.Globalization.TimeZoneDisplayNameType nameType, string uiCulture, ref string? displayName)
         {
             if (GlobalizationMode.Invariant)
             {
@@ -125,11 +127,28 @@ namespace System
                         return Interop.Globalization.GetTimeZoneDisplayName(locale, id, type, bufferPtr, buffer.Length);
                     }
                 },
-                CultureInfo.CurrentUICulture.Name,
+                uiCulture,
                 _id,
                 nameType,
                 out timeZoneDisplayName);
 
+            if (!result && uiCulture != FallbackCultureName)
+            {
+                // Try to fallback using FallbackCultureName just in case we can make it work.
+                result = Interop.CallStringMethod(
+                    (buffer, locale, id, type) =>
+                    {
+                        fixed (char* bufferPtr = buffer)
+                        {
+                            return Interop.Globalization.GetTimeZoneDisplayName(locale, id, type, bufferPtr, buffer.Length);
+                        }
+                    },
+                    FallbackCultureName,
+                    _id,
+                    nameType,
+                    out timeZoneDisplayName);
+            }
+
             // If there is an unknown error, don't set the displayName field.
             // It will be set to the abbreviation that was read out of the tzfile.
             if (result)
index 5ff7f8e..8738d64 100644 (file)
@@ -10,6 +10,7 @@ using System.Linq;
 using System.Runtime.InteropServices;
 using System.Runtime.Serialization.Formatters.Binary;
 using System.Text.RegularExpressions;
+using Microsoft.DotNet.RemoteExecutor;
 using Xunit;
 
 namespace System.Tests
@@ -2268,6 +2269,29 @@ namespace System.Tests
             Assert.True(ReferenceEquals(TimeZoneInfo.FindSystemTimeZoneById("UTC"), TimeZoneInfo.Utc));
         }
 
+        // We test the existance of a specific English time zone name to avoid failures on non-English platforms.
+        [ConditionalFact(nameof(IsEnglishUILanguage))]
+        public static void TestNameWithInvariantCulture()
+        {
+            RemoteExecutor.Invoke(() =>
+            {
+                // We call ICU to get the names. When passing invariant culture name to ICU, it fail and we'll use the abbreviated names at that time.
+                // We fixed this issue by avoid sending the invariant culture name to ICU and this test is confirming we work fine at that time.
+                CultureInfo.CurrentUICulture = CultureInfo.InvariantCulture;
+                TimeZoneInfo.ClearCachedData();
+
+                TimeZoneInfo pacific = TimeZoneInfo.FindSystemTimeZoneById(s_strPacific);
+
+                Assert.True(pacific.StandardName.IndexOf("Pacific", StringComparison.OrdinalIgnoreCase) >= 0, $"'{pacific.StandardName}' is not the expected standard name for Pacific time zone");
+                Assert.True(pacific.DaylightName.IndexOf("Pacific", StringComparison.OrdinalIgnoreCase) >= 0, $"'{pacific.DaylightName}' is not the expected daylight name for Pacific time zone");
+                Assert.True(pacific.DisplayName.IndexOf("Pacific", StringComparison.OrdinalIgnoreCase) >= 0, $"'{pacific.DisplayName}' is not the expected display name for Pacific time zone");
+
+            }).Dispose();
+
+        }
+
+        private static bool IsEnglishUILanguage() => CultureInfo.CurrentUICulture.Name == "en" || CultureInfo.CurrentUICulture.Name.StartsWith("en-", StringComparison.Ordinal);
+
         private static void VerifyConvertException<TException>(DateTimeOffset inputTime, string destinationTimeZoneId) where TException : Exception
         {
             Assert.ThrowsAny<TException>(() => TimeZoneInfo.ConvertTime(inputTime, TimeZoneInfo.FindSystemTimeZoneById(destinationTimeZoneId)));