// The .NET Foundation licenses this file to you under the MIT license.
using System.Globalization;
+using System.Threading;
+using System.Diagnostics;
namespace System
{
"Pacific/Pitcairn" // Prefer "Pitcairn Islands Time" over "Pitcairn Time"
};
- // Main function that is called during construction to populate the three display names
- private static void TryPopulateTimeZoneDisplayNamesFromGlobalizationData(string timeZoneId, TimeSpan baseUtcOffset, ref string? standardDisplayName, ref string? daylightDisplayName, ref string? displayName)
- {
- if (GlobalizationMode.Invariant)
- {
- return;
- }
-
- // Determine the culture to use
- CultureInfo uiCulture = CultureInfo.CurrentUICulture;
- if (uiCulture.Name.Length == 0)
- uiCulture = CultureInfo.GetCultureInfo(FallbackCultureName); // ICU doesn't work nicely with InvariantCulture
-
- // Attempt to populate the fields backing the StandardName, DaylightName, and DisplayName from globalization data.
- GetDisplayName(timeZoneId, Interop.Globalization.TimeZoneDisplayNameType.Standard, uiCulture.Name, ref standardDisplayName);
- GetDisplayName(timeZoneId, Interop.Globalization.TimeZoneDisplayNameType.DaylightSavings, uiCulture.Name, ref daylightDisplayName);
- GetFullValueForDisplayNameField(timeZoneId, baseUtcOffset, uiCulture, ref displayName);
- }
+ private static CultureInfo? _uiCulture;
// Helper function to get the standard display name for the UTC static time zone instance
private static string GetUtcStandardDisplayName()
}
#pragma warning restore IDE0060
+ private static CultureInfo UICulture
+ {
+ get
+ {
+ if (_uiCulture == null)
+ {
+ Debug.Assert(!GlobalizationMode.Invariant);
+ // Determine the culture to use
+ CultureInfo uiCulture = CultureInfo.CurrentUICulture;
+ if (uiCulture.Name.Length == 0)
+ uiCulture = CultureInfo.GetCultureInfo(FallbackCultureName); // ICU doesn't work nicely with InvariantCulture
+
+ Interlocked.CompareExchange(ref _uiCulture, uiCulture, null);
+ }
+
+ return _uiCulture;
+ }
+ }
+
+ private static void GetStandardDisplayName(string timeZoneId, ref string? displayName)
+ {
+ GetDisplayName(timeZoneId, Interop.Globalization.TimeZoneDisplayNameType.Standard, UICulture.Name, ref displayName);
+ }
+
+ private static void GetDaylightDisplayName(string timeZoneId, ref string? displayName)
+ {
+ GetDisplayName(timeZoneId, Interop.Globalization.TimeZoneDisplayNameType.DaylightSavings, UICulture.Name, ref displayName);
+ }
+
// Helper function that retrieves various forms of time zone display names from ICU
private static unsafe void GetDisplayName(string timeZoneId, Interop.Globalization.TimeZoneDisplayNameType nameType, string uiCulture, ref string? displayName)
{
}
// Helper function that builds the value backing the DisplayName field from globalization data.
- private static void GetFullValueForDisplayNameField(string timeZoneId, TimeSpan baseUtcOffset, CultureInfo uiCulture, ref string? displayName)
+ private static void GetFullValueForDisplayNameField(string timeZoneId, TimeSpan baseUtcOffset, ref string? displayName)
{
// There are a few diffent ways we might show the display name depending on the data.
// The algorithm used below should avoid duplicating the same words while still achieving the
// Try to get the generic name for this time zone.
string? genericName = null;
+ CultureInfo uiCulture = UICulture;
GetDisplayName(timeZoneId, Interop.Globalization.TimeZoneDisplayNameType.Generic, uiCulture.Name, ref genericName);
if (genericName == null)
{
public sealed partial class TimeZoneInfo
{
#pragma warning disable IDE0060
- static partial void TryPopulateTimeZoneDisplayNamesFromGlobalizationData(string timeZoneId, TimeSpan baseUtcOffset, ref string? standardDisplayName, ref string? daylightDisplayName, ref string? displayName);
+ static partial void GetFullValueForDisplayNameField(string timeZoneId, TimeSpan baseUtcOffset, ref string? displayName);
+
+ static partial void GetStandardDisplayName(string timeZoneId, ref string? displayName);
+
+ static partial void GetDaylightDisplayName(string timeZoneId, ref string? displayName);
private static string GetUtcStandardDisplayName()
{
{
private const string DefaultTimeZoneDirectory = "/usr/share/zoneinfo/";
- // UTC aliases per https://github.com/unicode-org/cldr/blob/master/common/bcp47/timezone.xml
+ // Set fallback values using abbreviations, base offset, and id
+ // These are expected in environments without time zone globalization data
+ private string? _standardAbbrevName;
+ private string? _daylightAbbrevName;
+
+ // Handle UTC and its aliases per https://github.com/unicode-org/cldr/blob/master/common/bcp47/timezone.xml
// Hard-coded because we need to treat all aliases of UTC the same even when globalization data is not available.
// (This list is not likely to change.)
- private static readonly string[] s_UtcAliases = new[] {
- "Etc/UTC",
- "Etc/UCT",
- "Etc/Universal",
- "Etc/Zulu",
- "UCT",
- "UTC",
- "Universal",
- "Zulu"
- };
+ private static bool IsUtcAlias (string id)
+ {
+ switch ((ushort)id[0])
+ {
+ case 69: // e
+ case 101: // E
+ return string.Equals(id, "Etc/UTC", StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(id, "Etc/Universal", StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(id, "Etc/UTC", StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(id, "Etc/Zulu", StringComparison.OrdinalIgnoreCase);
+ case 85: // u
+ case 117: // U
+ return string.Equals(id, "UCT", StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(id, "UTC", StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(id, "Universal", StringComparison.OrdinalIgnoreCase);
+ case 90: // z
+ case 122: // Z
+ return string.Equals(id, "Zulu", StringComparison.OrdinalIgnoreCase);
+ }
+
+ return false;
+ }
private TimeZoneInfo(byte[] data, string id, bool dstDisabled)
{
HasIanaId = true;
- // Handle UTC and its aliases
- if (StringArrayContains(_id, s_UtcAliases, StringComparison.OrdinalIgnoreCase))
+ if (IsUtcAlias(id))
{
- _standardDisplayName = GetUtcStandardDisplayName();
- _daylightDisplayName = _standardDisplayName;
- _displayName = GetUtcFullDisplayName(_id, _standardDisplayName);
_baseUtcOffset = TimeSpan.Zero;
_adjustmentRules = Array.Empty<AdjustmentRule>();
return;
}
- TZifHead t;
DateTime[] dts;
byte[] typeOfLocalTime;
TZifType[] transitionType;
string zoneAbbreviations;
string? futureTransitionsPosixFormat;
- string? standardAbbrevName = null;
- string? daylightAbbrevName = null;
// parse the raw TZif bytes; this method can throw ArgumentException when the data is malformed.
- TZif_ParseRaw(data, out t, out dts, out typeOfLocalTime, out transitionType, out zoneAbbreviations, out futureTransitionsPosixFormat);
+ TZif_ParseRaw(data, out dts, out typeOfLocalTime, out transitionType, out zoneAbbreviations, out futureTransitionsPosixFormat);
// find the best matching baseUtcOffset and display strings based on the current utcNow value.
// NOTE: read the Standard and Daylight display strings from the tzfile now in case they can't be loaded later
if (!transitionType[type].IsDst)
{
_baseUtcOffset = transitionType[type].UtcOffset;
- standardAbbrevName = TZif_GetZoneAbbreviation(zoneAbbreviations, transitionType[type].AbbreviationIndex);
+ _standardAbbrevName = TZif_GetZoneAbbreviation(zoneAbbreviations, transitionType[type].AbbreviationIndex);
}
else
{
- daylightAbbrevName = TZif_GetZoneAbbreviation(zoneAbbreviations, transitionType[type].AbbreviationIndex);
+ _daylightAbbrevName = TZif_GetZoneAbbreviation(zoneAbbreviations, transitionType[type].AbbreviationIndex);
}
}
if (!transitionType[i].IsDst)
{
_baseUtcOffset = transitionType[i].UtcOffset;
- standardAbbrevName = TZif_GetZoneAbbreviation(zoneAbbreviations, transitionType[i].AbbreviationIndex);
+ _standardAbbrevName = TZif_GetZoneAbbreviation(zoneAbbreviations, transitionType[i].AbbreviationIndex);
}
else
{
- daylightAbbrevName = TZif_GetZoneAbbreviation(zoneAbbreviations, transitionType[i].AbbreviationIndex);
+ _daylightAbbrevName = TZif_GetZoneAbbreviation(zoneAbbreviations, transitionType[i].AbbreviationIndex);
}
}
}
- // Set fallback values using abbreviations, base offset, and id
- // These are expected in environments without time zone globalization data
- _standardDisplayName = standardAbbrevName;
- _daylightDisplayName = daylightAbbrevName ?? standardAbbrevName;
- _displayName = string.Create(null, stackalloc char[256], $"(UTC{(_baseUtcOffset >= TimeSpan.Zero ? '+' : '-')}{_baseUtcOffset:hh\\:mm}) {_id}");
-
- // Try to populate the display names from the globalization data
- TryPopulateTimeZoneDisplayNamesFromGlobalizationData(_id, _baseUtcOffset, ref _standardDisplayName, ref _daylightDisplayName, ref _displayName);
-
// TZif supports seconds-level granularity with offsets but TimeZoneInfo only supports minutes since it aligns
// with DateTimeOffset, SQL Server, and the W3C XML Specification
if (_baseUtcOffset.Ticks % TimeSpan.TicksPerMinute != 0)
return rulesList.ToArray();
}
+ private string? PopulateDisplayName()
+ {
+ if (IsUtcAlias(Id))
+ return GetUtcFullDisplayName(Id, StandardName);
+
+ // Set fallback value using abbreviations, base offset, and id
+ // These are expected in environments without time zone globalization data
+ string? displayName = string.Create(null, stackalloc char[256], $"(UTC{(_baseUtcOffset >= TimeSpan.Zero ? '+' : '-')}{_baseUtcOffset:hh\\:mm}) {_id}");
+ if (GlobalizationMode.Invariant)
+ return displayName;
+
+ GetFullValueForDisplayNameField(Id, BaseUtcOffset, ref displayName);
+
+ return displayName;
+ }
+
+ private string? PopulateStandardDisplayName()
+ {
+ if (IsUtcAlias(Id))
+ return GetUtcStandardDisplayName();
+
+ string? standardDisplayName = _standardAbbrevName;
+ if (GlobalizationMode.Invariant)
+ return standardDisplayName;
+
+ GetStandardDisplayName(Id, ref standardDisplayName);
+
+ return standardDisplayName;
+ }
+
+ private string? PopulateDaylightDisplayName()
+ {
+ if (IsUtcAlias(Id))
+ return StandardName;
+
+ string? daylightDisplayName = _daylightAbbrevName ?? _standardAbbrevName;
+ if (GlobalizationMode.Invariant)
+ return daylightDisplayName;
+
+ GetDaylightDisplayName(Id, ref daylightDisplayName);
+
+ return daylightDisplayName;
+ }
+
private static void PopulateAllSystemTimeZones(CachedData cachedData)
{
Debug.Assert(Monitor.IsEntered(cachedData));
unixTime > DateTimeOffset.UnixMaxSeconds ? DateTime.MaxValue :
DateTimeOffset.FromUnixTimeSeconds(unixTime).UtcDateTime;
- private static void TZif_ParseRaw(byte[] data, out TZifHead t, out DateTime[] dts, out byte[] typeOfLocalTime, out TZifType[] transitionType,
+ private static void TZif_ParseRaw(byte[] data, out DateTime[] dts, out byte[] typeOfLocalTime, out TZifType[] transitionType,
out string zoneAbbreviations, out string? futureTransitionsPosixFormat)
{
futureTransitionsPosixFormat = null;
// read in the 44-byte TZ header containing the count/length fields
//
int index = 0;
- t = new TZifHead(data, index);
+ TZifHead t = new TZifHead(data, index);
index += TZifHead.Length;
int timeValuesLength = 4; // the first version uses 4-bytes to specify times
return (AdjustmentRule[])_adjustmentRules.Clone();
}
+ private static string? PopulateDisplayName()
+ {
+ // Keep window's implementation to populate via constructor
+ // This should not be reached
+ Debug.Assert(false);
+ return null;
+ }
+
+ private static string? PopulateStandardDisplayName()
+ {
+ // Keep window's implementation to populate via constructor
+ // This should not be reached
+ Debug.Assert(false);
+ return null;
+ }
+
+ private static string? PopulateDaylightDisplayName()
+ {
+ // Keep window's implementation to populate via constructor
+ // This should not be reached
+ Debug.Assert(false);
+ return null;
+ }
+
private static void PopulateAllSystemTimeZones(CachedData cachedData)
{
Debug.Assert(Monitor.IsEntered(cachedData));
value = new TimeZoneInfo(
id,
new TimeSpan(0, -(defaultTimeZoneInformation.Bias), 0),
- displayName,
- standardName,
- daylightName,
+ displayName ?? string.Empty,
+ standardName ?? string.Empty,
+ daylightName ?? string.Empty,
adjustmentRules,
disableDaylightSavingTime: false);
private const int MaxKeyLength = 255;
private readonly string _id;
- private readonly string? _displayName;
- private readonly string? _standardDisplayName;
- private readonly string? _daylightDisplayName;
+ private string? _displayName;
+ private string? _standardDisplayName;
+ private string? _daylightDisplayName;
private readonly TimeSpan _baseUtcOffset;
private readonly bool _supportsDaylightSavingTime;
private readonly AdjustmentRule[]? _adjustmentRules;
timeZone = new TimeZoneInfo(
timeZone._id,
timeZone._baseUtcOffset,
- timeZone._displayName,
- timeZone._standardDisplayName,
- timeZone._daylightDisplayName,
+ timeZone.DisplayName,
+ timeZone.StandardName,
+ timeZone.DaylightName,
timeZone._adjustmentRules,
disableDaylightSavingTime: false,
timeZone.HasIanaId);
/// </summary>
public bool HasIanaId { get; }
- public string DisplayName => _displayName ?? string.Empty;
+ public string DisplayName
+ {
+ get
+ {
+ if (_displayName == null)
+ Interlocked.CompareExchange(ref _displayName, PopulateDisplayName(), null);
+
+ return _displayName ?? string.Empty;
+ }
+ }
- public string StandardName => _standardDisplayName ?? string.Empty;
+ public string StandardName
+ {
+ get
+ {
+ if (_standardDisplayName == null)
+ Interlocked.CompareExchange(ref _standardDisplayName, PopulateStandardDisplayName(), null);
- public string DaylightName => _daylightDisplayName ?? string.Empty;
+ return _standardDisplayName ?? string.Empty;
+ }
+ }
+
+ public string DaylightName
+ {
+ get
+ {
+ if (_daylightDisplayName == null)
+ Interlocked.CompareExchange(ref _daylightDisplayName, PopulateDaylightDisplayName(), null);
+
+ return _daylightDisplayName ?? string.Empty;
+ }
+ }
public TimeSpan BaseUtcOffset => _baseUtcOffset;
_id = id;
_baseUtcOffset = baseUtcOffset;
- _displayName = displayName;
- _standardDisplayName = standardDisplayName;
- _daylightDisplayName = disableDaylightSavingTime ? null : daylightDisplayName;
+ _displayName = displayName ?? string.Empty;
+ _standardDisplayName = standardDisplayName ?? string.Empty;
+ _daylightDisplayName = disableDaylightSavingTime ? string.Empty : daylightDisplayName ?? string.Empty;
_supportsDaylightSavingTime = adjustmentRulesSupportDst && !disableDaylightSavingTime;
_adjustmentRules = adjustmentRules;