Skip to content
New issue

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

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

Already on GitHub? # to your account

Fix Time Zone Id Conversion with Lowercased Regions #53315

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
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
3 changes: 2 additions & 1 deletion src/libraries/Common/src/Interop/Interop.TimeZoneInfo.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Runtime.InteropServices;

internal static partial class Interop
Expand All @@ -16,7 +17,7 @@ internal static extern unsafe ResultCode GetTimeZoneDisplayName(
int resultLength);

[DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_WindowsIdToIanaId")]
internal static extern unsafe int WindowsIdToIanaId(string windowsId, [MarshalAs(UnmanagedType.LPStr)] string? region, char* ianaId, int ianaIdLength);
internal static extern unsafe int WindowsIdToIanaId(string windowsId, IntPtr region, char* ianaId, int ianaIdLength);

[DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_IanaIdToWindowsId")]
internal static extern unsafe int IanaIdToWindowsId(string ianaId, char* windowsId, int windowsIdLength);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,34 @@ private static unsafe bool TryConvertWindowsIdToIanaId(string windowsId, string?
}
}

// regionPtr will point at the region name encoded as ASCII.
IntPtr regionPtr = IntPtr.Zero;

// Regions usually are 2 or 3 characters length.
const int MaxRegionNameLength = 11;

// Ensure uppercasing the region as ICU require the region names be uppercased, otherwise ICU will assume default region and return unexpected result.
if (region is not null && region.Length < MaxRegionNameLength)
{
byte* regionInAscii = stackalloc byte[region.Length + 1];
int i = 0;
for (; i < region.Length && region[i] <= '\u007F'; i++)
{
regionInAscii[i] = (uint)(region[i] - 'a') <= ('z' - 'a') ? (byte)((region[i] - 'a') + 'A') : (byte)region[i];
}

if (i >= region.Length)
{
regionInAscii[region.Length] = 0;
regionPtr = new IntPtr(regionInAscii);
}

// In case getting unexpected region names, we just fallback using the default region (pasing null region name to the ICU API).
}

char* buffer = stackalloc char[100];
int length = Interop.Globalization.WindowsIdToIanaId(windowsId, region, buffer, 100);

int length = Interop.Globalization.WindowsIdToIanaId(windowsId, regionPtr, buffer, 100);
if (length > 0)
{
ianaId = allocate ? new string(buffer, 0, length) : null;
Expand Down
13 changes: 13 additions & 0 deletions src/libraries/System.Runtime/tests/System/TimeZoneInfoTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2696,6 +2696,19 @@ public static void IdsConversionsTest(string windowsId, string ianaId)
[InlineData("Central Europe Standard Time", "Europe/Tirane", "AL")]
[InlineData("Central Europe Standard Time", "Europe/Podgorica", "ME")]
[InlineData("Central Europe Standard Time", "Europe/Belgrade", "RS")]
// lowercased region name cases:
[InlineData("Cen. Australia Standard Time", "Australia/Adelaide", "au")]
[InlineData("AUS Central Standard Time", "Australia/Darwin", "au")]
[InlineData("E. Australia Standard Time", "Australia/Brisbane", "au")]
[InlineData("AUS Eastern Standard Time", "Australia/Sydney", "au")]
[InlineData("Tasmania Standard Time", "Australia/Hobart", "au")]
[InlineData("Romance Standard Time", "Europe/Madrid", "es")]
[InlineData("Romance Standard Time", "Europe/Madrid", "Es")]
[InlineData("Romance Standard Time", "Europe/Madrid", "eS")]
[InlineData("GMT Standard Time", "Europe/London", "gb")]
[InlineData("GMT Standard Time", "Europe/Dublin", "ie")]
[InlineData("W. Europe Standard Time", "Europe/Rome", "it")]
[InlineData("New Zealand Standard Time", "Pacific/Auckland", "nz")]
[ActiveIssue("https://github.com/dotnet/runtime/issues/52072", TestPlatforms.iOS | TestPlatforms.tvOS | TestPlatforms.MacCatalyst)]
public static void IdsConversionsWithRegionTest(string windowsId, string ianaId, string region)
{
Expand Down