summaryrefslogtreecommitdiff
path: root/src/mscorlib/src/System/TimeZoneInfo.Win32.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/mscorlib/src/System/TimeZoneInfo.Win32.cs')
-rw-r--r--src/mscorlib/src/System/TimeZoneInfo.Win32.cs1023
1 files changed, 1023 insertions, 0 deletions
diff --git a/src/mscorlib/src/System/TimeZoneInfo.Win32.cs b/src/mscorlib/src/System/TimeZoneInfo.Win32.cs
new file mode 100644
index 0000000000..79ee535505
--- /dev/null
+++ b/src/mscorlib/src/System/TimeZoneInfo.Win32.cs
@@ -0,0 +1,1023 @@
+// 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;
+ }
+ }
+
+ /// <summary>
+ /// Returns a cloned array of AdjustmentRule objects
+ /// </summary>
+ public AdjustmentRule[] GetAdjustmentRules()
+ {
+ if (_adjustmentRules == null)
+ {
+ return Array.Empty<AdjustmentRule>();
+ }
+
+ 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;
+ }
+
+ /// <summary>
+ /// 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".
+ /// </summary>
+ 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;
+
+ /// <summary>
+ /// Converts a Win32Native.RegistryTimeZoneInformation (REG_TZI_FORMAT struct) to an AdjustmentRule.
+ /// </summary>
+ 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);
+ }
+
+ /// <summary>
+ /// Helper function that searches the registry for a time zone entry
+ /// that matches the TimeZoneInformation struct.
+ /// </summary>
+ 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;
+ }
+
+ /// <summary>
+ /// Helper function for retrieving the local system time zone.
+ /// May throw COMException, TimeZoneNotFoundException, InvalidTimeZoneException.
+ /// Assumes cachedData lock is taken.
+ /// </summary>
+ /// <returns>A new TimeZoneInfo instance.</returns>
+ 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);
+ }
+
+ /// <summary>
+ /// 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.
+ /// </summary>
+ 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);
+ }
+
+ /// <summary>
+ /// Helper function for retrieving a TimeZoneInfo object by <time_zone_name>.
+ /// 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'.
+ /// </summary>
+ 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;
+ }
+
+ /// <summary>
+ /// 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
+ /// </summary>
+ 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;
+ }
+
+ /// <summary>
+ /// Helper function that takes:
+ /// 1. A string representing a <time_zone_name> registry key name.
+ /// 2. A RegistryTimeZoneInformation struct containing the default rule.
+ /// 3. An AdjustmentRule[] out-parameter.
+ /// </summary>
+ 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
+ // <time_zone_name>
+ // 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"
+ // * "<year1>" REG_BINARY REG_TZI_FORMAT
+ // See Win32Native.RegistryTimeZoneInformation
+ // * "<year2>" REG_BINARY REG_TZI_FORMAT
+ // See Win32Native.RegistryTimeZoneInformation
+ // * "<year3>" 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 "<time_zone_name>\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<AdjustmentRule> rulesList = new List<AdjustmentRule>(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, <FirstYear>
+ 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, <Year>
+ new DateTime(i, 12, 31), // December 31, <Year>
+ 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, <LastYear>
+ 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;
+ }
+
+ /// <summary>
+ /// Helper function that compares the StandardBias and StandardDate portion a
+ /// TimeZoneInformation struct to a time zone registry entry.
+ /// </summary>
+ 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;
+
+ /// <summary>
+ /// Helper function that compares a TimeZoneInformation struct to a time zone registry entry.
+ /// </summary>
+ 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;
+ }
+ }
+
+ /// <summary>
+ /// 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.
+ /// </summary>
+ 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;
+ }
+ }
+
+ /// <summary>
+ /// 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.
+ /// </summary>
+ 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;
+ }
+
+ /// <summary>
+ /// 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
+ /// </summary>
+ 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;
+ }
+
+ /// <summary>
+ /// Helper function that takes a string representing a <time_zone_name> registry key name
+ /// and returns a TimeZoneInfo instance.
+ /// </summary>
+ 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
+ // <time_zone_name>
+ // * 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;
+ }
+
+ }
+ }
+ }
+}