From 036dd1ff765d4e2ea2dad2245447be22988c2537 Mon Sep 17 00:00:00 2001 From: Alexander Radchenko Date: Tue, 27 Jun 2023 04:49:18 +0600 Subject: [PATCH 1/5] TryFindSystemTimeZoneById #66928 --- .../src/System/TimeZoneInfo.Unix.cs | 40 ++++------------ .../src/System/TimeZoneInfo.Win32.cs | 37 ++++----------- .../src/System/TimeZoneInfo.cs | 39 ++++++++++++++++ .../System.Runtime/ref/System.Runtime.cs | 1 + .../tests/System/TimeZoneInfoTests.cs | 46 +++++++++++++++++++ 5 files changed, 105 insertions(+), 58 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.cs index 4c74d5a254a93..431ee83e94d49 100644 --- a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.cs @@ -274,54 +274,34 @@ private static TimeZoneInfoResult TryGetTimeZoneFromLocalMachine(string id, out /// This function wraps the logic necessary to keep the private /// SystemTimeZones cache in working order /// - /// This function will either return a valid TimeZoneInfo instance or - /// it will throw 'InvalidTimeZoneException' / 'TimeZoneNotFoundException'. + /// This function will either return a valid TimeZoneInfo instance + /// when result equal TimeZoneInfoResult.Success /// - public static TimeZoneInfo FindSystemTimeZoneById(string id) + private static TimeZoneInfoResult TryFindSystemTimeZoneById(string id, out TimeZoneInfo? timeZone, out Exception? e) { // Special case for Utc as it will not exist in the dictionary with the rest // of the system time zones. There is no need to do this check for Local.Id // since Local is a real time zone that exists in the dictionary cache if (string.Equals(id, UtcId, StringComparison.OrdinalIgnoreCase)) { - return Utc; + timeZone = Utc; + e = default; + return TimeZoneInfoResult.Success; } ArgumentNullException.ThrowIfNull(id); if (id.Length == 0 || id.Contains('\0')) { - throw new TimeZoneNotFoundException(SR.Format(SR.TimeZoneNotFound_MissingData, id)); + timeZone = default; + e = default; + return TimeZoneInfoResult.TimeZoneNotFoundException; } - TimeZoneInfo? value; - Exception? e; - - TimeZoneInfoResult result; - CachedData cachedData = s_cachedData; lock (cachedData) { - result = TryGetTimeZone(id, false, out value, out e, cachedData, alwaysFallbackToLocalMachine: true); - } - - if (result == TimeZoneInfoResult.Success) - { - return value!; - } - else if (result == TimeZoneInfoResult.InvalidTimeZoneException) - { - Debug.Assert(e is InvalidTimeZoneException, - "TryGetTimeZone must create an InvalidTimeZoneException when it returns TimeZoneInfoResult.InvalidTimeZoneException"); - throw e; - } - else if (result == TimeZoneInfoResult.SecurityException) - { - throw new SecurityException(SR.Format(SR.Security_CannotReadFileData, id), e); - } - else - { - throw new TimeZoneNotFoundException(SR.Format(SR.TimeZoneNotFound_MissingData, id), e); + return TryGetTimeZone(id, false, out timeZone, out e, cachedData, alwaysFallbackToLocalMachine: true); } } diff --git a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Win32.cs b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Win32.cs index c748b9afa609a..8f3c806d9c23a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Win32.cs +++ b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Win32.cs @@ -320,48 +320,29 @@ private static TimeZoneInfo GetLocalTimeZoneFromWin32Data(in TIME_ZONE_INFORMATI /// This function will either return a valid TimeZoneInfo instance or /// it will throw 'InvalidTimeZoneException' / 'TimeZoneNotFoundException'. /// - public static TimeZoneInfo FindSystemTimeZoneById(string id) + private static TimeZoneInfoResult TryFindSystemTimeZoneById(string id, out TimeZoneInfo? timeZone, out Exception? e) { - ArgumentNullException.ThrowIfNull(id); - // Special case for Utc to avoid having TryGetTimeZone creating a new Utc object if (string.Equals(id, UtcId, StringComparison.OrdinalIgnoreCase)) { - return Utc; + timeZone = Utc; + e = default; + return TimeZoneInfoResult.Success; } + ArgumentNullException.ThrowIfNull(id); if (id.Length == 0 || id.Length > MaxKeyLength || id.Contains('\0')) { - throw new TimeZoneNotFoundException(SR.Format(SR.TimeZoneNotFound_MissingData, id)); + timeZone = default; + e = default; + return TimeZoneInfoResult.TimeZoneNotFoundException; } - TimeZoneInfo? value; - Exception? e; - - TimeZoneInfoResult result; - CachedData cachedData = s_cachedData; lock (cachedData) { - result = TryGetTimeZone(id, false, out value, out e, cachedData); - } - - if (result == TimeZoneInfoResult.Success) - { - return value!; - } - else if (result == TimeZoneInfoResult.InvalidTimeZoneException) - { - throw new InvalidTimeZoneException(SR.Format(SR.InvalidTimeZone_InvalidRegistryData, id), e); - } - else if (result == TimeZoneInfoResult.SecurityException) - { - throw new SecurityException(SR.Format(SR.Security_CannotReadRegistryData, id), e); - } - else - { - throw new TimeZoneNotFoundException(SR.Format(SR.TimeZoneNotFound_MissingData, id), e); + return TryGetTimeZone(id, false, out timeZone, out e, cachedData); } } diff --git a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.cs b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.cs index 2d354edaa1620..9c0ada77f61c6 100644 --- a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.cs @@ -8,6 +8,7 @@ using System.Globalization; using System.Runtime.CompilerServices; using System.Runtime.Serialization; +using System.Security; using System.Threading; namespace System @@ -556,6 +557,44 @@ public static DateTimeOffset ConvertTimeBySystemTimeZoneId(DateTimeOffset dateTi public static DateTime ConvertTimeBySystemTimeZoneId(DateTime dateTime, string destinationTimeZoneId) => ConvertTime(dateTime, FindSystemTimeZoneById(destinationTimeZoneId)); + /// + /// Helper function for retrieving a TimeZoneInfo object by time_zone_name. + /// This function wraps the logic necessary to keep the private + /// SystemTimeZones cache in working order + /// + /// This function will either return a valid TimeZoneInfo instance or + /// it will throw 'InvalidTimeZoneException' / 'TimeZoneNotFoundException'. + /// + public static TimeZoneInfo FindSystemTimeZoneById(string id) + { + TimeZoneInfo? value; + Exception? e; + + TimeZoneInfoResult result = TryFindSystemTimeZoneById(id, out value, out e); + + if (result == TimeZoneInfoResult.Success) + { + return value!; + } + else if (result == TimeZoneInfoResult.InvalidTimeZoneException) + { + Debug.Assert(e is InvalidTimeZoneException, + "TryGetTimeZone must create an InvalidTimeZoneException when it returns TimeZoneInfoResult.InvalidTimeZoneException"); + throw e; + } + else if (result == TimeZoneInfoResult.SecurityException) + { + throw new SecurityException(SR.Format(SR.Security_CannotReadFileData, id), e); + } + else + { + throw new TimeZoneNotFoundException(SR.Format(SR.TimeZoneNotFound_MissingData, id), e); + } + } + + public static bool TryFindSystemTimeZoneById(string id, [NotNullWhenAttribute(true)] out TimeZoneInfo? timeZoneInfo) + => TryFindSystemTimeZoneById(id, out timeZoneInfo, out _) == TimeZoneInfoResult.Success; + /// /// Converts the value of a DateTime object from sourceTimeZone to destinationTimeZone. /// diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index e5421cf35e93a..9de97af5b7c50 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -5610,6 +5610,7 @@ public static void ClearCachedData() { } public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] object? obj) { throw null; } public bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] System.TimeZoneInfo? other) { throw null; } public static System.TimeZoneInfo FindSystemTimeZoneById(string id) { throw null; } + public static bool TryFindSystemTimeZoneById(string id, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out System.TimeZoneInfo? timeZoneInfo) { throw null; } public static System.TimeZoneInfo FromSerializedString(string source) { throw null; } public System.TimeZoneInfo.AdjustmentRule[] GetAdjustmentRules() { throw null; } public System.TimeSpan[] GetAmbiguousTimeOffsets(System.DateTime dateTime) { throw null; } diff --git a/src/libraries/System.Runtime/tests/System/TimeZoneInfoTests.cs b/src/libraries/System.Runtime/tests/System/TimeZoneInfoTests.cs index 46dfb23a45877..18c033afd2ade 100644 --- a/src/libraries/System.Runtime/tests/System/TimeZoneInfoTests.cs +++ b/src/libraries/System.Runtime/tests/System/TimeZoneInfoTests.cs @@ -166,11 +166,13 @@ public static void LibyaTimeZone() try { tripoli = TimeZoneInfo.FindSystemTimeZoneById(s_strLibya); + Assert.True(TimeZoneInfo.TryFindSystemTimeZoneById(s_strLibya, out _)); } catch (Exception /* TimeZoneNotFoundException in netstandard1.7 test*/ ) { // Libya time zone not found Console.WriteLine("Warning: Libya time zone is not exist in this machine"); + Assert.False(TimeZoneInfo.TryFindSystemTimeZoneById(s_strLibya, out _)); return; } @@ -189,6 +191,7 @@ public static void TestYukonTZ() try { TimeZoneInfo yukon = TimeZoneInfo.FindSystemTimeZoneById("Yukon Standard Time"); + Assert.True(TimeZoneInfo.TryFindSystemTimeZoneById("Yukon Standard Time", out _)); // First, ensure we have the updated data TimeZoneInfo.AdjustmentRule[] rules = yukon.GetAdjustmentRules(); @@ -214,6 +217,8 @@ public static void TestYukonTZ() catch (TimeZoneNotFoundException) { // Some Windows versions don't carry the complete TZ data. Ignore the tests on such versions. + Assert.False(TimeZoneInfo.TryFindSystemTimeZoneById("Yukon Standard Time", out _)); + return; } } @@ -221,6 +226,7 @@ public static void TestYukonTZ() public static void RussianTimeZone() { TimeZoneInfo tz = TimeZoneInfo.FindSystemTimeZoneById(s_strRussian); + Assert.True(TimeZoneInfo.TryFindSystemTimeZoneById(s_strRussian, out _)); var inputUtcDate = new DateTime(2013, 6, 1, 0, 0, 0, DateTimeKind.Utc); DateTime russiaTime = TimeZoneInfo.ConvertTime(inputUtcDate, tz); @@ -2060,6 +2066,15 @@ public static void ClearCachedData() { TimeZoneInfo.ConvertTime(DateTime.Now, local, cst); }); + + Assert.True(TimeZoneInfo.TryFindSystemTimeZoneById(s_strSydney, out cst)); + local = TimeZoneInfo.Local; + + TimeZoneInfo.ClearCachedData(); + Assert.ThrowsAny(() => + { + TimeZoneInfo.ConvertTime(DateTime.Now, local, cst); + }); } [Fact] @@ -2721,6 +2736,9 @@ public static void EnsureUtcObjectSingleton() TimeZoneInfo utcObject = TimeZoneInfo.GetSystemTimeZones().Single(x => x.Id.Equals("UTC", StringComparison.OrdinalIgnoreCase)); Assert.True(ReferenceEquals(utcObject, TimeZoneInfo.Utc)); Assert.True(ReferenceEquals(TimeZoneInfo.FindSystemTimeZoneById("UTC"), TimeZoneInfo.Utc)); + + Assert.True(TimeZoneInfo.TryFindSystemTimeZoneById("UTC", out TimeZoneInfo tz)); + Assert.True(ReferenceEquals(tz, TimeZoneInfo.Utc)); } [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] @@ -2748,11 +2766,20 @@ public static void UsingAlternativeTimeZoneIdsTest(string windowsId, string iana Assert.Equal(tzi1.BaseUtcOffset, tzi2.BaseUtcOffset); Assert.NotEqual(tzi1.Id, tzi2.Id); + + Assert.True(TimeZoneInfo.TryFindSystemTimeZoneById(ianaId, out tzi1)); + Assert.True(TimeZoneInfo.TryFindSystemTimeZoneById(windowsId, out tzi2)); + + Assert.Equal(tzi1.BaseUtcOffset, tzi2.BaseUtcOffset); + Assert.NotEqual(tzi1.Id, tzi2.Id); } else { Assert.Throws(() => TimeZoneInfo.FindSystemTimeZoneById(s_isWindows ? ianaId : windowsId)); TimeZoneInfo tzi = TimeZoneInfo.FindSystemTimeZoneById(s_isWindows ? windowsId : ianaId); + + Assert.False(TimeZoneInfo.TryFindSystemTimeZoneById(s_isWindows ? ianaId : windowsId, out _)); + Assert.True(TimeZoneInfo.TryFindSystemTimeZoneById(s_isWindows ? windowsId : ianaId, out _)); } } @@ -2800,6 +2827,8 @@ public static void UnsupportedImplicitConversionTest() string nonNativeTzName = s_isWindows ? "America/Los_Angeles" : "Pacific Standard Time"; Assert.Throws(() => TimeZoneInfo.FindSystemTimeZoneById(nonNativeTzName)); + + Assert.False(TimeZoneInfo.TryFindSystemTimeZoneById(nonNativeTzName, out _)); } [ConditionalTheory(nameof(SupportIanaNamesConversion))] @@ -2948,6 +2977,23 @@ public static void ChangeLocalTimeZone(string id) TimeZoneInfo.ClearCachedData(); Environment.SetEnvironmentVariable("TZ", originalTZ); } + + try + { + TimeZoneInfo.ClearCachedData(); + Environment.SetEnvironmentVariable("TZ", id); + + TimeZoneInfo localtz = TimeZoneInfo.Local; + Assert.True(TimeZoneInfo.TryFindSystemTimeZoneById(id, out TimeZoneInfo tz)); + + Assert.Equal(tz.StandardName, localtz.StandardName); + Assert.Equal(tz.DisplayName, localtz.DisplayName); + } + finally + { + TimeZoneInfo.ClearCachedData(); + Environment.SetEnvironmentVariable("TZ", originalTZ); + } } [Fact] From ca8cdc8a8e9898cc9c651b40a3d2f68f459bd739 Mon Sep 17 00:00:00 2001 From: Alexander Radchenko Date: Tue, 27 Jun 2023 10:16:58 +0600 Subject: [PATCH 2/5] Update src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.cs Co-authored-by: Dan Moseley --- .../System.Private.CoreLib/src/System/TimeZoneInfo.Unix.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.cs index 431ee83e94d49..a7ad62e133550 100644 --- a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.cs @@ -284,7 +284,7 @@ private static TimeZoneInfoResult TryFindSystemTimeZoneById(string id, out TimeZ // since Local is a real time zone that exists in the dictionary cache if (string.Equals(id, UtcId, StringComparison.OrdinalIgnoreCase)) { - timeZone = Utc; + timeZone = Utc; e = default; return TimeZoneInfoResult.Success; } From 624036c99af14012b8e90164386f6e815549b20a Mon Sep 17 00:00:00 2001 From: Alexander Radchenko Date: Tue, 27 Jun 2023 11:07:53 +0600 Subject: [PATCH 3/5] Documentation --- .../src/System/TimeZoneInfo.Unix.cs | 8 ++++--- .../src/System/TimeZoneInfo.Win32.cs | 12 ++++++---- .../src/System/TimeZoneInfo.cs | 22 +++++++++++++++---- 3 files changed, 31 insertions(+), 11 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.cs index a7ad62e133550..0fef59fd4f741 100644 --- a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.cs @@ -272,10 +272,12 @@ private static TimeZoneInfoResult TryGetTimeZoneFromLocalMachine(string id, out /// /// Helper function for retrieving a TimeZoneInfo object by time_zone_name. /// This function wraps the logic necessary to keep the private - /// SystemTimeZones cache in working order + /// SystemTimeZones cache in working order. /// - /// This function will either return a valid TimeZoneInfo instance - /// when result equal TimeZoneInfoResult.Success + /// This function will either return: + /// TimeZoneInfoResult.Success and a valid instance and null Exception or + /// TimeZoneInfoResult.TimeZoneNotFoundException and null and Exception (can be null) or + /// other TimeZoneInfoResult and null and valid Exception. /// private static TimeZoneInfoResult TryFindSystemTimeZoneById(string id, out TimeZoneInfo? timeZone, out Exception? e) { diff --git a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Win32.cs b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Win32.cs index 8f3c806d9c23a..e24dbf48b6b14 100644 --- a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Win32.cs +++ b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Win32.cs @@ -315,14 +315,18 @@ private static TimeZoneInfo GetLocalTimeZoneFromWin32Data(in TIME_ZONE_INFORMATI /// /// Helper function for retrieving a TimeZoneInfo object by time_zone_name. /// This function wraps the logic necessary to keep the private - /// SystemTimeZones cache in working order + /// SystemTimeZones cache in working order. /// - /// This function will either return a valid TimeZoneInfo instance or - /// it will throw 'InvalidTimeZoneException' / 'TimeZoneNotFoundException'. + /// This function will either return: + /// TimeZoneInfoResult.Success and a valid instance and null Exception or + /// TimeZoneInfoResult.TimeZoneNotFoundException and null and Exception (can be null) or + /// other TimeZoneInfoResult and null and valid Exception. /// private static TimeZoneInfoResult TryFindSystemTimeZoneById(string id, out TimeZoneInfo? timeZone, out Exception? e) { - // Special case for Utc to avoid having TryGetTimeZone creating a new Utc object + // Special case for Utc as it will not exist in the dictionary with the rest + // of the system time zones. There is no need to do this check for Local.Id + // since Local is a real time zone that exists in the dictionary cache if (string.Equals(id, UtcId, StringComparison.OrdinalIgnoreCase)) { timeZone = Utc; diff --git a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.cs b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.cs index 9c0ada77f61c6..20e53fc0e4e8d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.cs @@ -558,13 +558,16 @@ public static DateTime ConvertTimeBySystemTimeZoneId(DateTime dateTime, string d ConvertTime(dateTime, FindSystemTimeZoneById(destinationTimeZoneId)); /// - /// Helper function for retrieving a TimeZoneInfo object by time_zone_name. + /// Helper function for retrieving a object by time zone name. /// This function wraps the logic necessary to keep the private - /// SystemTimeZones cache in working order + /// SystemTimeZones cache in working order. /// - /// This function will either return a valid TimeZoneInfo instance or - /// it will throw 'InvalidTimeZoneException' / 'TimeZoneNotFoundException'. + /// This function will either return a valid instance or + /// it will throw / / + /// /// + /// Time zone name. + /// Valid instance. public static TimeZoneInfo FindSystemTimeZoneById(string id) { TimeZoneInfo? value; @@ -592,6 +595,17 @@ public static TimeZoneInfo FindSystemTimeZoneById(string id) } } + /// + /// Helper function for retrieving a object by time zone name. + /// This function wraps the logic necessary to keep the private + /// SystemTimeZones cache in working order. + /// + /// This function will either return true and a valid + /// instance or return false and null. + /// + /// Time zone name. + /// A valid retrieved or null. + /// true if the object was successfully retrieved, false otherwise. public static bool TryFindSystemTimeZoneById(string id, [NotNullWhenAttribute(true)] out TimeZoneInfo? timeZoneInfo) => TryFindSystemTimeZoneById(id, out timeZoneInfo, out _) == TimeZoneInfoResult.Success; From 2899919b1fe40f51c8936b7e0dd7ff0d356c9dc9 Mon Sep 17 00:00:00 2001 From: Alexander Radchenko Date: Wed, 28 Jun 2023 03:44:05 +0600 Subject: [PATCH 4/5] converting if-else to switch statement moving the common code to timezoneinfo.cs --- .../src/System/TimeZoneInfo.Unix.cs | 38 ++--------- .../src/System/TimeZoneInfo.Win32.cs | 38 ++--------- .../src/System/TimeZoneInfo.cs | 68 ++++++++++++++----- 3 files changed, 60 insertions(+), 84 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.cs index 0fef59fd4f741..87a03dcdfa218 100644 --- a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.cs @@ -271,41 +271,13 @@ private static TimeZoneInfoResult TryGetTimeZoneFromLocalMachine(string id, out /// /// Helper function for retrieving a TimeZoneInfo object by time_zone_name. - /// This function wraps the logic necessary to keep the private - /// SystemTimeZones cache in working order. /// - /// This function will either return: - /// TimeZoneInfoResult.Success and a valid instance and null Exception or - /// TimeZoneInfoResult.TimeZoneNotFoundException and null and Exception (can be null) or - /// other TimeZoneInfoResult and null and valid Exception. + /// This function may return null. + /// + /// assumes cachedData lock is taken /// - private static TimeZoneInfoResult TryFindSystemTimeZoneById(string id, out TimeZoneInfo? timeZone, out Exception? e) - { - // Special case for Utc as it will not exist in the dictionary with the rest - // of the system time zones. There is no need to do this check for Local.Id - // since Local is a real time zone that exists in the dictionary cache - if (string.Equals(id, UtcId, StringComparison.OrdinalIgnoreCase)) - { - timeZone = Utc; - e = default; - return TimeZoneInfoResult.Success; - } - - ArgumentNullException.ThrowIfNull(id); - if (id.Length == 0 || id.Contains('\0')) - { - timeZone = default; - e = default; - return TimeZoneInfoResult.TimeZoneNotFoundException; - } - - CachedData cachedData = s_cachedData; - - lock (cachedData) - { - return TryGetTimeZone(id, false, out timeZone, out e, cachedData, alwaysFallbackToLocalMachine: true); - } - } + private static TimeZoneInfoResult TryGetTimeZone(string id, out TimeZoneInfo? timeZone, out Exception? e, CachedData cachedData) + => TryGetTimeZone(id, false, out timeZone, out e, cachedData, alwaysFallbackToLocalMachine: true); // DateTime.Now fast path that avoids allocating an historically accurate TimeZoneInfo.Local and just creates a 1-year (current year) accurate time zone internal static TimeSpan GetDateTimeNowUtcOffsetFromUtc(DateTime time, out bool isAmbiguousLocalDst) diff --git a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Win32.cs b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Win32.cs index e24dbf48b6b14..ef7cdfb33d00e 100644 --- a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Win32.cs +++ b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Win32.cs @@ -314,41 +314,13 @@ private static TimeZoneInfo GetLocalTimeZoneFromWin32Data(in TIME_ZONE_INFORMATI /// /// Helper function for retrieving a TimeZoneInfo object by time_zone_name. - /// This function wraps the logic necessary to keep the private - /// SystemTimeZones cache in working order. /// - /// This function will either return: - /// TimeZoneInfoResult.Success and a valid instance and null Exception or - /// TimeZoneInfoResult.TimeZoneNotFoundException and null and Exception (can be null) or - /// other TimeZoneInfoResult and null and valid Exception. + /// This function may return null. + /// + /// assumes cachedData lock is taken /// - private static TimeZoneInfoResult TryFindSystemTimeZoneById(string id, out TimeZoneInfo? timeZone, out Exception? e) - { - // Special case for Utc as it will not exist in the dictionary with the rest - // of the system time zones. There is no need to do this check for Local.Id - // since Local is a real time zone that exists in the dictionary cache - if (string.Equals(id, UtcId, StringComparison.OrdinalIgnoreCase)) - { - timeZone = Utc; - e = default; - return TimeZoneInfoResult.Success; - } - - ArgumentNullException.ThrowIfNull(id); - if (id.Length == 0 || id.Length > MaxKeyLength || id.Contains('\0')) - { - timeZone = default; - e = default; - return TimeZoneInfoResult.TimeZoneNotFoundException; - } - - CachedData cachedData = s_cachedData; - - lock (cachedData) - { - return TryGetTimeZone(id, false, out timeZone, out e, cachedData); - } - } + private static TimeZoneInfoResult TryGetTimeZone(string id, out TimeZoneInfo? timeZone, out Exception? e, CachedData cachedData) + => TryGetTimeZone(id, false, out timeZone, out e, cachedData); // DateTime.Now fast path that avoids allocating an historically accurate TimeZoneInfo.Local and just creates a 1-year (current year) accurate time zone internal static TimeSpan GetDateTimeNowUtcOffsetFromUtc(DateTime time, out bool isAmbiguousLocalDst) diff --git a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.cs b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.cs index 20e53fc0e4e8d..116cdc69fc581 100644 --- a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.cs @@ -574,24 +574,18 @@ public static TimeZoneInfo FindSystemTimeZoneById(string id) Exception? e; TimeZoneInfoResult result = TryFindSystemTimeZoneById(id, out value, out e); - - if (result == TimeZoneInfoResult.Success) - { - return value!; - } - else if (result == TimeZoneInfoResult.InvalidTimeZoneException) - { - Debug.Assert(e is InvalidTimeZoneException, - "TryGetTimeZone must create an InvalidTimeZoneException when it returns TimeZoneInfoResult.InvalidTimeZoneException"); - throw e; - } - else if (result == TimeZoneInfoResult.SecurityException) - { - throw new SecurityException(SR.Format(SR.Security_CannotReadFileData, id), e); - } - else - { - throw new TimeZoneNotFoundException(SR.Format(SR.TimeZoneNotFound_MissingData, id), e); + switch (result) + { + case TimeZoneInfoResult.Success: + return value!; + case TimeZoneInfoResult.InvalidTimeZoneException: + Debug.Assert(e is InvalidTimeZoneException, + "TryGetTimeZone must create an InvalidTimeZoneException when it returns TimeZoneInfoResult.InvalidTimeZoneException"); + throw e; + case TimeZoneInfoResult.SecurityException: + throw new SecurityException(SR.Format(SR.Security_CannotReadFileData, id), e); + default: + throw new TimeZoneNotFoundException(SR.Format(SR.TimeZoneNotFound_MissingData, id), e); } } @@ -609,6 +603,44 @@ public static TimeZoneInfo FindSystemTimeZoneById(string id) public static bool TryFindSystemTimeZoneById(string id, [NotNullWhenAttribute(true)] out TimeZoneInfo? timeZoneInfo) => TryFindSystemTimeZoneById(id, out timeZoneInfo, out _) == TimeZoneInfoResult.Success; + /// + /// Helper function for retrieving a TimeZoneInfo object by time_zone_name. + /// This function wraps the logic necessary to keep the private + /// SystemTimeZones cache in working order. + /// + /// This function will either return: + /// TimeZoneInfoResult.Success and a valid instance and null Exception or + /// TimeZoneInfoResult.TimeZoneNotFoundException and null and Exception (can be null) or + /// other TimeZoneInfoResult and null and valid Exception. + /// + private static TimeZoneInfoResult TryFindSystemTimeZoneById(string id, out TimeZoneInfo? timeZone, out Exception? e) + { + // Special case for Utc as it will not exist in the dictionary with the rest + // of the system time zones. There is no need to do this check for Local.Id + // since Local is a real time zone that exists in the dictionary cache + if (string.Equals(id, UtcId, StringComparison.OrdinalIgnoreCase)) + { + timeZone = Utc; + e = default; + return TimeZoneInfoResult.Success; + } + + ArgumentNullException.ThrowIfNull(id); + if (id.Length == 0 || id.Length > MaxKeyLength || id.Contains('\0')) + { + timeZone = default; + e = default; + return TimeZoneInfoResult.TimeZoneNotFoundException; + } + + CachedData cachedData = s_cachedData; + + lock (cachedData) + { + return TryGetTimeZone(id, out timeZone, out e, cachedData); + } + } + /// /// Converts the value of a DateTime object from sourceTimeZone to destinationTimeZone. /// From d2acd92e95dabbcc862450ccd2fb0c25dc4bc391 Mon Sep 17 00:00:00 2001 From: Alexander Radchenko Date: Wed, 28 Jun 2023 13:37:32 +0600 Subject: [PATCH 5/5] MaxKeyLength --- .../System.Private.CoreLib/src/System/TimeZoneInfo.Win32.cs | 1 - src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.cs | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Win32.cs b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Win32.cs index ef7cdfb33d00e..3344e9ef82b77 100644 --- a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Win32.cs +++ b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Win32.cs @@ -32,7 +32,6 @@ public sealed partial class TimeZoneInfo private const string FirstEntryValue = "FirstEntry"; private const string LastEntryValue = "LastEntry"; - private const int MaxKeyLength = 255; private const string InvariantUtcStandardDisplayName = "Coordinated Universal Time"; private sealed partial class CachedData diff --git a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.cs b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.cs index 116cdc69fc581..97c6d0a10f7ae 100644 --- a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.cs @@ -41,6 +41,8 @@ private enum TimeZoneInfoResult SecurityException = 3 } + private const int MaxKeyLength = 255; + private readonly string _id; private readonly string? _displayName; private readonly string? _standardDisplayName;