Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TryFindSystemTimeZoneById #88071

Merged
merged 5 commits into from
Jun 29, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
/// </summary>
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;
AlexRadch marked this conversation as resolved.
Show resolved Hide resolved
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);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'.
/// </summary>
public static TimeZoneInfo FindSystemTimeZoneById(string id)
private static TimeZoneInfoResult TryFindSystemTimeZoneById(string id, out TimeZoneInfo? timeZone, out Exception? e)
AlexRadch marked this conversation as resolved.
Show resolved Hide resolved
{
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);
}
}

Expand Down
39 changes: 39 additions & 0 deletions src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Security;
using System.Threading;

namespace System
Expand Down Expand Up @@ -556,6 +557,44 @@ public static DateTimeOffset ConvertTimeBySystemTimeZoneId(DateTimeOffset dateTi
public static DateTime ConvertTimeBySystemTimeZoneId(DateTime dateTime, string destinationTimeZoneId) =>
ConvertTime(dateTime, FindSystemTimeZoneById(destinationTimeZoneId));

/// <summary>
/// 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'.
/// </summary>
AlexRadch marked this conversation as resolved.
Show resolved Hide resolved
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)
AlexRadch marked this conversation as resolved.
Show resolved Hide resolved
{
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)
AlexRadch marked this conversation as resolved.
Show resolved Hide resolved
=> TryFindSystemTimeZoneById(id, out timeZoneInfo, out _) == TimeZoneInfoResult.Success;

/// <summary>
/// Converts the value of a DateTime object from sourceTimeZone to destinationTimeZone.
/// </summary>
Expand Down
1 change: 1 addition & 0 deletions src/libraries/System.Runtime/ref/System.Runtime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down
46 changes: 46 additions & 0 deletions src/libraries/System.Runtime/tests/System/TimeZoneInfoTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand All @@ -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();
Expand All @@ -214,13 +217,16 @@ 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;
}
}

[Fact]
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);
Expand Down Expand Up @@ -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<ArgumentException>(() =>
{
TimeZoneInfo.ConvertTime(DateTime.Now, local, cst);
});
}

[Fact]
Expand Down Expand Up @@ -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))]
Expand Down Expand Up @@ -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<TimeZoneNotFoundException>(() => 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 _));
}
}

Expand Down Expand Up @@ -2800,6 +2827,8 @@ public static void UnsupportedImplicitConversionTest()
string nonNativeTzName = s_isWindows ? "America/Los_Angeles" : "Pacific Standard Time";

Assert.Throws<TimeZoneNotFoundException>(() => TimeZoneInfo.FindSystemTimeZoneById(nonNativeTzName));

Assert.False(TimeZoneInfo.TryFindSystemTimeZoneById(nonNativeTzName, out _));
}

[ConditionalTheory(nameof(SupportIanaNamesConversion))]
Expand Down Expand Up @@ -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]
Expand Down
Loading