[libs][Unix][perf] Lazily initialize TimeZoneInfo names and order GetSystemTimeZones...
authorMitchell Hwang <16830051+mdh1418@users.noreply.github.com>
Mon, 10 Jul 2023 14:07:01 +0000 (10:07 -0400)
committerGitHub <noreply@github.com>
Mon, 10 Jul 2023 14:07:01 +0000 (10:07 -0400)
* [libs] Remove unnecessary assignment TZifHead

* [libs][perf] Add lazy initialization for TimeZoneInfo names

* Reduce comparisons for UTC alias and remove static array allocation

* [libs] Lazy init display names for utc aliases

* [libs][perf] Order system time zones by id

* Directly compare numerical value

* Fix internal field naming

* Remove TryPopulateTimeZoneDisplayNamesFromGlobalizationData

* Make lazy initialization methods static on windows

* Revert "[libs][perf] Order system time zones by id"

This reverts commit 580a765775693a2e8f5de81a46591c8a94890088.

* Fix lazy initialization for Minimal Globalization Data

* Avoid lazy initialization where internal display name fields are set to null

* Fix CreateLocal not preserving lazy initialized names

* Prevent unintended lazy initialization in CreateCustomTimeZone

* Make UICulture a property

* Substitute null name properties with empty string in default constructor

* Assert not reached in Invariant mode

src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.FullGlobalizationData.Unix.cs
src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.MinimalGlobalizationData.cs
src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.cs
src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Win32.cs
src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.cs

index 2b38138..b396927 100644 (file)
@@ -2,6 +2,8 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 
 using System.Globalization;
+using System.Threading;
+using System.Diagnostics;
 
 namespace System
 {
@@ -21,24 +23,7 @@ namespace System
             "Pacific/Pitcairn"    // Prefer "Pitcairn Islands Time" over "Pitcairn Time"
         };
 
-        // Main function that is called during construction to populate the three display names
-        private static void TryPopulateTimeZoneDisplayNamesFromGlobalizationData(string timeZoneId, TimeSpan baseUtcOffset, ref string? standardDisplayName, ref string? daylightDisplayName, ref string? displayName)
-        {
-            if (GlobalizationMode.Invariant)
-            {
-                return;
-            }
-
-            // Determine the culture to use
-            CultureInfo uiCulture = CultureInfo.CurrentUICulture;
-            if (uiCulture.Name.Length == 0)
-                uiCulture = CultureInfo.GetCultureInfo(FallbackCultureName); // ICU doesn't work nicely with InvariantCulture
-
-            // Attempt to populate the fields backing the StandardName, DaylightName, and DisplayName from globalization data.
-            GetDisplayName(timeZoneId, Interop.Globalization.TimeZoneDisplayNameType.Standard, uiCulture.Name, ref standardDisplayName);
-            GetDisplayName(timeZoneId, Interop.Globalization.TimeZoneDisplayNameType.DaylightSavings, uiCulture.Name, ref daylightDisplayName);
-            GetFullValueForDisplayNameField(timeZoneId, baseUtcOffset, uiCulture, ref displayName);
-        }
+        private static CultureInfo? _uiCulture;
 
         // Helper function to get the standard display name for the UTC static time zone instance
         private static string GetUtcStandardDisplayName()
@@ -67,6 +52,35 @@ namespace System
         }
 #pragma warning restore IDE0060
 
+        private static CultureInfo UICulture
+        {
+            get
+            {
+                if (_uiCulture == null)
+                {
+                    Debug.Assert(!GlobalizationMode.Invariant);
+                    // Determine the culture to use
+                    CultureInfo uiCulture = CultureInfo.CurrentUICulture;
+                    if (uiCulture.Name.Length == 0)
+                        uiCulture = CultureInfo.GetCultureInfo(FallbackCultureName); // ICU doesn't work nicely with InvariantCulture
+
+                    Interlocked.CompareExchange(ref _uiCulture, uiCulture, null);
+                }
+
+                return _uiCulture;
+            }
+        }
+
+        private static void GetStandardDisplayName(string timeZoneId, ref string? displayName)
+        {
+            GetDisplayName(timeZoneId, Interop.Globalization.TimeZoneDisplayNameType.Standard, UICulture.Name, ref displayName);
+        }
+
+        private static void GetDaylightDisplayName(string timeZoneId, ref string? displayName)
+        {
+            GetDisplayName(timeZoneId, Interop.Globalization.TimeZoneDisplayNameType.DaylightSavings, UICulture.Name, ref displayName);
+        }
+
         // Helper function that retrieves various forms of time zone display names from ICU
         private static unsafe void GetDisplayName(string timeZoneId, Interop.Globalization.TimeZoneDisplayNameType nameType, string uiCulture, ref string? displayName)
         {
@@ -115,7 +129,7 @@ namespace System
         }
 
         // Helper function that builds the value backing the DisplayName field from globalization data.
-        private static void GetFullValueForDisplayNameField(string timeZoneId, TimeSpan baseUtcOffset, CultureInfo uiCulture, ref string? displayName)
+        private static void GetFullValueForDisplayNameField(string timeZoneId, TimeSpan baseUtcOffset, ref string? displayName)
         {
             // There are a few diffent ways we might show the display name depending on the data.
             // The algorithm used below should avoid duplicating the same words while still achieving the
@@ -123,6 +137,7 @@ namespace System
 
             // Try to get the generic name for this time zone.
             string? genericName = null;
+            CultureInfo uiCulture = UICulture;
             GetDisplayName(timeZoneId, Interop.Globalization.TimeZoneDisplayNameType.Generic, uiCulture.Name, ref genericName);
             if (genericName == null)
             {
index d04de84..fa1d25e 100644 (file)
@@ -6,7 +6,11 @@ namespace System
     public sealed partial class TimeZoneInfo
     {
 #pragma warning disable IDE0060
-        static partial void TryPopulateTimeZoneDisplayNamesFromGlobalizationData(string timeZoneId, TimeSpan baseUtcOffset, ref string? standardDisplayName, ref string? daylightDisplayName, ref string? displayName);
+        static partial void GetFullValueForDisplayNameField(string timeZoneId, TimeSpan baseUtcOffset, ref string? displayName);
+
+        static partial void GetStandardDisplayName(string timeZoneId, ref string? displayName);
+
+        static partial void GetDaylightDisplayName(string timeZoneId, ref string? displayName);
 
         private static string GetUtcStandardDisplayName()
         {
index 87a03dc..992035b 100644 (file)
@@ -18,19 +18,36 @@ namespace System
     {
         private const string DefaultTimeZoneDirectory = "/usr/share/zoneinfo/";
 
-        // UTC aliases per https://github.com/unicode-org/cldr/blob/master/common/bcp47/timezone.xml
+        // Set fallback values using abbreviations, base offset, and id
+        // These are expected in environments without time zone globalization data
+        private string? _standardAbbrevName;
+        private string? _daylightAbbrevName;
+
+        // Handle UTC and its aliases per https://github.com/unicode-org/cldr/blob/master/common/bcp47/timezone.xml
         // Hard-coded because we need to treat all aliases of UTC the same even when globalization data is not available.
         // (This list is not likely to change.)
-        private static readonly string[] s_UtcAliases = new[] {
-            "Etc/UTC",
-            "Etc/UCT",
-            "Etc/Universal",
-            "Etc/Zulu",
-            "UCT",
-            "UTC",
-            "Universal",
-            "Zulu"
-        };
+        private static bool IsUtcAlias (string id)
+        {
+            switch ((ushort)id[0])
+            {
+                case 69: // e
+                case 101: // E
+                    return string.Equals(id, "Etc/UTC", StringComparison.OrdinalIgnoreCase) ||
+                           string.Equals(id, "Etc/Universal", StringComparison.OrdinalIgnoreCase) ||
+                           string.Equals(id, "Etc/UTC", StringComparison.OrdinalIgnoreCase) ||
+                           string.Equals(id, "Etc/Zulu", StringComparison.OrdinalIgnoreCase);
+                case 85: // u
+                case 117: // U
+                    return string.Equals(id, "UCT", StringComparison.OrdinalIgnoreCase) ||
+                           string.Equals(id, "UTC", StringComparison.OrdinalIgnoreCase) ||
+                           string.Equals(id, "Universal", StringComparison.OrdinalIgnoreCase);
+                case 90: // z
+                case 122: // Z
+                    return string.Equals(id, "Zulu", StringComparison.OrdinalIgnoreCase);
+            }
+
+            return false;
+        }
 
         private TimeZoneInfo(byte[] data, string id, bool dstDisabled)
         {
@@ -38,28 +55,21 @@ namespace System
 
             HasIanaId = true;
 
-            // Handle UTC and its aliases
-            if (StringArrayContains(_id, s_UtcAliases, StringComparison.OrdinalIgnoreCase))
+            if (IsUtcAlias(id))
             {
-                _standardDisplayName = GetUtcStandardDisplayName();
-                _daylightDisplayName = _standardDisplayName;
-                _displayName = GetUtcFullDisplayName(_id, _standardDisplayName);
                 _baseUtcOffset = TimeSpan.Zero;
                 _adjustmentRules = Array.Empty<AdjustmentRule>();
                 return;
             }
 
-            TZifHead t;
             DateTime[] dts;
             byte[] typeOfLocalTime;
             TZifType[] transitionType;
             string zoneAbbreviations;
             string? futureTransitionsPosixFormat;
-            string? standardAbbrevName = null;
-            string? daylightAbbrevName = null;
 
             // parse the raw TZif bytes; this method can throw ArgumentException when the data is malformed.
-            TZif_ParseRaw(data, out t, out dts, out typeOfLocalTime, out transitionType, out zoneAbbreviations, out futureTransitionsPosixFormat);
+            TZif_ParseRaw(data, out dts, out typeOfLocalTime, out transitionType, out zoneAbbreviations, out futureTransitionsPosixFormat);
 
             // find the best matching baseUtcOffset and display strings based on the current utcNow value.
             // NOTE: read the Standard and Daylight display strings from the tzfile now in case they can't be loaded later
@@ -71,11 +81,11 @@ namespace System
                 if (!transitionType[type].IsDst)
                 {
                     _baseUtcOffset = transitionType[type].UtcOffset;
-                    standardAbbrevName = TZif_GetZoneAbbreviation(zoneAbbreviations, transitionType[type].AbbreviationIndex);
+                    _standardAbbrevName = TZif_GetZoneAbbreviation(zoneAbbreviations, transitionType[type].AbbreviationIndex);
                 }
                 else
                 {
-                    daylightAbbrevName = TZif_GetZoneAbbreviation(zoneAbbreviations, transitionType[type].AbbreviationIndex);
+                    _daylightAbbrevName = TZif_GetZoneAbbreviation(zoneAbbreviations, transitionType[type].AbbreviationIndex);
                 }
             }
 
@@ -88,24 +98,15 @@ namespace System
                     if (!transitionType[i].IsDst)
                     {
                         _baseUtcOffset = transitionType[i].UtcOffset;
-                        standardAbbrevName = TZif_GetZoneAbbreviation(zoneAbbreviations, transitionType[i].AbbreviationIndex);
+                        _standardAbbrevName = TZif_GetZoneAbbreviation(zoneAbbreviations, transitionType[i].AbbreviationIndex);
                     }
                     else
                     {
-                        daylightAbbrevName = TZif_GetZoneAbbreviation(zoneAbbreviations, transitionType[i].AbbreviationIndex);
+                        _daylightAbbrevName = TZif_GetZoneAbbreviation(zoneAbbreviations, transitionType[i].AbbreviationIndex);
                     }
                 }
             }
 
-            // Set fallback values using abbreviations, base offset, and id
-            // These are expected in environments without time zone globalization data
-            _standardDisplayName = standardAbbrevName;
-            _daylightDisplayName = daylightAbbrevName ?? standardAbbrevName;
-            _displayName = string.Create(null, stackalloc char[256], $"(UTC{(_baseUtcOffset >= TimeSpan.Zero ? '+' : '-')}{_baseUtcOffset:hh\\:mm}) {_id}");
-
-            // Try to populate the display names from the globalization data
-            TryPopulateTimeZoneDisplayNamesFromGlobalizationData(_id, _baseUtcOffset, ref _standardDisplayName, ref _daylightDisplayName, ref _displayName);
-
             // TZif supports seconds-level granularity with offsets but TimeZoneInfo only supports minutes since it aligns
             // with DateTimeOffset, SQL Server, and the W3C XML Specification
             if (_baseUtcOffset.Ticks % TimeSpan.TicksPerMinute != 0)
@@ -219,6 +220,50 @@ namespace System
             return rulesList.ToArray();
         }
 
+        private string? PopulateDisplayName()
+        {
+            if (IsUtcAlias(Id))
+                return GetUtcFullDisplayName(Id, StandardName);
+
+            // Set fallback value using abbreviations, base offset, and id
+            // These are expected in environments without time zone globalization data
+            string? displayName = string.Create(null, stackalloc char[256], $"(UTC{(_baseUtcOffset >= TimeSpan.Zero ? '+' : '-')}{_baseUtcOffset:hh\\:mm}) {_id}");
+            if (GlobalizationMode.Invariant)
+                return displayName;
+
+            GetFullValueForDisplayNameField(Id, BaseUtcOffset, ref displayName);
+
+            return displayName;
+        }
+
+        private string? PopulateStandardDisplayName()
+        {
+            if (IsUtcAlias(Id))
+                return GetUtcStandardDisplayName();
+
+            string? standardDisplayName = _standardAbbrevName;
+            if (GlobalizationMode.Invariant)
+                return standardDisplayName;
+
+            GetStandardDisplayName(Id, ref standardDisplayName);
+
+            return standardDisplayName;
+        }
+
+        private string? PopulateDaylightDisplayName()
+        {
+            if (IsUtcAlias(Id))
+                return StandardName;
+
+            string? daylightDisplayName = _daylightAbbrevName ?? _standardAbbrevName;
+            if (GlobalizationMode.Invariant)
+                return daylightDisplayName;
+
+            GetDaylightDisplayName(Id, ref daylightDisplayName);
+
+            return daylightDisplayName;
+        }
+
         private static void PopulateAllSystemTimeZones(CachedData cachedData)
         {
             Debug.Assert(Monitor.IsEntered(cachedData));
@@ -1065,7 +1110,7 @@ namespace System
             unixTime > DateTimeOffset.UnixMaxSeconds ? DateTime.MaxValue :
             DateTimeOffset.FromUnixTimeSeconds(unixTime).UtcDateTime;
 
-        private static void TZif_ParseRaw(byte[] data, out TZifHead t, out DateTime[] dts, out byte[] typeOfLocalTime, out TZifType[] transitionType,
+        private static void TZif_ParseRaw(byte[] data, out DateTime[] dts, out byte[] typeOfLocalTime, out TZifType[] transitionType,
                                           out string zoneAbbreviations, out string? futureTransitionsPosixFormat)
         {
             futureTransitionsPosixFormat = null;
@@ -1073,7 +1118,7 @@ namespace System
             // read in the 44-byte TZ header containing the count/length fields
             //
             int index = 0;
-            t = new TZifHead(data, index);
+            TZifHead t = new TZifHead(data, index);
             index += TZifHead.Length;
 
             int timeValuesLength = 4; // the first version uses 4-bytes to specify times
index 3344e9e..1bee4bd 100644 (file)
@@ -88,6 +88,30 @@ namespace System
             return (AdjustmentRule[])_adjustmentRules.Clone();
         }
 
+        private static string? PopulateDisplayName()
+        {
+            // Keep window's implementation to populate via constructor
+            // This should not be reached
+            Debug.Assert(false);
+            return null;
+        }
+
+        private static string? PopulateStandardDisplayName()
+        {
+            // Keep window's implementation to populate via constructor
+            // This should not be reached
+            Debug.Assert(false);
+            return null;
+        }
+
+        private static string? PopulateDaylightDisplayName()
+        {
+            // Keep window's implementation to populate via constructor
+            // This should not be reached
+            Debug.Assert(false);
+            return null;
+        }
+
         private static void PopulateAllSystemTimeZones(CachedData cachedData)
         {
             Debug.Assert(Monitor.IsEntered(cachedData));
@@ -900,9 +924,9 @@ namespace System
                     value = new TimeZoneInfo(
                         id,
                         new TimeSpan(0, -(defaultTimeZoneInformation.Bias), 0),
-                        displayName,
-                        standardName,
-                        daylightName,
+                        displayName ?? string.Empty,
+                        standardName ?? string.Empty,
+                        daylightName ?? string.Empty,
                         adjustmentRules,
                         disableDaylightSavingTime: false);
 
index 97c6d0a..5832129 100644 (file)
@@ -44,9 +44,9 @@ namespace System
         private const int MaxKeyLength = 255;
 
         private readonly string _id;
-        private readonly string? _displayName;
-        private readonly string? _standardDisplayName;
-        private readonly string? _daylightDisplayName;
+        private string? _displayName;
+        private string? _standardDisplayName;
+        private string? _daylightDisplayName;
         private readonly TimeSpan _baseUtcOffset;
         private readonly bool _supportsDaylightSavingTime;
         private readonly AdjustmentRule[]? _adjustmentRules;
@@ -85,9 +85,9 @@ namespace System
                         timeZone = new TimeZoneInfo(
                                             timeZone._id,
                                             timeZone._baseUtcOffset,
-                                            timeZone._displayName,
-                                            timeZone._standardDisplayName,
-                                            timeZone._daylightDisplayName,
+                                            timeZone.DisplayName,
+                                            timeZone.StandardName,
+                                            timeZone.DaylightName,
                                             timeZone._adjustmentRules,
                                             disableDaylightSavingTime: false,
                                             timeZone.HasIanaId);
@@ -146,11 +146,38 @@ namespace System
         /// </summary>
         public bool HasIanaId { get; }
 
-        public string DisplayName => _displayName ?? string.Empty;
+        public string DisplayName
+        {
+            get
+            {
+                if (_displayName == null)
+                    Interlocked.CompareExchange(ref _displayName, PopulateDisplayName(), null);
+
+                return _displayName ?? string.Empty;
+            }
+        }
 
-        public string StandardName => _standardDisplayName ?? string.Empty;
+        public string StandardName
+        {
+            get
+            {
+                if (_standardDisplayName == null)
+                    Interlocked.CompareExchange(ref _standardDisplayName, PopulateStandardDisplayName(), null);
 
-        public string DaylightName => _daylightDisplayName ?? string.Empty;
+                return _standardDisplayName ?? string.Empty;
+            }
+        }
+
+        public string DaylightName
+        {
+            get
+            {
+                if (_daylightDisplayName == null)
+                    Interlocked.CompareExchange(ref _daylightDisplayName, PopulateDaylightDisplayName(), null);
+
+                return _daylightDisplayName ?? string.Empty;
+            }
+        }
 
         public TimeSpan BaseUtcOffset => _baseUtcOffset;
 
@@ -962,9 +989,9 @@ namespace System
 
             _id = id;
             _baseUtcOffset = baseUtcOffset;
-            _displayName = displayName;
-            _standardDisplayName = standardDisplayName;
-            _daylightDisplayName = disableDaylightSavingTime ? null : daylightDisplayName;
+            _displayName = displayName ?? string.Empty;
+            _standardDisplayName = standardDisplayName ?? string.Empty;
+            _daylightDisplayName = disableDaylightSavingTime ? string.Empty : daylightDisplayName ?? string.Empty;
             _supportsDaylightSavingTime = adjustmentRulesSupportDst && !disableDaylightSavingTime;
             _adjustmentRules = adjustmentRules;