// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Security;
using System.Text;
using System.Threading;
using Microsoft.Win32;
namespace System
{
public sealed partial class TimeZoneInfo
{
// registry constants for the 'Time Zones' hive
//
private const string TimeZonesRegistryHive = @"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones";
private const string DisplayValue = "Display";
private const string DaylightValue = "Dlt";
private const string StandardValue = "Std";
private const string MuiDisplayValue = "MUI_Display";
private const string MuiDaylightValue = "MUI_Dlt";
private const string MuiStandardValue = "MUI_Std";
private const string TimeZoneInfoValue = "TZI";
private const string FirstEntryValue = "FirstEntry";
private const string LastEntryValue = "LastEntry";
private const int MaxKeyLength = 255;
private const int RegByteLength = 44;
#pragma warning disable 0420
private sealed partial class CachedData
{
private static TimeZoneInfo GetCurrentOneYearLocal()
{
// load the data from the OS
Win32Native.TimeZoneInformation timeZoneInformation;
long result = UnsafeNativeMethods.GetTimeZoneInformation(out timeZoneInformation);
return result == Win32Native.TIME_ZONE_ID_INVALID ?
CreateCustomTimeZone(LocalId, TimeSpan.Zero, LocalId, LocalId) :
GetLocalTimeZoneFromWin32Data(timeZoneInformation, dstDisabled: false);
}
private volatile OffsetAndRule _oneYearLocalFromUtc;
public OffsetAndRule GetOneYearLocalFromUtc(int year)
{
OffsetAndRule oneYearLocFromUtc = _oneYearLocalFromUtc;
if (oneYearLocFromUtc == null || oneYearLocFromUtc.Year != year)
{
TimeZoneInfo currentYear = GetCurrentOneYearLocal();
AdjustmentRule rule = currentYear._adjustmentRules == null ? null : currentYear._adjustmentRules[0];
oneYearLocFromUtc = new OffsetAndRule(year, currentYear.BaseUtcOffset, rule);
_oneYearLocalFromUtc = oneYearLocFromUtc;
}
return oneYearLocFromUtc;
}
}
#pragma warning restore 0420
private sealed class OffsetAndRule
{
public readonly int Year;
public readonly TimeSpan Offset;
public readonly AdjustmentRule Rule;
public OffsetAndRule(int year, TimeSpan offset, AdjustmentRule rule)
{
Year = year;
Offset = offset;
Rule = rule;
}
}
///
/// Returns a cloned array of AdjustmentRule objects
///
public AdjustmentRule[] GetAdjustmentRules()
{
if (_adjustmentRules == null)
{
return Array.Empty();
}
return (AdjustmentRule[])_adjustmentRules.Clone();
}
private static void PopulateAllSystemTimeZones(CachedData cachedData)
{
Debug.Assert(Monitor.IsEntered(cachedData));
using (RegistryKey reg = Registry.LocalMachine.OpenSubKey(TimeZonesRegistryHive, writable: false))
{
if (reg != null)
{
foreach (string keyName in reg.GetSubKeyNames())
{
TimeZoneInfo value;
Exception ex;
TryGetTimeZone(keyName, false, out value, out ex, cachedData); // populate the cache
}
}
}
}
private TimeZoneInfo(Win32Native.TimeZoneInformation zone, bool dstDisabled)
{
if (string.IsNullOrEmpty(zone.StandardName))
{
_id = LocalId; // the ID must contain at least 1 character - initialize _id to "Local"
}
else
{
_id = zone.StandardName;
}
_baseUtcOffset = new TimeSpan(0, -(zone.Bias), 0);
if (!dstDisabled)
{
// only create the adjustment rule if DST is enabled
Win32Native.RegistryTimeZoneInformation regZone = new Win32Native.RegistryTimeZoneInformation(zone);
AdjustmentRule rule = CreateAdjustmentRuleFromTimeZoneInformation(regZone, DateTime.MinValue.Date, DateTime.MaxValue.Date, zone.Bias);
if (rule != null)
{
_adjustmentRules = new AdjustmentRule[1];
_adjustmentRules[0] = rule;
}
}
ValidateTimeZoneInfo(_id, _baseUtcOffset, _adjustmentRules, out _supportsDaylightSavingTime);
_displayName = zone.StandardName;
_standardDisplayName = zone.StandardName;
_daylightDisplayName = zone.DaylightName;
}
///
/// Helper function to check if the current TimeZoneInformation struct does not support DST.
/// This check returns true when the DaylightDate == StandardDate.
/// This check is only meant to be used for "Local".
///
private static bool CheckDaylightSavingTimeNotSupported(Win32Native.TimeZoneInformation timeZone) =>
timeZone.DaylightDate.Year == timeZone.StandardDate.Year &&
timeZone.DaylightDate.Month == timeZone.StandardDate.Month &&
timeZone.DaylightDate.DayOfWeek == timeZone.StandardDate.DayOfWeek &&
timeZone.DaylightDate.Day == timeZone.StandardDate.Day &&
timeZone.DaylightDate.Hour == timeZone.StandardDate.Hour &&
timeZone.DaylightDate.Minute == timeZone.StandardDate.Minute &&
timeZone.DaylightDate.Second == timeZone.StandardDate.Second &&
timeZone.DaylightDate.Milliseconds == timeZone.StandardDate.Milliseconds;
///
/// Converts a Win32Native.RegistryTimeZoneInformation (REG_TZI_FORMAT struct) to an AdjustmentRule.
///
private static AdjustmentRule CreateAdjustmentRuleFromTimeZoneInformation(Win32Native.RegistryTimeZoneInformation timeZoneInformation, DateTime startDate, DateTime endDate, int defaultBaseUtcOffset)
{
bool supportsDst = timeZoneInformation.StandardDate.Month != 0;
if (!supportsDst)
{
if (timeZoneInformation.Bias == defaultBaseUtcOffset)
{
// this rule will not contain any information to be used to adjust dates. just ignore it
return null;
}
return AdjustmentRule.CreateAdjustmentRule(
startDate,
endDate,
TimeSpan.Zero, // no daylight saving transition
TransitionTime.CreateFixedDateRule(DateTime.MinValue, 1, 1),
TransitionTime.CreateFixedDateRule(DateTime.MinValue.AddMilliseconds(1), 1, 1),
new TimeSpan(0, defaultBaseUtcOffset - timeZoneInformation.Bias, 0), // Bias delta is all what we need from this rule
noDaylightTransitions: false);
}
//
// Create an AdjustmentRule with TransitionTime objects
//
TransitionTime daylightTransitionStart;
if (!TransitionTimeFromTimeZoneInformation(timeZoneInformation, out daylightTransitionStart, readStartDate: true))
{
return null;
}
TransitionTime daylightTransitionEnd;
if (!TransitionTimeFromTimeZoneInformation(timeZoneInformation, out daylightTransitionEnd, readStartDate: false))
{
return null;
}
if (daylightTransitionStart.Equals(daylightTransitionEnd))
{
// this happens when the time zone does support DST but the OS has DST disabled
return null;
}
return AdjustmentRule.CreateAdjustmentRule(
startDate,
endDate,
new TimeSpan(0, -timeZoneInformation.DaylightBias, 0),
daylightTransitionStart,
daylightTransitionEnd,
new TimeSpan(0, defaultBaseUtcOffset - timeZoneInformation.Bias, 0),
noDaylightTransitions: false);
}
///
/// Helper function that searches the registry for a time zone entry
/// that matches the TimeZoneInformation struct.
///
private static string FindIdFromTimeZoneInformation(Win32Native.TimeZoneInformation timeZone, out bool dstDisabled)
{
dstDisabled = false;
using (RegistryKey key = Registry.LocalMachine.OpenSubKey(TimeZonesRegistryHive, writable: false))
{
if (key == null)
{
return null;
}
foreach (string keyName in key.GetSubKeyNames())
{
if (TryCompareTimeZoneInformationToRegistry(timeZone, keyName, out dstDisabled))
{
return keyName;
}
}
}
return null;
}
///
/// Helper function for retrieving the local system time zone.
/// May throw COMException, TimeZoneNotFoundException, InvalidTimeZoneException.
/// Assumes cachedData lock is taken.
///
/// A new TimeZoneInfo instance.
private static TimeZoneInfo GetLocalTimeZone(CachedData cachedData)
{
Debug.Assert(Monitor.IsEntered(cachedData));
string id = null;
//
// Try using the "kernel32!GetDynamicTimeZoneInformation" API to get the "id"
//
var dynamicTimeZoneInformation = new Win32Native.DynamicTimeZoneInformation();
// call kernel32!GetDynamicTimeZoneInformation...
long result = UnsafeNativeMethods.GetDynamicTimeZoneInformation(out dynamicTimeZoneInformation);
if (result == Win32Native.TIME_ZONE_ID_INVALID)
{
// return a dummy entry
return CreateCustomTimeZone(LocalId, TimeSpan.Zero, LocalId, LocalId);
}
var timeZoneInformation = new Win32Native.TimeZoneInformation(dynamicTimeZoneInformation);
bool dstDisabled = dynamicTimeZoneInformation.DynamicDaylightTimeDisabled;
// check to see if we can use the key name returned from the API call
if (!string.IsNullOrEmpty(dynamicTimeZoneInformation.TimeZoneKeyName))
{
TimeZoneInfo zone;
Exception ex;
if (TryGetTimeZone(dynamicTimeZoneInformation.TimeZoneKeyName, dstDisabled, out zone, out ex, cachedData) == TimeZoneInfoResult.Success)
{
// successfully loaded the time zone from the registry
return zone;
}
}
// the key name was not returned or it pointed to a bogus entry - search for the entry ourselves
id = FindIdFromTimeZoneInformation(timeZoneInformation, out dstDisabled);
if (id != null)
{
TimeZoneInfo zone;
Exception ex;
if (TryGetTimeZone(id, dstDisabled, out zone, out ex, cachedData) == TimeZoneInfoResult.Success)
{
// successfully loaded the time zone from the registry
return zone;
}
}
// We could not find the data in the registry. Fall back to using
// the data from the Win32 API
return GetLocalTimeZoneFromWin32Data(timeZoneInformation, dstDisabled);
}
///
/// Helper function used by 'GetLocalTimeZone()' - this function wraps a bunch of
/// try/catch logic for handling the TimeZoneInfo private constructor that takes
/// a Win32Native.TimeZoneInformation structure.
///
private static TimeZoneInfo GetLocalTimeZoneFromWin32Data(Win32Native.TimeZoneInformation timeZoneInformation, bool dstDisabled)
{
// first try to create the TimeZoneInfo with the original 'dstDisabled' flag
try
{
return new TimeZoneInfo(timeZoneInformation, dstDisabled);
}
catch (ArgumentException) { }
catch (InvalidTimeZoneException) { }
// if 'dstDisabled' was false then try passing in 'true' as a last ditch effort
if (!dstDisabled)
{
try
{
return new TimeZoneInfo(timeZoneInformation, dstDisabled: true);
}
catch (ArgumentException) { }
catch (InvalidTimeZoneException) { }
}
// the data returned from Windows is completely bogus; return a dummy entry
return CreateCustomTimeZone(LocalId, TimeSpan.Zero, LocalId, LocalId);
}
///
/// Helper function for retrieving a TimeZoneInfo object by .
/// 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'.
///
public static TimeZoneInfo FindSystemTimeZoneById(string id)
{
// 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;
}
if (id == null)
{
throw new ArgumentNullException(nameof(id));
}
else if (id.Length == 0 || id.Length > MaxKeyLength || id.Contains("\0"))
{
throw new TimeZoneNotFoundException(Environment.GetResourceString("TimeZoneNotFound_MissingData", id));
}
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(Environment.GetResourceString("InvalidTimeZone_InvalidRegistryData", id), e);
}
else if (result == TimeZoneInfoResult.SecurityException)
{
throw new SecurityException(Environment.GetResourceString("Security_CannotReadRegistryData", id), e);
}
else
{
throw new TimeZoneNotFoundException(Environment.GetResourceString("TimeZoneNotFound_MissingData", id), e);
}
}
// DateTime.Now fast path that avoids allocating an historically accurate TimeZoneInfo.Local and just creates a 1-year (current year) accurate time zone
internal static TimeSpan GetDateTimeNowUtcOffsetFromUtc(DateTime time, out bool isAmbiguousLocalDst)
{
bool isDaylightSavings = false;
isAmbiguousLocalDst = false;
TimeSpan baseOffset;
int timeYear = time.Year;
OffsetAndRule match = s_cachedData.GetOneYearLocalFromUtc(timeYear);
baseOffset = match.Offset;
if (match.Rule != null)
{
baseOffset = baseOffset + match.Rule.BaseUtcOffsetDelta;
if (match.Rule.HasDaylightSaving)
{
isDaylightSavings = GetIsDaylightSavingsFromUtc(time, timeYear, match.Offset, match.Rule, out isAmbiguousLocalDst, Local);
baseOffset += (isDaylightSavings ? match.Rule.DaylightDelta : TimeSpan.Zero /* FUTURE: rule.StandardDelta */);
}
}
return baseOffset;
}
///
/// Converts a Win32Native.RegistryTimeZoneInformation (REG_TZI_FORMAT struct) to a TransitionTime
/// - When the argument 'readStart' is true the corresponding daylightTransitionTimeStart field is read
/// - When the argument 'readStart' is false the corresponding dayightTransitionTimeEnd field is read
///
private static bool TransitionTimeFromTimeZoneInformation(Win32Native.RegistryTimeZoneInformation timeZoneInformation, out TransitionTime transitionTime, bool readStartDate)
{
//
// SYSTEMTIME -
//
// If the time zone does not support daylight saving time or if the caller needs
// to disable daylight saving time, the wMonth member in the SYSTEMTIME structure
// must be zero. If this date is specified, the DaylightDate value in the
// TIME_ZONE_INFORMATION structure must also be specified. Otherwise, the system
// assumes the time zone data is invalid and no changes will be applied.
//
bool supportsDst = (timeZoneInformation.StandardDate.Month != 0);
if (!supportsDst)
{
transitionTime = default(TransitionTime);
return false;
}
//
// SYSTEMTIME -
//
// * FixedDateRule -
// If the Year member is not zero, the transition date is absolute; it will only occur one time
//
// * FloatingDateRule -
// To select the correct day in the month, set the Year member to zero, the Hour and Minute
// members to the transition time, the DayOfWeek member to the appropriate weekday, and the
// Day member to indicate the occurence of the day of the week within the month (first through fifth).
//
// Using this notation, specify the 2:00a.m. on the first Sunday in April as follows:
// Hour = 2,
// Month = 4,
// DayOfWeek = 0,
// Day = 1.
//
// Specify 2:00a.m. on the last Thursday in October as follows:
// Hour = 2,
// Month = 10,
// DayOfWeek = 4,
// Day = 5.
//
if (readStartDate)
{
//
// read the "daylightTransitionStart"
//
if (timeZoneInformation.DaylightDate.Year == 0)
{
transitionTime = TransitionTime.CreateFloatingDateRule(
new DateTime(1, /* year */
1, /* month */
1, /* day */
timeZoneInformation.DaylightDate.Hour,
timeZoneInformation.DaylightDate.Minute,
timeZoneInformation.DaylightDate.Second,
timeZoneInformation.DaylightDate.Milliseconds),
timeZoneInformation.DaylightDate.Month,
timeZoneInformation.DaylightDate.Day, /* Week 1-5 */
(DayOfWeek)timeZoneInformation.DaylightDate.DayOfWeek);
}
else
{
transitionTime = TransitionTime.CreateFixedDateRule(
new DateTime(1, /* year */
1, /* month */
1, /* day */
timeZoneInformation.DaylightDate.Hour,
timeZoneInformation.DaylightDate.Minute,
timeZoneInformation.DaylightDate.Second,
timeZoneInformation.DaylightDate.Milliseconds),
timeZoneInformation.DaylightDate.Month,
timeZoneInformation.DaylightDate.Day);
}
}
else
{
//
// read the "daylightTransitionEnd"
//
if (timeZoneInformation.StandardDate.Year == 0)
{
transitionTime = TransitionTime.CreateFloatingDateRule(
new DateTime(1, /* year */
1, /* month */
1, /* day */
timeZoneInformation.StandardDate.Hour,
timeZoneInformation.StandardDate.Minute,
timeZoneInformation.StandardDate.Second,
timeZoneInformation.StandardDate.Milliseconds),
timeZoneInformation.StandardDate.Month,
timeZoneInformation.StandardDate.Day, /* Week 1-5 */
(DayOfWeek)timeZoneInformation.StandardDate.DayOfWeek);
}
else
{
transitionTime = TransitionTime.CreateFixedDateRule(
new DateTime(1, /* year */
1, /* month */
1, /* day */
timeZoneInformation.StandardDate.Hour,
timeZoneInformation.StandardDate.Minute,
timeZoneInformation.StandardDate.Second,
timeZoneInformation.StandardDate.Milliseconds),
timeZoneInformation.StandardDate.Month,
timeZoneInformation.StandardDate.Day);
}
}
return true;
}
///
/// Helper function that takes:
/// 1. A string representing a registry key name.
/// 2. A RegistryTimeZoneInformation struct containing the default rule.
/// 3. An AdjustmentRule[] out-parameter.
///
private static bool TryCreateAdjustmentRules(string id, Win32Native.RegistryTimeZoneInformation defaultTimeZoneInformation, out AdjustmentRule[] rules, out Exception e, int defaultBaseUtcOffset)
{
e = null;
try
{
// Optional, Dynamic Time Zone Registry Data
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
//
// HKLM
// Software
// Microsoft
// Windows NT
// CurrentVersion
// Time Zones
//
// Dynamic DST
// * "FirstEntry" REG_DWORD "1980"
// First year in the table. If the current year is less than this value,
// this entry will be used for DST boundaries
// * "LastEntry" REG_DWORD "2038"
// Last year in the table. If the current year is greater than this value,
// this entry will be used for DST boundaries"
// * "" REG_BINARY REG_TZI_FORMAT
// See Win32Native.RegistryTimeZoneInformation
// * "" REG_BINARY REG_TZI_FORMAT
// See Win32Native.RegistryTimeZoneInformation
// * "" REG_BINARY REG_TZI_FORMAT
// See Win32Native.RegistryTimeZoneInformation
//
using (RegistryKey dynamicKey = Registry.LocalMachine.OpenSubKey(TimeZonesRegistryHive + "\\" + id + "\\Dynamic DST", writable: false))
{
if (dynamicKey == null)
{
AdjustmentRule rule = CreateAdjustmentRuleFromTimeZoneInformation(
defaultTimeZoneInformation, DateTime.MinValue.Date, DateTime.MaxValue.Date, defaultBaseUtcOffset);
rules = rule == null ? null : new[] { rule };
return true;
}
//
// loop over all of the "\Dynamic DST" hive entries
//
// read FirstEntry {MinValue - (year1, 12, 31)}
// read MiddleEntry {(yearN, 1, 1) - (yearN, 12, 31)}
// read LastEntry {(yearN, 1, 1) - MaxValue }
// read the FirstEntry and LastEntry key values (ex: "1980", "2038")
int first = (int)dynamicKey.GetValue(FirstEntryValue, -1, RegistryValueOptions.None);
int last = (int)dynamicKey.GetValue(LastEntryValue, -1, RegistryValueOptions.None);
if (first == -1 || last == -1 || first > last)
{
rules = null;
return false;
}
// read the first year entry
Win32Native.RegistryTimeZoneInformation dtzi;
byte[] regValue = dynamicKey.GetValue(first.ToString(CultureInfo.InvariantCulture), null, RegistryValueOptions.None) as byte[];
if (regValue == null || regValue.Length != RegByteLength)
{
rules = null;
return false;
}
dtzi = new Win32Native.RegistryTimeZoneInformation(regValue);
if (first == last)
{
// there is just 1 dynamic rule for this time zone.
AdjustmentRule rule = CreateAdjustmentRuleFromTimeZoneInformation(dtzi, DateTime.MinValue.Date, DateTime.MaxValue.Date, defaultBaseUtcOffset);
rules = rule == null ? null : new[] { rule };
return true;
}
List rulesList = new List(1);
// there are more than 1 dynamic rules for this time zone.
AdjustmentRule firstRule = CreateAdjustmentRuleFromTimeZoneInformation(
dtzi,
DateTime.MinValue.Date, // MinValue
new DateTime(first, 12, 31), // December 31,
defaultBaseUtcOffset);
if (firstRule != null)
{
rulesList.Add(firstRule);
}
// read the middle year entries
for (int i = first + 1; i < last; i++)
{
regValue = dynamicKey.GetValue(i.ToString(CultureInfo.InvariantCulture), null, RegistryValueOptions.None) as byte[];
if (regValue == null || regValue.Length != RegByteLength)
{
rules = null;
return false;
}
dtzi = new Win32Native.RegistryTimeZoneInformation(regValue);
AdjustmentRule middleRule = CreateAdjustmentRuleFromTimeZoneInformation(
dtzi,
new DateTime(i, 1, 1), // January 01,
new DateTime(i, 12, 31), // December 31,
defaultBaseUtcOffset);
if (middleRule != null)
{
rulesList.Add(middleRule);
}
}
// read the last year entry
regValue = dynamicKey.GetValue(last.ToString(CultureInfo.InvariantCulture), null, RegistryValueOptions.None) as byte[];
dtzi = new Win32Native.RegistryTimeZoneInformation(regValue);
if (regValue == null || regValue.Length != RegByteLength)
{
rules = null;
return false;
}
AdjustmentRule lastRule = CreateAdjustmentRuleFromTimeZoneInformation(
dtzi,
new DateTime(last, 1, 1), // January 01,
DateTime.MaxValue.Date, // MaxValue
defaultBaseUtcOffset);
if (lastRule != null)
{
rulesList.Add(lastRule);
}
// convert the ArrayList to an AdjustmentRule array
rules = rulesList.ToArray();
if (rules != null && rules.Length == 0)
{
rules = null;
}
} // end of: using (RegistryKey dynamicKey...
}
catch (InvalidCastException ex)
{
// one of the RegistryKey.GetValue calls could not be cast to an expected value type
rules = null;
e = ex;
return false;
}
catch (ArgumentOutOfRangeException ex)
{
rules = null;
e = ex;
return false;
}
catch (ArgumentException ex)
{
rules = null;
e = ex;
return false;
}
return true;
}
///
/// Helper function that compares the StandardBias and StandardDate portion a
/// TimeZoneInformation struct to a time zone registry entry.
///
private static bool TryCompareStandardDate(Win32Native.TimeZoneInformation timeZone, Win32Native.RegistryTimeZoneInformation registryTimeZoneInfo) =>
timeZone.Bias == registryTimeZoneInfo.Bias &&
timeZone.StandardBias == registryTimeZoneInfo.StandardBias &&
timeZone.StandardDate.Year == registryTimeZoneInfo.StandardDate.Year &&
timeZone.StandardDate.Month == registryTimeZoneInfo.StandardDate.Month &&
timeZone.StandardDate.DayOfWeek == registryTimeZoneInfo.StandardDate.DayOfWeek &&
timeZone.StandardDate.Day == registryTimeZoneInfo.StandardDate.Day &&
timeZone.StandardDate.Hour == registryTimeZoneInfo.StandardDate.Hour &&
timeZone.StandardDate.Minute == registryTimeZoneInfo.StandardDate.Minute &&
timeZone.StandardDate.Second == registryTimeZoneInfo.StandardDate.Second &&
timeZone.StandardDate.Milliseconds == registryTimeZoneInfo.StandardDate.Milliseconds;
///
/// Helper function that compares a TimeZoneInformation struct to a time zone registry entry.
///
private static bool TryCompareTimeZoneInformationToRegistry(Win32Native.TimeZoneInformation timeZone, string id, out bool dstDisabled)
{
dstDisabled = false;
using (RegistryKey key = Registry.LocalMachine.OpenSubKey(TimeZonesRegistryHive + "\\" + id, writable: false))
{
if (key == null)
{
return false;
}
Win32Native.RegistryTimeZoneInformation registryTimeZoneInfo;
byte[] regValue = key.GetValue(TimeZoneInfoValue, null, RegistryValueOptions.None) as byte[];
if (regValue == null || regValue.Length != RegByteLength) return false;
registryTimeZoneInfo = new Win32Native.RegistryTimeZoneInformation(regValue);
//
// first compare the bias and standard date information between the data from the Win32 API
// and the data from the registry...
//
bool result = TryCompareStandardDate(timeZone, registryTimeZoneInfo);
if (!result)
{
return false;
}
result = dstDisabled || CheckDaylightSavingTimeNotSupported(timeZone) ||
//
// since Daylight Saving Time is not "disabled", do a straight comparision between
// the Win32 API data and the registry data ...
//
(timeZone.DaylightBias == registryTimeZoneInfo.DaylightBias &&
timeZone.DaylightDate.Year == registryTimeZoneInfo.DaylightDate.Year &&
timeZone.DaylightDate.Month == registryTimeZoneInfo.DaylightDate.Month &&
timeZone.DaylightDate.DayOfWeek == registryTimeZoneInfo.DaylightDate.DayOfWeek &&
timeZone.DaylightDate.Day == registryTimeZoneInfo.DaylightDate.Day &&
timeZone.DaylightDate.Hour == registryTimeZoneInfo.DaylightDate.Hour &&
timeZone.DaylightDate.Minute == registryTimeZoneInfo.DaylightDate.Minute &&
timeZone.DaylightDate.Second == registryTimeZoneInfo.DaylightDate.Second &&
timeZone.DaylightDate.Milliseconds == registryTimeZoneInfo.DaylightDate.Milliseconds);
// Finally compare the "StandardName" string value...
//
// we do not compare "DaylightName" as this TimeZoneInformation field may contain
// either "StandardName" or "DaylightName" depending on the time of year and current machine settings
//
if (result)
{
string registryStandardName = key.GetValue(StandardValue, string.Empty, RegistryValueOptions.None) as string;
result = string.Equals(registryStandardName, timeZone.StandardName, StringComparison.Ordinal);
}
return result;
}
}
///
/// Helper function for retrieving a localized string resource via MUI.
/// The function expects a string in the form: "@resource.dll, -123"
///
/// "resource.dll" is a language-neutral portable executable (LNPE) file in
/// the %windir%\system32 directory. The OS is queried to find the best-fit
/// localized resource file for this LNPE (ex: %windir%\system32\en-us\resource.dll.mui).
/// If a localized resource file exists, we LoadString resource ID "123" and
/// return it to our caller.
///
private static string TryGetLocalizedNameByMuiNativeResource(string resource)
{
if (string.IsNullOrEmpty(resource))
{
return string.Empty;
}
// parse "@tzres.dll, -100"
//
// filePath = "C:\Windows\System32\tzres.dll"
// resourceId = -100
//
string[] resources = resource.Split(',');
if (resources.Length != 2)
{
return string.Empty;
}
string filePath;
int resourceId;
// get the path to Windows\System32
string system32 = Environment.SystemDirectory;
// trim the string "@tzres.dll" => "tzres.dll"
string tzresDll = resources[0].TrimStart('@');
try
{
filePath = Path.Combine(system32, tzresDll);
}
catch (ArgumentException)
{
// there were probably illegal characters in the path
return string.Empty;
}
if (!int.TryParse(resources[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out resourceId))
{
return string.Empty;
}
resourceId = -resourceId;
try
{
StringBuilder fileMuiPath = StringBuilderCache.Acquire(Path.MaxPath);
fileMuiPath.Length = Path.MaxPath;
int fileMuiPathLength = Path.MaxPath;
int languageLength = 0;
long enumerator = 0;
bool succeeded = UnsafeNativeMethods.GetFileMUIPath(
Win32Native.MUI_PREFERRED_UI_LANGUAGES,
filePath, null /* language */, ref languageLength,
fileMuiPath, ref fileMuiPathLength, ref enumerator);
if (!succeeded)
{
StringBuilderCache.Release(fileMuiPath);
return string.Empty;
}
return TryGetLocalizedNameByNativeResource(StringBuilderCache.GetStringAndRelease(fileMuiPath), resourceId);
}
catch (EntryPointNotFoundException)
{
return string.Empty;
}
}
///
/// Helper function for retrieving a localized string resource via a native resource DLL.
/// The function expects a string in the form: "C:\Windows\System32\en-us\resource.dll"
///
/// "resource.dll" is a language-specific resource DLL.
/// If the localized resource DLL exists, LoadString(resource) is returned.
///
private static string TryGetLocalizedNameByNativeResource(string filePath, int resource)
{
using (SafeLibraryHandle handle =
UnsafeNativeMethods.LoadLibraryEx(filePath, IntPtr.Zero, Win32Native.LOAD_LIBRARY_AS_DATAFILE))
{
if (!handle.IsInvalid)
{
StringBuilder localizedResource = StringBuilderCache.Acquire(Win32Native.LOAD_STRING_MAX_LENGTH);
localizedResource.Length = Win32Native.LOAD_STRING_MAX_LENGTH;
int result = UnsafeNativeMethods.LoadString(handle, resource,
localizedResource, localizedResource.Length);
if (result != 0)
{
return StringBuilderCache.GetStringAndRelease(localizedResource);
}
}
}
return string.Empty;
}
///
/// Helper function for retrieving the DisplayName, StandardName, and DaylightName from the registry
///
/// The function first checks the MUI_ key-values, and if they exist, it loads the strings from the MUI
/// resource dll(s). When the keys do not exist, the function falls back to reading from the standard
/// key-values
///
private static bool TryGetLocalizedNamesByRegistryKey(RegistryKey key, out string displayName, out string standardName, out string daylightName)
{
displayName = string.Empty;
standardName = string.Empty;
daylightName = string.Empty;
// read the MUI_ registry keys
string displayNameMuiResource = key.GetValue(MuiDisplayValue, string.Empty, RegistryValueOptions.None) as string;
string standardNameMuiResource = key.GetValue(MuiStandardValue, string.Empty, RegistryValueOptions.None) as string;
string daylightNameMuiResource = key.GetValue(MuiDaylightValue, string.Empty, RegistryValueOptions.None) as string;
// try to load the strings from the native resource DLL(s)
if (!string.IsNullOrEmpty(displayNameMuiResource))
{
displayName = TryGetLocalizedNameByMuiNativeResource(displayNameMuiResource);
}
if (!string.IsNullOrEmpty(standardNameMuiResource))
{
standardName = TryGetLocalizedNameByMuiNativeResource(standardNameMuiResource);
}
if (!string.IsNullOrEmpty(daylightNameMuiResource))
{
daylightName = TryGetLocalizedNameByMuiNativeResource(daylightNameMuiResource);
}
// fallback to using the standard registry keys
if (string.IsNullOrEmpty(displayName))
{
displayName = key.GetValue(DisplayValue, string.Empty, RegistryValueOptions.None) as string;
}
if (string.IsNullOrEmpty(standardName))
{
standardName = key.GetValue(StandardValue, string.Empty, RegistryValueOptions.None) as string;
}
if (string.IsNullOrEmpty(daylightName))
{
daylightName = key.GetValue(DaylightValue, string.Empty, RegistryValueOptions.None) as string;
}
return true;
}
///
/// Helper function that takes a string representing a registry key name
/// and returns a TimeZoneInfo instance.
///
private static TimeZoneInfoResult TryGetTimeZoneFromLocalMachine(string id, out TimeZoneInfo value, out Exception e)
{
e = null;
// Standard Time Zone Registry Data
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// HKLM
// Software
// Microsoft
// Windows NT
// CurrentVersion
// Time Zones
//
// * STD, REG_SZ "Standard Time Name"
// (For OS installed zones, this will always be English)
// * MUI_STD, REG_SZ "@tzres.dll,-1234"
// Indirect string to localized resource for Standard Time,
// add "%windir%\system32\" after "@"
// * DLT, REG_SZ "Daylight Time Name"
// (For OS installed zones, this will always be English)
// * MUI_DLT, REG_SZ "@tzres.dll,-1234"
// Indirect string to localized resource for Daylight Time,
// add "%windir%\system32\" after "@"
// * Display, REG_SZ "Display Name like (GMT-8:00) Pacific Time..."
// * MUI_Display, REG_SZ "@tzres.dll,-1234"
// Indirect string to localized resource for the Display,
// add "%windir%\system32\" after "@"
// * TZI, REG_BINARY REG_TZI_FORMAT
// See Win32Native.RegistryTimeZoneInformation
//
using (RegistryKey key = Registry.LocalMachine.OpenSubKey(TimeZonesRegistryHive + "\\" + id, writable: false))
{
if (key == null)
{
value = null;
return TimeZoneInfoResult.TimeZoneNotFoundException;
}
Win32Native.RegistryTimeZoneInformation defaultTimeZoneInformation;
byte[] regValue = key.GetValue(TimeZoneInfoValue, null, RegistryValueOptions.None) as byte[];
if (regValue == null || regValue.Length != RegByteLength)
{
// the registry value could not be cast to a byte array
value = null;
return TimeZoneInfoResult.InvalidTimeZoneException;
}
defaultTimeZoneInformation = new Win32Native.RegistryTimeZoneInformation(regValue);
AdjustmentRule[] adjustmentRules;
if (!TryCreateAdjustmentRules(id, defaultTimeZoneInformation, out adjustmentRules, out e, defaultTimeZoneInformation.Bias))
{
value = null;
return TimeZoneInfoResult.InvalidTimeZoneException;
}
string displayName;
string standardName;
string daylightName;
if (!TryGetLocalizedNamesByRegistryKey(key, out displayName, out standardName, out daylightName))
{
value = null;
return TimeZoneInfoResult.InvalidTimeZoneException;
}
try
{
value = new TimeZoneInfo(
id,
new TimeSpan(0, -(defaultTimeZoneInformation.Bias), 0),
displayName,
standardName,
daylightName,
adjustmentRules,
disableDaylightSavingTime: false);
return TimeZoneInfoResult.Success;
}
catch (ArgumentException ex)
{
// TimeZoneInfo constructor can throw ArgumentException and InvalidTimeZoneException
value = null;
e = ex;
return TimeZoneInfoResult.InvalidTimeZoneException;
}
catch (InvalidTimeZoneException ex)
{
// TimeZoneInfo constructor can throw ArgumentException and InvalidTimeZoneException
value = null;
e = ex;
return TimeZoneInfoResult.InvalidTimeZoneException;
}
}
}
}
}