Skip to content

Commit 2b09eb6

Browse files
[WASM] Use consistent display names for UTC time zone (#50650)
1 parent b3d4eb2 commit 2b09eb6

File tree

6 files changed

+68
-23
lines changed

6 files changed

+68
-23
lines changed

src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.FullGlobalizationData.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ namespace System
77
{
88
public sealed partial class TimeZoneInfo
99
{
10+
private const string InvariantUtcStandardDisplayName = "Coordinated Universal Time";
1011
private const string FallbackCultureName = "en-US";
1112
private const string GmtId = "GMT";
1213

@@ -53,6 +54,12 @@ private static string GetUtcStandardDisplayName()
5354
return standardDisplayName;
5455
}
5556

57+
// Helper function to get the full display name for the UTC static time zone instance
58+
private static string GetUtcFullDisplayName(string timeZoneId, string standardDisplayName)
59+
{
60+
return $"(UTC) {standardDisplayName}";
61+
}
62+
5663
// Helper function that retrieves various forms of time zone display names from ICU
5764
private static unsafe void GetDisplayName(string timeZoneId, Interop.Globalization.TimeZoneDisplayNameType nameType, string uiCulture, ref string? displayName)
5865
{

src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.MinimalGlobalizationData.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,14 @@ private static void TryPopulateTimeZoneDisplayNamesFromGlobalizationData(string
1212

1313
private static string GetUtcStandardDisplayName()
1414
{
15-
// Just use the invariant display name.
16-
return InvariantUtcStandardDisplayName;
15+
// For this target, be consistent with other time zone display names that use an abbreviation.
16+
return "UTC";
17+
}
18+
19+
private static string GetUtcFullDisplayName(string timeZoneId, string standardDisplayName)
20+
{
21+
// For this target, be consistent with other time zone display names that use the ID.
22+
return $"(UTC) {timeZoneId}";
1723
}
1824

1925
private static string? GetAlternativeId(string id)

src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ private TimeZoneInfo(byte[] data, string id, bool dstDisabled)
4646
{
4747
_standardDisplayName = GetUtcStandardDisplayName();
4848
_daylightDisplayName = _standardDisplayName;
49-
_displayName = $"(UTC) {_standardDisplayName}";
49+
_displayName = GetUtcFullDisplayName(_id, _standardDisplayName);
5050
_baseUtcOffset = TimeSpan.Zero;
5151
_adjustmentRules = Array.Empty<AdjustmentRule>();
5252
return;

src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Win32.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ public sealed partial class TimeZoneInfo
3434
private const string LastEntryValue = "LastEntry";
3535

3636
private const int MaxKeyLength = 255;
37+
private const string InvariantUtcStandardDisplayName = "Coordinated Universal Time";
3738

3839
private sealed partial class CachedData
3940
{
@@ -1038,5 +1039,11 @@ private static string GetUtcStandardDisplayName()
10381039

10391040
return standardDisplayName;
10401041
}
1042+
1043+
// Helper function to get the full display name for the UTC static time zone instance
1044+
private static string GetUtcFullDisplayName(string timeZoneId, string standardDisplayName)
1045+
{
1046+
return $"(UTC) {standardDisplayName}";
1047+
}
10411048
}
10421049
}

src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ private enum TimeZoneInfoResult
5252
// constants for TimeZoneInfo.Local and TimeZoneInfo.Utc
5353
private const string UtcId = "UTC";
5454
private const string LocalId = "Local";
55-
private const string InvariantUtcStandardDisplayName = "Coordinated Universal Time";
5655

5756
private static readonly TimeZoneInfo s_utcTimeZone = CreateUtcTimeZone();
5857

@@ -2020,7 +2019,7 @@ private static bool IsValidAdjustmentRuleOffset(TimeSpan baseUtcOffset, Adjustme
20202019
private static TimeZoneInfo CreateUtcTimeZone()
20212020
{
20222021
string standardDisplayName = GetUtcStandardDisplayName();
2023-
string displayName = $"(UTC) {standardDisplayName}";
2022+
string displayName = GetUtcFullDisplayName(UtcId, standardDisplayName);
20242023
return CreateCustomTimeZone(UtcId, TimeSpan.Zero, displayName, standardDisplayName);
20252024
}
20262025
}

src/libraries/System.Runtime/tests/System/TimeZoneInfoTests.cs

Lines changed: 44 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2335,14 +2335,55 @@ public static IEnumerable<object[]> SystemTimeZonesTestData()
23352335
private const string IanaAbbreviationPattern = @"^(?:[A-Z][A-Za-z]+|[+-]\d{2}|[+-]\d{4})$";
23362336
private static readonly Regex s_IanaAbbreviationRegex = new Regex(IanaAbbreviationPattern);
23372337

2338+
// UTC aliases per https://github.com/unicode-org/cldr/blob/master/common/bcp47/timezone.xml
2339+
// (This list is not likely to change.)
2340+
private static readonly string[] s_UtcAliases = new[] {
2341+
"Etc/UTC",
2342+
"Etc/UCT",
2343+
"Etc/Universal",
2344+
"Etc/Zulu",
2345+
"UCT",
2346+
"UTC",
2347+
"Universal",
2348+
"Zulu"
2349+
};
2350+
23382351
[Theory]
23392352
[MemberData(nameof(SystemTimeZonesTestData))]
23402353
[PlatformSpecific(TestPlatforms.AnyUnix)]
23412354
public static void TimeZoneDisplayNames_Unix(TimeZoneInfo timeZone)
23422355
{
2343-
if (timeZone.Id == TimeZoneInfo.Utc.Id || timeZone.StandardName == TimeZoneInfo.Utc.StandardName)
2356+
bool isUtc = s_UtcAliases.Contains(timeZone.Id, StringComparer.OrdinalIgnoreCase);
2357+
2358+
if (PlatformDetection.IsBrowser)
23442359
{
2345-
// UTC's display name is always the string "(UTC) " and the same text as the standard name.
2360+
// Browser platform doesn't have full ICU names, but uses the IANA IDs and abbreviations instead.
2361+
2362+
// The display name will be the offset plus the ID.
2363+
// The offset is checked separately in TimeZoneInfo_DisplayNameStartsWithOffset
2364+
Assert.True(timeZone.DisplayName.EndsWith(" " + timeZone.Id),
2365+
$"Id: \"{timeZone.Id}\", DisplayName should have ended with the ID, Actual DisplayName: \"{timeZone.DisplayName}\"");
2366+
2367+
if (isUtc)
2368+
{
2369+
// Make sure UTC and its aliases have exactly "UTC" for the standard and daylight names
2370+
Assert.True(timeZone.StandardName == "UTC",
2371+
$"Id: \"{timeZone.Id}\", Expected StandardName: \"UTC\", Actual StandardName: \"{timeZone.StandardName}\"");
2372+
Assert.True(timeZone.DaylightName == "UTC",
2373+
$"Id: \"{timeZone.Id}\", Expected DaylightName: \"UTC\", Actual DaylightName: \"{timeZone.DaylightName}\"");
2374+
}
2375+
else
2376+
{
2377+
// For other time zones, match any valid IANA time zone abbreviation, including numeric forms
2378+
Assert.True(s_IanaAbbreviationRegex.IsMatch(timeZone.StandardName),
2379+
$"Id: \"{timeZone.Id}\", StandardName should have matched the pattern @\"{IanaAbbreviationPattern}\", Actual StandardName: \"{timeZone.StandardName}\"");
2380+
Assert.True(s_IanaAbbreviationRegex.IsMatch(timeZone.DaylightName),
2381+
$"Id: \"{timeZone.Id}\", DaylightName should have matched the pattern @\"{IanaAbbreviationPattern}\", Actual DaylightName: \"{timeZone.DaylightName}\"");
2382+
}
2383+
}
2384+
else if (isUtc)
2385+
{
2386+
// UTC's display name is the string "(UTC) " and the same text as the standard name.
23462387
Assert.True(timeZone.DisplayName == $"(UTC) {timeZone.StandardName}",
23472388
$"Id: \"{timeZone.Id}\", Expected DisplayName: \"(UTC) {timeZone.StandardName}\", Actual DisplayName: \"{timeZone.DisplayName}\"");
23482389

@@ -2354,21 +2395,6 @@ public static void TimeZoneDisplayNames_Unix(TimeZoneInfo timeZone)
23542395
Assert.True(timeZone.DaylightName == TimeZoneInfo.Utc.DaylightName,
23552396
$"Id: \"{timeZone.Id}\", Expected DaylightName: \"{TimeZoneInfo.Utc.DaylightName}\", Actual DaylightName: \"{timeZone.DaylightName}\"");
23562397
}
2357-
else if (PlatformDetection.IsBrowser)
2358-
{
2359-
// Browser platform doesn't have full ICU names, but uses the IANA data instead.
2360-
2361-
// The display name will be the offset plus the ID.
2362-
// The offset is checked separately in TimeZoneInfo_DisplayNameStartsWithOffset
2363-
Assert.True(timeZone.DisplayName.EndsWith(" " + timeZone.Id),
2364-
$"Id: \"{timeZone.Id}\", DisplayName should have ended with the ID, Actual DisplayName: \"{timeZone.DisplayName}\"");
2365-
2366-
// Match any valid IANA time zone abbreviation, including numeric forms
2367-
Assert.True(s_IanaAbbreviationRegex.IsMatch(timeZone.StandardName),
2368-
$"Id: \"{timeZone.Id}\", StandardName should have matched the pattern @\"{IanaAbbreviationPattern}\", Actual StandardName: \"{timeZone.StandardName}\"");
2369-
Assert.True(s_IanaAbbreviationRegex.IsMatch(timeZone.DaylightName),
2370-
$"Id: \"{timeZone.Id}\", DaylightName should have matched the pattern @\"{IanaAbbreviationPattern}\", Actual DaylightName: \"{timeZone.DaylightName}\"");
2371-
}
23722398
else
23732399
{
23742400
// All we can really say generically here is that they aren't empty.
@@ -2530,7 +2556,7 @@ public static void TimeZoneInfo_DaylightDeltaIsNoMoreThan12Hours()
25302556
[MemberData(nameof(SystemTimeZonesTestData))]
25312557
public static void TimeZoneInfo_DisplayNameStartsWithOffset(TimeZoneInfo tzi)
25322558
{
2533-
if (tzi.StandardName == TimeZoneInfo.Utc.StandardName)
2559+
if (s_UtcAliases.Contains(tzi.Id, StringComparer.OrdinalIgnoreCase))
25342560
{
25352561
// UTC and all of its aliases (Etc/UTC, and others) start with just "(UTC) "
25362562
Assert.StartsWith("(UTC) ", tzi.DisplayName);

0 commit comments

Comments
 (0)