--- /dev/null
+// 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
+ }
+ }
+}
--- /dev/null
+// 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;
+ }
+
+ }
+ }
+ }
+}
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
{
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";
// 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;
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);
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.
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>
/// </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,
}
/// <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.
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>
}
/// <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
}
}
}
-
-#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
}
}