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 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>
        /// 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 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);
                }
            }
        }
 /// <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 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>
        /// 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);
        }