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

IANA To/From Windows Ids Conversion APIs #51093

Merged
merged 3 commits into from
Apr 14, 2021
Merged
Show file tree
Hide file tree
Changes from 2 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
2 changes: 1 addition & 1 deletion src/libraries/Common/src/Interop/Interop.TimeZoneInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,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, char* ianaId, int ianaIdLength);
internal static extern unsafe int WindowsIdToIanaId(string windowsId, string? 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 @@ -21,13 +21,36 @@ static const UChar EXEMPLAR_CITY_PATTERN_UCHAR[] = {'V', 'V', 'V', '\0'};
/*
Convert Windows Time Zone Id to IANA Id
*/
int32_t GlobalizationNative_WindowsIdToIanaId(const UChar* windowsId, UChar* ianaId, int32_t ianaIdLength)
int32_t GlobalizationNative_WindowsIdToIanaId(const UChar* windowsId, const UChar* region, UChar* ianaId, int32_t ianaIdLength)
{
UErrorCode status = U_ZERO_ERROR;

if (ucal_getTimeZoneIDForWindowsID_ptr != NULL)
{
int32_t ianaIdFilledLength = ucal_getTimeZoneIDForWindowsID(windowsId, -1, NULL, ianaId, ianaIdLength, &status);
#define BUFFER_LENGTH 41 // enough length for the region code including the null termination
char buffer[BUFFER_LENGTH];
tarekgh marked this conversation as resolved.
Show resolved Hide resolved
char* regionName = NULL;

if (region != NULL)
{
regionName = buffer;
int length = 0;
while (length < BUFFER_LENGTH - 1 && region[length] != '\0')
{
buffer[length] = (char)region[length];
length++;
}

if (length == BUFFER_LENGTH - 1)
{
// long region name which shouldn't be correct.
return 0;
}

buffer[length] = '\0';
}

int32_t ianaIdFilledLength = ucal_getTimeZoneIDForWindowsID(windowsId, -1, regionName, ianaId, ianaIdLength, &status);
if (U_SUCCESS(status))
{
return ianaIdFilledLength;
Expand Down Expand Up @@ -91,7 +114,7 @@ static void GetTimeZoneDisplayName_FromPattern(const char* locale, const UChar*
if (U_SUCCESS(*err))
{
udat_format(dateFormatter, timestamp, result, resultLength, NULL, err);
udat_close(dateFormatter);
udat_close(dateFormatter);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,6 @@ typedef enum
TimeZoneDisplayName_ExemplarCity = 4,
} TimeZoneDisplayNameType;

PALEXPORT int32_t GlobalizationNative_WindowsIdToIanaId(const UChar* windowsId, UChar* ianaId, int32_t ianaIdLength);
PALEXPORT int32_t GlobalizationNative_WindowsIdToIanaId(const UChar* windowsId, const UChar* region, UChar* ianaId, int32_t ianaIdLength);
PALEXPORT int32_t GlobalizationNative_IanaIdToWindowsId(const UChar* ianaId, UChar* windowsId, int32_t windowsIdLength);
PALEXPORT ResultCode GlobalizationNative_GetTimeZoneDisplayName(const UChar* localeName, const UChar* timeZoneId, TimeZoneDisplayNameType type, UChar* result, int32_t resultLength);
Original file line number Diff line number Diff line change
Expand Up @@ -231,36 +231,11 @@ private static string GetExemplarCityName(string timeZoneId, string uiCultureNam
return timeZoneId.Substring(i + 1).Replace('_', ' ');
}

// Helper function that returns an alternative ID using ICU data. Used primarily for converting from Windows IDs.
private static unsafe string? GetAlternativeId(string id)
// Helper function that returns an alternative ID using ICU data. Used primarily for converting from Windows IDs.
private static unsafe string? GetAlternativeId(string id, out bool idIsIana)
{
if (!GlobalizationMode.Invariant)
{
if (id.Equals("utc", StringComparison.OrdinalIgnoreCase))
{
// Special case UTC, as previously ICU would convert it to "Etc/GMT" which is incorrect name for UTC.
return "Etc/UTC";
}

foreach (char c in id)
{
// ICU uses some characters as a separator and trim the id at that character.
// while we should fail if the Id contained one of these characters.
if (c == '\\' || c == '\n' || c == '\r')
{
return null;
}
}

char* buffer = stackalloc char[100];
int length = Interop.Globalization.WindowsIdToIanaId(id, buffer, 100);
if (length > 0)
{
return new string(buffer, 0, length);
}
}

return null;
idIsIana = false;
return TryConvertWindowsIdToIanaId(id, null, out string? ianaId) ? ianaId : null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ private static string GetUtcFullDisplayName(string timeZoneId, string standardDi
return $"(UTC) {timeZoneId}";
}

private static string? GetAlternativeId(string id)
private static string? GetAlternativeId(string id, out bool idIsIana)
{
// No alternative IDs in this target.
idIsIana = false;
return null;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ private TimeZoneInfo(byte[] data, string id, bool dstDisabled)
{
_id = id;

HasIanaId = true;

// Handle UTC and its aliases
if (StringArrayContains(_id, s_UtcAliases, StringComparison.OrdinalIgnoreCase))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,29 +106,10 @@ private static void PopulateAllSystemTimeZones(CachedData cachedData)
}
}

private static unsafe string? GetAlternativeId(string id)
private static string? GetAlternativeId(string id, out bool idIsIana)
{
if (!GlobalizationMode.Invariant && !GlobalizationMode.UseNls)
{
foreach (char c in id)
{
// ICU uses some characters as a separator and trim the id at that character.
// while we should fail if the Id contained one of these characters.
if (c == '\\' || c == '\n' || c == '\r')
{
return null;
}
}

char* buffer = stackalloc char[100];
int length = Interop.Globalization.IanaIdToWindowsId(id, buffer, 100);
if (length > 0)
{
return new string(buffer, 0, length);
}
}

return null;
idIsIana = true;
return TryConvertIanaIdToWindowsId(id, out string? windowsId) ? windowsId : null;
}

private TimeZoneInfo(in TIME_ZONE_INFORMATION zone, bool dstDisabled)
Expand Down
123 changes: 116 additions & 7 deletions src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using System.Threading;

Expand Down Expand Up @@ -136,6 +137,11 @@ public DateTimeKind GetCorrespondingKind(TimeZoneInfo? timeZone)

public string Id => _id;

/// <summary>
/// Returns true if this TimeZoneInfo object has IANA Id.
/// </summary>
public bool HasIanaId { get; }

public string DisplayName => _displayName ?? string.Empty;

public string StandardName => _standardDisplayName ?? string.Empty;
Expand Down Expand Up @@ -880,7 +886,8 @@ private TimeZoneInfo(
string? standardDisplayName,
string? daylightDisplayName,
AdjustmentRule[]? adjustmentRules,
bool disableDaylightSavingTime)
bool disableDaylightSavingTime,
bool hasIanaId = false)
{
ValidateTimeZoneInfo(id, baseUtcOffset, adjustmentRules, out bool adjustmentRulesSupportDst);

Expand All @@ -891,6 +898,8 @@ private TimeZoneInfo(
_daylightDisplayName = disableDaylightSavingTime ? null : daylightDisplayName;
_supportsDaylightSavingTime = adjustmentRulesSupportDst && !disableDaylightSavingTime;
_adjustmentRules = adjustmentRules;

HasIanaId = _id.Equals(UtcId, StringComparison.OrdinalIgnoreCase) ? true : hasIanaId;
}

/// <summary>
Expand All @@ -902,14 +911,17 @@ public static TimeZoneInfo CreateCustomTimeZone(
string? displayName,
string? standardDisplayName)
{
bool hasIanaId = TimeZoneInfo.TryConvertIanaIdToWindowsId(id, out string _);

return new TimeZoneInfo(
id,
baseUtcOffset,
displayName,
standardDisplayName,
standardDisplayName,
adjustmentRules: null,
disableDaylightSavingTime: false);
disableDaylightSavingTime: false,
hasIanaId);
}

/// <summary>
Expand Down Expand Up @@ -950,14 +962,111 @@ public static TimeZoneInfo CreateCustomTimeZone(
adjustmentRules = (AdjustmentRule[])adjustmentRules.Clone();
}

bool hasIanaId = TimeZoneInfo.TryConvertIanaIdToWindowsId(id, out string _);
tarekgh marked this conversation as resolved.
Show resolved Hide resolved

return new TimeZoneInfo(
id,
baseUtcOffset,
displayName,
standardDisplayName,
daylightDisplayName,
adjustmentRules,
disableDaylightSavingTime);
disableDaylightSavingTime,
hasIanaId);
}

/// <summary>
/// Tries to convert IANA time zone Id to Windows Id.
/// </summary>
/// <param name="ianaId">The IANA time zone Id.</param>
/// <param name="windowsId">String object hold the Windows Id which resulted from the IANA Id conversion.</param>
/// <returns>True if the Id conversion succeed, false otherwise .</returns>
public static unsafe bool TryConvertIanaIdToWindowsId(string ianaId, out string? windowsId)
tarekgh marked this conversation as resolved.
Show resolved Hide resolved
{
windowsId = null;

// This functionality is not enabled in the browser for the sake of size reduction.
#if !TARGET_BROWSER
tarekgh marked this conversation as resolved.
Show resolved Hide resolved
if (GlobalizationMode.Invariant || GlobalizationMode.UseNls || ianaId is null)
{
return false;
}

foreach (char c in ianaId)
{
// ICU uses some characters as a separator and trim the id at that character.
// while we should fail if the Id contained one of these characters.
if (c == '\\' || c == '\n' || c == '\r')
{
return false;
}
}

char* buffer = stackalloc char[100];
int length = Interop.Globalization.IanaIdToWindowsId(ianaId, buffer, 100);
if (length > 0)
{
windowsId = new string(buffer, 0, length);
return true;
}
#endif // !TARGET_BROWSER

return false;
}

/// <summary>
/// Tries to convert Windows time zone Id to IANA Id.
/// </summary>
/// <param name="windowsId">The Windows time zone Id.</param>
/// <param name="ianaId">String object hold the IANA Id which resulted from the Windows Id conversion.</param>
/// <returns>True if the Id conversion succeed, false otherwise .</returns>
public static bool TryConvertWindowsIdToIanaId(string windowsId, out string? ianaId) => TryConvertWindowsIdToIanaId(windowsId, null, out ianaId);
tarekgh marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// Tries to convert Windows time zone Id to IANA Id.
/// </summary>
/// <param name="windowsId">The Windows time zone Id.</param>
/// <param name="region">The ISO 3166 for the country/region.</param>
/// <param name="ianaId">String object hold the IANA Id which resulted from the Windows Id conversion.</param>
/// <returns>True if the Id conversion succeed, false otherwise .</returns>
public static unsafe bool TryConvertWindowsIdToIanaId(string windowsId, string? region, out string? ianaId)
tarekgh marked this conversation as resolved.
Show resolved Hide resolved
{
ianaId = null;

// This functionality is not enabled in the browser for the sake of size reduction.
#if !TARGET_BROWSER
tarekgh marked this conversation as resolved.
Show resolved Hide resolved
if (GlobalizationMode.Invariant || GlobalizationMode.UseNls || windowsId is null)
{
return false;
}

if (windowsId.Equals("utc", StringComparison.OrdinalIgnoreCase))
{
// Special case UTC, as previously ICU would convert it to "Etc/GMT" which is incorrect name for UTC.
ianaId = "Etc/UTC";
return true;
}

foreach (char c in windowsId)
{
// ICU uses some characters as a separator and trim the id at that character.
// while we should fail if the Id contained one of these characters.
if (c == '\\' || c == '\n' || c == '\r')
{
return false;
}
}

char* buffer = stackalloc char[100];
int length = Interop.Globalization.WindowsIdToIanaId(windowsId, region, buffer, 100);
if (length > 0)
{
ianaId = new string(buffer, 0, length);
return true;
}
#endif // !TARGET_BROWSER

return false;
}

void IDeserializationCallback.OnDeserialization(object? sender)
Expand Down Expand Up @@ -1794,7 +1903,7 @@ private static TimeZoneInfoResult TryGetTimeZone(string id, bool dstDisabled, ou
TimeZoneInfoResult result = TryGetTimeZoneUsingId(id, dstDisabled, out value, out e, cachedData, alwaysFallbackToLocalMachine);
if (result != TimeZoneInfoResult.Success)
{
string? alternativeId = GetAlternativeId(id);
string? alternativeId = GetAlternativeId(id, out bool idIsIana);
if (alternativeId != null)
{
result = TryGetTimeZoneUsingId(alternativeId, dstDisabled, out value, out e, cachedData, alwaysFallbackToLocalMachine);
Expand All @@ -1804,7 +1913,7 @@ private static TimeZoneInfoResult TryGetTimeZone(string id, bool dstDisabled, ou
if (value!._equivalentZones == null)
{
zone = new TimeZoneInfo(id, value!._baseUtcOffset, value!._displayName, value!._standardDisplayName,
value!._daylightDisplayName, value!._adjustmentRules, dstDisabled && value!._supportsDaylightSavingTime);
value!._daylightDisplayName, value!._adjustmentRules, dstDisabled && value!._supportsDaylightSavingTime, idIsIana);
value!._equivalentZones = new List<TimeZoneInfo>();
lock (value!._equivalentZones)
{
Expand All @@ -1824,7 +1933,7 @@ private static TimeZoneInfoResult TryGetTimeZone(string id, bool dstDisabled, ou
if (zone == null)
{
zone = new TimeZoneInfo(id, value!._baseUtcOffset, value!._displayName, value!._standardDisplayName,
value!._daylightDisplayName, value!._adjustmentRules, dstDisabled && value!._supportsDaylightSavingTime);
value!._daylightDisplayName, value!._adjustmentRules, dstDisabled && value!._supportsDaylightSavingTime, idIsIana);
lock (value!._equivalentZones)
{
value!._equivalentZones.Add(zone);
Expand Down Expand Up @@ -1861,7 +1970,7 @@ private static TimeZoneInfoResult TryGetTimeZoneUsingId(string id, bool dstDisab
else
{
value = new TimeZoneInfo(match._id, match._baseUtcOffset, match._displayName, match._standardDisplayName,
match._daylightDisplayName, match._adjustmentRules, disableDaylightSavingTime: false);
match._daylightDisplayName, match._adjustmentRules, disableDaylightSavingTime: false, match.HasIanaId);
}

return result;
Expand Down
4 changes: 4 additions & 0 deletions src/libraries/System.Runtime/ref/System.Runtime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3851,6 +3851,7 @@ internal TimeZoneInfo() { }
public System.TimeSpan BaseUtcOffset { get { throw null; } }
public string DaylightName { get { throw null; } }
public string DisplayName { get { throw null; } }
public bool HasIanaId { get; }
public string Id { get { throw null; } }
public static System.TimeZoneInfo Local { get { throw null; } }
public string StandardName { get { throw null; } }
Expand Down Expand Up @@ -3890,6 +3891,9 @@ void System.Runtime.Serialization.IDeserializationCallback.OnDeserialization(obj
void System.Runtime.Serialization.ISerializable.GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { }
public string ToSerializedString() { throw null; }
public override string ToString() { throw null; }
public static bool TryConvertIanaIdToWindowsId(string ianaId, out string windowsId) { throw null; }
tarekgh marked this conversation as resolved.
Show resolved Hide resolved
public static bool TryConvertWindowsIdToIanaId(string windowsId, out string? ianaId) { throw null; }
public static bool TryConvertWindowsIdToIanaId(string windowsId, string? region, out string? ianaId) { throw null; }
public sealed partial class AdjustmentRule : System.IEquatable<System.TimeZoneInfo.AdjustmentRule?>, System.Runtime.Serialization.IDeserializationCallback, System.Runtime.Serialization.ISerializable
{
internal AdjustmentRule() { }
Expand Down
Loading