Remove ifdefs from TimeZoneInfo
authorJustin Van Patten <jvp@justinvp.com>
Tue, 27 Dec 2016 22:49:32 +0000 (14:49 -0800)
committerJustin Van Patten <jvp@justinvp.com>
Tue, 27 Dec 2016 22:49:32 +0000 (14:49 -0800)
Move Win32 and Unix -specific implementation details to their own files.

src/mscorlib/mscorlib.shared.sources.props
src/mscorlib/src/System/TimeZoneInfo.Unix.cs [new file with mode: 0644]
src/mscorlib/src/System/TimeZoneInfo.Win32.cs [new file with mode: 0644]
src/mscorlib/src/System/TimeZoneInfo.cs

index 28d77b5..17783db 100644 (file)
     <SystemSources Condition="'$(FeatureSpanOfT)' == 'true'" Include="$(BclSourcesRoot)\System\Span.cs" />
     <SystemSources Condition="'$(FeatureSpanOfT)' == 'true'" Include="$(BclSourcesRoot)\System\ReadOnlySpan.cs" />
   </ItemGroup>
+  <ItemGroup Condition="'$(TargetsUnix)' == 'true'">
+    <SystemSources Include="$(BclSourcesRoot)\System\TimeZoneInfo.Unix.cs" />
+  </ItemGroup>
+  <ItemGroup Condition="'$(TargetsUnix)' != 'true'">
+    <SystemSources Include="$(BclSourcesRoot)\System\TimeZoneInfo.Win32.cs" />
+  </ItemGroup>
   <ItemGroup>
     <InternalSources Condition="'$(FeatureCoreclr)' == 'true'" Include="$(BclSourcesRoot)\Internal\Runtime\Augments\EnvironmentAugments.cs" />
     <InternalSources Include="$(BclSourcesRoot)\Internal\Runtime\Augments\RuntimeThread.cs" />
diff --git a/src/mscorlib/src/System/TimeZoneInfo.Unix.cs b/src/mscorlib/src/System/TimeZoneInfo.Unix.cs
new file mode 100644 (file)
index 0000000..1be080d
--- /dev/null
@@ -0,0 +1,1460 @@
+// 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.Diagnostics;
+using System.Diagnostics.Contracts;
+using System.Globalization;
+using System.IO;
+using System.Text;
+using System.Threading;
+using System.Security;
+
+namespace System
+{
+    public sealed partial class TimeZoneInfo
+    {
+        private const string DefaultTimeZoneDirectory = "/usr/share/zoneinfo/";
+        private const string ZoneTabFileName = "zone.tab";
+        private const string TimeZoneEnvironmentVariable = "TZ";
+        private const string TimeZoneDirectoryEnvironmentVariable = "TZDIR";
+
+        private TimeZoneInfo(byte[] data, string id, bool dstDisabled)
+        {
+            TZifHead t;
+            DateTime[] dts;
+            byte[] typeOfLocalTime;
+            TZifType[] transitionType;
+            string zoneAbbreviations;
+            bool[] StandardTime;
+            bool[] GmtTime;
+            string futureTransitionsPosixFormat;
+
+            // parse the raw TZif bytes; this method can throw ArgumentException when the data is malformed.
+            TZif_ParseRaw(data, out t, out dts, out typeOfLocalTime, out transitionType, out zoneAbbreviations, out StandardTime, out GmtTime, out futureTransitionsPosixFormat);
+
+            _id = id;
+            _displayName = LocalId;
+            _baseUtcOffset = TimeSpan.Zero;
+
+            // find the best matching baseUtcOffset and display strings based on the current utcNow value.
+            // NOTE: read the display strings from the the tzfile now in case they can't be loaded later
+            // from the globalization data.
+            DateTime utcNow = DateTime.UtcNow;
+            for (int i = 0; i < dts.Length && dts[i] <= utcNow; i++)
+            {
+                int type = typeOfLocalTime[i];
+                if (!transitionType[type].IsDst)
+                {
+                    _baseUtcOffset = transitionType[type].UtcOffset;
+                    _standardDisplayName = TZif_GetZoneAbbreviation(zoneAbbreviations, transitionType[type].AbbreviationIndex);
+                }
+                else
+                {
+                    _daylightDisplayName = TZif_GetZoneAbbreviation(zoneAbbreviations, transitionType[type].AbbreviationIndex);
+                }
+            }
+
+            if (dts.Length == 0)
+            {
+                // time zones like Africa/Bujumbura and Etc/GMT* have no transition times but still contain
+                // TZifType entries that may contain a baseUtcOffset and display strings
+                for (int i = 0; i < transitionType.Length; i++)
+                {
+                    if (!transitionType[i].IsDst)
+                    {
+                        _baseUtcOffset = transitionType[i].UtcOffset;
+                        _standardDisplayName = TZif_GetZoneAbbreviation(zoneAbbreviations, transitionType[i].AbbreviationIndex);
+                    }
+                    else
+                    {
+                        _daylightDisplayName = TZif_GetZoneAbbreviation(zoneAbbreviations, transitionType[i].AbbreviationIndex);
+                    }
+                }
+            }
+            _displayName = _standardDisplayName;
+
+            GetDisplayName(Interop.GlobalizationInterop.TimeZoneDisplayNameType.Generic, ref _displayName);
+            GetDisplayName(Interop.GlobalizationInterop.TimeZoneDisplayNameType.Standard, ref _standardDisplayName);
+            GetDisplayName(Interop.GlobalizationInterop.TimeZoneDisplayNameType.DaylightSavings, ref _daylightDisplayName);
+
+            // TZif supports seconds-level granularity with offsets but TimeZoneInfo only supports minutes since it aligns
+            // with DateTimeOffset, SQL Server, and the W3C XML Specification
+            if (_baseUtcOffset.Ticks % TimeSpan.TicksPerMinute != 0)
+            {
+                _baseUtcOffset = new TimeSpan(_baseUtcOffset.Hours, _baseUtcOffset.Minutes, 0);
+            }
+
+            if (!dstDisabled)
+            {
+                // only create the adjustment rule if DST is enabled
+                TZif_GenerateAdjustmentRules(out _adjustmentRules, _baseUtcOffset, dts, typeOfLocalTime, transitionType, StandardTime, GmtTime, futureTransitionsPosixFormat);
+            }
+
+            ValidateTimeZoneInfo(_id, _baseUtcOffset, _adjustmentRules, out _supportsDaylightSavingTime);
+        }
+
+        private void GetDisplayName(Interop.GlobalizationInterop.TimeZoneDisplayNameType nameType, ref string displayName)
+        {
+            string timeZoneDisplayName;
+            bool result = Interop.CallStringMethod(
+                (locale, id, type, stringBuilder) => Interop.GlobalizationInterop.GetTimeZoneDisplayName(
+                    locale,
+                    id,
+                    type,
+                    stringBuilder,
+                    stringBuilder.Capacity),
+                CultureInfo.CurrentUICulture.Name,
+                _id,
+                nameType,
+                out timeZoneDisplayName);
+
+            // If there is an unknown error, don't set the displayName field.
+            // It will be set to the abbreviation that was read out of the tzfile.
+            if (result)
+            {
+                displayName = timeZoneDisplayName;
+            }
+        }
+
+        /// <summary>
+        /// Returns a cloned array of AdjustmentRule objects
+        /// </summary>
+        public AdjustmentRule[] GetAdjustmentRules()
+        {
+            if (_adjustmentRules == null)
+            {
+                return Array.Empty<AdjustmentRule>();
+            }
+
+            // The rules we use in Unix cares mostly about the start and end dates but doesn\92t fill the transition start and end info.
+            // as the rules now is public, we should fill it properly so the caller doesn\92t have to know how we use it internally
+            // and can use it as it is used in Windows
+
+            AdjustmentRule[] rules = new AdjustmentRule[_adjustmentRules.Length];
+
+            for (int i = 0; i < _adjustmentRules.Length; i++)
+            {
+                var rule = _adjustmentRules[i];
+                var start = rule.DateStart.Kind == DateTimeKind.Utc ?
+                            new DateTime(TimeZoneInfo.ConvertTime(rule.DateStart, this).Ticks, DateTimeKind.Unspecified) :
+                            rule.DateStart;
+                var end = rule.DateEnd.Kind == DateTimeKind.Utc ?
+                            new DateTime(TimeZoneInfo.ConvertTime(rule.DateEnd, this).Ticks - 1, DateTimeKind.Unspecified) :
+                            rule.DateEnd;
+
+                var startTransition = TimeZoneInfo.TransitionTime.CreateFixedDateRule(new DateTime(1, 1, 1, start.Hour, start.Minute, start.Second), start.Month, start.Day);
+                var endTransition = TimeZoneInfo.TransitionTime.CreateFixedDateRule(new DateTime(1, 1, 1, end.Hour, end.Minute, end.Second), end.Month, end.Day);
+
+                rules[i] = TimeZoneInfo.AdjustmentRule.CreateAdjustmentRule(start.Date, end.Date, rule.DaylightDelta, startTransition, endTransition);
+            }
+
+            return rules;
+        }
+
+        private static void PopulateAllSystemTimeZones(CachedData cachedData)
+        {
+            Debug.Assert(Monitor.IsEntered(cachedData));
+
+            string timeZoneDirectory = GetTimeZoneDirectory();
+            foreach (string timeZoneId in GetTimeZoneIds(timeZoneDirectory))
+            {
+                TimeZoneInfo value;
+                Exception ex;
+                TryGetTimeZone(timeZoneId, false, out value, out ex, cachedData, alwaysFallbackToLocalMachine: true);  // populate the cache
+            }
+        }
+
+        /// <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));
+
+            // Without Registry support, create the TimeZoneInfo from a TZ file
+            return GetLocalTimeZoneFromTzFile();
+        }
+
+        private static TimeZoneInfoResult TryGetTimeZoneFromLocalMachine(string id, out TimeZoneInfo value, out Exception e)
+        {
+            value = null;
+            e = null;
+
+            string timeZoneDirectory = GetTimeZoneDirectory();
+            string timeZoneFilePath = Path.Combine(timeZoneDirectory, id);
+            byte[] rawData;
+            try
+            {
+                rawData = File.ReadAllBytes(timeZoneFilePath);
+            }
+            catch (UnauthorizedAccessException ex)
+            {
+                e = ex;
+                return TimeZoneInfoResult.SecurityException;
+            }
+            catch (FileNotFoundException ex)
+            {
+                e = ex;
+                return TimeZoneInfoResult.TimeZoneNotFoundException;
+            }
+            catch (DirectoryNotFoundException ex)
+            {
+                e = ex;
+                return TimeZoneInfoResult.TimeZoneNotFoundException;
+            }
+            catch (IOException ex)
+            {
+                e = new InvalidTimeZoneException(Environment.GetResourceString("InvalidTimeZone_InvalidFileData", id, timeZoneFilePath), ex);
+                return TimeZoneInfoResult.InvalidTimeZoneException;
+            }
+
+            value = GetTimeZoneFromTzData(rawData, id);
+
+            if (value == null)
+            {
+                e = new InvalidTimeZoneException(Environment.GetResourceString("InvalidTimeZone_InvalidFileData", id, timeZoneFilePath));
+                return TimeZoneInfoResult.InvalidTimeZoneException;
+            }
+
+            return TimeZoneInfoResult.Success;
+        }
+
+        /// <summary>
+        /// Returns a collection of TimeZone Id values from the zone.tab file in the timeZoneDirectory.
+        /// </summary>
+        /// <remarks>
+        /// Lines that start with # are comments and are skipped.
+        /// </remarks>
+        private static IEnumerable<string> GetTimeZoneIds(string timeZoneDirectory)
+        {
+            string[] zoneTabFileLines = null;
+            try
+            {
+                zoneTabFileLines = File.ReadAllLines(Path.Combine(timeZoneDirectory, ZoneTabFileName));
+            }
+            catch (IOException) { }
+            catch (UnauthorizedAccessException) { }
+
+            List<string> timeZoneIds = new List<string>();
+            if (zoneTabFileLines != null)
+            {
+                foreach (string zoneTabFileLine in zoneTabFileLines)
+                {
+                    if (!string.IsNullOrEmpty(zoneTabFileLine) && !zoneTabFileLine.StartsWith("#"))
+                    {
+                        // the format of the line is "country-code \t coordinates \t TimeZone Id \t comments"
+
+                        int firstTabIndex = zoneTabFileLine.IndexOf('\t');
+                        if (firstTabIndex != -1)
+                        {
+                            int secondTabIndex = zoneTabFileLine.IndexOf('\t', firstTabIndex + 1);
+                            if (secondTabIndex != -1)
+                            {
+                                string timeZoneId;
+                                int startIndex = secondTabIndex + 1;
+                                int thirdTabIndex = zoneTabFileLine.IndexOf('\t', startIndex);
+                                if (thirdTabIndex != -1)
+                                {
+                                    int length = thirdTabIndex - startIndex;
+                                    timeZoneId = zoneTabFileLine.Substring(startIndex, length);
+                                }
+                                else
+                                {
+                                    timeZoneId = zoneTabFileLine.Substring(startIndex);
+                                }
+
+                                if (!string.IsNullOrEmpty(timeZoneId))
+                                {
+                                    timeZoneIds.Add(timeZoneId);
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+
+            return timeZoneIds;
+        }
+
+        /// <summary>
+        /// Gets the tzfile raw data for the current 'local' time zone using the following rules.
+        /// 1. Read the TZ environment variable.  If it is set, use it.
+        /// 2. Look for the data in /etc/localtime.
+        /// 3. Look for the data in GetTimeZoneDirectory()/localtime.
+        /// 4. Use UTC if all else fails.
+        /// </summary>
+        private static bool TryGetLocalTzFile(out byte[] rawData, out string id)
+        {
+            rawData = null;
+            id = null;
+            string tzVariable = GetTzEnvironmentVariable();
+
+            // If the env var is null, use the localtime file
+            if (tzVariable == null)
+            {
+                return
+                    TryLoadTzFile("/etc/localtime", ref rawData, ref id) ||
+                    TryLoadTzFile(Path.Combine(GetTimeZoneDirectory(), "localtime"), ref rawData, ref id);
+            }
+
+            // If it's empty, use UTC (TryGetLocalTzFile() should return false).
+            if (tzVariable.Length == 0)
+            {
+                return false;
+            }
+
+            // Otherwise, use the path from the env var.  If it's not absolute, make it relative
+            // to the system timezone directory
+            string tzFilePath;
+            if (tzVariable[0] != '/')
+            {
+                id = tzVariable;
+                tzFilePath = Path.Combine(GetTimeZoneDirectory(), tzVariable);
+            }
+            else
+            {
+                tzFilePath = tzVariable;
+            }
+            return TryLoadTzFile(tzFilePath, ref rawData, ref id);
+        }
+
+        private static string GetTzEnvironmentVariable()
+        {
+            string result = Environment.GetEnvironmentVariable(TimeZoneEnvironmentVariable);
+            if (!string.IsNullOrEmpty(result))
+            {
+                if (result[0] == ':')
+                {
+                    // strip off the ':' prefix
+                    result = result.Substring(1);
+                }
+            }
+
+            return result;
+        }
+
+        private static bool TryLoadTzFile(string tzFilePath, ref byte[] rawData, ref string id)
+        {
+            if (File.Exists(tzFilePath))
+            {
+                try
+                {
+                    rawData = File.ReadAllBytes(tzFilePath);
+                    if (string.IsNullOrEmpty(id))
+                    {
+                        id = FindTimeZoneIdUsingReadLink(tzFilePath);
+
+                        if (string.IsNullOrEmpty(id))
+                        {
+                            id = FindTimeZoneId(rawData);
+                        }
+                    }
+                    return true;
+                }
+                catch (IOException) { }
+                catch (SecurityException) { }
+                catch (UnauthorizedAccessException) { }
+            }
+            return false;
+        }
+
+        /// <summary>
+        /// Finds the time zone id by using 'readlink' on the path to see if tzFilePath is
+        /// a symlink to a file.
+        /// </summary>
+        private static string FindTimeZoneIdUsingReadLink(string tzFilePath)
+        {
+            string id = null;
+
+            StringBuilder symlinkPathBuilder = StringBuilderCache.Acquire(Path.MaxPath);
+            bool result = Interop.GlobalizationInterop.ReadLink(tzFilePath, symlinkPathBuilder, (uint)symlinkPathBuilder.Capacity);
+            if (result)
+            {
+                string symlinkPath = StringBuilderCache.GetStringAndRelease(symlinkPathBuilder);
+                // time zone Ids have to point under the time zone directory
+                string timeZoneDirectory = GetTimeZoneDirectory();
+                if (symlinkPath.StartsWith(timeZoneDirectory))
+                {
+                    id = symlinkPath.Substring(timeZoneDirectory.Length);
+                }
+            }
+            else
+            {
+                StringBuilderCache.Release(symlinkPathBuilder);
+            }
+
+            return id;
+        }
+
+        /// <summary>
+        /// Find the time zone id by searching all the tzfiles for the one that matches rawData
+        /// and return its file name.
+        /// </summary>
+        private static string FindTimeZoneId(byte[] rawData)
+        {
+            // default to "Local" if we can't find the right tzfile
+            string id = LocalId;
+            string timeZoneDirectory = GetTimeZoneDirectory();
+            string localtimeFilePath = Path.Combine(timeZoneDirectory, "localtime");
+            string posixrulesFilePath = Path.Combine(timeZoneDirectory, "posixrules");
+            byte[] buffer = new byte[rawData.Length];
+
+            try
+            {
+                foreach (string filePath in Directory.EnumerateFiles(timeZoneDirectory, "*", SearchOption.AllDirectories))
+                {
+                    // skip the localtime and posixrules file, since they won't give us the correct id
+                    if (!string.Equals(filePath, localtimeFilePath, StringComparison.OrdinalIgnoreCase)
+                        && !string.Equals(filePath, posixrulesFilePath, StringComparison.OrdinalIgnoreCase))
+                    {
+                        if (CompareTimeZoneFile(filePath, buffer, rawData))
+                        {
+                            // if all bytes are the same, this must be the right tz file
+                            id = filePath;
+
+                            // strip off the root time zone directory
+                            if (id.StartsWith(timeZoneDirectory))
+                            {
+                                id = id.Substring(timeZoneDirectory.Length);
+                            }
+                            break;
+                        }
+                    }
+                }
+            }
+            catch (IOException) { }
+            catch (SecurityException) { }
+            catch (UnauthorizedAccessException) { }
+
+            return id;
+        }
+
+        private static bool CompareTimeZoneFile(string filePath, byte[] buffer, byte[] rawData)
+        {
+            try
+            {
+                using (FileStream stream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
+                {
+                    if (stream.Length == rawData.Length)
+                    {
+                        int index = 0;
+                        int count = rawData.Length;
+
+                        while (count > 0)
+                        {
+                            int n = stream.Read(buffer, index, count);
+                            if (n == 0)
+                                __Error.EndOfFile();
+
+                            int end = index + n;
+                            for (; index < end; index++)
+                            {
+                                if (buffer[index] != rawData[index])
+                                {
+                                    return false;
+                                }
+                            }
+
+                            count -= n;
+                        }
+
+                        return true;
+                    }
+                }
+            }
+            catch (IOException) { }
+            catch (SecurityException) { }
+            catch (UnauthorizedAccessException) { }
+
+            return false;
+        }
+
+        /// <summary>
+        /// Helper function used by 'GetLocalTimeZone()' - this function wraps the call
+        /// for loading time zone data from computers without Registry support.
+        ///
+        /// The TryGetLocalTzFile() call returns a Byte[] containing the compiled tzfile.
+        /// </summary>
+        private static TimeZoneInfo GetLocalTimeZoneFromTzFile()
+        {
+            byte[] rawData;
+            string id;
+            if (TryGetLocalTzFile(out rawData, out id))
+            {
+                TimeZoneInfo result = GetTimeZoneFromTzData(rawData, id);
+                if (result != null)
+                {
+                    return result;
+                }
+            }
+
+            // if we can't find a local time zone, return UTC
+            return Utc;
+        }
+
+        private static TimeZoneInfo GetTimeZoneFromTzData(byte[] rawData, string id)
+        {
+            if (rawData != null)
+            {
+                try
+                {
+                    return new TimeZoneInfo(rawData, id, dstDisabled: false); // create a TimeZoneInfo instance from the TZif data w/ DST support
+                }
+                catch (ArgumentException) { }
+                catch (InvalidTimeZoneException) { }
+                try
+                {
+                    return new TimeZoneInfo(rawData, id, dstDisabled: true); // create a TimeZoneInfo instance from the TZif data w/o DST support
+                }
+                catch (ArgumentException) { }
+                catch (InvalidTimeZoneException) { }
+            }
+
+            return null;
+        }
+
+        private static string GetTimeZoneDirectory()
+        {
+            string tzDirectory = Environment.GetEnvironmentVariable(TimeZoneDirectoryEnvironmentVariable);
+
+            if (tzDirectory == null)
+            {
+                tzDirectory = DefaultTimeZoneDirectory;
+            }
+            else if (!tzDirectory.EndsWith(Path.DirectorySeparatorChar))
+            {
+                tzDirectory += Path.DirectorySeparatorChar;
+            }
+
+            return tzDirectory;
+        }
+
+        /// <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.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, alwaysFallbackToLocalMachine: true);
+            }
+
+            if (result == TimeZoneInfoResult.Success)
+            {
+                return value;
+            }
+            else if (result == TimeZoneInfoResult.InvalidTimeZoneException)
+            {
+                Debug.Assert(e is InvalidTimeZoneException,
+                    "TryGetTimeZone must create an InvalidTimeZoneException when it returns TimeZoneInfoResult.InvalidTimeZoneException");
+                throw e;
+            }
+            else if (result == TimeZoneInfoResult.SecurityException)
+            {
+                throw new SecurityException(Environment.GetResourceString("Security_CannotReadFileData", 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;
+            // Use the standard code path for Unix since there isn't a faster way of handling current-year-only time zones
+            return GetUtcOffsetFromUtc(time, Local, out isDaylightSavings, out isAmbiguousLocalDst);
+        }
+
+        // TZFILE(5)                   BSD File Formats Manual                  TZFILE(5)
+        //
+        // NAME
+        //      tzfile -- timezone information
+        //
+        // SYNOPSIS
+        //      #include "/usr/src/lib/libc/stdtime/tzfile.h"
+        //
+        // DESCRIPTION
+        //      The time zone information files used by tzset(3) begin with the magic
+        //      characters ``TZif'' to identify them as time zone information files, fol-
+        //      lowed by sixteen bytes reserved for future use, followed by four four-
+        //      byte values written in a ``standard'' byte order (the high-order byte of
+        //      the value is written first).  These values are, in order:
+        //
+        //      tzh_ttisgmtcnt  The number of UTC/local indicators stored in the file.
+        //      tzh_ttisstdcnt  The number of standard/wall indicators stored in the
+        //                      file.
+        //      tzh_leapcnt     The number of leap seconds for which data is stored in
+        //                      the file.
+        //      tzh_timecnt     The number of ``transition times'' for which data is
+        //                      stored in the file.
+        //      tzh_typecnt     The number of ``local time types'' for which data is
+        //                      stored in the file (must not be zero).
+        //      tzh_charcnt     The number of characters of ``time zone abbreviation
+        //                      strings'' stored in the file.
+        //
+        //      The above header is followed by tzh_timecnt four-byte values of type
+        //      long, sorted in ascending order.  These values are written in ``stan-
+        //      dard'' byte order.  Each is used as a transition time (as returned by
+        //      time(3)) at which the rules for computing local time change.  Next come
+        //      tzh_timecnt one-byte values of type unsigned char; each one tells which
+        //      of the different types of ``local time'' types described in the file is
+        //      associated with the same-indexed transition time.  These values serve as
+        //      indices into an array of ttinfo structures that appears next in the file;
+        //      these structures are defined as follows:
+        //
+        //            struct ttinfo {
+        //                    long    tt_gmtoff;
+        //                    int     tt_isdst;
+        //                    unsigned int    tt_abbrind;
+        //            };
+        //
+        //      Each structure is written as a four-byte value for tt_gmtoff of type
+        //      long, in a standard byte order, followed by a one-byte value for tt_isdst
+        //      and a one-byte value for tt_abbrind.  In each structure, tt_gmtoff gives
+        //      the number of seconds to be added to UTC, tt_isdst tells whether tm_isdst
+        //      should be set by localtime(3) and tt_abbrind serves as an index into the
+        //      array of time zone abbreviation characters that follow the ttinfo struc-
+        //      ture(s) in the file.
+        //
+        //      Then there are tzh_leapcnt pairs of four-byte values, written in standard
+        //      byte order; the first value of each pair gives the time (as returned by
+        //      time(3)) at which a leap second occurs; the second gives the total number
+        //      of leap seconds to be applied after the given time.  The pairs of values
+        //      are sorted in ascending order by time.b
+        //
+        //      Then there are tzh_ttisstdcnt standard/wall indicators, each stored as a
+        //      one-byte value; they tell whether the transition times associated with
+        //      local time types were specified as standard time or wall clock time, and
+        //      are used when a time zone file is used in handling POSIX-style time zone
+        //      environment variables.
+        //
+        //      Finally there are tzh_ttisgmtcnt UTC/local indicators, each stored as a
+        //      one-byte value; they tell whether the transition times associated with
+        //      local time types were specified as UTC or local time, and are used when a
+        //      time zone file is used in handling POSIX-style time zone environment
+        //      variables.
+        //
+        //      localtime uses the first standard-time ttinfo structure in the file (or
+        //      simply the first ttinfo structure in the absence of a standard-time
+        //      structure) if either tzh_timecnt is zero or the time argument is less
+        //      than the first transition time recorded in the file.
+        //
+        // SEE ALSO
+        //      ctime(3), time2posix(3), zic(8)
+        //
+        // BSD                           September 13, 1994                           BSD
+        //
+        //
+        //
+        // TIME(3)                  BSD Library Functions Manual                  TIME(3)
+        //
+        // NAME
+        //      time -- get time of day
+        //
+        // LIBRARY
+        //      Standard C Library (libc, -lc)
+        //
+        // SYNOPSIS
+        //      #include <time.h>
+        //
+        //      time_t
+        //      time(time_t *tloc);
+        //
+        // DESCRIPTION
+        //      The time() function returns the value of time in seconds since 0 hours, 0
+        //      minutes, 0 seconds, January 1, 1970, Coordinated Universal Time, without
+        //      including leap seconds.  If an error occurs, time() returns the value
+        //      (time_t)-1.
+        //
+        //      The return value is also stored in *tloc, provided that tloc is non-null.
+        //
+        // ERRORS
+        //      The time() function may fail for any of the reasons described in
+        //      gettimeofday(2).
+        //
+        // SEE ALSO
+        //      gettimeofday(2), ctime(3)
+        //
+        // STANDARDS
+        //      The time function conforms to IEEE Std 1003.1-2001 (``POSIX.1'').
+        //
+        // BUGS
+        //      Neither ISO/IEC 9899:1999 (``ISO C99'') nor IEEE Std 1003.1-2001
+        //      (``POSIX.1'') requires time() to set errno on failure; thus, it is impos-
+        //      sible for an application to distinguish the valid time value -1 (repre-
+        //      senting the last UTC second of 1969) from the error return value.
+        //
+        //      Systems conforming to earlier versions of the C and POSIX standards
+        //      (including older versions of FreeBSD) did not set *tloc in the error
+        //      case.
+        //
+        // HISTORY
+        //      A time() function appeared in Version 6 AT&T UNIX.
+        //
+        // BSD                              July 18, 2003                             BSD
+        //
+        //
+        private static void TZif_GenerateAdjustmentRules(out AdjustmentRule[] rules, TimeSpan baseUtcOffset, DateTime[] dts, byte[] typeOfLocalTime,
+            TZifType[] transitionType, bool[] StandardTime, bool[] GmtTime, string futureTransitionsPosixFormat)
+        {
+            rules = null;
+
+            if (dts.Length > 0)
+            {
+                int index = 0;
+                List<AdjustmentRule> rulesList = new List<AdjustmentRule>();
+
+                while (index <= dts.Length)
+                {
+                    TZif_GenerateAdjustmentRule(ref index, baseUtcOffset, rulesList, dts, typeOfLocalTime, transitionType, StandardTime, GmtTime, futureTransitionsPosixFormat);
+                }
+
+                rules = rulesList.ToArray();
+                if (rules != null && rules.Length == 0)
+                {
+                    rules = null;
+                }
+            }
+        }
+
+        private static void TZif_GenerateAdjustmentRule(ref int index, TimeSpan timeZoneBaseUtcOffset, List<AdjustmentRule> rulesList, DateTime[] dts,
+            byte[] typeOfLocalTime, TZifType[] transitionTypes, bool[] StandardTime, bool[] GmtTime, string futureTransitionsPosixFormat)
+        {
+            // To generate AdjustmentRules, use the following approach:
+            // The first AdjustmentRule will go from DateTime.MinValue to the first transition time greater than DateTime.MinValue.
+            // Each middle AdjustmentRule wil go from dts[index-1] to dts[index].
+            // The last AdjustmentRule will go from dts[dts.Length-1] to Datetime.MaxValue.
+
+            // 0. Skip any DateTime.MinValue transition times. In newer versions of the tzfile, there
+            // is a "big bang" transition time, which is before the year 0001. Since any times before year 0001
+            // cannot be represented by DateTime, there is no reason to make AdjustmentRules for these unrepresentable time periods.
+            // 1. If there are no DateTime.MinValue times, the first AdjustmentRule goes from DateTime.MinValue
+            // to the first transition and uses the first standard transitionType (or the first transitionType if none of them are standard)
+            // 2. Create an AdjustmentRule for each transition, i.e. from dts[index - 1] to dts[index].
+            // This rule uses the transitionType[index - 1] and the whole AdjustmentRule only describes a single offset - either
+            // all daylight savings, or all stanard time.
+            // 3. After all the transitions are filled out, the last AdjustmentRule is created from either:
+            //   a. a POSIX-style timezone description ("futureTransitionsPosixFormat"), if there is one or
+            //   b. continue the last transition offset until DateTime.Max
+
+            while (index < dts.Length && dts[index] == DateTime.MinValue)
+            {
+                index++;
+            }
+
+            if (index == 0)
+            {
+                TZifType transitionType = TZif_GetEarlyDateTransitionType(transitionTypes);
+                DateTime endTransitionDate = dts[index];
+
+                TimeSpan transitionOffset = TZif_CalculateTransitionOffsetFromBase(transitionType.UtcOffset, timeZoneBaseUtcOffset);
+                TimeSpan daylightDelta = transitionType.IsDst ? transitionOffset : TimeSpan.Zero;
+                TimeSpan baseUtcDelta = transitionType.IsDst ? TimeSpan.Zero : transitionOffset;
+
+                AdjustmentRule r = AdjustmentRule.CreateAdjustmentRule(
+                        DateTime.MinValue,
+                        endTransitionDate.AddTicks(-1),
+                        daylightDelta,
+                        default(TransitionTime),
+                        default(TransitionTime),
+                        baseUtcDelta,
+                        noDaylightTransitions: true);
+                rulesList.Add(r);
+            }
+            else if (index < dts.Length)
+            {
+                DateTime startTransitionDate = dts[index - 1];
+                TZifType startTransitionType = transitionTypes[typeOfLocalTime[index - 1]];
+
+                DateTime endTransitionDate = dts[index];
+
+                TimeSpan transitionOffset = TZif_CalculateTransitionOffsetFromBase(startTransitionType.UtcOffset, timeZoneBaseUtcOffset);
+                TimeSpan daylightDelta = startTransitionType.IsDst ? transitionOffset : TimeSpan.Zero;
+                TimeSpan baseUtcDelta = startTransitionType.IsDst ? TimeSpan.Zero : transitionOffset;
+
+                TransitionTime dstStart;
+                if (startTransitionType.IsDst)
+                {
+                    // the TransitionTime fields are not used when AdjustmentRule.NoDaylightTransitions == true.
+                    // However, there are some cases in the past where DST = true, and the daylight savings offset
+                    // now equals what the current BaseUtcOffset is.  In that case, the AdjustmentRule.DaylightOffset
+                    // is going to be TimeSpan.Zero.  But we still need to return 'true' from AdjustmentRule.HasDaylightSaving.
+                    // To ensure we always return true from HasDaylightSaving, make a "special" dstStart that will make the logic
+                    // in HasDaylightSaving return true.
+                    dstStart = TransitionTime.CreateFixedDateRule(DateTime.MinValue.AddMilliseconds(2), 1, 1);
+                }
+                else
+                {
+                    dstStart = default(TransitionTime);
+                }
+
+                AdjustmentRule r = AdjustmentRule.CreateAdjustmentRule(
+                        startTransitionDate,
+                        endTransitionDate.AddTicks(-1),
+                        daylightDelta,
+                        dstStart,
+                        default(TransitionTime),
+                        baseUtcDelta,
+                        noDaylightTransitions: true);
+                rulesList.Add(r);
+            }
+            else
+            {
+                // create the AdjustmentRule that will be used for all DateTimes after the last transition
+
+                // NOTE: index == dts.Length
+                DateTime startTransitionDate = dts[index - 1];
+
+                if (!string.IsNullOrEmpty(futureTransitionsPosixFormat))
+                {
+                    AdjustmentRule r = TZif_CreateAdjustmentRuleForPosixFormat(futureTransitionsPosixFormat, startTransitionDate, timeZoneBaseUtcOffset);
+                    if (r != null)
+                    {
+                        rulesList.Add(r);
+                    }
+                }
+                else
+                {
+                    // just use the last transition as the rule which will be used until the end of time
+
+                    TZifType transitionType = transitionTypes[typeOfLocalTime[index - 1]];
+                    TimeSpan transitionOffset = TZif_CalculateTransitionOffsetFromBase(transitionType.UtcOffset, timeZoneBaseUtcOffset);
+                    TimeSpan daylightDelta = transitionType.IsDst ? transitionOffset : TimeSpan.Zero;
+                    TimeSpan baseUtcDelta = transitionType.IsDst ? TimeSpan.Zero : transitionOffset;
+
+                    AdjustmentRule r = AdjustmentRule.CreateAdjustmentRule(
+                        startTransitionDate,
+                        DateTime.MaxValue,
+                        daylightDelta,
+                        default(TransitionTime),
+                        default(TransitionTime),
+                        baseUtcDelta,
+                        noDaylightTransitions: true);
+                    rulesList.Add(r);
+                }
+            }
+
+            index++;
+        }
+
+        private static TimeSpan TZif_CalculateTransitionOffsetFromBase(TimeSpan transitionOffset, TimeSpan timeZoneBaseUtcOffset)
+        {
+            TimeSpan result = transitionOffset - timeZoneBaseUtcOffset;
+
+            // TZif supports seconds-level granularity with offsets but TimeZoneInfo only supports minutes since it aligns
+            // with DateTimeOffset, SQL Server, and the W3C XML Specification
+            if (result.Ticks % TimeSpan.TicksPerMinute != 0)
+            {
+                result = new TimeSpan(result.Hours, result.Minutes, 0);
+            }
+
+            return result;
+        }
+
+        /// <summary>
+        /// Gets the first standard-time transition type, or simply the first transition type
+        /// if there are no standard transition types.
+        /// </summary>>
+        /// <remarks>
+        /// from 'man tzfile':
+        /// localtime(3)  uses the first standard-time ttinfo structure in the file
+        /// (or simply the first ttinfo structure in the absence of a standard-time
+        /// structure)  if  either tzh_timecnt is zero or the time argument is less
+        /// than the first transition time recorded in the file.
+        /// </remarks>
+        private static TZifType TZif_GetEarlyDateTransitionType(TZifType[] transitionTypes)
+        {
+            foreach (TZifType transitionType in transitionTypes)
+            {
+                if (!transitionType.IsDst)
+                {
+                    return transitionType;
+                }
+            }
+
+            if (transitionTypes.Length > 0)
+            {
+                return transitionTypes[0];
+            }
+
+            throw new InvalidTimeZoneException(Environment.GetResourceString("InvalidTimeZone_NoTTInfoStructures"));
+        }
+
+        /// <summary>
+        /// Creates an AdjustmentRule given the POSIX TZ environment variable string.
+        /// </summary>
+        /// <remarks>
+        /// See http://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html for the format and semantics of this POSX string.
+        /// </remarks>
+        private static AdjustmentRule TZif_CreateAdjustmentRuleForPosixFormat(string posixFormat, DateTime startTransitionDate, TimeSpan timeZoneBaseUtcOffset)
+        {
+            string standardName;
+            string standardOffset;
+            string daylightSavingsName;
+            string daylightSavingsOffset;
+            string start;
+            string startTime;
+            string end;
+            string endTime;
+
+            if (TZif_ParsePosixFormat(posixFormat, out standardName, out standardOffset, out daylightSavingsName,
+                out daylightSavingsOffset, out start, out startTime, out end, out endTime))
+            {
+                // a valid posixFormat has at least standardName and standardOffset
+
+                TimeSpan? parsedBaseOffset = TZif_ParseOffsetString(standardOffset);
+                if (parsedBaseOffset.HasValue)
+                {
+                    TimeSpan baseOffset = parsedBaseOffset.Value.Negate(); // offsets are backwards in POSIX notation
+                    baseOffset = TZif_CalculateTransitionOffsetFromBase(baseOffset, timeZoneBaseUtcOffset);
+
+                    // having a daylightSavingsName means there is a DST rule
+                    if (!string.IsNullOrEmpty(daylightSavingsName))
+                    {
+                        TimeSpan? parsedDaylightSavings = TZif_ParseOffsetString(daylightSavingsOffset);
+                        TimeSpan daylightSavingsTimeSpan;
+                        if (!parsedDaylightSavings.HasValue)
+                        {
+                            // default DST to 1 hour if it isn't specified
+                            daylightSavingsTimeSpan = new TimeSpan(1, 0, 0);
+                        }
+                        else
+                        {
+                            daylightSavingsTimeSpan = parsedDaylightSavings.Value.Negate(); // offsets are backwards in POSIX notation
+                            daylightSavingsTimeSpan = TZif_CalculateTransitionOffsetFromBase(daylightSavingsTimeSpan, timeZoneBaseUtcOffset);
+                            daylightSavingsTimeSpan = TZif_CalculateTransitionOffsetFromBase(daylightSavingsTimeSpan, baseOffset);
+                        }
+
+                        TransitionTime dstStart = TZif_CreateTransitionTimeFromPosixRule(start, startTime);
+                        TransitionTime dstEnd = TZif_CreateTransitionTimeFromPosixRule(end, endTime);
+
+                        return AdjustmentRule.CreateAdjustmentRule(
+                            startTransitionDate,
+                            DateTime.MaxValue,
+                            daylightSavingsTimeSpan,
+                            dstStart,
+                            dstEnd,
+                            baseOffset,
+                            noDaylightTransitions: false);
+                    }
+                    else
+                    {
+                        // if there is no daylightSavingsName, the whole AdjustmentRule should be with no transitions - just the baseOffset
+                        return AdjustmentRule.CreateAdjustmentRule(
+                               startTransitionDate,
+                               DateTime.MaxValue,
+                               TimeSpan.Zero,
+                               default(TransitionTime),
+                               default(TransitionTime),
+                               baseOffset,
+                               noDaylightTransitions: true);
+                    }
+                }
+            }
+
+            return null;
+        }
+
+        private static TimeSpan? TZif_ParseOffsetString(string offset)
+        {
+            TimeSpan? result = null;
+
+            if (!string.IsNullOrEmpty(offset))
+            {
+                bool negative = offset[0] == '-';
+                if (negative || offset[0] == '+')
+                {
+                    offset = offset.Substring(1);
+                }
+
+                // Try parsing just hours first.
+                // Note, TimeSpan.TryParseExact "%h" can't be used here because some time zones using values
+                // like "26" or "144" and TimeSpan parsing would turn that into 26 or 144 *days* instead of hours.
+                int hours;
+                if (int.TryParse(offset, out hours))
+                {
+                    result = new TimeSpan(hours, 0, 0);
+                }
+                else
+                {
+                    TimeSpan parsedTimeSpan;
+                    if (TimeSpan.TryParseExact(offset, "g", CultureInfo.InvariantCulture, out parsedTimeSpan))
+                    {
+                        result = parsedTimeSpan;
+                    }
+                }
+
+                if (result.HasValue && negative)
+                {
+                    result = result.Value.Negate();
+                }
+            }
+
+            return result;
+        }
+
+        private static TransitionTime TZif_CreateTransitionTimeFromPosixRule(string date, string time)
+        {
+            if (string.IsNullOrEmpty(date))
+            {
+                return default(TransitionTime);
+            }
+
+            if (date[0] == 'M')
+            {
+                // Mm.w.d
+                // This specifies day d of week w of month m. The day d must be between 0(Sunday) and 6.The week w must be between 1 and 5;
+                // week 1 is the first week in which day d occurs, and week 5 specifies the last d day in the month. The month m should be between 1 and 12.
+
+                int month;
+                int week;
+                DayOfWeek day;
+                if (!TZif_ParseMDateRule(date, out month, out week, out day))
+                {
+                    throw new InvalidTimeZoneException(Environment.GetResourceString("InvalidTimeZone_UnparseablePosixMDateString", date));
+                }
+
+                DateTime timeOfDay;
+                TimeSpan? timeOffset = TZif_ParseOffsetString(time);
+                if (timeOffset.HasValue)
+                {
+                    // This logic isn't correct and can't be corrected until https://github.com/dotnet/corefx/issues/2618 is fixed.
+                    // Some time zones use time values like, "26", "144", or "-2".
+                    // This allows the week to sometimes be week 4 and sometimes week 5 in the month.
+                    // For now, strip off any 'days' in the offset, and just get the time of day correct
+                    timeOffset = new TimeSpan(timeOffset.Value.Hours, timeOffset.Value.Minutes, timeOffset.Value.Seconds);
+                    if (timeOffset.Value < TimeSpan.Zero)
+                    {
+                        timeOfDay = new DateTime(1, 1, 2, 0, 0, 0);
+                    }
+                    else
+                    {
+                        timeOfDay = new DateTime(1, 1, 1, 0, 0, 0);
+                    }
+
+                    timeOfDay += timeOffset.Value;
+                }
+                else
+                {
+                    // default to 2AM.
+                    timeOfDay = new DateTime(1, 1, 1, 2, 0, 0);
+                }
+
+                return TransitionTime.CreateFloatingDateRule(timeOfDay, month, week, day);
+            }
+            else
+            {
+                // Jn
+                // This specifies the Julian day, with n between 1 and 365.February 29 is never counted, even in leap years.
+
+                // n
+                // This specifies the Julian day, with n between 0 and 365.February 29 is counted in leap years.
+
+                // These two rules cannot be expressed with the current AdjustmentRules
+                // One of them *could* be supported if we relaxed the TransitionTime validation rules, and allowed
+                // "IsFixedDateRule = true, Month = 0, Day = n" to mean the nth day of the year, picking one of the rules above
+
+                throw new InvalidTimeZoneException(Environment.GetResourceString("InvalidTimeZone_JulianDayNotSupported"));
+            }
+        }
+
+        /// <summary>
+        /// Parses a string like Mm.w.d into month, week and DayOfWeek values.
+        /// </summary>
+        /// <returns>
+        /// true if the parsing succeeded; otherwise, false.
+        /// </returns>
+        private static bool TZif_ParseMDateRule(string dateRule, out int month, out int week, out DayOfWeek dayOfWeek)
+        {
+            month = 0;
+            week = 0;
+            dayOfWeek = default(DayOfWeek);
+
+            if (dateRule[0] == 'M')
+            {
+                int firstDotIndex = dateRule.IndexOf('.');
+                if (firstDotIndex > 0)
+                {
+                    int secondDotIndex = dateRule.IndexOf('.', firstDotIndex + 1);
+                    if (secondDotIndex > 0)
+                    {
+                        string monthString = dateRule.Substring(1, firstDotIndex - 1);
+                        string weekString = dateRule.Substring(firstDotIndex + 1, secondDotIndex - firstDotIndex - 1);
+                        string dayString = dateRule.Substring(secondDotIndex + 1);
+
+                        if (int.TryParse(monthString, out month))
+                        {
+                            if (int.TryParse(weekString, out week))
+                            {
+                                int day;
+                                if (int.TryParse(dayString, out day))
+                                {
+                                    dayOfWeek = (DayOfWeek)day;
+                                    return true;
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+
+            return false;
+        }
+
+        private static bool TZif_ParsePosixFormat(
+            string posixFormat,
+            out string standardName,
+            out string standardOffset,
+            out string daylightSavingsName,
+            out string daylightSavingsOffset,
+            out string start,
+            out string startTime,
+            out string end,
+            out string endTime)
+        {
+            standardName = null;
+            standardOffset = null;
+            daylightSavingsName = null;
+            daylightSavingsOffset = null;
+            start = null;
+            startTime = null;
+            end = null;
+            endTime = null;
+
+            int index = 0;
+            standardName = TZif_ParsePosixName(posixFormat, ref index);
+            standardOffset = TZif_ParsePosixOffset(posixFormat, ref index);
+
+            daylightSavingsName = TZif_ParsePosixName(posixFormat, ref index);
+            if (!string.IsNullOrEmpty(daylightSavingsName))
+            {
+                daylightSavingsOffset = TZif_ParsePosixOffset(posixFormat, ref index);
+
+                if (index < posixFormat.Length && posixFormat[index] == ',')
+                {
+                    index++;
+                    TZif_ParsePosixDateTime(posixFormat, ref index, out start, out startTime);
+
+                    if (index < posixFormat.Length && posixFormat[index] == ',')
+                    {
+                        index++;
+                        TZif_ParsePosixDateTime(posixFormat, ref index, out end, out endTime);
+                    }
+                }
+            }
+
+            return !string.IsNullOrEmpty(standardName) && !string.IsNullOrEmpty(standardOffset);
+        }
+
+        private static string TZif_ParsePosixName(string posixFormat, ref int index) =>
+            TZif_ParsePosixString(posixFormat, ref index, c => char.IsDigit(c) || c == '+' || c == '-' || c == ',');
+
+        private static string TZif_ParsePosixOffset(string posixFormat, ref int index) =>
+            TZif_ParsePosixString(posixFormat, ref index, c => !char.IsDigit(c) && c != '+' && c != '-' && c != ':');
+
+        private static void TZif_ParsePosixDateTime(string posixFormat, ref int index, out string date, out string time)
+        {
+            time = null;
+
+            date = TZif_ParsePosixDate(posixFormat, ref index);
+            if (index < posixFormat.Length && posixFormat[index] == '/')
+            {
+                index++;
+                time = TZif_ParsePosixTime(posixFormat, ref index);
+            }
+        }
+
+        private static string TZif_ParsePosixDate(string posixFormat, ref int index) =>
+            TZif_ParsePosixString(posixFormat, ref index, c => c == '/' || c == ',');
+
+        private static string TZif_ParsePosixTime(string posixFormat, ref int index) =>
+            TZif_ParsePosixString(posixFormat, ref index, c => c == ',');
+
+        private static string TZif_ParsePosixString(string posixFormat, ref int index, Func<char, bool> breakCondition)
+        {
+            int startIndex = index;
+            for (; index < posixFormat.Length; index++)
+            {
+                char current = posixFormat[index];
+                if (breakCondition(current))
+                {
+                    break;
+                }
+            }
+
+            return posixFormat.Substring(startIndex, index - startIndex);
+        }
+
+        // Returns the Substring from zoneAbbreviations starting at index and ending at '\0'
+        // zoneAbbreviations is expected to be in the form: "PST\0PDT\0PWT\0\PPT"
+        private static string TZif_GetZoneAbbreviation(string zoneAbbreviations, int index)
+        {
+            int lastIndex = zoneAbbreviations.IndexOf('\0', index);
+            return lastIndex > 0 ?
+                zoneAbbreviations.Substring(index, lastIndex - index) :
+                zoneAbbreviations.Substring(index);
+        }
+
+        // Converts an array of bytes into an int - always using standard byte order (Big Endian)
+        // per TZif file standard
+        private static unsafe int TZif_ToInt32(byte[] value, int startIndex)
+        {
+            fixed (byte* pbyte = &value[startIndex])
+            {
+                return (*pbyte << 24) | (*(pbyte + 1) << 16) | (*(pbyte + 2) << 8) | (*(pbyte + 3));
+            }
+        }
+
+        // Converts an array of bytes into a long - always using standard byte order (Big Endian)
+        // per TZif file standard
+        private static unsafe long TZif_ToInt64(byte[] value, int startIndex)
+        {
+            fixed (byte* pbyte = &value[startIndex])
+            {
+                int i1 = (*pbyte << 24) | (*(pbyte + 1) << 16) | (*(pbyte + 2) << 8) | (*(pbyte + 3));
+                int i2 = (*(pbyte + 4) << 24) | (*(pbyte + 5) << 16) | (*(pbyte + 6) << 8) | (*(pbyte + 7));
+                return (uint)i2 | ((long)i1 << 32);
+            }
+        }
+
+        private static long TZif_ToUnixTime(byte[] value, int startIndex, TZVersion version) =>
+            version != TZVersion.V1 ?
+                TZif_ToInt64(value, startIndex) :
+                TZif_ToInt32(value, startIndex);
+
+        private static DateTime TZif_UnixTimeToDateTime(long unixTime) =>
+            unixTime < DateTimeOffset.UnixMinSeconds ? DateTime.MinValue :
+            unixTime > DateTimeOffset.UnixMaxSeconds ? DateTime.MaxValue :
+            DateTimeOffset.FromUnixTimeSeconds(unixTime).UtcDateTime;
+
+        private static void TZif_ParseRaw(byte[] data, out TZifHead t, out DateTime[] dts, out byte[] typeOfLocalTime, out TZifType[] transitionType,
+                                          out string zoneAbbreviations, out bool[] StandardTime, out bool[] GmtTime, out string futureTransitionsPosixFormat)
+        {
+            // initialize the out parameters in case the TZifHead ctor throws
+            dts = null;
+            typeOfLocalTime = null;
+            transitionType = null;
+            zoneAbbreviations = string.Empty;
+            StandardTime = null;
+            GmtTime = null;
+            futureTransitionsPosixFormat = null;
+
+            // read in the 44-byte TZ header containing the count/length fields
+            //
+            int index = 0;
+            t = new TZifHead(data, index);
+            index += TZifHead.Length;
+
+            int timeValuesLength = 4; // the first version uses 4-bytes to specify times
+            if (t.Version != TZVersion.V1)
+            {
+                // move index past the V1 information to read the V2 information
+                index += (int)((timeValuesLength * t.TimeCount) + t.TimeCount + (6 * t.TypeCount) + ((timeValuesLength + 4) * t.LeapCount) + t.IsStdCount + t.IsGmtCount + t.CharCount);
+
+                // read the V2 header
+                t = new TZifHead(data, index);
+                index += TZifHead.Length;
+                timeValuesLength = 8; // the second version uses 8-bytes
+            }
+
+            // initialize the containers for the rest of the TZ data
+            dts = new DateTime[t.TimeCount];
+            typeOfLocalTime = new byte[t.TimeCount];
+            transitionType = new TZifType[t.TypeCount];
+            zoneAbbreviations = string.Empty;
+            StandardTime = new bool[t.TypeCount];
+            GmtTime = new bool[t.TypeCount];
+
+            // read in the UTC transition points and convert them to Windows
+            //
+            for (int i = 0; i < t.TimeCount; i++)
+            {
+                long unixTime = TZif_ToUnixTime(data, index, t.Version);
+                dts[i] = TZif_UnixTimeToDateTime(unixTime);
+                index += timeValuesLength;
+            }
+
+            // read in the Type Indices; there is a 1:1 mapping of UTC transition points to Type Indices
+            // these indices directly map to the array index in the transitionType array below
+            //
+            for (int i = 0; i < t.TimeCount; i++)
+            {
+                typeOfLocalTime[i] = data[index];
+                index += 1;
+            }
+
+            // read in the Type table.  Each 6-byte entry represents
+            // {UtcOffset, IsDst, AbbreviationIndex}
+            //
+            // each AbbreviationIndex is a character index into the zoneAbbreviations string below
+            //
+            for (int i = 0; i < t.TypeCount; i++)
+            {
+                transitionType[i] = new TZifType(data, index);
+                index += 6;
+            }
+
+            // read in the Abbreviation ASCII string.  This string will be in the form:
+            // "PST\0PDT\0PWT\0\PPT"
+            //
+            Encoding enc = Encoding.UTF8;
+            zoneAbbreviations = enc.GetString(data, index, (int)t.CharCount);
+            index += (int)t.CharCount;
+
+            // skip ahead of the Leap-Seconds Adjustment data.  In a future release, consider adding
+            // support for Leap-Seconds
+            //
+            index += (int)(t.LeapCount * (timeValuesLength + 4)); // skip the leap second transition times
+
+            // read in the Standard Time table.  There should be a 1:1 mapping between Type-Index and Standard
+            // Time table entries.
+            //
+            // TRUE     =     transition time is standard time
+            // FALSE    =     transition time is wall clock time
+            // ABSENT   =     transition time is wall clock time
+            //
+            for (int i = 0; i < t.IsStdCount && i < t.TypeCount && index < data.Length; i++)
+            {
+                StandardTime[i] = (data[index++] != 0);
+            }
+
+            // read in the GMT Time table.  There should be a 1:1 mapping between Type-Index and GMT Time table
+            // entries.
+            //
+            // TRUE     =     transition time is UTC
+            // FALSE    =     transition time is local time
+            // ABSENT   =     transition time is local time
+            //
+            for (int i = 0; i < t.IsGmtCount && i < t.TypeCount && index < data.Length; i++)
+            {
+                GmtTime[i] = (data[index++] != 0);
+            }
+
+            if (t.Version != TZVersion.V1)
+            {
+                // read the POSIX-style format, which should be wrapped in newlines with the last newline at the end of the file
+                if (data[index++] == '\n' && data[data.Length - 1] == '\n')
+                {
+                    futureTransitionsPosixFormat = enc.GetString(data, index, data.Length - index - 1);
+                }
+            }
+        }
+
+        private struct TZifType
+        {
+            public const int Length = 6;
+
+            public readonly TimeSpan UtcOffset;
+            public readonly bool IsDst;
+            public readonly byte AbbreviationIndex;
+
+            public TZifType(byte[] data, int index)
+            {
+                if (data == null || data.Length < index + Length)
+                {
+                    throw new ArgumentException(Environment.GetResourceString("Argument_TimeZoneInfoInvalidTZif"), nameof(data));
+                }
+                Contract.EndContractBlock();
+                UtcOffset = new TimeSpan(0, 0, TZif_ToInt32(data, index + 00));
+                IsDst = (data[index + 4] != 0);
+                AbbreviationIndex = data[index + 5];
+            }
+        }
+
+        private struct TZifHead
+        {
+            public const int Length = 44;
+
+            public readonly uint Magic; // TZ_MAGIC "TZif"
+            public readonly TZVersion Version; // 1 byte for a \0 or 2 or 3
+            // public byte[15] Reserved; // reserved for future use
+            public readonly uint IsGmtCount; // number of transition time flags
+            public readonly uint IsStdCount; // number of transition time flags
+            public readonly uint LeapCount; // number of leap seconds
+            public readonly uint TimeCount; // number of transition times
+            public readonly uint TypeCount; // number of local time types
+            public readonly uint CharCount; // number of abbreviated characters
+
+            public TZifHead(byte[] data, int index)
+            {
+                if (data == null || data.Length < Length)
+                {
+                    throw new ArgumentException("bad data", nameof(data));
+                }
+                Contract.EndContractBlock();
+
+                Magic = (uint)TZif_ToInt32(data, index + 00);
+
+                if (Magic != 0x545A6966)
+                {
+                    // 0x545A6966 = {0x54, 0x5A, 0x69, 0x66} = "TZif"
+                    throw new ArgumentException(Environment.GetResourceString("Argument_TimeZoneInfoBadTZif"), nameof(data));
+                }
+
+                byte version = data[index + 04];
+                Version =
+                    version == '2' ? TZVersion.V2 :
+                    version == '3' ? TZVersion.V3 :
+                    TZVersion.V1;  // default/fallback to V1 to guard against future, unsupported version numbers
+
+                // skip the 15 byte reserved field
+
+                // don't use the BitConverter class which parses data
+                // based on the Endianess of the machine architecture.
+                // this data is expected to always be in "standard byte order",
+                // regardless of the machine it is being processed on.
+
+                IsGmtCount = (uint)TZif_ToInt32(data, index + 20);
+                IsStdCount = (uint)TZif_ToInt32(data, index + 24);
+                LeapCount = (uint)TZif_ToInt32(data, index + 28);
+                TimeCount = (uint)TZif_ToInt32(data, index + 32);
+                TypeCount = (uint)TZif_ToInt32(data, index + 36);
+                CharCount = (uint)TZif_ToInt32(data, index + 40);
+            }
+        }
+
+        private enum TZVersion : byte
+        {
+            V1 = 0,
+            V2,
+            V3,
+            // when adding more versions, ensure all the logic using TZVersion is still correct
+        }
+    }
+}
diff --git a/src/mscorlib/src/System/TimeZoneInfo.Win32.cs b/src/mscorlib/src/System/TimeZoneInfo.Win32.cs
new file mode 100644 (file)
index 0000000..9de671e
--- /dev/null
@@ -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(',', StringSplitOptions.None);
+            if (resources.Length != 2)
+            {
+                return string.Empty;
+            }
+
+            string filePath;
+            int resourceId;
+
+            // get the path to Windows\System32
+            string system32 = Environment.UnsafeGetFolderPath(Environment.SpecialFolder.System);
+
+            // 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;
+                }
+
+            }
+        }
+    }
+}
index 7b1c394..fc5625b 100644 (file)
@@ -6,12 +6,8 @@ using System.Collections.Generic;
 using System.Collections.ObjectModel;
 using System.Diagnostics.Contracts;
 using System.Globalization;
-using System.IO;
 using System.Runtime.Serialization;
-using System.Security;
-using System.Text;
 using System.Threading;
-using Microsoft.Win32;
 
 namespace System
 {
@@ -50,31 +46,6 @@ namespace System
         private readonly bool _supportsDaylightSavingTime;
         private readonly AdjustmentRule[] _adjustmentRules;
 
-#if FEATURE_WIN32_REGISTRY
-        // 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;
-#endif // FEATURE_WIN32_REGISTRY
-
-#if PLATFORM_UNIX
-        private const string DefaultTimeZoneDirectory = "/usr/share/zoneinfo/";
-        private const string ZoneTabFileName = "zone.tab";
-        private const string TimeZoneEnvironmentVariable = "TZ";
-        private const string TimeZoneDirectoryEnvironmentVariable = "TZDIR";
-#endif // PLATFORM_UNIX
-
         // constants for TimeZoneInfo.Local and TimeZoneInfo.Utc
         private const string UtcId = "UTC";
         private const string LocalId = "Local";
@@ -90,7 +61,7 @@ namespace System
         // there is a chance that the internal ConvertTime calls will throw since 'source' won't be reference equal to the new TimeZoneInfo.Local.
         //
 #pragma warning disable 0420
-        private sealed class CachedData
+        private sealed partial class CachedData
         {
             private volatile TimeZoneInfo _localTimeZone;
 
@@ -166,51 +137,9 @@ namespace System
             public Dictionary<string, TimeZoneInfo> _systemTimeZones;
             public ReadOnlyCollection<TimeZoneInfo> _readOnlySystemTimeZones;
             public bool _allSystemTimeZonesRead;
-
-#if FEATURE_WIN32_REGISTRY
-            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;
-            }
-
-#endif // FEATURE_WIN32_REGISTRY
         };
 #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;
-            }
-        }
-
         // used by GetUtcOffsetFromUtc (DateTime.Now, DateTime.ToLocalTime) for max/min whole-day range checks
         private static readonly DateTime s_maxDateOnly = new DateTime(9999, 12, 31);
         private static readonly DateTime s_minDateOnly = new DateTime(1, 1, 2);
@@ -227,55 +156,6 @@ namespace System
 
         public bool SupportsDaylightSavingTime => _supportsDaylightSavingTime;
 
-#if PLATFORM_UNIX
-        // The rules we use in Unix cares mostly about the start and end dates but doesn’t fill the transition start and end info.
-        // as the rules now is public, we should fill it properly so the caller doesn’t have to know how we use it internally
-        // and can use it as it is used in Windows
-
-        private AdjustmentRule[] GetFilledRules()
-        {
-            Debug.Assert(_adjustmentRules != null, "_adjustmentRules expected to be not null");
-            AdjustmentRule[] rules = new AdjustmentRule[_adjustmentRules.Length];
-
-            for (int i = 0; i < _adjustmentRules.Length; i++)
-            {
-                var rule = _adjustmentRules[i];
-                var start = rule.DateStart.Kind == DateTimeKind.Utc ?
-                            new DateTime(TimeZoneInfo.ConvertTime(rule.DateStart, this).Ticks, DateTimeKind.Unspecified) :
-                            rule.DateStart;
-                var end = rule.DateEnd.Kind == DateTimeKind.Utc ?
-                            new DateTime(TimeZoneInfo.ConvertTime(rule.DateEnd, this).Ticks - 1, DateTimeKind.Unspecified) :
-                            rule.DateEnd;
-
-                var startTransition = TimeZoneInfo.TransitionTime.CreateFixedDateRule(new DateTime(1, 1, 1, start.Hour, start.Minute, start.Second), start.Month, start.Day);
-                var endTransition = TimeZoneInfo.TransitionTime.CreateFixedDateRule(new DateTime(1, 1, 1, end.Hour, end.Minute, end.Second), end.Month, end.Day);
-
-                rules[i] = TimeZoneInfo.AdjustmentRule.CreateAdjustmentRule(start.Date, end.Date, rule.DaylightDelta, startTransition, endTransition);
-            }
-
-            return rules;
-        }
-#endif // PLATFORM_UNIX
-
-        /// <summary>
-        /// Returns a cloned array of AdjustmentRule objects
-        /// </summary>
-        public AdjustmentRule[] GetAdjustmentRules()
-        {
-            if (_adjustmentRules == null)
-            {
-                return Array.Empty<AdjustmentRule>();
-            }
-            else
-            {
-#if PLATFORM_UNIX
-                return GetFilledRules();
-#else
-                return (AdjustmentRule[])_adjustmentRules.Clone();
-#endif // PLATFORM_UNIX
-            }
-        }
-
         /// <summary>
         /// Returns an array of TimeSpan objects representing all of
         /// possible UTC offset values for this ambiguous time.
@@ -940,34 +820,6 @@ namespace System
             return cachedData._readOnlySystemTimeZones;
         }
 
-        private static void PopulateAllSystemTimeZones(CachedData cachedData)
-        {
-            Debug.Assert(Monitor.IsEntered(cachedData));
-
-#if FEATURE_WIN32_REGISTRY
-            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
-                    }
-                }
-            }
-#elif PLATFORM_UNIX // FEATURE_WIN32_REGISTRY
-            string timeZoneDirectory = GetTimeZoneDirectory();
-            foreach (string timeZoneId in GetTimeZoneIds(timeZoneDirectory))
-            {
-                TimeZoneInfo value;
-                Exception ex;
-                TryGetTimeZone(timeZoneId, false, out value, out ex, cachedData);  // populate the cache
-            }
-#endif // FEATURE_WIN32_REGISTRY
-        }
-
         /// <summary>
         /// Value equality on the "adjustmentRules" array
         /// </summary>
@@ -1054,139 +906,6 @@ namespace System
         /// </summary>
         public static TimeZoneInfo Utc => s_utcTimeZone;
 
-#if FEATURE_WIN32_REGISTRY
-        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;
-        }
-#endif // FEATURE_WIN32_REGISTRY
-
-#if PLATFORM_UNIX
-        private TimeZoneInfo(byte[] data, string id, bool dstDisabled)
-        {
-            TZifHead t;
-            DateTime[] dts;
-            byte[] typeOfLocalTime;
-            TZifType[] transitionType;
-            string zoneAbbreviations;
-            bool[] StandardTime;
-            bool[] GmtTime;
-            string futureTransitionsPosixFormat;
-
-            // parse the raw TZif bytes; this method can throw ArgumentException when the data is malformed.
-            TZif_ParseRaw(data, out t, out dts, out typeOfLocalTime, out transitionType, out zoneAbbreviations, out StandardTime, out GmtTime, out futureTransitionsPosixFormat);
-
-            _id = id;
-            _displayName = LocalId;
-            _baseUtcOffset = TimeSpan.Zero;
-
-            // find the best matching baseUtcOffset and display strings based on the current utcNow value.
-            // NOTE: read the display strings from the the tzfile now in case they can't be loaded later
-            // from the globalization data.
-            DateTime utcNow = DateTime.UtcNow;
-            for (int i = 0; i < dts.Length && dts[i] <= utcNow; i++)
-            {
-                int type = typeOfLocalTime[i];
-                if (!transitionType[type].IsDst)
-                {
-                    _baseUtcOffset = transitionType[type].UtcOffset;
-                    _standardDisplayName = TZif_GetZoneAbbreviation(zoneAbbreviations, transitionType[type].AbbreviationIndex);
-                }
-                else
-                {
-                    _daylightDisplayName = TZif_GetZoneAbbreviation(zoneAbbreviations, transitionType[type].AbbreviationIndex);
-                }
-            }
-
-            if (dts.Length == 0)
-            {
-                // time zones like Africa/Bujumbura and Etc/GMT* have no transition times but still contain
-                // TZifType entries that may contain a baseUtcOffset and display strings
-                for (int i = 0; i < transitionType.Length; i++)
-                {
-                    if (!transitionType[i].IsDst)
-                    {
-                        _baseUtcOffset = transitionType[i].UtcOffset;
-                        _standardDisplayName = TZif_GetZoneAbbreviation(zoneAbbreviations, transitionType[i].AbbreviationIndex);
-                    }
-                    else
-                    {
-                        _daylightDisplayName = TZif_GetZoneAbbreviation(zoneAbbreviations, transitionType[i].AbbreviationIndex);
-                    }
-                }
-            }
-            _displayName = _standardDisplayName;
-
-            GetDisplayName(Interop.GlobalizationInterop.TimeZoneDisplayNameType.Generic, ref _displayName);
-            GetDisplayName(Interop.GlobalizationInterop.TimeZoneDisplayNameType.Standard, ref _standardDisplayName);
-            GetDisplayName(Interop.GlobalizationInterop.TimeZoneDisplayNameType.DaylightSavings, ref _daylightDisplayName);
-
-            // TZif supports seconds-level granularity with offsets but TimeZoneInfo only supports minutes since it aligns
-            // with DateTimeOffset, SQL Server, and the W3C XML Specification
-            if (_baseUtcOffset.Ticks % TimeSpan.TicksPerMinute != 0)
-            {
-                _baseUtcOffset = new TimeSpan(_baseUtcOffset.Hours, _baseUtcOffset.Minutes, 0);
-            }
-
-            if (!dstDisabled)
-            {
-                // only create the adjustment rule if DST is enabled
-                TZif_GenerateAdjustmentRules(out _adjustmentRules, _baseUtcOffset, dts, typeOfLocalTime, transitionType, StandardTime, GmtTime, futureTransitionsPosixFormat);
-            }
-
-            ValidateTimeZoneInfo(_id, _baseUtcOffset, _adjustmentRules, out _supportsDaylightSavingTime);
-        }
-
-        private void GetDisplayName(Interop.GlobalizationInterop.TimeZoneDisplayNameType nameType, ref string displayName)
-        {
-            string timeZoneDisplayName;
-            bool result = Interop.CallStringMethod(
-                (locale, id, type, stringBuilder) => Interop.GlobalizationInterop.GetTimeZoneDisplayName(
-                    locale,
-                    id,
-                    type,
-                    stringBuilder,
-                    stringBuilder.Capacity),
-                CultureInfo.CurrentUICulture.Name,
-                _id,
-                nameType,
-                out timeZoneDisplayName);
-
-            // If there is an unknown error, don't set the displayName field.
-            // It will be set to the abbreviation that was read out of the tzfile.
-            if (result)
-            {
-                displayName = timeZoneDisplayName;
-            }
-        }
-
-#endif // PLATFORM_UNIX
-
         private TimeZoneInfo(
                 string id,
                 TimeSpan baseUtcOffset,
@@ -1437,21 +1156,6 @@ namespace System
         }
 
         /// <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>
         /// Helper function that converts a dateTime from UTC into the destinationTimeZone
         /// - Returns DateTime.MaxValue when the converted value is too large.
         /// - Returns DateTime.MinValue when the converted value is too small.
@@ -1474,91 +1178,6 @@ namespace System
                 new DateTime(ticks);
         }
 
-#if FEATURE_WIN32_REGISTRY
-        /// <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;
-        }
-#endif // FEATURE_WIN32_REGISTRY
-
         /// <summary>
         /// Helper function that returns a DaylightTime from a year and AdjustmentRule.
         /// </summary>
@@ -2002,2224 +1621,248 @@ namespace System
         }
 
         /// <summary>
-        /// Helper function for retrieving the local system time zone.
-        /// May throw COMException, TimeZoneNotFoundException, InvalidTimeZoneException.
-        /// Assumes cachedData lock is taken.
+        /// Helper function that calculates the UTC offset for a dateTime in a timeZone.
+        /// This function assumes that the dateTime is already converted into the timeZone.
         /// </summary>
-        /// <returns>A new TimeZoneInfo instance.</returns>
-        private static TimeZoneInfo GetLocalTimeZone(CachedData cachedData)
+        private static TimeSpan GetUtcOffset(DateTime time, TimeZoneInfo zone, TimeZoneInfoOptions flags)
         {
-            Debug.Assert(Monitor.IsEntered(cachedData));
-
-#if FEATURE_WIN32_REGISTRY
-            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);
+            TimeSpan baseOffset = zone.BaseUtcOffset;
+            AdjustmentRule rule = zone.GetAdjustmentRuleForTime(time);
 
-            if (id != null)
+            if (rule != null)
             {
-                TimeZoneInfo zone;
-                Exception ex;
-                if (TryGetTimeZone(id, dstDisabled, out zone, out ex, cachedData) == TimeZoneInfoResult.Success)
+                baseOffset = baseOffset + rule.BaseUtcOffsetDelta;
+                if (rule.HasDaylightSaving)
                 {
-                    // successfully loaded the time zone from the registry
-                    return zone;
+                    DaylightTimeStruct daylightTime = zone.GetDaylightTime(time.Year, rule);
+                    bool isDaylightSavings = GetIsDaylightSavings(time, rule, daylightTime, flags);
+                    baseOffset += (isDaylightSavings ? rule.DaylightDelta : TimeSpan.Zero /* FUTURE: rule.StandardDelta */);
                 }
             }
 
-            // We could not find the data in the registry.  Fall back to using
-            // the data from the Win32 API
-            return GetLocalTimeZoneFromWin32Data(timeZoneInformation, dstDisabled);
-
-#elif PLATFORM_UNIX // FEATURE_WIN32_REGISTRY
-            // Without Registry support, create the TimeZoneInfo from a TZ file
-            return GetLocalTimeZoneFromTzFile();
-#endif // FEATURE_WIN32_REGISTRY
+            return baseOffset;
         }
 
-#if PLATFORM_UNIX
-        private static TimeZoneInfoResult TryGetTimeZoneByFile(string id, out TimeZoneInfo value, out Exception e)
+        /// <summary>
+        /// Helper function that calculates the UTC offset for a UTC-dateTime in a timeZone.
+        /// This function assumes that the dateTime is represented in UTC and has *not* already been converted into the timeZone.
+        /// </summary>
+        private static TimeSpan GetUtcOffsetFromUtc(DateTime time, TimeZoneInfo zone)
         {
-            value = null;
-            e = null;
-
-            string timeZoneDirectory = GetTimeZoneDirectory();
-            string timeZoneFilePath = Path.Combine(timeZoneDirectory, id);
-            byte[] rawData;
-            try
-            {
-                rawData = File.ReadAllBytes(timeZoneFilePath);
-            }
-            catch (UnauthorizedAccessException ex)
-            {
-                e = ex;
-                return TimeZoneInfoResult.SecurityException;
-            }
-            catch (FileNotFoundException ex)
-            {
-                e = ex;
-                return TimeZoneInfoResult.TimeZoneNotFoundException;
-            }
-            catch (DirectoryNotFoundException ex)
-            {
-                e = ex;
-                return TimeZoneInfoResult.TimeZoneNotFoundException;
-            }
-            catch (IOException ex)
-            {
-                e = new InvalidTimeZoneException(Environment.GetResourceString("InvalidTimeZone_InvalidFileData", id, timeZoneFilePath), ex);
-                return TimeZoneInfoResult.InvalidTimeZoneException;
-            }
-
-            value = GetTimeZoneFromTzData(rawData, id);
-
-            if (value == null)
-            {
-                e = new InvalidTimeZoneException(Environment.GetResourceString("InvalidTimeZone_InvalidFileData", id, timeZoneFilePath));
-                return TimeZoneInfoResult.InvalidTimeZoneException;
-            }
-
-            return TimeZoneInfoResult.Success;
+            bool isDaylightSavings;
+            return GetUtcOffsetFromUtc(time, zone, out isDaylightSavings);
         }
 
         /// <summary>
-        /// Returns a collection of TimeZone Id values from the zone.tab file in the timeZoneDirectory.
+        /// Helper function that calculates the UTC offset for a UTC-dateTime in a timeZone.
+        /// This function assumes that the dateTime is represented in UTC and has *not* already been converted into the timeZone.
         /// </summary>
-        /// <remarks>
-        /// Lines that start with # are comments and are skipped.
-        /// </remarks>
-        private static IEnumerable<string> GetTimeZoneIds(string timeZoneDirectory)
+        private static TimeSpan GetUtcOffsetFromUtc(DateTime time, TimeZoneInfo zone, out bool isDaylightSavings)
         {
-            string[] zoneTabFileLines = null;
-            try
-            {
-                zoneTabFileLines = File.ReadAllLines(Path.Combine(timeZoneDirectory, ZoneTabFileName));
-            }
-            catch (IOException) { }
-            catch (UnauthorizedAccessException) { }
-
-            List<string> timeZoneIds = new List<string>();
-            if (zoneTabFileLines != null)
-            {
-                foreach (string zoneTabFileLine in zoneTabFileLines)
-                {
-                    if (!string.IsNullOrEmpty(zoneTabFileLine) && !zoneTabFileLine.StartsWith("#"))
-                    {
-                        // the format of the line is "country-code \t coordinates \t TimeZone Id \t comments"
-
-                        int firstTabIndex = zoneTabFileLine.IndexOf('\t');
-                        if (firstTabIndex != -1)
-                        {
-                            int secondTabIndex = zoneTabFileLine.IndexOf('\t', firstTabIndex + 1);
-                            if (secondTabIndex != -1)
-                            {
-                                string timeZoneId;
-                                int startIndex = secondTabIndex + 1;
-                                int thirdTabIndex = zoneTabFileLine.IndexOf('\t', startIndex);
-                                if (thirdTabIndex != -1)
-                                {
-                                    int length = thirdTabIndex - startIndex;
-                                    timeZoneId = zoneTabFileLine.Substring(startIndex, length);
-                                }
-                                else
-                                {
-                                    timeZoneId = zoneTabFileLine.Substring(startIndex);
-                                }
-
-                                if (!string.IsNullOrEmpty(timeZoneId))
-                                {
-                                    timeZoneIds.Add(timeZoneId);
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-
-            return timeZoneIds;
+            bool isAmbiguousLocalDst;
+            return GetUtcOffsetFromUtc(time, zone, out isDaylightSavings, out isAmbiguousLocalDst);
         }
 
         /// <summary>
-        /// Gets the tzfile raw data for the current 'local' time zone using the following rules.
-        /// 1. Read the TZ environment variable.  If it is set, use it.
-        /// 2. Look for the data in /etc/localtime.
-        /// 3. Look for the data in GetTimeZoneDirectory()/localtime.
-        /// 4. Use UTC if all else fails.
+        /// Helper function that calculates the UTC offset for a UTC-dateTime in a timeZone.
+        /// This function assumes that the dateTime is represented in UTC and has *not* already been converted into the timeZone.
         /// </summary>
-        private static bool TryGetLocalTzFile(out byte[] rawData, out string id)
+        internal static TimeSpan GetUtcOffsetFromUtc(DateTime time, TimeZoneInfo zone, out bool isDaylightSavings, out bool isAmbiguousLocalDst)
         {
-            rawData = null;
-            id = null;
-            string tzVariable = GetTzEnvironmentVariable();
-
-            // If the env var is null, use the localtime file
-            if (tzVariable == null)
-            {
-                return
-                    TryLoadTzFile("/etc/localtime", ref rawData, ref id) ||
-                    TryLoadTzFile(Path.Combine(GetTimeZoneDirectory(), "localtime"), ref rawData, ref id);
-            }
+            isDaylightSavings = false;
+            isAmbiguousLocalDst = false;
+            TimeSpan baseOffset = zone.BaseUtcOffset;
+            int year;
+            AdjustmentRule rule;
 
-            // If it's empty, use UTC (TryGetLocalTzFile() should return false).
-            if (tzVariable.Length == 0)
+            if (time > s_maxDateOnly)
             {
-                return false;
+                rule = zone.GetAdjustmentRuleForTime(DateTime.MaxValue);
+                year = 9999;
             }
-
-            // Otherwise, use the path from the env var.  If it's not absolute, make it relative
-            // to the system timezone directory
-            string tzFilePath;
-            if (tzVariable[0] != '/')
+            else if (time < s_minDateOnly)
             {
-                id = tzVariable;
-                tzFilePath = Path.Combine(GetTimeZoneDirectory(), tzVariable);
+                rule = zone.GetAdjustmentRuleForTime(DateTime.MinValue);
+                year = 1;
             }
             else
             {
-                tzFilePath = tzVariable;
+                rule = zone.GetAdjustmentRuleForTime(time, dateTimeisUtc: true);
+
+                // As we get the associated rule using the adjusted targetTime, we should use the adjusted year (targetTime.Year) too as after adding the baseOffset,
+                // sometimes the year value can change if the input datetime was very close to the beginning or the end of the year. Examples of such cases:
+                //      Libya Standard Time when used with the date 2011-12-31T23:59:59.9999999Z
+                //      "W. Australia Standard Time" used with date 2005-12-31T23:59:00.0000000Z
+                DateTime targetTime = time + baseOffset;
+                year = targetTime.Year;
             }
-            return TryLoadTzFile(tzFilePath, ref rawData, ref id);
-        }
 
-        private static string GetTzEnvironmentVariable()
-        {
-            string result = Environment.GetEnvironmentVariable(TimeZoneEnvironmentVariable);
-            if (!string.IsNullOrEmpty(result))
+            if (rule != null)
             {
-                if (result[0] == ':')
+                baseOffset = baseOffset + rule.BaseUtcOffsetDelta;
+                if (rule.HasDaylightSaving)
                 {
-                    // strip off the ':' prefix
-                    result = result.Substring(1);
+                    isDaylightSavings = GetIsDaylightSavingsFromUtc(time, year, zone._baseUtcOffset, rule, out isAmbiguousLocalDst, zone);
+                    baseOffset += (isDaylightSavings ? rule.DaylightDelta : TimeSpan.Zero /* FUTURE: rule.StandardDelta */);
                 }
             }
 
-            return result;
-        }
-
-        private static bool TryLoadTzFile(string tzFilePath, ref byte[] rawData, ref string id)
-        {
-            if (File.Exists(tzFilePath))
-            {
-                try
-                {
-                    rawData = File.ReadAllBytes(tzFilePath);
-                    if (string.IsNullOrEmpty(id))
-                    {
-                        id = FindTimeZoneIdUsingReadLink(tzFilePath);
-
-                        if (string.IsNullOrEmpty(id))
-                        {
-                            id = FindTimeZoneId(rawData);
-                        }
-                    }
-                    return true;
-                }
-                catch (IOException) { }
-                catch (SecurityException) { }
-                catch (UnauthorizedAccessException) { }
-            }
-            return false;
-        }
-
-        /// <summary>
-        /// Finds the time zone id by using 'readlink' on the path to see if tzFilePath is
-        /// a symlink to a file.
-        /// </summary>
-        private static string FindTimeZoneIdUsingReadLink(string tzFilePath)
-        {
-            string id = null;
-
-            StringBuilder symlinkPathBuilder = StringBuilderCache.Acquire(Path.MaxPath);
-            bool result = Interop.GlobalizationInterop.ReadLink(tzFilePath, symlinkPathBuilder, (uint)symlinkPathBuilder.Capacity);
-            if (result)
-            {
-                string symlinkPath = StringBuilderCache.GetStringAndRelease(symlinkPathBuilder);
-                // time zone Ids have to point under the time zone directory
-                string timeZoneDirectory = GetTimeZoneDirectory();
-                if (symlinkPath.StartsWith(timeZoneDirectory))
-                {
-                    id = symlinkPath.Substring(timeZoneDirectory.Length);
-                }
-            }
-            else
-            {
-                StringBuilderCache.Release(symlinkPathBuilder);
-            }
-
-            return id;
-        }
-
-        /// <summary>
-        /// Find the time zone id by searching all the tzfiles for the one that matches rawData
-        /// and return its file name.
-        /// </summary>
-        private static string FindTimeZoneId(byte[] rawData)
-        {
-            // default to "Local" if we can't find the right tzfile
-            string id = LocalId;
-            string timeZoneDirectory = GetTimeZoneDirectory();
-            string localtimeFilePath = Path.Combine(timeZoneDirectory, "localtime");
-            string posixrulesFilePath = Path.Combine(timeZoneDirectory, "posixrules");
-            byte[] buffer = new byte[rawData.Length];
-
-            try
-            {
-                foreach (string filePath in Directory.EnumerateFiles(timeZoneDirectory, "*", SearchOption.AllDirectories))
-                {
-                    // skip the localtime and posixrules file, since they won't give us the correct id
-                    if (!string.Equals(filePath, localtimeFilePath, StringComparison.OrdinalIgnoreCase)
-                        && !string.Equals(filePath, posixrulesFilePath, StringComparison.OrdinalIgnoreCase))
-                    {
-                        if (CompareTimeZoneFile(filePath, buffer, rawData))
-                        {
-                            // if all bytes are the same, this must be the right tz file
-                            id = filePath;
-
-                            // strip off the root time zone directory
-                            if (id.StartsWith(timeZoneDirectory))
-                            {
-                                id = id.Substring(timeZoneDirectory.Length);
-                            }
-                            break;
-                        }
-                    }
-                }
-            }
-            catch (IOException) { }
-            catch (SecurityException) { }
-            catch (UnauthorizedAccessException) { }
-
-            return id;
-        }
-
-        private static bool CompareTimeZoneFile(string filePath, byte[] buffer, byte[] rawData)
-        {
-            try
-            {
-                using (FileStream stream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
-                {
-                    if (stream.Length == rawData.Length)
-                    {
-                        int index = 0;
-                        int count = rawData.Length;
-
-                        while (count > 0)
-                        {
-                            int n = stream.Read(buffer, index, count);
-                            if (n == 0)
-                                __Error.EndOfFile();
-
-                            int end = index + n;
-                            for (; index < end; index++)
-                            {
-                                if (buffer[index] != rawData[index])
-                                {
-                                    return false;
-                                }
-                            }
-
-                            count -= n;
-                        }
-
-                        return true;
-                    }
-                }
-            }
-            catch (IOException) { }
-            catch (SecurityException) { }
-            catch (UnauthorizedAccessException) { }
-
-            return false;
-        }
-
-        /// <summary>
-        /// Helper function used by 'GetLocalTimeZone()' - this function wraps the call
-        /// for loading time zone data from computers without Registry support.
-        ///
-        /// The TryGetLocalTzFile() call returns a Byte[] containing the compiled tzfile.
-        /// </summary>
-        private static TimeZoneInfo GetLocalTimeZoneFromTzFile()
-        {
-            byte[] rawData;
-            string id;
-            if (TryGetLocalTzFile(out rawData, out id))
-            {
-                TimeZoneInfo result = GetTimeZoneFromTzData(rawData, id);
-                if (result != null)
-                {
-                    return result;
-                }
-            }
-
-            // if we can't find a local time zone, return UTC
-            return Utc;
-        }
-
-        private static TimeZoneInfo GetTimeZoneFromTzData(byte[] rawData, string id)
-        {
-            if (rawData != null)
-            {
-                try
-                {
-                    return new TimeZoneInfo(rawData, id, dstDisabled: false); // create a TimeZoneInfo instance from the TZif data w/ DST support
-                }
-                catch (ArgumentException) { }
-                catch (InvalidTimeZoneException) { }
-                try
-                {
-                    return new TimeZoneInfo(rawData, id, dstDisabled: true); // create a TimeZoneInfo instance from the TZif data w/o DST support
-                }
-                catch (ArgumentException) { }
-                catch (InvalidTimeZoneException) { }
-            }
-
-            return null;
-        }
-
-        private static string GetTimeZoneDirectory()
-        {
-            string tzDirectory = Environment.GetEnvironmentVariable(TimeZoneDirectoryEnvironmentVariable);
-
-            if (tzDirectory == null)
-            {
-                tzDirectory = DefaultTimeZoneDirectory;
-            }
-            else if (!tzDirectory.EndsWith(Path.DirectorySeparatorChar))
-            {
-                tzDirectory += Path.DirectorySeparatorChar;
-            }
-
-            return tzDirectory;
-        }
-#elif FEATURE_WIN32_REGISTRY // PLATFORM_UNIX
-
-        /// <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);
-        }
-#endif // PLATFORM_UNIX
-
-        /// <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 (!IsValidSystemTimeZoneId(id))
-            {
-                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)
-            {
-#if FEATURE_WIN32_REGISTRY
-                throw new InvalidTimeZoneException(Environment.GetResourceString("InvalidTimeZone_InvalidRegistryData", id), e);
-#elif PLATFORM_UNIX
-                Debug.Assert(e is InvalidTimeZoneException,
-                    "TryGetTimeZone must create an InvalidTimeZoneException when it returns TimeZoneInfoResult.InvalidTimeZoneException");
-                throw e;
-#endif
-            }
-            else if (result == TimeZoneInfoResult.SecurityException)
-            {
-#if FEATURE_WIN32_REGISTRY
-                throw new SecurityException(Environment.GetResourceString("Security_CannotReadRegistryData", id), e);
-#elif PLATFORM_UNIX
-                throw new SecurityException(Environment.GetResourceString("Security_CannotReadFileData", id), e);
-#endif
-            }
-            else
-            {
-                throw new TimeZoneNotFoundException(Environment.GetResourceString("TimeZoneNotFound_MissingData", id), e);
-            }
-        }
-
-        private static bool IsValidSystemTimeZoneId(string id)
-        {
-            bool isValid = id.Length != 0 && !id.Contains("\0");
-
-#if FEATURE_WIN32_REGISTRY
-            isValid &= id.Length <= MaxKeyLength;
-#endif // FEATURE_WIN32_REGISTRY
-
-            return isValid;
-        }
-
-        /// <summary>
-        /// Helper function that calculates the UTC offset for a dateTime in a timeZone.
-        /// This function assumes that the dateTime is already converted into the timeZone.
-        /// </summary>
-        private static TimeSpan GetUtcOffset(DateTime time, TimeZoneInfo zone, TimeZoneInfoOptions flags)
-        {
-            TimeSpan baseOffset = zone.BaseUtcOffset;
-            AdjustmentRule rule = zone.GetAdjustmentRuleForTime(time);
-
-            if (rule != null)
-            {
-                baseOffset = baseOffset + rule.BaseUtcOffsetDelta;
-                if (rule.HasDaylightSaving)
-                {
-                    DaylightTimeStruct daylightTime = zone.GetDaylightTime(time.Year, rule);
-                    bool isDaylightSavings = GetIsDaylightSavings(time, rule, daylightTime, flags);
-                    baseOffset += (isDaylightSavings ? rule.DaylightDelta : TimeSpan.Zero /* FUTURE: rule.StandardDelta */);
-                }
-            }
-
-            return baseOffset;
-        }
-
-        /// <summary>
-        /// Helper function that calculates the UTC offset for a UTC-dateTime in a timeZone.
-        /// This function assumes that the dateTime is represented in UTC and has *not* already been converted into the timeZone.
-        /// </summary>
-        private static TimeSpan GetUtcOffsetFromUtc(DateTime time, TimeZoneInfo zone)
-        {
-            bool isDaylightSavings;
-            return GetUtcOffsetFromUtc(time, zone, out isDaylightSavings);
-        }
-
-        /// <summary>
-        /// Helper function that calculates the UTC offset for a UTC-dateTime in a timeZone.
-        /// This function assumes that the dateTime is represented in UTC and has *not* already been converted into the timeZone.
-        /// </summary>
-        private static TimeSpan GetUtcOffsetFromUtc(DateTime time, TimeZoneInfo zone, out bool isDaylightSavings)
-        {
-            bool isAmbiguousLocalDst;
-            return GetUtcOffsetFromUtc(time, zone, out isDaylightSavings, out isAmbiguousLocalDst);
-        }
-
-        // 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;
-#if FEATURE_WIN32_REGISTRY
-            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;
-#elif PLATFORM_UNIX
-            // Use the standard code path for Unix since there isn't a faster way of handling current-year-only time zones
-            return GetUtcOffsetFromUtc(time, Local, out isDaylightSavings, out isAmbiguousLocalDst);
-#endif // FEATURE_WIN32_REGISTRY
-        }
-
-        /// <summary>
-        /// Helper function that calculates the UTC offset for a UTC-dateTime in a timeZone.
-        /// This function assumes that the dateTime is represented in UTC and has *not* already been converted into the timeZone.
-        /// </summary>
-        internal static TimeSpan GetUtcOffsetFromUtc(DateTime time, TimeZoneInfo zone, out bool isDaylightSavings, out bool isAmbiguousLocalDst)
-        {
-            isDaylightSavings = false;
-            isAmbiguousLocalDst = false;
-            TimeSpan baseOffset = zone.BaseUtcOffset;
-            int year;
-            AdjustmentRule rule;
-
-            if (time > s_maxDateOnly)
-            {
-                rule = zone.GetAdjustmentRuleForTime(DateTime.MaxValue);
-                year = 9999;
-            }
-            else if (time < s_minDateOnly)
-            {
-                rule = zone.GetAdjustmentRuleForTime(DateTime.MinValue);
-                year = 1;
-            }
-            else
-            {
-                rule = zone.GetAdjustmentRuleForTime(time, dateTimeisUtc: true);
-
-                // As we get the associated rule using the adjusted targetTime, we should use the adjusted year (targetTime.Year) too as after adding the baseOffset,
-                // sometimes the year value can change if the input datetime was very close to the beginning or the end of the year. Examples of such cases:
-                //      Libya Standard Time when used with the date 2011-12-31T23:59:59.9999999Z
-                //      "W. Australia Standard Time" used with date 2005-12-31T23:59:00.0000000Z
-                DateTime targetTime = time + baseOffset;
-                year = targetTime.Year;
-            }
-
-            if (rule != null)
-            {
-                baseOffset = baseOffset + rule.BaseUtcOffsetDelta;
-                if (rule.HasDaylightSaving)
-                {
-                    isDaylightSavings = GetIsDaylightSavingsFromUtc(time, year, zone._baseUtcOffset, rule, out isAmbiguousLocalDst, zone);
-                    baseOffset += (isDaylightSavings ? rule.DaylightDelta : TimeSpan.Zero /* FUTURE: rule.StandardDelta */);
-                }
-            }
-
-            return baseOffset;
-        }
-
-#if FEATURE_WIN32_REGISTRY
-        /// <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;
-        }
-#endif // FEATURE_WIN32_REGISTRY
-
-        /// <summary>
-        /// Helper function that converts a year and TransitionTime into a DateTime.
-        /// </summary>
-        internal static DateTime TransitionTimeToDateTime(int year, TransitionTime transitionTime)
-        {
-            DateTime value;
-            DateTime timeOfDay = transitionTime.TimeOfDay;
-
-            if (transitionTime.IsFixedDateRule)
-            {
-                // create a DateTime from the passed in year and the properties on the transitionTime
-
-                // if the day is out of range for the month then use the last day of the month
-                int day = DateTime.DaysInMonth(year, transitionTime.Month);
-
-                value = new DateTime(year, transitionTime.Month, (day < transitionTime.Day) ? day : transitionTime.Day,
-                            timeOfDay.Hour, timeOfDay.Minute, timeOfDay.Second, timeOfDay.Millisecond);
-            }
-            else
-            {
-                if (transitionTime.Week <= 4)
-                {
-                    //
-                    // Get the (transitionTime.Week)th Sunday.
-                    //
-                    value = new DateTime(year, transitionTime.Month, 1,
-                            timeOfDay.Hour, timeOfDay.Minute, timeOfDay.Second, timeOfDay.Millisecond);
-
-                    int dayOfWeek = (int)value.DayOfWeek;
-                    int delta = (int)transitionTime.DayOfWeek - dayOfWeek;
-                    if (delta < 0)
-                    {
-                        delta += 7;
-                    }
-                    delta += 7 * (transitionTime.Week - 1);
-
-                    if (delta > 0)
-                    {
-                        value = value.AddDays(delta);
-                    }
-                }
-                else
-                {
-                    //
-                    // If TransitionWeek is greater than 4, we will get the last week.
-                    //
-                    int daysInMonth = DateTime.DaysInMonth(year, transitionTime.Month);
-                    value = new DateTime(year, transitionTime.Month, daysInMonth,
-                            timeOfDay.Hour, timeOfDay.Minute, timeOfDay.Second, timeOfDay.Millisecond);
-
-                    // This is the day of week for the last day of the month.
-                    int dayOfWeek = (int)value.DayOfWeek;
-                    int delta = dayOfWeek - (int)transitionTime.DayOfWeek;
-                    if (delta < 0)
-                    {
-                        delta += 7;
-                    }
-
-                    if (delta > 0)
-                    {
-                        value = value.AddDays(-delta);
-                    }
-                }
-            }
-            return value;
-        }
-
-#if FEATURE_WIN32_REGISTRY
-        /// <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(',', StringSplitOptions.None);
-            if (resources.Length != 2)
-            {
-                return string.Empty;
-            }
-
-            string filePath;
-            int resourceId;
-
-            // get the path to Windows\System32
-            string system32 = Environment.UnsafeGetFolderPath(Environment.SpecialFolder.System);
-
-            // 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;
-        }
-
-        //
-        // TryGetTimeZoneByRegistryKey -
-        //
-        // Helper function that takes a string representing a <time_zone_name> registry key name
-        // and returns a TimeZoneInfo instance.
-        //
-        // returns
-        //     TimeZoneInfoResult.InvalidTimeZoneException,
-        //     TimeZoneInfoResult.TimeZoneNotFoundException,
-        //     TimeZoneInfoResult.SecurityException,
-        //     TimeZoneInfoResult.Success
-        //
-        //
-        // 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
-        //
-        private static TimeZoneInfoResult TryGetTimeZoneByRegistryKey(string id, out TimeZoneInfo value, out Exception e)
-        {
-            e = null;
-
-            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;
-                }
-
-            }
-        }
-#endif // FEATURE_WIN32_REGISTRY
-
-        /// <summary>
-        /// Helper function for retrieving a TimeZoneInfo object by <time_zone_name>.
-        ///
-        /// This function may return null.
-        ///
-        /// assumes cachedData lock is taken
-        /// </summary>
-        private static TimeZoneInfoResult TryGetTimeZone(string id, bool dstDisabled, out TimeZoneInfo value, out Exception e, CachedData cachedData)
-        {
-            Debug.Assert(Monitor.IsEntered(cachedData));
-
-            TimeZoneInfoResult result = TimeZoneInfoResult.Success;
-            e = null;
-            TimeZoneInfo match = null;
-
-            // check the cache
-            if (cachedData._systemTimeZones != null)
-            {
-                if (cachedData._systemTimeZones.TryGetValue(id, out match))
-                {
-                    if (dstDisabled && match._supportsDaylightSavingTime)
-                    {
-                        // we found a cache hit but we want a time zone without DST and this one has DST data
-                        value = CreateCustomTimeZone(match._id, match._baseUtcOffset, match._displayName, match._standardDisplayName);
-                    }
-                    else
-                    {
-                        value = new TimeZoneInfo(match._id, match._baseUtcOffset, match._displayName, match._standardDisplayName,
-                                              match._daylightDisplayName, match._adjustmentRules, disableDaylightSavingTime: false);
-                    }
-                    return result;
-                }
-            }
-
-            // fall back to reading from the local machine
-            // when the cache is not fully populated
-            if (!cachedData._allSystemTimeZonesRead)
-            {
-                result = TryGetTimeZoneFromLocalMachine(id, dstDisabled, out value, out e, cachedData);
-            }
-#if PLATFORM_UNIX
-            // On UNIX, there may be some tzfiles that aren't in the zones.tab file, and thus aren't returned from GetSystemTimeZones().
-            // If a caller asks for one of these zones before calling GetSystemTimeZones(), the time zone is returned successfully. But if
-            // GetSystemTimeZones() is called first, FindSystemTimeZoneById will throw TimeZoneNotFoundException, which is inconsistent.
-            // To fix this, even if _allSystemTimeZonesRead is true, try reading the tzfile from disk, but don't add the time zone to the
-            // list returned from GetSystemTimeZones(). These time zones will only be available if asked for directly.
-            else
-            {
-                result = TryGetTimeZoneFromLocalMachine(id, dstDisabled, out value, out e, cachedData);
-            }
-#else
-            else
-            {
-                result = TimeZoneInfoResult.TimeZoneNotFoundException;
-                value = null;
-            }
-#endif // PLATFORM_UNIX
-
-            return result;
-        }
-
-        private static TimeZoneInfoResult TryGetTimeZoneFromLocalMachine(string id, bool dstDisabled, out TimeZoneInfo value, out Exception e, CachedData cachedData)
-        {
-            TimeZoneInfoResult result;
-            TimeZoneInfo match;
-
-#if FEATURE_WIN32_REGISTRY
-            result = TryGetTimeZoneByRegistryKey(id, out match, out e);
-#elif PLATFORM_UNIX
-            result = TryGetTimeZoneByFile(id, out match, out e);
-#endif // FEATURE_WIN32_REGISTRY
-
-            if (result == TimeZoneInfoResult.Success)
-            {
-                if (cachedData._systemTimeZones == null)
-                    cachedData._systemTimeZones = new Dictionary<string, TimeZoneInfo>();
-
-                cachedData._systemTimeZones.Add(id, match);
-
-                if (dstDisabled && match._supportsDaylightSavingTime)
-                {
-                    // we found a cache hit but we want a time zone without DST and this one has DST data
-                    value = CreateCustomTimeZone(match._id, match._baseUtcOffset, match._displayName, match._standardDisplayName);
-                }
-                else
-                {
-                    value = new TimeZoneInfo(match._id, match._baseUtcOffset, match._displayName, match._standardDisplayName,
-                                          match._daylightDisplayName, match._adjustmentRules, disableDaylightSavingTime: false);
-                }
-            }
-            else
-            {
-                value = null;
-            }
-
-            return result;
-        }
-
-#if PLATFORM_UNIX
-        // TZFILE(5)                   BSD File Formats Manual                  TZFILE(5)
-        //
-        // NAME
-        //      tzfile -- timezone information
-        //
-        // SYNOPSIS
-        //      #include "/usr/src/lib/libc/stdtime/tzfile.h"
-        //
-        // DESCRIPTION
-        //      The time zone information files used by tzset(3) begin with the magic
-        //      characters ``TZif'' to identify them as time zone information files, fol-
-        //      lowed by sixteen bytes reserved for future use, followed by four four-
-        //      byte values written in a ``standard'' byte order (the high-order byte of
-        //      the value is written first).  These values are, in order:
-        //
-        //      tzh_ttisgmtcnt  The number of UTC/local indicators stored in the file.
-        //      tzh_ttisstdcnt  The number of standard/wall indicators stored in the
-        //                      file.
-        //      tzh_leapcnt     The number of leap seconds for which data is stored in
-        //                      the file.
-        //      tzh_timecnt     The number of ``transition times'' for which data is
-        //                      stored in the file.
-        //      tzh_typecnt     The number of ``local time types'' for which data is
-        //                      stored in the file (must not be zero).
-        //      tzh_charcnt     The number of characters of ``time zone abbreviation
-        //                      strings'' stored in the file.
-        //
-        //      The above header is followed by tzh_timecnt four-byte values of type
-        //      long, sorted in ascending order.  These values are written in ``stan-
-        //      dard'' byte order.  Each is used as a transition time (as returned by
-        //      time(3)) at which the rules for computing local time change.  Next come
-        //      tzh_timecnt one-byte values of type unsigned char; each one tells which
-        //      of the different types of ``local time'' types described in the file is
-        //      associated with the same-indexed transition time.  These values serve as
-        //      indices into an array of ttinfo structures that appears next in the file;
-        //      these structures are defined as follows:
-        //
-        //            struct ttinfo {
-        //                    long    tt_gmtoff;
-        //                    int     tt_isdst;
-        //                    unsigned int    tt_abbrind;
-        //            };
-        //
-        //      Each structure is written as a four-byte value for tt_gmtoff of type
-        //      long, in a standard byte order, followed by a one-byte value for tt_isdst
-        //      and a one-byte value for tt_abbrind.  In each structure, tt_gmtoff gives
-        //      the number of seconds to be added to UTC, tt_isdst tells whether tm_isdst
-        //      should be set by localtime(3) and tt_abbrind serves as an index into the
-        //      array of time zone abbreviation characters that follow the ttinfo struc-
-        //      ture(s) in the file.
-        //
-        //      Then there are tzh_leapcnt pairs of four-byte values, written in standard
-        //      byte order; the first value of each pair gives the time (as returned by
-        //      time(3)) at which a leap second occurs; the second gives the total number
-        //      of leap seconds to be applied after the given time.  The pairs of values
-        //      are sorted in ascending order by time.b
-        //
-        //      Then there are tzh_ttisstdcnt standard/wall indicators, each stored as a
-        //      one-byte value; they tell whether the transition times associated with
-        //      local time types were specified as standard time or wall clock time, and
-        //      are used when a time zone file is used in handling POSIX-style time zone
-        //      environment variables.
-        //
-        //      Finally there are tzh_ttisgmtcnt UTC/local indicators, each stored as a
-        //      one-byte value; they tell whether the transition times associated with
-        //      local time types were specified as UTC or local time, and are used when a
-        //      time zone file is used in handling POSIX-style time zone environment
-        //      variables.
-        //
-        //      localtime uses the first standard-time ttinfo structure in the file (or
-        //      simply the first ttinfo structure in the absence of a standard-time
-        //      structure) if either tzh_timecnt is zero or the time argument is less
-        //      than the first transition time recorded in the file.
-        //
-        // SEE ALSO
-        //      ctime(3), time2posix(3), zic(8)
-        //
-        // BSD                           September 13, 1994                           BSD
-        //
-        //
-        //
-        // TIME(3)                  BSD Library Functions Manual                  TIME(3)
-        //
-        // NAME
-        //      time -- get time of day
-        //
-        // LIBRARY
-        //      Standard C Library (libc, -lc)
-        //
-        // SYNOPSIS
-        //      #include <time.h>
-        //
-        //      time_t
-        //      time(time_t *tloc);
-        //
-        // DESCRIPTION
-        //      The time() function returns the value of time in seconds since 0 hours, 0
-        //      minutes, 0 seconds, January 1, 1970, Coordinated Universal Time, without
-        //      including leap seconds.  If an error occurs, time() returns the value
-        //      (time_t)-1.
-        //
-        //      The return value is also stored in *tloc, provided that tloc is non-null.
-        //
-        // ERRORS
-        //      The time() function may fail for any of the reasons described in
-        //      gettimeofday(2).
-        //
-        // SEE ALSO
-        //      gettimeofday(2), ctime(3)
-        //
-        // STANDARDS
-        //      The time function conforms to IEEE Std 1003.1-2001 (``POSIX.1'').
-        //
-        // BUGS
-        //      Neither ISO/IEC 9899:1999 (``ISO C99'') nor IEEE Std 1003.1-2001
-        //      (``POSIX.1'') requires time() to set errno on failure; thus, it is impos-
-        //      sible for an application to distinguish the valid time value -1 (repre-
-        //      senting the last UTC second of 1969) from the error return value.
-        //
-        //      Systems conforming to earlier versions of the C and POSIX standards
-        //      (including older versions of FreeBSD) did not set *tloc in the error
-        //      case.
-        //
-        // HISTORY
-        //      A time() function appeared in Version 6 AT&T UNIX.
-        //
-        // BSD                              July 18, 2003                             BSD
-        //
-        //
-        private static void TZif_GenerateAdjustmentRules(out AdjustmentRule[] rules, TimeSpan baseUtcOffset, DateTime[] dts, byte[] typeOfLocalTime,
-            TZifType[] transitionType, bool[] StandardTime, bool[] GmtTime, string futureTransitionsPosixFormat)
-        {
-            rules = null;
-
-            if (dts.Length > 0)
-            {
-                int index = 0;
-                List<AdjustmentRule> rulesList = new List<AdjustmentRule>();
-
-                while (index <= dts.Length)
-                {
-                    TZif_GenerateAdjustmentRule(ref index, baseUtcOffset, rulesList, dts, typeOfLocalTime, transitionType, StandardTime, GmtTime, futureTransitionsPosixFormat);
-                }
-
-                rules = rulesList.ToArray();
-                if (rules != null && rules.Length == 0)
-                {
-                    rules = null;
-                }
-            }
-        }
-
-        private static void TZif_GenerateAdjustmentRule(ref int index, TimeSpan timeZoneBaseUtcOffset, List<AdjustmentRule> rulesList, DateTime[] dts,
-            byte[] typeOfLocalTime, TZifType[] transitionTypes, bool[] StandardTime, bool[] GmtTime, string futureTransitionsPosixFormat)
-        {
-            // To generate AdjustmentRules, use the following approach:
-            // The first AdjustmentRule will go from DateTime.MinValue to the first transition time greater than DateTime.MinValue.
-            // Each middle AdjustmentRule wil go from dts[index-1] to dts[index].
-            // The last AdjustmentRule will go from dts[dts.Length-1] to Datetime.MaxValue.
-
-            // 0. Skip any DateTime.MinValue transition times. In newer versions of the tzfile, there
-            // is a "big bang" transition time, which is before the year 0001. Since any times before year 0001
-            // cannot be represented by DateTime, there is no reason to make AdjustmentRules for these unrepresentable time periods.
-            // 1. If there are no DateTime.MinValue times, the first AdjustmentRule goes from DateTime.MinValue
-            // to the first transition and uses the first standard transitionType (or the first transitionType if none of them are standard)
-            // 2. Create an AdjustmentRule for each transition, i.e. from dts[index - 1] to dts[index].
-            // This rule uses the transitionType[index - 1] and the whole AdjustmentRule only describes a single offset - either
-            // all daylight savings, or all stanard time.
-            // 3. After all the transitions are filled out, the last AdjustmentRule is created from either:
-            //   a. a POSIX-style timezone description ("futureTransitionsPosixFormat"), if there is one or
-            //   b. continue the last transition offset until DateTime.Max
-
-            while (index < dts.Length && dts[index] == DateTime.MinValue)
-            {
-                index++;
-            }
-
-            if (index == 0)
-            {
-                TZifType transitionType = TZif_GetEarlyDateTransitionType(transitionTypes);
-                DateTime endTransitionDate = dts[index];
-
-                TimeSpan transitionOffset = TZif_CalculateTransitionOffsetFromBase(transitionType.UtcOffset, timeZoneBaseUtcOffset);
-                TimeSpan daylightDelta = transitionType.IsDst ? transitionOffset : TimeSpan.Zero;
-                TimeSpan baseUtcDelta = transitionType.IsDst ? TimeSpan.Zero : transitionOffset;
-
-                AdjustmentRule r = AdjustmentRule.CreateAdjustmentRule(
-                        DateTime.MinValue,
-                        endTransitionDate.AddTicks(-1),
-                        daylightDelta,
-                        default(TransitionTime),
-                        default(TransitionTime),
-                        baseUtcDelta,
-                        noDaylightTransitions: true);
-                rulesList.Add(r);
-            }
-            else if (index < dts.Length)
-            {
-                DateTime startTransitionDate = dts[index - 1];
-                TZifType startTransitionType = transitionTypes[typeOfLocalTime[index - 1]];
-
-                DateTime endTransitionDate = dts[index];
-
-                TimeSpan transitionOffset = TZif_CalculateTransitionOffsetFromBase(startTransitionType.UtcOffset, timeZoneBaseUtcOffset);
-                TimeSpan daylightDelta = startTransitionType.IsDst ? transitionOffset : TimeSpan.Zero;
-                TimeSpan baseUtcDelta = startTransitionType.IsDst ? TimeSpan.Zero : transitionOffset;
-
-                TransitionTime dstStart;
-                if (startTransitionType.IsDst)
-                {
-                    // the TransitionTime fields are not used when AdjustmentRule.NoDaylightTransitions == true.
-                    // However, there are some cases in the past where DST = true, and the daylight savings offset
-                    // now equals what the current BaseUtcOffset is.  In that case, the AdjustmentRule.DaylightOffset
-                    // is going to be TimeSpan.Zero.  But we still need to return 'true' from AdjustmentRule.HasDaylightSaving.
-                    // To ensure we always return true from HasDaylightSaving, make a "special" dstStart that will make the logic
-                    // in HasDaylightSaving return true.
-                    dstStart = TransitionTime.CreateFixedDateRule(DateTime.MinValue.AddMilliseconds(2), 1, 1);
-                }
-                else
-                {
-                    dstStart = default(TransitionTime);
-                }
-
-                AdjustmentRule r = AdjustmentRule.CreateAdjustmentRule(
-                        startTransitionDate,
-                        endTransitionDate.AddTicks(-1),
-                        daylightDelta,
-                        dstStart,
-                        default(TransitionTime),
-                        baseUtcDelta,
-                        noDaylightTransitions: true);
-                rulesList.Add(r);
-            }
-            else
-            {
-                // create the AdjustmentRule that will be used for all DateTimes after the last transition
-
-                // NOTE: index == dts.Length
-                DateTime startTransitionDate = dts[index - 1];
-
-                if (!string.IsNullOrEmpty(futureTransitionsPosixFormat))
-                {
-                    AdjustmentRule r = TZif_CreateAdjustmentRuleForPosixFormat(futureTransitionsPosixFormat, startTransitionDate, timeZoneBaseUtcOffset);
-                    if (r != null)
-                    {
-                        rulesList.Add(r);
-                    }
-                }
-                else
-                {
-                    // just use the last transition as the rule which will be used until the end of time
-
-                    TZifType transitionType = transitionTypes[typeOfLocalTime[index - 1]];
-                    TimeSpan transitionOffset = TZif_CalculateTransitionOffsetFromBase(transitionType.UtcOffset, timeZoneBaseUtcOffset);
-                    TimeSpan daylightDelta = transitionType.IsDst ? transitionOffset : TimeSpan.Zero;
-                    TimeSpan baseUtcDelta = transitionType.IsDst ? TimeSpan.Zero : transitionOffset;
-
-                    AdjustmentRule r = AdjustmentRule.CreateAdjustmentRule(
-                        startTransitionDate,
-                        DateTime.MaxValue,
-                        daylightDelta,
-                        default(TransitionTime),
-                        default(TransitionTime),
-                        baseUtcDelta,
-                        noDaylightTransitions: true);
-                    rulesList.Add(r);
-                }
-            }
-
-            index++;
-        }
-
-        private static TimeSpan TZif_CalculateTransitionOffsetFromBase(TimeSpan transitionOffset, TimeSpan timeZoneBaseUtcOffset)
-        {
-            TimeSpan result = transitionOffset - timeZoneBaseUtcOffset;
-
-            // TZif supports seconds-level granularity with offsets but TimeZoneInfo only supports minutes since it aligns
-            // with DateTimeOffset, SQL Server, and the W3C XML Specification
-            if (result.Ticks % TimeSpan.TicksPerMinute != 0)
-            {
-                result = new TimeSpan(result.Hours, result.Minutes, 0);
-            }
-
-            return result;
+            return baseOffset;
         }
 
         /// <summary>
-        /// Gets the first standard-time transition type, or simply the first transition type
-        /// if there are no standard transition types.
-        /// </summary>>
-        /// <remarks>
-        /// from 'man tzfile':
-        /// localtime(3)  uses the first standard-time ttinfo structure in the file
-        /// (or simply the first ttinfo structure in the absence of a standard-time
-        /// structure)  if  either tzh_timecnt is zero or the time argument is less
-        /// than the first transition time recorded in the file.
-        /// </remarks>
-        private static TZifType TZif_GetEarlyDateTransitionType(TZifType[] transitionTypes)
+        /// Helper function that converts a year and TransitionTime into a DateTime.
+        /// </summary>
+        internal static DateTime TransitionTimeToDateTime(int year, TransitionTime transitionTime)
         {
-            foreach (TZifType transitionType in transitionTypes)
-            {
-                if (!transitionType.IsDst)
-                {
-                    return transitionType;
-                }
-            }
+            DateTime value;
+            DateTime timeOfDay = transitionTime.TimeOfDay;
 
-            if (transitionTypes.Length > 0)
+            if (transitionTime.IsFixedDateRule)
             {
-                return transitionTypes[0];
-            }
+                // create a DateTime from the passed in year and the properties on the transitionTime
 
-            throw new InvalidTimeZoneException(Environment.GetResourceString("InvalidTimeZone_NoTTInfoStructures"));
-        }
+                // if the day is out of range for the month then use the last day of the month
+                int day = DateTime.DaysInMonth(year, transitionTime.Month);
 
-        /// <summary>
-        /// Creates an AdjustmentRule given the POSIX TZ environment variable string.
-        /// </summary>
-        /// <remarks>
-        /// See http://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html for the format and semantics of this POSX string.
-        /// </remarks>
-        private static AdjustmentRule TZif_CreateAdjustmentRuleForPosixFormat(string posixFormat, DateTime startTransitionDate, TimeSpan timeZoneBaseUtcOffset)
-        {
-            string standardName;
-            string standardOffset;
-            string daylightSavingsName;
-            string daylightSavingsOffset;
-            string start;
-            string startTime;
-            string end;
-            string endTime;
-
-            if (TZif_ParsePosixFormat(posixFormat, out standardName, out standardOffset, out daylightSavingsName,
-                out daylightSavingsOffset, out start, out startTime, out end, out endTime))
+                value = new DateTime(year, transitionTime.Month, (day < transitionTime.Day) ? day : transitionTime.Day,
+                            timeOfDay.Hour, timeOfDay.Minute, timeOfDay.Second, timeOfDay.Millisecond);
+            }
+            else
             {
-                // a valid posixFormat has at least standardName and standardOffset
-
-                TimeSpan? parsedBaseOffset = TZif_ParseOffsetString(standardOffset);
-                if (parsedBaseOffset.HasValue)
+                if (transitionTime.Week <= 4)
                 {
-                    TimeSpan baseOffset = parsedBaseOffset.Value.Negate(); // offsets are backwards in POSIX notation
-                    baseOffset = TZif_CalculateTransitionOffsetFromBase(baseOffset, timeZoneBaseUtcOffset);
-
-                    // having a daylightSavingsName means there is a DST rule
-                    if (!string.IsNullOrEmpty(daylightSavingsName))
-                    {
-                        TimeSpan? parsedDaylightSavings = TZif_ParseOffsetString(daylightSavingsOffset);
-                        TimeSpan daylightSavingsTimeSpan;
-                        if (!parsedDaylightSavings.HasValue)
-                        {
-                            // default DST to 1 hour if it isn't specified
-                            daylightSavingsTimeSpan = new TimeSpan(1, 0, 0);
-                        }
-                        else
-                        {
-                            daylightSavingsTimeSpan = parsedDaylightSavings.Value.Negate(); // offsets are backwards in POSIX notation
-                            daylightSavingsTimeSpan = TZif_CalculateTransitionOffsetFromBase(daylightSavingsTimeSpan, timeZoneBaseUtcOffset);
-                            daylightSavingsTimeSpan = TZif_CalculateTransitionOffsetFromBase(daylightSavingsTimeSpan, baseOffset);
-                        }
+                    //
+                    // Get the (transitionTime.Week)th Sunday.
+                    //
+                    value = new DateTime(year, transitionTime.Month, 1,
+                            timeOfDay.Hour, timeOfDay.Minute, timeOfDay.Second, timeOfDay.Millisecond);
 
-                        TransitionTime dstStart = TZif_CreateTransitionTimeFromPosixRule(start, startTime);
-                        TransitionTime dstEnd = TZif_CreateTransitionTimeFromPosixRule(end, endTime);
-
-                        return AdjustmentRule.CreateAdjustmentRule(
-                            startTransitionDate,
-                            DateTime.MaxValue,
-                            daylightSavingsTimeSpan,
-                            dstStart,
-                            dstEnd,
-                            baseOffset,
-                            noDaylightTransitions: false);
-                    }
-                    else
+                    int dayOfWeek = (int)value.DayOfWeek;
+                    int delta = (int)transitionTime.DayOfWeek - dayOfWeek;
+                    if (delta < 0)
                     {
-                        // if there is no daylightSavingsName, the whole AdjustmentRule should be with no transitions - just the baseOffset
-                        return AdjustmentRule.CreateAdjustmentRule(
-                               startTransitionDate,
-                               DateTime.MaxValue,
-                               TimeSpan.Zero,
-                               default(TransitionTime),
-                               default(TransitionTime),
-                               baseOffset,
-                               noDaylightTransitions: true);
+                        delta += 7;
                     }
-                }
-            }
-
-            return null;
-        }
-
-        private static TimeSpan? TZif_ParseOffsetString(string offset)
-        {
-            TimeSpan? result = null;
-
-            if (!string.IsNullOrEmpty(offset))
-            {
-                bool negative = offset[0] == '-';
-                if (negative || offset[0] == '+')
-                {
-                    offset = offset.Substring(1);
-                }
+                    delta += 7 * (transitionTime.Week - 1);
 
-                // Try parsing just hours first.
-                // Note, TimeSpan.TryParseExact "%h" can't be used here because some time zones using values
-                // like "26" or "144" and TimeSpan parsing would turn that into 26 or 144 *days* instead of hours.
-                int hours;
-                if (int.TryParse(offset, out hours))
-                {
-                    result = new TimeSpan(hours, 0, 0);
-                }
-                else
-                {
-                    TimeSpan parsedTimeSpan;
-                    if (TimeSpan.TryParseExact(offset, "g", CultureInfo.InvariantCulture, out parsedTimeSpan))
+                    if (delta > 0)
                     {
-                        result = parsedTimeSpan;
+                        value = value.AddDays(delta);
                     }
                 }
-
-                if (result.HasValue && negative)
-                {
-                    result = result.Value.Negate();
-                }
-            }
-
-            return result;
-        }
-
-        private static TransitionTime TZif_CreateTransitionTimeFromPosixRule(string date, string time)
-        {
-            if (string.IsNullOrEmpty(date))
-            {
-                return default(TransitionTime);
-            }
-
-            if (date[0] == 'M')
-            {
-                // Mm.w.d
-                // This specifies day d of week w of month m. The day d must be between 0(Sunday) and 6.The week w must be between 1 and 5;
-                // week 1 is the first week in which day d occurs, and week 5 specifies the last d day in the month. The month m should be between 1 and 12.
-
-                int month;
-                int week;
-                DayOfWeek day;
-                if (!TZif_ParseMDateRule(date, out month, out week, out day))
+                else
                 {
-                    throw new InvalidTimeZoneException(Environment.GetResourceString("InvalidTimeZone_UnparseablePosixMDateString", date));
-                }
+                    //
+                    // If TransitionWeek is greater than 4, we will get the last week.
+                    //
+                    int daysInMonth = DateTime.DaysInMonth(year, transitionTime.Month);
+                    value = new DateTime(year, transitionTime.Month, daysInMonth,
+                            timeOfDay.Hour, timeOfDay.Minute, timeOfDay.Second, timeOfDay.Millisecond);
 
-                DateTime timeOfDay;
-                TimeSpan? timeOffset = TZif_ParseOffsetString(time);
-                if (timeOffset.HasValue)
-                {
-                    // This logic isn't correct and can't be corrected until https://github.com/dotnet/corefx/issues/2618 is fixed.
-                    // Some time zones use time values like, "26", "144", or "-2".
-                    // This allows the week to sometimes be week 4 and sometimes week 5 in the month.
-                    // For now, strip off any 'days' in the offset, and just get the time of day correct
-                    timeOffset = new TimeSpan(timeOffset.Value.Hours, timeOffset.Value.Minutes, timeOffset.Value.Seconds);
-                    if (timeOffset.Value < TimeSpan.Zero)
+                    // This is the day of week for the last day of the month.
+                    int dayOfWeek = (int)value.DayOfWeek;
+                    int delta = dayOfWeek - (int)transitionTime.DayOfWeek;
+                    if (delta < 0)
                     {
-                        timeOfDay = new DateTime(1, 1, 2, 0, 0, 0);
+                        delta += 7;
                     }
-                    else
+
+                    if (delta > 0)
                     {
-                        timeOfDay = new DateTime(1, 1, 1, 0, 0, 0);
+                        value = value.AddDays(-delta);
                     }
-
-                    timeOfDay += timeOffset.Value;
-                }
-                else
-                {
-                    // default to 2AM.
-                    timeOfDay = new DateTime(1, 1, 1, 2, 0, 0);
                 }
-
-                return TransitionTime.CreateFloatingDateRule(timeOfDay, month, week, day);
-            }
-            else
-            {
-                // Jn
-                // This specifies the Julian day, with n between 1 and 365.February 29 is never counted, even in leap years.
-
-                // n
-                // This specifies the Julian day, with n between 0 and 365.February 29 is counted in leap years.
-
-                // These two rules cannot be expressed with the current AdjustmentRules
-                // One of them *could* be supported if we relaxed the TransitionTime validation rules, and allowed
-                // "IsFixedDateRule = true, Month = 0, Day = n" to mean the nth day of the year, picking one of the rules above
-
-                throw new InvalidTimeZoneException(Environment.GetResourceString("InvalidTimeZone_JulianDayNotSupported"));
             }
+            return value;
         }
 
         /// <summary>
-        /// Parses a string like Mm.w.d into month, week and DayOfWeek values.
+        /// Helper function for retrieving a TimeZoneInfo object by <time_zone_name>.
+        ///
+        /// This function may return null.
+        ///
+        /// assumes cachedData lock is taken
         /// </summary>
-        /// <returns>
-        /// true if the parsing succeeded; otherwise, false.
-        /// </returns>
-        private static bool TZif_ParseMDateRule(string dateRule, out int month, out int week, out DayOfWeek dayOfWeek)
+        private static TimeZoneInfoResult TryGetTimeZone(string id, bool dstDisabled, out TimeZoneInfo value, out Exception e, CachedData cachedData, bool alwaysFallbackToLocalMachine = false)
         {
-            month = 0;
-            week = 0;
-            dayOfWeek = default(DayOfWeek);
+            Debug.Assert(Monitor.IsEntered(cachedData));
+
+            TimeZoneInfoResult result = TimeZoneInfoResult.Success;
+            e = null;
+            TimeZoneInfo match = null;
 
-            if (dateRule[0] == 'M')
+            // check the cache
+            if (cachedData._systemTimeZones != null)
             {
-                int firstDotIndex = dateRule.IndexOf('.');
-                if (firstDotIndex > 0)
+                if (cachedData._systemTimeZones.TryGetValue(id, out match))
                 {
-                    int secondDotIndex = dateRule.IndexOf('.', firstDotIndex + 1);
-                    if (secondDotIndex > 0)
+                    if (dstDisabled && match._supportsDaylightSavingTime)
                     {
-                        string monthString = dateRule.Substring(1, firstDotIndex - 1);
-                        string weekString = dateRule.Substring(firstDotIndex + 1, secondDotIndex - firstDotIndex - 1);
-                        string dayString = dateRule.Substring(secondDotIndex + 1);
-
-                        if (int.TryParse(monthString, out month))
-                        {
-                            if (int.TryParse(weekString, out week))
-                            {
-                                int day;
-                                if (int.TryParse(dayString, out day))
-                                {
-                                    dayOfWeek = (DayOfWeek)day;
-                                    return true;
-                                }
-                            }
-                        }
+                        // we found a cache hit but we want a time zone without DST and this one has DST data
+                        value = CreateCustomTimeZone(match._id, match._baseUtcOffset, match._displayName, match._standardDisplayName);
                     }
-                }
-            }
-
-            return false;
-        }
-
-        private static bool TZif_ParsePosixFormat(
-            string posixFormat,
-            out string standardName,
-            out string standardOffset,
-            out string daylightSavingsName,
-            out string daylightSavingsOffset,
-            out string start,
-            out string startTime,
-            out string end,
-            out string endTime)
-        {
-            standardName = null;
-            standardOffset = null;
-            daylightSavingsName = null;
-            daylightSavingsOffset = null;
-            start = null;
-            startTime = null;
-            end = null;
-            endTime = null;
-
-            int index = 0;
-            standardName = TZif_ParsePosixName(posixFormat, ref index);
-            standardOffset = TZif_ParsePosixOffset(posixFormat, ref index);
-
-            daylightSavingsName = TZif_ParsePosixName(posixFormat, ref index);
-            if (!string.IsNullOrEmpty(daylightSavingsName))
-            {
-                daylightSavingsOffset = TZif_ParsePosixOffset(posixFormat, ref index);
-
-                if (index < posixFormat.Length && posixFormat[index] == ',')
-                {
-                    index++;
-                    TZif_ParsePosixDateTime(posixFormat, ref index, out start, out startTime);
-
-                    if (index < posixFormat.Length && posixFormat[index] == ',')
+                    else
                     {
-                        index++;
-                        TZif_ParsePosixDateTime(posixFormat, ref index, out end, out endTime);
+                        value = new TimeZoneInfo(match._id, match._baseUtcOffset, match._displayName, match._standardDisplayName,
+                                              match._daylightDisplayName, match._adjustmentRules, disableDaylightSavingTime: false);
                     }
+                    return result;
                 }
             }
 
-            return !string.IsNullOrEmpty(standardName) && !string.IsNullOrEmpty(standardOffset);
-        }
-
-        private static string TZif_ParsePosixName(string posixFormat, ref int index) =>
-            TZif_ParsePosixString(posixFormat, ref index, c => char.IsDigit(c) || c == '+' || c == '-' || c == ',');
-
-        private static string TZif_ParsePosixOffset(string posixFormat, ref int index) =>
-            TZif_ParsePosixString(posixFormat, ref index, c => !char.IsDigit(c) && c != '+' && c != '-' && c != ':');
-
-        private static void TZif_ParsePosixDateTime(string posixFormat, ref int index, out string date, out string time)
-        {
-            time = null;
-
-            date = TZif_ParsePosixDate(posixFormat, ref index);
-            if (index < posixFormat.Length && posixFormat[index] == '/')
-            {
-                index++;
-                time = TZif_ParsePosixTime(posixFormat, ref index);
-            }
-        }
-
-        private static string TZif_ParsePosixDate(string posixFormat, ref int index) =>
-            TZif_ParsePosixString(posixFormat, ref index, c => c == '/' || c == ',');
-
-        private static string TZif_ParsePosixTime(string posixFormat, ref int index) =>
-            TZif_ParsePosixString(posixFormat, ref index, c => c == ',');
-
-        private static string TZif_ParsePosixString(string posixFormat, ref int index, Func<char, bool> breakCondition)
-        {
-            int startIndex = index;
-            for (; index < posixFormat.Length; index++)
+            // Fall back to reading from the local machine when the cache is not fully populated.
+            // On UNIX, there may be some tzfiles that aren't in the zones.tab file, and thus aren't returned from GetSystemTimeZones().
+            // If a caller asks for one of these zones before calling GetSystemTimeZones(), the time zone is returned successfully. But if
+            // GetSystemTimeZones() is called first, FindSystemTimeZoneById will throw TimeZoneNotFoundException, which is inconsistent.
+            // To fix this, when 'alwaysFallbackToLocalMachine' is true, even if _allSystemTimeZonesRead is true, try reading the tzfile
+            // from disk, but don't add the time zone to the list returned from GetSystemTimeZones(). These time zones will only be
+            // available if asked for directly.
+            if (!cachedData._allSystemTimeZonesRead || alwaysFallbackToLocalMachine)
             {
-                char current = posixFormat[index];
-                if (breakCondition(current))
-                {
-                    break;
-                }
+                result = TryGetTimeZoneFromLocalMachine(id, dstDisabled, out value, out e, cachedData);
             }
-
-            return posixFormat.Substring(startIndex, index - startIndex);
-        }
-
-        // Returns the Substring from zoneAbbreviations starting at index and ending at '\0'
-        // zoneAbbreviations is expected to be in the form: "PST\0PDT\0PWT\0\PPT"
-        private static string TZif_GetZoneAbbreviation(string zoneAbbreviations, int index)
-        {
-            int lastIndex = zoneAbbreviations.IndexOf('\0', index);
-            return lastIndex > 0 ?
-                zoneAbbreviations.Substring(index, lastIndex - index) :
-                zoneAbbreviations.Substring(index);
-        }
-
-        // Converts an array of bytes into an int - always using standard byte order (Big Endian)
-        // per TZif file standard
-        private static unsafe int TZif_ToInt32(byte[] value, int startIndex)
-        {
-            fixed (byte* pbyte = &value[startIndex])
+            else
             {
-                return (*pbyte << 24) | (*(pbyte + 1) << 16) | (*(pbyte + 2) << 8) | (*(pbyte + 3));
+                result = TimeZoneInfoResult.TimeZoneNotFoundException;
+                value = null;
             }
-        }
 
-        // Converts an array of bytes into a long - always using standard byte order (Big Endian)
-        // per TZif file standard
-        private static unsafe long TZif_ToInt64(byte[] value, int startIndex)
-        {
-            fixed (byte* pbyte = &value[startIndex])
-            {
-                int i1 = (*pbyte << 24) | (*(pbyte + 1) << 16) | (*(pbyte + 2) << 8) | (*(pbyte + 3));
-                int i2 = (*(pbyte + 4) << 24) | (*(pbyte + 5) << 16) | (*(pbyte + 6) << 8) | (*(pbyte + 7));
-                return (uint)i2 | ((long)i1 << 32);
-            }
+            return result;
         }
 
-        private static long TZif_ToUnixTime(byte[] value, int startIndex, TZVersion version) =>
-            version != TZVersion.V1 ?
-                TZif_ToInt64(value, startIndex) :
-                TZif_ToInt32(value, startIndex);
-
-        private static DateTime TZif_UnixTimeToDateTime(long unixTime) =>
-            unixTime < DateTimeOffset.UnixMinSeconds ? DateTime.MinValue :
-            unixTime > DateTimeOffset.UnixMaxSeconds ? DateTime.MaxValue :
-            DateTimeOffset.FromUnixTimeSeconds(unixTime).UtcDateTime;
-
-        private static void TZif_ParseRaw(byte[] data, out TZifHead t, out DateTime[] dts, out byte[] typeOfLocalTime, out TZifType[] transitionType,
-                                          out string zoneAbbreviations, out bool[] StandardTime, out bool[] GmtTime, out string futureTransitionsPosixFormat)
+        private static TimeZoneInfoResult TryGetTimeZoneFromLocalMachine(string id, bool dstDisabled, out TimeZoneInfo value, out Exception e, CachedData cachedData)
         {
-            // initialize the out parameters in case the TZifHead ctor throws
-            dts = null;
-            typeOfLocalTime = null;
-            transitionType = null;
-            zoneAbbreviations = string.Empty;
-            StandardTime = null;
-            GmtTime = null;
-            futureTransitionsPosixFormat = null;
-
-            // read in the 44-byte TZ header containing the count/length fields
-            //
-            int index = 0;
-            t = new TZifHead(data, index);
-            index += TZifHead.Length;
-
-            int timeValuesLength = 4; // the first version uses 4-bytes to specify times
-            if (t.Version != TZVersion.V1)
-            {
-                // move index past the V1 information to read the V2 information
-                index += (int)((timeValuesLength * t.TimeCount) + t.TimeCount + (6 * t.TypeCount) + ((timeValuesLength + 4) * t.LeapCount) + t.IsStdCount + t.IsGmtCount + t.CharCount);
-
-                // read the V2 header
-                t = new TZifHead(data, index);
-                index += TZifHead.Length;
-                timeValuesLength = 8; // the second version uses 8-bytes
-            }
-
-            // initialize the containers for the rest of the TZ data
-            dts = new DateTime[t.TimeCount];
-            typeOfLocalTime = new byte[t.TimeCount];
-            transitionType = new TZifType[t.TypeCount];
-            zoneAbbreviations = string.Empty;
-            StandardTime = new bool[t.TypeCount];
-            GmtTime = new bool[t.TypeCount];
-
-            // read in the UTC transition points and convert them to Windows
-            //
-            for (int i = 0; i < t.TimeCount; i++)
-            {
-                long unixTime = TZif_ToUnixTime(data, index, t.Version);
-                dts[i] = TZif_UnixTimeToDateTime(unixTime);
-                index += timeValuesLength;
-            }
+            TimeZoneInfoResult result;
+            TimeZoneInfo match;
 
-            // read in the Type Indices; there is a 1:1 mapping of UTC transition points to Type Indices
-            // these indices directly map to the array index in the transitionType array below
-            //
-            for (int i = 0; i < t.TimeCount; i++)
-            {
-                typeOfLocalTime[i] = data[index];
-                index += 1;
-            }
+            result = TryGetTimeZoneFromLocalMachine(id, out match, out e);
 
-            // read in the Type table.  Each 6-byte entry represents
-            // {UtcOffset, IsDst, AbbreviationIndex}
-            //
-            // each AbbreviationIndex is a character index into the zoneAbbreviations string below
-            //
-            for (int i = 0; i < t.TypeCount; i++)
+            if (result == TimeZoneInfoResult.Success)
             {
-                transitionType[i] = new TZifType(data, index);
-                index += 6;
-            }
-
-            // read in the Abbreviation ASCII string.  This string will be in the form:
-            // "PST\0PDT\0PWT\0\PPT"
-            //
-            Encoding enc = Encoding.UTF8;
-            zoneAbbreviations = enc.GetString(data, index, (int)t.CharCount);
-            index += (int)t.CharCount;
+                if (cachedData._systemTimeZones == null)
+                    cachedData._systemTimeZones = new Dictionary<string, TimeZoneInfo>();
 
-            // skip ahead of the Leap-Seconds Adjustment data.  In a future release, consider adding
-            // support for Leap-Seconds
-            //
-            index += (int)(t.LeapCount * (timeValuesLength + 4)); // skip the leap second transition times
+                cachedData._systemTimeZones.Add(id, match);
 
-            // read in the Standard Time table.  There should be a 1:1 mapping between Type-Index and Standard
-            // Time table entries.
-            //
-            // TRUE     =     transition time is standard time
-            // FALSE    =     transition time is wall clock time
-            // ABSENT   =     transition time is wall clock time
-            //
-            for (int i = 0; i < t.IsStdCount && i < t.TypeCount && index < data.Length; i++)
-            {
-                StandardTime[i] = (data[index++] != 0);
+                if (dstDisabled && match._supportsDaylightSavingTime)
+                {
+                    // we found a cache hit but we want a time zone without DST and this one has DST data
+                    value = CreateCustomTimeZone(match._id, match._baseUtcOffset, match._displayName, match._standardDisplayName);
+                }
+                else
+                {
+                    value = new TimeZoneInfo(match._id, match._baseUtcOffset, match._displayName, match._standardDisplayName,
+                                          match._daylightDisplayName, match._adjustmentRules, disableDaylightSavingTime: false);
+                }
             }
-
-            // read in the GMT Time table.  There should be a 1:1 mapping between Type-Index and GMT Time table
-            // entries.
-            //
-            // TRUE     =     transition time is UTC
-            // FALSE    =     transition time is local time
-            // ABSENT   =     transition time is local time
-            //
-            for (int i = 0; i < t.IsGmtCount && i < t.TypeCount && index < data.Length; i++)
+            else
             {
-                GmtTime[i] = (data[index++] != 0);
+                value = null;
             }
 
-            if (t.Version != TZVersion.V1)
-            {
-                // read the POSIX-style format, which should be wrapped in newlines with the last newline at the end of the file
-                if (data[index++] == '\n' && data[data.Length - 1] == '\n')
-                {
-                    futureTransitionsPosixFormat = enc.GetString(data, index, data.Length - index - 1);
-                }
-            }
+            return result;
         }
-#endif // PLATFORM_UNIX
 
         /// <summary>
         /// Helper function that validates the TimeSpan is within +/- 14.0 hours
@@ -4296,88 +1939,5 @@ namespace System
                 }
             }
         }
-
-#if PLATFORM_UNIX
-        private struct TZifType
-        {
-            public const int Length = 6;
-
-            public readonly TimeSpan UtcOffset;
-            public readonly bool IsDst;
-            public readonly byte AbbreviationIndex;
-
-            public TZifType(byte[] data, int index)
-            {
-                if (data == null || data.Length < index + Length)
-                {
-                    throw new ArgumentException(Environment.GetResourceString("Argument_TimeZoneInfoInvalidTZif"), nameof(data));
-                }
-                Contract.EndContractBlock();
-                UtcOffset = new TimeSpan(0, 0, TZif_ToInt32(data, index + 00));
-                IsDst = (data[index + 4] != 0);
-                AbbreviationIndex = data[index + 5];
-            }
-        }
-
-        private struct TZifHead
-        {
-            public const int Length = 44;
-
-            public readonly uint Magic; // TZ_MAGIC "TZif"
-            public readonly TZVersion Version; // 1 byte for a \0 or 2 or 3
-            // public byte[15] Reserved; // reserved for future use
-            public readonly uint IsGmtCount; // number of transition time flags
-            public readonly uint IsStdCount; // number of transition time flags
-            public readonly uint LeapCount; // number of leap seconds
-            public readonly uint TimeCount; // number of transition times
-            public readonly uint TypeCount; // number of local time types
-            public readonly uint CharCount; // number of abbreviated characters
-
-            public TZifHead(byte[] data, int index)
-            {
-                if (data == null || data.Length < Length)
-                {
-                    throw new ArgumentException("bad data", nameof(data));
-                }
-                Contract.EndContractBlock();
-
-                Magic = (uint)TZif_ToInt32(data, index + 00);
-
-                if (Magic != 0x545A6966)
-                {
-                    // 0x545A6966 = {0x54, 0x5A, 0x69, 0x66} = "TZif"
-                    throw new ArgumentException(Environment.GetResourceString("Argument_TimeZoneInfoBadTZif"), nameof(data));
-                }
-
-                byte version = data[index + 04];
-                Version =
-                    version == '2' ? TZVersion.V2 :
-                    version == '3' ? TZVersion.V3 :
-                    TZVersion.V1;  // default/fallback to V1 to guard against future, unsupported version numbers
-
-                // skip the 15 byte reserved field
-
-                // don't use the BitConverter class which parses data
-                // based on the Endianess of the machine architecture.
-                // this data is expected to always be in "standard byte order",
-                // regardless of the machine it is being processed on.
-
-                IsGmtCount = (uint)TZif_ToInt32(data, index + 20);
-                IsStdCount = (uint)TZif_ToInt32(data, index + 24);
-                LeapCount = (uint)TZif_ToInt32(data, index + 28);
-                TimeCount = (uint)TZif_ToInt32(data, index + 32);
-                TypeCount = (uint)TZif_ToInt32(data, index + 36);
-                CharCount = (uint)TZif_ToInt32(data, index + 40);
-            }
-        }
-
-        private enum TZVersion : byte
-        {
-            V1 = 0,
-            V2,
-            V3,
-            // when adding more versions, ensure all the logic using TZVersion is still correct
-        }
-#endif // PLATFORM_UNIX
     }
 }