410eaf3ff1d01c909eaf56739151c0fc39611242
[platform/upstream/coreclr.git] / src / mscorlib / shared / System / TimeZoneInfo.Unix.cs
1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4
5 using System.Collections.Generic;
6 using System.Diagnostics;
7 using System.Globalization;
8 using System.IO;
9 using System.Text;
10 using System.Threading;
11 using System.Security;
12
13 using Internal.IO;
14
15 namespace System
16 {
17     public sealed partial class TimeZoneInfo
18     {
19         private const string DefaultTimeZoneDirectory = "/usr/share/zoneinfo/";
20         private const string ZoneTabFileName = "zone.tab";
21         private const string TimeZoneEnvironmentVariable = "TZ";
22         private const string TimeZoneDirectoryEnvironmentVariable = "TZDIR";
23
24         private TimeZoneInfo(byte[] data, string id, bool dstDisabled)
25         {
26             TZifHead t;
27             DateTime[] dts;
28             byte[] typeOfLocalTime;
29             TZifType[] transitionType;
30             string zoneAbbreviations;
31             bool[] StandardTime;
32             bool[] GmtTime;
33             string futureTransitionsPosixFormat;
34
35             // parse the raw TZif bytes; this method can throw ArgumentException when the data is malformed.
36             TZif_ParseRaw(data, out t, out dts, out typeOfLocalTime, out transitionType, out zoneAbbreviations, out StandardTime, out GmtTime, out futureTransitionsPosixFormat);
37
38             _id = id;
39             _displayName = LocalId;
40             _baseUtcOffset = TimeSpan.Zero;
41
42             // find the best matching baseUtcOffset and display strings based on the current utcNow value.
43             // NOTE: read the display strings from the tzfile now in case they can't be loaded later
44             // from the globalization data.
45             DateTime utcNow = DateTime.UtcNow;
46             for (int i = 0; i < dts.Length && dts[i] <= utcNow; i++)
47             {
48                 int type = typeOfLocalTime[i];
49                 if (!transitionType[type].IsDst)
50                 {
51                     _baseUtcOffset = transitionType[type].UtcOffset;
52                     _standardDisplayName = TZif_GetZoneAbbreviation(zoneAbbreviations, transitionType[type].AbbreviationIndex);
53                 }
54                 else
55                 {
56                     _daylightDisplayName = TZif_GetZoneAbbreviation(zoneAbbreviations, transitionType[type].AbbreviationIndex);
57                 }
58             }
59
60             if (dts.Length == 0)
61             {
62                 // time zones like Africa/Bujumbura and Etc/GMT* have no transition times but still contain
63                 // TZifType entries that may contain a baseUtcOffset and display strings
64                 for (int i = 0; i < transitionType.Length; i++)
65                 {
66                     if (!transitionType[i].IsDst)
67                     {
68                         _baseUtcOffset = transitionType[i].UtcOffset;
69                         _standardDisplayName = TZif_GetZoneAbbreviation(zoneAbbreviations, transitionType[i].AbbreviationIndex);
70                     }
71                     else
72                     {
73                         _daylightDisplayName = TZif_GetZoneAbbreviation(zoneAbbreviations, transitionType[i].AbbreviationIndex);
74                     }
75                 }
76             }
77             _displayName = _standardDisplayName;
78
79             GetDisplayName(Interop.Globalization.TimeZoneDisplayNameType.Generic, ref _displayName);
80             GetDisplayName(Interop.Globalization.TimeZoneDisplayNameType.Standard, ref _standardDisplayName);
81             GetDisplayName(Interop.Globalization.TimeZoneDisplayNameType.DaylightSavings, ref _daylightDisplayName);
82
83             // TZif supports seconds-level granularity with offsets but TimeZoneInfo only supports minutes since it aligns
84             // with DateTimeOffset, SQL Server, and the W3C XML Specification
85             if (_baseUtcOffset.Ticks % TimeSpan.TicksPerMinute != 0)
86             {
87                 _baseUtcOffset = new TimeSpan(_baseUtcOffset.Hours, _baseUtcOffset.Minutes, 0);
88             }
89
90             if (!dstDisabled)
91             {
92                 // only create the adjustment rule if DST is enabled
93                 TZif_GenerateAdjustmentRules(out _adjustmentRules, _baseUtcOffset, dts, typeOfLocalTime, transitionType, StandardTime, GmtTime, futureTransitionsPosixFormat);
94             }
95
96             ValidateTimeZoneInfo(_id, _baseUtcOffset, _adjustmentRules, out _supportsDaylightSavingTime);
97         }
98
99         private void GetDisplayName(Interop.Globalization.TimeZoneDisplayNameType nameType, ref string displayName)
100         {
101             if (GlobalizationMode.Invariant)
102             {
103                 displayName = _standardDisplayName;
104                 return;
105             }
106
107             string timeZoneDisplayName;
108             bool result = Interop.CallStringMethod(
109                 (locale, id, type, stringBuilder) => Interop.Globalization.GetTimeZoneDisplayName(
110                     locale,
111                     id,
112                     type,
113                     stringBuilder,
114                     stringBuilder.Capacity),
115                 CultureInfo.CurrentUICulture.Name,
116                 _id,
117                 nameType,
118                 out timeZoneDisplayName);
119
120             // If there is an unknown error, don't set the displayName field.
121             // It will be set to the abbreviation that was read out of the tzfile.
122             if (result)
123             {
124                 displayName = timeZoneDisplayName;
125             }
126         }
127
128         /// <summary>
129         /// Returns a cloned array of AdjustmentRule objects
130         /// </summary>
131         public AdjustmentRule[] GetAdjustmentRules()
132         {
133             if (_adjustmentRules == null)
134             {
135                 return Array.Empty<AdjustmentRule>();
136             }
137
138             // The rules we use in Unix care mostly about the start and end dates but don't fill the transition start and end info.
139             // as the rules now is public, we should fill it properly so the caller doesn't have to know how we use it internally
140             // and can use it as it is used in Windows
141
142             AdjustmentRule[] rules = new AdjustmentRule[_adjustmentRules.Length];
143
144             for (int i = 0; i < _adjustmentRules.Length; i++)
145             {
146                 var rule = _adjustmentRules[i];
147                 var start = rule.DateStart.Kind == DateTimeKind.Utc ?
148                             // At the daylight start we didn't start the daylight saving yet then we convert to Local time
149                             // by adding the _baseUtcOffset to the UTC time
150                             new DateTime(rule.DateStart.Ticks + _baseUtcOffset.Ticks, DateTimeKind.Unspecified) :
151                             rule.DateStart;
152                 var end = rule.DateEnd.Kind == DateTimeKind.Utc ?
153                             // At the daylight saving end, the UTC time is mapped to local time which is already shifted by the daylight delta
154                             // we calculate the local time by adding _baseUtcOffset + DaylightDelta to the UTC time
155                             new DateTime(rule.DateEnd.Ticks + _baseUtcOffset.Ticks + rule.DaylightDelta.Ticks, DateTimeKind.Unspecified) :
156                             rule.DateEnd;
157
158                 var startTransition = TimeZoneInfo.TransitionTime.CreateFixedDateRule(new DateTime(1, 1, 1, start.Hour, start.Minute, start.Second), start.Month, start.Day);
159                 var endTransition = TimeZoneInfo.TransitionTime.CreateFixedDateRule(new DateTime(1, 1, 1, end.Hour, end.Minute, end.Second), end.Month, end.Day);
160
161                 rules[i] = TimeZoneInfo.AdjustmentRule.CreateAdjustmentRule(start.Date, end.Date, rule.DaylightDelta, startTransition, endTransition);
162             }
163
164             return rules;
165         }
166
167         private static void PopulateAllSystemTimeZones(CachedData cachedData)
168         {
169             Debug.Assert(Monitor.IsEntered(cachedData));
170
171             string timeZoneDirectory = GetTimeZoneDirectory();
172             foreach (string timeZoneId in GetTimeZoneIds(timeZoneDirectory))
173             {
174                 TimeZoneInfo value;
175                 Exception ex;
176                 TryGetTimeZone(timeZoneId, false, out value, out ex, cachedData, alwaysFallbackToLocalMachine: true);  // populate the cache
177             }
178         }
179
180         /// <summary>
181         /// Helper function for retrieving the local system time zone.
182         /// May throw COMException, TimeZoneNotFoundException, InvalidTimeZoneException.
183         /// Assumes cachedData lock is taken.
184         /// </summary>
185         /// <returns>A new TimeZoneInfo instance.</returns>
186         private static TimeZoneInfo GetLocalTimeZone(CachedData cachedData)
187         {
188             Debug.Assert(Monitor.IsEntered(cachedData));
189
190             // Without Registry support, create the TimeZoneInfo from a TZ file
191             return GetLocalTimeZoneFromTzFile();
192         }
193
194         private static TimeZoneInfoResult TryGetTimeZoneFromLocalMachine(string id, out TimeZoneInfo value, out Exception e)
195         {
196             value = null;
197             e = null;
198
199             string timeZoneDirectory = GetTimeZoneDirectory();
200             string timeZoneFilePath = Path.Combine(timeZoneDirectory, id);
201             byte[] rawData;
202             try
203             {
204                 rawData = File.ReadAllBytes(timeZoneFilePath);
205             }
206             catch (UnauthorizedAccessException ex)
207             {
208                 e = ex;
209                 return TimeZoneInfoResult.SecurityException;
210             }
211             catch (FileNotFoundException ex)
212             {
213                 e = ex;
214                 return TimeZoneInfoResult.TimeZoneNotFoundException;
215             }
216             catch (DirectoryNotFoundException ex)
217             {
218                 e = ex;
219                 return TimeZoneInfoResult.TimeZoneNotFoundException;
220             }
221             catch (IOException ex)
222             {
223                 e = new InvalidTimeZoneException(SR.Format(SR.InvalidTimeZone_InvalidFileData, id, timeZoneFilePath), ex);
224                 return TimeZoneInfoResult.InvalidTimeZoneException;
225             }
226
227             value = GetTimeZoneFromTzData(rawData, id);
228
229             if (value == null)
230             {
231                 e = new InvalidTimeZoneException(SR.Format(SR.InvalidTimeZone_InvalidFileData, id, timeZoneFilePath));
232                 return TimeZoneInfoResult.InvalidTimeZoneException;
233             }
234
235             return TimeZoneInfoResult.Success;
236         }
237
238         /// <summary>
239         /// Returns a collection of TimeZone Id values from the zone.tab file in the timeZoneDirectory.
240         /// </summary>
241         /// <remarks>
242         /// Lines that start with # are comments and are skipped.
243         /// </remarks>
244         private static List<string> GetTimeZoneIds(string timeZoneDirectory)
245         {
246             List<string> timeZoneIds = new List<string>();
247
248             try
249             {
250                 using (StreamReader sr = new StreamReader(Path.Combine(timeZoneDirectory, ZoneTabFileName), Encoding.UTF8))
251                 {
252                     string zoneTabFileLine;
253                     while ((zoneTabFileLine = sr.ReadLine()) != null)
254                     {
255                         if (!string.IsNullOrEmpty(zoneTabFileLine) && zoneTabFileLine[0] != '#')
256                         {
257                             // the format of the line is "country-code \t coordinates \t TimeZone Id \t comments"
258
259                             int firstTabIndex = zoneTabFileLine.IndexOf('\t');
260                             if (firstTabIndex != -1)
261                             {
262                                 int secondTabIndex = zoneTabFileLine.IndexOf('\t', firstTabIndex + 1);
263                                 if (secondTabIndex != -1)
264                                 {
265                                     string timeZoneId;
266                                     int startIndex = secondTabIndex + 1;
267                                     int thirdTabIndex = zoneTabFileLine.IndexOf('\t', startIndex);
268                                     if (thirdTabIndex != -1)
269                                     {
270                                         int length = thirdTabIndex - startIndex;
271                                         timeZoneId = zoneTabFileLine.Substring(startIndex, length);
272                                     }
273                                     else
274                                     {
275                                         timeZoneId = zoneTabFileLine.Substring(startIndex);
276                                     }
277
278                                     if (!string.IsNullOrEmpty(timeZoneId))
279                                     {
280                                         timeZoneIds.Add(timeZoneId);
281                                     }
282                                 }
283                             }
284                         }
285                     }
286                 }
287             }
288             catch (IOException) { }
289             catch (UnauthorizedAccessException) { }
290
291             return timeZoneIds;
292         }
293
294         /// <summary>
295         /// Gets the tzfile raw data for the current 'local' time zone using the following rules.
296         /// 1. Read the TZ environment variable.  If it is set, use it.
297         /// 2. Look for the data in /etc/localtime.
298         /// 3. Look for the data in GetTimeZoneDirectory()/localtime.
299         /// 4. Use UTC if all else fails.
300         /// </summary>
301         private static bool TryGetLocalTzFile(out byte[] rawData, out string id)
302         {
303             rawData = null;
304             id = null;
305             string tzVariable = GetTzEnvironmentVariable();
306
307             // If the env var is null, use the localtime file
308             if (tzVariable == null)
309             {
310                 return
311                     TryLoadTzFile("/etc/localtime", ref rawData, ref id) ||
312                     TryLoadTzFile(Path.Combine(GetTimeZoneDirectory(), "localtime"), ref rawData, ref id);
313             }
314
315             // If it's empty, use UTC (TryGetLocalTzFile() should return false).
316             if (tzVariable.Length == 0)
317             {
318                 return false;
319             }
320
321             // Otherwise, use the path from the env var.  If it's not absolute, make it relative
322             // to the system timezone directory
323             string tzFilePath;
324             if (tzVariable[0] != '/')
325             {
326                 id = tzVariable;
327                 tzFilePath = Path.Combine(GetTimeZoneDirectory(), tzVariable);
328             }
329             else
330             {
331                 tzFilePath = tzVariable;
332             }
333             return TryLoadTzFile(tzFilePath, ref rawData, ref id);
334         }
335
336         private static string GetTzEnvironmentVariable()
337         {
338             string result = Environment.GetEnvironmentVariable(TimeZoneEnvironmentVariable);
339             if (!string.IsNullOrEmpty(result))
340             {
341                 if (result[0] == ':')
342                 {
343                     // strip off the ':' prefix
344                     result = result.Substring(1);
345                 }
346             }
347
348             return result;
349         }
350
351         private static bool TryLoadTzFile(string tzFilePath, ref byte[] rawData, ref string id)
352         {
353             if (File.Exists(tzFilePath))
354             {
355                 try
356                 {
357                     rawData = File.ReadAllBytes(tzFilePath);
358                     if (string.IsNullOrEmpty(id))
359                     {
360                         id = FindTimeZoneIdUsingReadLink(tzFilePath);
361
362                         if (string.IsNullOrEmpty(id))
363                         {
364                             id = FindTimeZoneId(rawData);
365                         }
366                     }
367                     return true;
368                 }
369                 catch (IOException) { }
370                 catch (SecurityException) { }
371                 catch (UnauthorizedAccessException) { }
372             }
373             return false;
374         }
375
376         /// <summary>
377         /// Finds the time zone id by using 'readlink' on the path to see if tzFilePath is
378         /// a symlink to a file.
379         /// </summary>
380         private static string FindTimeZoneIdUsingReadLink(string tzFilePath)
381         {
382             string id = null;
383
384             string symlinkPath = Interop.Sys.ReadLink(tzFilePath);
385             if (symlinkPath != null)
386             {
387                 // Use Path.Combine to resolve links that contain a relative path (e.g. /etc/localtime).
388                 symlinkPath = Path.Combine(tzFilePath, symlinkPath);
389
390                 string timeZoneDirectory = GetTimeZoneDirectory();
391                 if (symlinkPath.StartsWith(timeZoneDirectory, StringComparison.Ordinal))
392                 {
393                     id = symlinkPath.Substring(timeZoneDirectory.Length);
394                 }
395             }
396
397             return id;
398         }
399
400         /// <summary>
401         /// Enumerate files 
402         /// </summary>
403         private static IEnumerable<string> EnumerateFilesRecursively(string path)
404         {
405             List<string> toExplore = null; // List used as a stack
406
407             string currentPath = path;
408             for(;;)
409             {
410                 using (Microsoft.Win32.SafeHandles.SafeDirectoryHandle dirHandle = Interop.Sys.OpenDir(currentPath))
411                 {
412                     if (dirHandle.IsInvalid)
413                     {
414                         throw Interop.GetExceptionForIoErrno(Interop.Sys.GetLastErrorInfo(), currentPath, isDirectory: true);
415                     }
416
417                     // Read each entry from the enumerator
418                     Interop.Sys.DirectoryEntry dirent;
419                     while (Interop.Sys.ReadDir(dirHandle, out dirent) == 0)
420                     {
421                         if (dirent.InodeName == "." || dirent.InodeName == "..")
422                             continue;
423
424                         string fullPath = Path.Combine(currentPath, dirent.InodeName);
425
426                         // Get from the dir entry whether the entry is a file or directory.
427                         // We classify everything as a file unless we know it to be a directory.
428                         bool isDir;
429                         if (dirent.InodeType == Interop.Sys.NodeType.DT_DIR)
430                         {
431                             // We know it's a directory.
432                             isDir = true;
433                         }
434                         else if (dirent.InodeType == Interop.Sys.NodeType.DT_LNK || dirent.InodeType == Interop.Sys.NodeType.DT_UNKNOWN)
435                         {
436                             // It's a symlink or unknown: stat to it to see if we can resolve it to a directory.
437                             // If we can't (e.g. symlink to a file, broken symlink, etc.), we'll just treat it as a file.
438
439                             Interop.Sys.FileStatus fileinfo;
440                             if (Interop.Sys.Stat(fullPath, out fileinfo) >= 0)
441                             {
442                                 isDir = (fileinfo.Mode & Interop.Sys.FileTypes.S_IFMT) == Interop.Sys.FileTypes.S_IFDIR;
443                             }
444                             else
445                             {
446                                 isDir = false;
447                             }
448                         }
449                         else
450                         {
451                             // Otherwise, treat it as a file.  This includes regular files, FIFOs, etc.
452                             isDir = false;
453                         }
454
455                         // Yield the result if the user has asked for it.  In the case of directories,
456                         // always explore it by pushing it onto the stack, regardless of whether
457                         // we're returning directories.
458                         if (isDir)
459                         {
460                             if (toExplore == null)
461                             {
462                                 toExplore = new List<string>();
463                             }
464                             toExplore.Add(fullPath);
465                         }
466                         else
467                         {
468                             yield return fullPath;
469                         }
470                     }
471                 }
472
473                 if (toExplore == null || toExplore.Count == 0)
474                     break;
475
476                 currentPath = toExplore[toExplore.Count - 1];
477                 toExplore.RemoveAt(toExplore.Count - 1);
478             }
479         }
480
481         /// <summary>
482         /// Find the time zone id by searching all the tzfiles for the one that matches rawData
483         /// and return its file name.
484         /// </summary>
485         private static string FindTimeZoneId(byte[] rawData)
486         {
487             // default to "Local" if we can't find the right tzfile
488             string id = LocalId;
489             string timeZoneDirectory = GetTimeZoneDirectory();
490             string localtimeFilePath = Path.Combine(timeZoneDirectory, "localtime");
491             string posixrulesFilePath = Path.Combine(timeZoneDirectory, "posixrules");
492             byte[] buffer = new byte[rawData.Length];
493
494             try
495             {
496                 foreach (string filePath in EnumerateFilesRecursively(timeZoneDirectory))
497                 {
498                     // skip the localtime and posixrules file, since they won't give us the correct id
499                     if (!string.Equals(filePath, localtimeFilePath, StringComparison.OrdinalIgnoreCase)
500                         && !string.Equals(filePath, posixrulesFilePath, StringComparison.OrdinalIgnoreCase))
501                     {
502                         if (CompareTimeZoneFile(filePath, buffer, rawData))
503                         {
504                             // if all bytes are the same, this must be the right tz file
505                             id = filePath;
506
507                             // strip off the root time zone directory
508                             if (id.StartsWith(timeZoneDirectory, StringComparison.Ordinal))
509                             {
510                                 id = id.Substring(timeZoneDirectory.Length);
511                             }
512                             break;
513                         }
514                     }
515                 }
516             }
517             catch (IOException) { }
518             catch (SecurityException) { }
519             catch (UnauthorizedAccessException) { }
520
521             return id;
522         }
523
524         private static bool CompareTimeZoneFile(string filePath, byte[] buffer, byte[] rawData)
525         {
526             try
527             {
528                 // bufferSize == 1 used to avoid unnecessary buffer in FileStream
529                 using (FileStream stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: 1))
530                 {
531                     if (stream.Length == rawData.Length)
532                     {
533                         int index = 0;
534                         int count = rawData.Length;
535
536                         while (count > 0)
537                         {
538                             int n = stream.Read(buffer, index, count);
539                             if (n == 0)
540                                 throw Error.GetEndOfFile();
541
542                             int end = index + n;
543                             for (; index < end; index++)
544                             {
545                                 if (buffer[index] != rawData[index])
546                                 {
547                                     return false;
548                                 }
549                             }
550
551                             count -= n;
552                         }
553
554                         return true;
555                     }
556                 }
557             }
558             catch (IOException) { }
559             catch (SecurityException) { }
560             catch (UnauthorizedAccessException) { }
561
562             return false;
563         }
564
565         /// <summary>
566         /// Helper function used by 'GetLocalTimeZone()' - this function wraps the call
567         /// for loading time zone data from computers without Registry support.
568         ///
569         /// The TryGetLocalTzFile() call returns a Byte[] containing the compiled tzfile.
570         /// </summary>
571         private static TimeZoneInfo GetLocalTimeZoneFromTzFile()
572         {
573             byte[] rawData;
574             string id;
575             if (TryGetLocalTzFile(out rawData, out id))
576             {
577                 TimeZoneInfo result = GetTimeZoneFromTzData(rawData, id);
578                 if (result != null)
579                 {
580                     return result;
581                 }
582             }
583
584             // if we can't find a local time zone, return UTC
585             return Utc;
586         }
587
588         private static TimeZoneInfo GetTimeZoneFromTzData(byte[] rawData, string id)
589         {
590             if (rawData != null)
591             {
592                 try
593                 {
594                     return new TimeZoneInfo(rawData, id, dstDisabled: false); // create a TimeZoneInfo instance from the TZif data w/ DST support
595                 }
596                 catch (ArgumentException) { }
597                 catch (InvalidTimeZoneException) { }
598                 try
599                 {
600                     return new TimeZoneInfo(rawData, id, dstDisabled: true); // create a TimeZoneInfo instance from the TZif data w/o DST support
601                 }
602                 catch (ArgumentException) { }
603                 catch (InvalidTimeZoneException) { }
604             }
605
606             return null;
607         }
608
609         private static string GetTimeZoneDirectory()
610         {
611             string tzDirectory = Environment.GetEnvironmentVariable(TimeZoneDirectoryEnvironmentVariable);
612
613             if (tzDirectory == null)
614             {
615                 tzDirectory = DefaultTimeZoneDirectory;
616             }
617             else if (!tzDirectory.EndsWith(Path.DirectorySeparatorChar))
618             {
619                 tzDirectory += Path.DirectorySeparatorChar;
620             }
621
622             return tzDirectory;
623         }
624
625         /// <summary>
626         /// Helper function for retrieving a TimeZoneInfo object by <time_zone_name>.
627         /// This function wraps the logic necessary to keep the private
628         /// SystemTimeZones cache in working order
629         ///
630         /// This function will either return a valid TimeZoneInfo instance or
631         /// it will throw 'InvalidTimeZoneException' / 'TimeZoneNotFoundException'.
632         /// </summary>
633         public static TimeZoneInfo FindSystemTimeZoneById(string id)
634         {
635             // Special case for Utc as it will not exist in the dictionary with the rest
636             // of the system time zones.  There is no need to do this check for Local.Id
637             // since Local is a real time zone that exists in the dictionary cache
638             if (string.Equals(id, UtcId, StringComparison.OrdinalIgnoreCase))
639             {
640                 return Utc;
641             }
642
643             if (id == null)
644             {
645                 throw new ArgumentNullException(nameof(id));
646             }
647             else if (id.Length == 0 || id.Contains('\0'))
648             {
649                 throw new TimeZoneNotFoundException(SR.Format(SR.TimeZoneNotFound_MissingData, id));
650             }
651
652             TimeZoneInfo value;
653             Exception e;
654
655             TimeZoneInfoResult result;
656
657             CachedData cachedData = s_cachedData;
658
659             lock (cachedData)
660             {
661                 result = TryGetTimeZone(id, false, out value, out e, cachedData, alwaysFallbackToLocalMachine: true);
662             }
663
664             if (result == TimeZoneInfoResult.Success)
665             {
666                 return value;
667             }
668             else if (result == TimeZoneInfoResult.InvalidTimeZoneException)
669             {
670                 Debug.Assert(e is InvalidTimeZoneException,
671                     "TryGetTimeZone must create an InvalidTimeZoneException when it returns TimeZoneInfoResult.InvalidTimeZoneException");
672                 throw e;
673             }
674             else if (result == TimeZoneInfoResult.SecurityException)
675             {
676                 throw new SecurityException(SR.Format(SR.Security_CannotReadFileData, id), e);
677             }
678             else
679             {
680                 throw new TimeZoneNotFoundException(SR.Format(SR.TimeZoneNotFound_MissingData, id), e);
681             }
682         }
683
684         // DateTime.Now fast path that avoids allocating an historically accurate TimeZoneInfo.Local and just creates a 1-year (current year) accurate time zone
685         internal static TimeSpan GetDateTimeNowUtcOffsetFromUtc(DateTime time, out bool isAmbiguousLocalDst)
686         {
687             bool isDaylightSavings;
688             // Use the standard code path for Unix since there isn't a faster way of handling current-year-only time zones
689             return GetUtcOffsetFromUtc(time, Local, out isDaylightSavings, out isAmbiguousLocalDst);
690         }
691
692         // TZFILE(5)                   BSD File Formats Manual                  TZFILE(5)
693         //
694         // NAME
695         //      tzfile -- timezone information
696         //
697         // SYNOPSIS
698         //      #include "/usr/src/lib/libc/stdtime/tzfile.h"
699         //
700         // DESCRIPTION
701         //      The time zone information files used by tzset(3) begin with the magic
702         //      characters ``TZif'' to identify them as time zone information files, fol-
703         //      lowed by sixteen bytes reserved for future use, followed by four four-
704         //      byte values written in a ``standard'' byte order (the high-order byte of
705         //      the value is written first).  These values are, in order:
706         //
707         //      tzh_ttisgmtcnt  The number of UTC/local indicators stored in the file.
708         //      tzh_ttisstdcnt  The number of standard/wall indicators stored in the
709         //                      file.
710         //      tzh_leapcnt     The number of leap seconds for which data is stored in
711         //                      the file.
712         //      tzh_timecnt     The number of ``transition times'' for which data is
713         //                      stored in the file.
714         //      tzh_typecnt     The number of ``local time types'' for which data is
715         //                      stored in the file (must not be zero).
716         //      tzh_charcnt     The number of characters of ``time zone abbreviation
717         //                      strings'' stored in the file.
718         //
719         //      The above header is followed by tzh_timecnt four-byte values of type
720         //      long, sorted in ascending order.  These values are written in ``stan-
721         //      dard'' byte order.  Each is used as a transition time (as returned by
722         //      time(3)) at which the rules for computing local time change.  Next come
723         //      tzh_timecnt one-byte values of type unsigned char; each one tells which
724         //      of the different types of ``local time'' types described in the file is
725         //      associated with the same-indexed transition time.  These values serve as
726         //      indices into an array of ttinfo structures that appears next in the file;
727         //      these structures are defined as follows:
728         //
729         //            struct ttinfo {
730         //                    long    tt_gmtoff;
731         //                    int     tt_isdst;
732         //                    unsigned int    tt_abbrind;
733         //            };
734         //
735         //      Each structure is written as a four-byte value for tt_gmtoff of type
736         //      long, in a standard byte order, followed by a one-byte value for tt_isdst
737         //      and a one-byte value for tt_abbrind.  In each structure, tt_gmtoff gives
738         //      the number of seconds to be added to UTC, tt_isdst tells whether tm_isdst
739         //      should be set by localtime(3) and tt_abbrind serves as an index into the
740         //      array of time zone abbreviation characters that follow the ttinfo struc-
741         //      ture(s) in the file.
742         //
743         //      Then there are tzh_leapcnt pairs of four-byte values, written in standard
744         //      byte order; the first value of each pair gives the time (as returned by
745         //      time(3)) at which a leap second occurs; the second gives the total number
746         //      of leap seconds to be applied after the given time.  The pairs of values
747         //      are sorted in ascending order by time.b
748         //
749         //      Then there are tzh_ttisstdcnt standard/wall indicators, each stored as a
750         //      one-byte value; they tell whether the transition times associated with
751         //      local time types were specified as standard time or wall clock time, and
752         //      are used when a time zone file is used in handling POSIX-style time zone
753         //      environment variables.
754         //
755         //      Finally there are tzh_ttisgmtcnt UTC/local indicators, each stored as a
756         //      one-byte value; they tell whether the transition times associated with
757         //      local time types were specified as UTC or local time, and are used when a
758         //      time zone file is used in handling POSIX-style time zone environment
759         //      variables.
760         //
761         //      localtime uses the first standard-time ttinfo structure in the file (or
762         //      simply the first ttinfo structure in the absence of a standard-time
763         //      structure) if either tzh_timecnt is zero or the time argument is less
764         //      than the first transition time recorded in the file.
765         //
766         // SEE ALSO
767         //      ctime(3), time2posix(3), zic(8)
768         //
769         // BSD                           September 13, 1994                           BSD
770         //
771         //
772         //
773         // TIME(3)                  BSD Library Functions Manual                  TIME(3)
774         //
775         // NAME
776         //      time -- get time of day
777         //
778         // LIBRARY
779         //      Standard C Library (libc, -lc)
780         //
781         // SYNOPSIS
782         //      #include <time.h>
783         //
784         //      time_t
785         //      time(time_t *tloc);
786         //
787         // DESCRIPTION
788         //      The time() function returns the value of time in seconds since 0 hours, 0
789         //      minutes, 0 seconds, January 1, 1970, Coordinated Universal Time, without
790         //      including leap seconds.  If an error occurs, time() returns the value
791         //      (time_t)-1.
792         //
793         //      The return value is also stored in *tloc, provided that tloc is non-null.
794         //
795         // ERRORS
796         //      The time() function may fail for any of the reasons described in
797         //      gettimeofday(2).
798         //
799         // SEE ALSO
800         //      gettimeofday(2), ctime(3)
801         //
802         // STANDARDS
803         //      The time function conforms to IEEE Std 1003.1-2001 (``POSIX.1'').
804         //
805         // BUGS
806         //      Neither ISO/IEC 9899:1999 (``ISO C99'') nor IEEE Std 1003.1-2001
807         //      (``POSIX.1'') requires time() to set errno on failure; thus, it is impos-
808         //      sible for an application to distinguish the valid time value -1 (repre-
809         //      senting the last UTC second of 1969) from the error return value.
810         //
811         //      Systems conforming to earlier versions of the C and POSIX standards
812         //      (including older versions of FreeBSD) did not set *tloc in the error
813         //      case.
814         //
815         // HISTORY
816         //      A time() function appeared in Version 6 AT&T UNIX.
817         //
818         // BSD                              July 18, 2003                             BSD
819         //
820         //
821         private static void TZif_GenerateAdjustmentRules(out AdjustmentRule[] rules, TimeSpan baseUtcOffset, DateTime[] dts, byte[] typeOfLocalTime,
822             TZifType[] transitionType, bool[] StandardTime, bool[] GmtTime, string futureTransitionsPosixFormat)
823         {
824             rules = null;
825
826             if (dts.Length > 0)
827             {
828                 int index = 0;
829                 List<AdjustmentRule> rulesList = new List<AdjustmentRule>();
830
831                 while (index <= dts.Length)
832                 {
833                     TZif_GenerateAdjustmentRule(ref index, baseUtcOffset, rulesList, dts, typeOfLocalTime, transitionType, StandardTime, GmtTime, futureTransitionsPosixFormat);
834                 }
835
836                 rules = rulesList.ToArray();
837                 if (rules != null && rules.Length == 0)
838                 {
839                     rules = null;
840                 }
841             }
842         }
843
844         private static void TZif_GenerateAdjustmentRule(ref int index, TimeSpan timeZoneBaseUtcOffset, List<AdjustmentRule> rulesList, DateTime[] dts,
845             byte[] typeOfLocalTime, TZifType[] transitionTypes, bool[] StandardTime, bool[] GmtTime, string futureTransitionsPosixFormat)
846         {
847             // To generate AdjustmentRules, use the following approach:
848             // The first AdjustmentRule will go from DateTime.MinValue to the first transition time greater than DateTime.MinValue.
849             // Each middle AdjustmentRule wil go from dts[index-1] to dts[index].
850             // The last AdjustmentRule will go from dts[dts.Length-1] to Datetime.MaxValue.
851
852             // 0. Skip any DateTime.MinValue transition times. In newer versions of the tzfile, there
853             // is a "big bang" transition time, which is before the year 0001. Since any times before year 0001
854             // cannot be represented by DateTime, there is no reason to make AdjustmentRules for these unrepresentable time periods.
855             // 1. If there are no DateTime.MinValue times, the first AdjustmentRule goes from DateTime.MinValue
856             // to the first transition and uses the first standard transitionType (or the first transitionType if none of them are standard)
857             // 2. Create an AdjustmentRule for each transition, i.e. from dts[index - 1] to dts[index].
858             // This rule uses the transitionType[index - 1] and the whole AdjustmentRule only describes a single offset - either
859             // all daylight savings, or all stanard time.
860             // 3. After all the transitions are filled out, the last AdjustmentRule is created from either:
861             //   a. a POSIX-style timezone description ("futureTransitionsPosixFormat"), if there is one or
862             //   b. continue the last transition offset until DateTime.Max
863
864             while (index < dts.Length && dts[index] == DateTime.MinValue)
865             {
866                 index++;
867             }
868
869             if (index == 0)
870             {
871                 TZifType transitionType = TZif_GetEarlyDateTransitionType(transitionTypes);
872                 DateTime endTransitionDate = dts[index];
873
874                 TimeSpan transitionOffset = TZif_CalculateTransitionOffsetFromBase(transitionType.UtcOffset, timeZoneBaseUtcOffset);
875                 TimeSpan daylightDelta = transitionType.IsDst ? transitionOffset : TimeSpan.Zero;
876                 TimeSpan baseUtcDelta = transitionType.IsDst ? TimeSpan.Zero : transitionOffset;
877
878                 AdjustmentRule r = AdjustmentRule.CreateAdjustmentRule(
879                         DateTime.MinValue,
880                         endTransitionDate.AddTicks(-1),
881                         daylightDelta,
882                         default(TransitionTime),
883                         default(TransitionTime),
884                         baseUtcDelta,
885                         noDaylightTransitions: true);
886                 rulesList.Add(r);
887             }
888             else if (index < dts.Length)
889             {
890                 DateTime startTransitionDate = dts[index - 1];
891                 TZifType startTransitionType = transitionTypes[typeOfLocalTime[index - 1]];
892
893                 DateTime endTransitionDate = dts[index];
894
895                 TimeSpan transitionOffset = TZif_CalculateTransitionOffsetFromBase(startTransitionType.UtcOffset, timeZoneBaseUtcOffset);
896                 TimeSpan daylightDelta = startTransitionType.IsDst ? transitionOffset : TimeSpan.Zero;
897                 TimeSpan baseUtcDelta = startTransitionType.IsDst ? TimeSpan.Zero : transitionOffset;
898
899                 TransitionTime dstStart;
900                 if (startTransitionType.IsDst)
901                 {
902                     // the TransitionTime fields are not used when AdjustmentRule.NoDaylightTransitions == true.
903                     // However, there are some cases in the past where DST = true, and the daylight savings offset
904                     // now equals what the current BaseUtcOffset is.  In that case, the AdjustmentRule.DaylightOffset
905                     // is going to be TimeSpan.Zero.  But we still need to return 'true' from AdjustmentRule.HasDaylightSaving.
906                     // To ensure we always return true from HasDaylightSaving, make a "special" dstStart that will make the logic
907                     // in HasDaylightSaving return true.
908                     dstStart = TransitionTime.CreateFixedDateRule(DateTime.MinValue.AddMilliseconds(2), 1, 1);
909                 }
910                 else
911                 {
912                     dstStart = default(TransitionTime);
913                 }
914
915                 AdjustmentRule r = AdjustmentRule.CreateAdjustmentRule(
916                         startTransitionDate,
917                         endTransitionDate.AddTicks(-1),
918                         daylightDelta,
919                         dstStart,
920                         default(TransitionTime),
921                         baseUtcDelta,
922                         noDaylightTransitions: true);
923                 rulesList.Add(r);
924             }
925             else
926             {
927                 // create the AdjustmentRule that will be used for all DateTimes after the last transition
928
929                 // NOTE: index == dts.Length
930                 DateTime startTransitionDate = dts[index - 1];
931
932                 if (!string.IsNullOrEmpty(futureTransitionsPosixFormat))
933                 {
934                     AdjustmentRule r = TZif_CreateAdjustmentRuleForPosixFormat(futureTransitionsPosixFormat, startTransitionDate, timeZoneBaseUtcOffset);
935                     if (r != null)
936                     {
937                         rulesList.Add(r);
938                     }
939                 }
940                 else
941                 {
942                     // just use the last transition as the rule which will be used until the end of time
943
944                     TZifType transitionType = transitionTypes[typeOfLocalTime[index - 1]];
945                     TimeSpan transitionOffset = TZif_CalculateTransitionOffsetFromBase(transitionType.UtcOffset, timeZoneBaseUtcOffset);
946                     TimeSpan daylightDelta = transitionType.IsDst ? transitionOffset : TimeSpan.Zero;
947                     TimeSpan baseUtcDelta = transitionType.IsDst ? TimeSpan.Zero : transitionOffset;
948
949                     AdjustmentRule r = AdjustmentRule.CreateAdjustmentRule(
950                         startTransitionDate,
951                         DateTime.MaxValue,
952                         daylightDelta,
953                         default(TransitionTime),
954                         default(TransitionTime),
955                         baseUtcDelta,
956                         noDaylightTransitions: true);
957                     rulesList.Add(r);
958                 }
959             }
960
961             index++;
962         }
963
964         private static TimeSpan TZif_CalculateTransitionOffsetFromBase(TimeSpan transitionOffset, TimeSpan timeZoneBaseUtcOffset)
965         {
966             TimeSpan result = transitionOffset - timeZoneBaseUtcOffset;
967
968             // TZif supports seconds-level granularity with offsets but TimeZoneInfo only supports minutes since it aligns
969             // with DateTimeOffset, SQL Server, and the W3C XML Specification
970             if (result.Ticks % TimeSpan.TicksPerMinute != 0)
971             {
972                 result = new TimeSpan(result.Hours, result.Minutes, 0);
973             }
974
975             return result;
976         }
977
978         /// <summary>
979         /// Gets the first standard-time transition type, or simply the first transition type
980         /// if there are no standard transition types.
981         /// </summary>>
982         /// <remarks>
983         /// from 'man tzfile':
984         /// localtime(3)  uses the first standard-time ttinfo structure in the file
985         /// (or simply the first ttinfo structure in the absence of a standard-time
986         /// structure)  if  either tzh_timecnt is zero or the time argument is less
987         /// than the first transition time recorded in the file.
988         /// </remarks>
989         private static TZifType TZif_GetEarlyDateTransitionType(TZifType[] transitionTypes)
990         {
991             foreach (TZifType transitionType in transitionTypes)
992             {
993                 if (!transitionType.IsDst)
994                 {
995                     return transitionType;
996                 }
997             }
998
999             if (transitionTypes.Length > 0)
1000             {
1001                 return transitionTypes[0];
1002             }
1003
1004             throw new InvalidTimeZoneException(SR.InvalidTimeZone_NoTTInfoStructures);
1005         }
1006
1007         /// <summary>
1008         /// Creates an AdjustmentRule given the POSIX TZ environment variable string.
1009         /// </summary>
1010         /// <remarks>
1011         /// See http://man7.org/linux/man-pages/man3/tzset.3.html for the format and semantics of this POSX string.
1012         /// </remarks>
1013         private static AdjustmentRule TZif_CreateAdjustmentRuleForPosixFormat(string posixFormat, DateTime startTransitionDate, TimeSpan timeZoneBaseUtcOffset)
1014         {
1015             string standardName;
1016             string standardOffset;
1017             string daylightSavingsName;
1018             string daylightSavingsOffset;
1019             string start;
1020             string startTime;
1021             string end;
1022             string endTime;
1023
1024             if (TZif_ParsePosixFormat(posixFormat, out standardName, out standardOffset, out daylightSavingsName,
1025                 out daylightSavingsOffset, out start, out startTime, out end, out endTime))
1026             {
1027                 // a valid posixFormat has at least standardName and standardOffset
1028
1029                 TimeSpan? parsedBaseOffset = TZif_ParseOffsetString(standardOffset);
1030                 if (parsedBaseOffset.HasValue)
1031                 {
1032                     TimeSpan baseOffset = parsedBaseOffset.Value.Negate(); // offsets are backwards in POSIX notation
1033                     baseOffset = TZif_CalculateTransitionOffsetFromBase(baseOffset, timeZoneBaseUtcOffset);
1034
1035                     // having a daylightSavingsName means there is a DST rule
1036                     if (!string.IsNullOrEmpty(daylightSavingsName))
1037                     {
1038                         TimeSpan? parsedDaylightSavings = TZif_ParseOffsetString(daylightSavingsOffset);
1039                         TimeSpan daylightSavingsTimeSpan;
1040                         if (!parsedDaylightSavings.HasValue)
1041                         {
1042                             // default DST to 1 hour if it isn't specified
1043                             daylightSavingsTimeSpan = new TimeSpan(1, 0, 0);
1044                         }
1045                         else
1046                         {
1047                             daylightSavingsTimeSpan = parsedDaylightSavings.Value.Negate(); // offsets are backwards in POSIX notation
1048                             daylightSavingsTimeSpan = TZif_CalculateTransitionOffsetFromBase(daylightSavingsTimeSpan, timeZoneBaseUtcOffset);
1049                             daylightSavingsTimeSpan = TZif_CalculateTransitionOffsetFromBase(daylightSavingsTimeSpan, baseOffset);
1050                         }
1051
1052                         TransitionTime dstStart = TZif_CreateTransitionTimeFromPosixRule(start, startTime);
1053                         TransitionTime dstEnd = TZif_CreateTransitionTimeFromPosixRule(end, endTime);
1054
1055                         return AdjustmentRule.CreateAdjustmentRule(
1056                             startTransitionDate,
1057                             DateTime.MaxValue,
1058                             daylightSavingsTimeSpan,
1059                             dstStart,
1060                             dstEnd,
1061                             baseOffset,
1062                             noDaylightTransitions: false);
1063                     }
1064                     else
1065                     {
1066                         // if there is no daylightSavingsName, the whole AdjustmentRule should be with no transitions - just the baseOffset
1067                         return AdjustmentRule.CreateAdjustmentRule(
1068                                startTransitionDate,
1069                                DateTime.MaxValue,
1070                                TimeSpan.Zero,
1071                                default(TransitionTime),
1072                                default(TransitionTime),
1073                                baseOffset,
1074                                noDaylightTransitions: true);
1075                     }
1076                 }
1077             }
1078
1079             return null;
1080         }
1081
1082         private static TimeSpan? TZif_ParseOffsetString(string offset)
1083         {
1084             TimeSpan? result = null;
1085
1086             if (!string.IsNullOrEmpty(offset))
1087             {
1088                 bool negative = offset[0] == '-';
1089                 if (negative || offset[0] == '+')
1090                 {
1091                     offset = offset.Substring(1);
1092                 }
1093
1094                 // Try parsing just hours first.
1095                 // Note, TimeSpan.TryParseExact "%h" can't be used here because some time zones using values
1096                 // like "26" or "144" and TimeSpan parsing would turn that into 26 or 144 *days* instead of hours.
1097                 int hours;
1098                 if (int.TryParse(offset, out hours))
1099                 {
1100                     result = new TimeSpan(hours, 0, 0);
1101                 }
1102                 else
1103                 {
1104                     TimeSpan parsedTimeSpan;
1105                     if (TimeSpan.TryParseExact(offset, "g", CultureInfo.InvariantCulture, out parsedTimeSpan))
1106                     {
1107                         result = parsedTimeSpan;
1108                     }
1109                 }
1110
1111                 if (result.HasValue && negative)
1112                 {
1113                     result = result.Value.Negate();
1114                 }
1115             }
1116
1117             return result;
1118         }
1119
1120         private static TransitionTime TZif_CreateTransitionTimeFromPosixRule(string date, string time)
1121         {
1122             if (string.IsNullOrEmpty(date))
1123             {
1124                 return default(TransitionTime);
1125             }
1126
1127             if (date[0] == 'M')
1128             {
1129                 // Mm.w.d
1130                 // 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;
1131                 // 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.
1132
1133                 int month;
1134                 int week;
1135                 DayOfWeek day;
1136                 if (!TZif_ParseMDateRule(date, out month, out week, out day))
1137                 {
1138                     throw new InvalidTimeZoneException(SR.Format(SR.InvalidTimeZone_UnparseablePosixMDateString, date));
1139                 }
1140
1141                 DateTime timeOfDay;
1142                 TimeSpan? timeOffset = TZif_ParseOffsetString(time);
1143                 if (timeOffset.HasValue)
1144                 {
1145                     // This logic isn't correct and can't be corrected until https://github.com/dotnet/corefx/issues/2618 is fixed.
1146                     // Some time zones use time values like, "26", "144", or "-2".
1147                     // This allows the week to sometimes be week 4 and sometimes week 5 in the month.
1148                     // For now, strip off any 'days' in the offset, and just get the time of day correct
1149                     timeOffset = new TimeSpan(timeOffset.Value.Hours, timeOffset.Value.Minutes, timeOffset.Value.Seconds);
1150                     if (timeOffset.Value < TimeSpan.Zero)
1151                     {
1152                         timeOfDay = new DateTime(1, 1, 2, 0, 0, 0);
1153                     }
1154                     else
1155                     {
1156                         timeOfDay = new DateTime(1, 1, 1, 0, 0, 0);
1157                     }
1158
1159                     timeOfDay += timeOffset.Value;
1160                 }
1161                 else
1162                 {
1163                     // default to 2AM.
1164                     timeOfDay = new DateTime(1, 1, 1, 2, 0, 0);
1165                 }
1166
1167                 return TransitionTime.CreateFloatingDateRule(timeOfDay, month, week, day);
1168             }
1169             else
1170             {
1171                 // Jn
1172                 // This specifies the Julian day, with n between 1 and 365.February 29 is never counted, even in leap years.
1173
1174                 // n
1175                 // This specifies the Julian day, with n between 0 and 365.February 29 is counted in leap years.
1176
1177                 // These two rules cannot be expressed with the current AdjustmentRules
1178                 // One of them *could* be supported if we relaxed the TransitionTime validation rules, and allowed
1179                 // "IsFixedDateRule = true, Month = 0, Day = n" to mean the nth day of the year, picking one of the rules above
1180
1181                 throw new InvalidTimeZoneException(SR.InvalidTimeZone_JulianDayNotSupported);
1182             }
1183         }
1184
1185         /// <summary>
1186         /// Parses a string like Mm.w.d into month, week and DayOfWeek values.
1187         /// </summary>
1188         /// <returns>
1189         /// true if the parsing succeeded; otherwise, false.
1190         /// </returns>
1191         private static bool TZif_ParseMDateRule(string dateRule, out int month, out int week, out DayOfWeek dayOfWeek)
1192         {
1193             if (dateRule[0] == 'M')
1194             {
1195                 int firstDotIndex = dateRule.IndexOf('.');
1196                 if (firstDotIndex > 0)
1197                 {
1198                     int secondDotIndex = dateRule.IndexOf('.', firstDotIndex + 1);
1199                     if (secondDotIndex > 0)
1200                     {
1201                         if (int.TryParse(dateRule.AsSpan().Slice(1, firstDotIndex - 1), out month) &&
1202                             int.TryParse(dateRule.AsSpan().Slice(firstDotIndex + 1, secondDotIndex - firstDotIndex - 1), out week) &&
1203                             int.TryParse(dateRule.AsSpan().Slice(secondDotIndex + 1), out int day))
1204                         {
1205                             dayOfWeek = (DayOfWeek)day;
1206                             return true;
1207                         }
1208                     }
1209                 }
1210             }
1211
1212             month = 0;
1213             week = 0;
1214             dayOfWeek = default(DayOfWeek);
1215             return false;
1216         }
1217
1218         private static bool TZif_ParsePosixFormat(
1219             string posixFormat,
1220             out string standardName,
1221             out string standardOffset,
1222             out string daylightSavingsName,
1223             out string daylightSavingsOffset,
1224             out string start,
1225             out string startTime,
1226             out string end,
1227             out string endTime)
1228         {
1229             standardName = null;
1230             standardOffset = null;
1231             daylightSavingsName = null;
1232             daylightSavingsOffset = null;
1233             start = null;
1234             startTime = null;
1235             end = null;
1236             endTime = null;
1237
1238             int index = 0;
1239             standardName = TZif_ParsePosixName(posixFormat, ref index);
1240             standardOffset = TZif_ParsePosixOffset(posixFormat, ref index);
1241
1242             daylightSavingsName = TZif_ParsePosixName(posixFormat, ref index);
1243             if (!string.IsNullOrEmpty(daylightSavingsName))
1244             {
1245                 daylightSavingsOffset = TZif_ParsePosixOffset(posixFormat, ref index);
1246
1247                 if (index < posixFormat.Length && posixFormat[index] == ',')
1248                 {
1249                     index++;
1250                     TZif_ParsePosixDateTime(posixFormat, ref index, out start, out startTime);
1251
1252                     if (index < posixFormat.Length && posixFormat[index] == ',')
1253                     {
1254                         index++;
1255                         TZif_ParsePosixDateTime(posixFormat, ref index, out end, out endTime);
1256                     }
1257                 }
1258             }
1259
1260             return !string.IsNullOrEmpty(standardName) && !string.IsNullOrEmpty(standardOffset);
1261         }
1262
1263         private static string TZif_ParsePosixName(string posixFormat, ref int index)
1264         {
1265             bool isBracketEnclosed = index < posixFormat.Length && posixFormat[index] == '<';
1266             if (isBracketEnclosed)
1267             {
1268                 // move past the opening bracket
1269                 index++;
1270
1271                 string result = TZif_ParsePosixString(posixFormat, ref index, c => c == '>');
1272
1273                 // move past the closing bracket
1274                 if (index < posixFormat.Length && posixFormat[index] == '>')
1275                 {
1276                     index++;
1277                 }
1278
1279                 return result;
1280             }
1281             else
1282             {
1283                 return TZif_ParsePosixString(
1284                     posixFormat,
1285                     ref index,
1286                     c => char.IsDigit(c) || c == '+' || c == '-' || c == ',');
1287             }
1288         }
1289
1290         private static string TZif_ParsePosixOffset(string posixFormat, ref int index) =>
1291             TZif_ParsePosixString(posixFormat, ref index, c => !char.IsDigit(c) && c != '+' && c != '-' && c != ':');
1292
1293         private static void TZif_ParsePosixDateTime(string posixFormat, ref int index, out string date, out string time)
1294         {
1295             time = null;
1296
1297             date = TZif_ParsePosixDate(posixFormat, ref index);
1298             if (index < posixFormat.Length && posixFormat[index] == '/')
1299             {
1300                 index++;
1301                 time = TZif_ParsePosixTime(posixFormat, ref index);
1302             }
1303         }
1304
1305         private static string TZif_ParsePosixDate(string posixFormat, ref int index) =>
1306             TZif_ParsePosixString(posixFormat, ref index, c => c == '/' || c == ',');
1307
1308         private static string TZif_ParsePosixTime(string posixFormat, ref int index) =>
1309             TZif_ParsePosixString(posixFormat, ref index, c => c == ',');
1310
1311         private static string TZif_ParsePosixString(string posixFormat, ref int index, Func<char, bool> breakCondition)
1312         {
1313             int startIndex = index;
1314             for (; index < posixFormat.Length; index++)
1315             {
1316                 char current = posixFormat[index];
1317                 if (breakCondition(current))
1318                 {
1319                     break;
1320                 }
1321             }
1322
1323             return posixFormat.Substring(startIndex, index - startIndex);
1324         }
1325
1326         // Returns the Substring from zoneAbbreviations starting at index and ending at '\0'
1327         // zoneAbbreviations is expected to be in the form: "PST\0PDT\0PWT\0\PPT"
1328         private static string TZif_GetZoneAbbreviation(string zoneAbbreviations, int index)
1329         {
1330             int lastIndex = zoneAbbreviations.IndexOf('\0', index);
1331             return lastIndex > 0 ?
1332                 zoneAbbreviations.Substring(index, lastIndex - index) :
1333                 zoneAbbreviations.Substring(index);
1334         }
1335
1336         // Converts an array of bytes into an int - always using standard byte order (Big Endian)
1337         // per TZif file standard
1338         private static unsafe int TZif_ToInt32(byte[] value, int startIndex)
1339         {
1340             fixed (byte* pbyte = &value[startIndex])
1341             {
1342                 return (*pbyte << 24) | (*(pbyte + 1) << 16) | (*(pbyte + 2) << 8) | (*(pbyte + 3));
1343             }
1344         }
1345
1346         // Converts an array of bytes into a long - always using standard byte order (Big Endian)
1347         // per TZif file standard
1348         private static unsafe long TZif_ToInt64(byte[] value, int startIndex)
1349         {
1350             fixed (byte* pbyte = &value[startIndex])
1351             {
1352                 int i1 = (*pbyte << 24) | (*(pbyte + 1) << 16) | (*(pbyte + 2) << 8) | (*(pbyte + 3));
1353                 int i2 = (*(pbyte + 4) << 24) | (*(pbyte + 5) << 16) | (*(pbyte + 6) << 8) | (*(pbyte + 7));
1354                 return (uint)i2 | ((long)i1 << 32);
1355             }
1356         }
1357
1358         private static long TZif_ToUnixTime(byte[] value, int startIndex, TZVersion version) =>
1359             version != TZVersion.V1 ?
1360                 TZif_ToInt64(value, startIndex) :
1361                 TZif_ToInt32(value, startIndex);
1362
1363         private static DateTime TZif_UnixTimeToDateTime(long unixTime) =>
1364             unixTime < DateTimeOffset.UnixMinSeconds ? DateTime.MinValue :
1365             unixTime > DateTimeOffset.UnixMaxSeconds ? DateTime.MaxValue :
1366             DateTimeOffset.FromUnixTimeSeconds(unixTime).UtcDateTime;
1367
1368         private static void TZif_ParseRaw(byte[] data, out TZifHead t, out DateTime[] dts, out byte[] typeOfLocalTime, out TZifType[] transitionType,
1369                                           out string zoneAbbreviations, out bool[] StandardTime, out bool[] GmtTime, out string futureTransitionsPosixFormat)
1370         {
1371             // initialize the out parameters in case the TZifHead ctor throws
1372             dts = null;
1373             typeOfLocalTime = null;
1374             transitionType = null;
1375             zoneAbbreviations = string.Empty;
1376             StandardTime = null;
1377             GmtTime = null;
1378             futureTransitionsPosixFormat = null;
1379
1380             // read in the 44-byte TZ header containing the count/length fields
1381             //
1382             int index = 0;
1383             t = new TZifHead(data, index);
1384             index += TZifHead.Length;
1385
1386             int timeValuesLength = 4; // the first version uses 4-bytes to specify times
1387             if (t.Version != TZVersion.V1)
1388             {
1389                 // move index past the V1 information to read the V2 information
1390                 index += (int)((timeValuesLength * t.TimeCount) + t.TimeCount + (6 * t.TypeCount) + ((timeValuesLength + 4) * t.LeapCount) + t.IsStdCount + t.IsGmtCount + t.CharCount);
1391
1392                 // read the V2 header
1393                 t = new TZifHead(data, index);
1394                 index += TZifHead.Length;
1395                 timeValuesLength = 8; // the second version uses 8-bytes
1396             }
1397
1398             // initialize the containers for the rest of the TZ data
1399             dts = new DateTime[t.TimeCount];
1400             typeOfLocalTime = new byte[t.TimeCount];
1401             transitionType = new TZifType[t.TypeCount];
1402             zoneAbbreviations = string.Empty;
1403             StandardTime = new bool[t.TypeCount];
1404             GmtTime = new bool[t.TypeCount];
1405
1406             // read in the UTC transition points and convert them to Windows
1407             //
1408             for (int i = 0; i < t.TimeCount; i++)
1409             {
1410                 long unixTime = TZif_ToUnixTime(data, index, t.Version);
1411                 dts[i] = TZif_UnixTimeToDateTime(unixTime);
1412                 index += timeValuesLength;
1413             }
1414
1415             // read in the Type Indices; there is a 1:1 mapping of UTC transition points to Type Indices
1416             // these indices directly map to the array index in the transitionType array below
1417             //
1418             for (int i = 0; i < t.TimeCount; i++)
1419             {
1420                 typeOfLocalTime[i] = data[index];
1421                 index += 1;
1422             }
1423
1424             // read in the Type table.  Each 6-byte entry represents
1425             // {UtcOffset, IsDst, AbbreviationIndex}
1426             //
1427             // each AbbreviationIndex is a character index into the zoneAbbreviations string below
1428             //
1429             for (int i = 0; i < t.TypeCount; i++)
1430             {
1431                 transitionType[i] = new TZifType(data, index);
1432                 index += 6;
1433             }
1434
1435             // read in the Abbreviation ASCII string.  This string will be in the form:
1436             // "PST\0PDT\0PWT\0\PPT"
1437             //
1438             Encoding enc = Encoding.UTF8;
1439             zoneAbbreviations = enc.GetString(data, index, (int)t.CharCount);
1440             index += (int)t.CharCount;
1441
1442             // skip ahead of the Leap-Seconds Adjustment data.  In a future release, consider adding
1443             // support for Leap-Seconds
1444             //
1445             index += (int)(t.LeapCount * (timeValuesLength + 4)); // skip the leap second transition times
1446
1447             // read in the Standard Time table.  There should be a 1:1 mapping between Type-Index and Standard
1448             // Time table entries.
1449             //
1450             // TRUE     =     transition time is standard time
1451             // FALSE    =     transition time is wall clock time
1452             // ABSENT   =     transition time is wall clock time
1453             //
1454             for (int i = 0; i < t.IsStdCount && i < t.TypeCount && index < data.Length; i++)
1455             {
1456                 StandardTime[i] = (data[index++] != 0);
1457             }
1458
1459             // read in the GMT Time table.  There should be a 1:1 mapping between Type-Index and GMT Time table
1460             // entries.
1461             //
1462             // TRUE     =     transition time is UTC
1463             // FALSE    =     transition time is local time
1464             // ABSENT   =     transition time is local time
1465             //
1466             for (int i = 0; i < t.IsGmtCount && i < t.TypeCount && index < data.Length; i++)
1467             {
1468                 GmtTime[i] = (data[index++] != 0);
1469             }
1470
1471             if (t.Version != TZVersion.V1)
1472             {
1473                 // read the POSIX-style format, which should be wrapped in newlines with the last newline at the end of the file
1474                 if (data[index++] == '\n' && data[data.Length - 1] == '\n')
1475                 {
1476                     futureTransitionsPosixFormat = enc.GetString(data, index, data.Length - index - 1);
1477                 }
1478             }
1479         }
1480
1481         private struct TZifType
1482         {
1483             public const int Length = 6;
1484
1485             public readonly TimeSpan UtcOffset;
1486             public readonly bool IsDst;
1487             public readonly byte AbbreviationIndex;
1488
1489             public TZifType(byte[] data, int index)
1490             {
1491                 if (data == null || data.Length < index + Length)
1492                 {
1493                     throw new ArgumentException(SR.Argument_TimeZoneInfoInvalidTZif, nameof(data));
1494                 }
1495                 UtcOffset = new TimeSpan(0, 0, TZif_ToInt32(data, index + 00));
1496                 IsDst = (data[index + 4] != 0);
1497                 AbbreviationIndex = data[index + 5];
1498             }
1499         }
1500
1501         private struct TZifHead
1502         {
1503             public const int Length = 44;
1504
1505             public readonly uint Magic; // TZ_MAGIC "TZif"
1506             public readonly TZVersion Version; // 1 byte for a \0 or 2 or 3
1507             // public byte[15] Reserved; // reserved for future use
1508             public readonly uint IsGmtCount; // number of transition time flags
1509             public readonly uint IsStdCount; // number of transition time flags
1510             public readonly uint LeapCount; // number of leap seconds
1511             public readonly uint TimeCount; // number of transition times
1512             public readonly uint TypeCount; // number of local time types
1513             public readonly uint CharCount; // number of abbreviated characters
1514
1515             public TZifHead(byte[] data, int index)
1516             {
1517                 if (data == null || data.Length < Length)
1518                 {
1519                     throw new ArgumentException("bad data", nameof(data));
1520                 }
1521
1522                 Magic = (uint)TZif_ToInt32(data, index + 00);
1523
1524                 if (Magic != 0x545A6966)
1525                 {
1526                     // 0x545A6966 = {0x54, 0x5A, 0x69, 0x66} = "TZif"
1527                     throw new ArgumentException(SR.Argument_TimeZoneInfoBadTZif, nameof(data));
1528                 }
1529
1530                 byte version = data[index + 04];
1531                 Version =
1532                     version == '2' ? TZVersion.V2 :
1533                     version == '3' ? TZVersion.V3 :
1534                     TZVersion.V1;  // default/fallback to V1 to guard against future, unsupported version numbers
1535
1536                 // skip the 15 byte reserved field
1537
1538                 // don't use the BitConverter class which parses data
1539                 // based on the Endianess of the machine architecture.
1540                 // this data is expected to always be in "standard byte order",
1541                 // regardless of the machine it is being processed on.
1542
1543                 IsGmtCount = (uint)TZif_ToInt32(data, index + 20);
1544                 IsStdCount = (uint)TZif_ToInt32(data, index + 24);
1545                 LeapCount = (uint)TZif_ToInt32(data, index + 28);
1546                 TimeCount = (uint)TZif_ToInt32(data, index + 32);
1547                 TypeCount = (uint)TZif_ToInt32(data, index + 36);
1548                 CharCount = (uint)TZif_ToInt32(data, index + 40);
1549             }
1550         }
1551
1552         private enum TZVersion : byte
1553         {
1554             V1 = 0,
1555             V2,
1556             V3,
1557             // when adding more versions, ensure all the logic using TZVersion is still correct
1558         }
1559     }
1560 }