From 1aacfed602bb116042cb170e4790260714cd3b50 Mon Sep 17 00:00:00 2001 From: Eric Erhardt Date: Tue, 11 Aug 2015 17:19:55 -0500 Subject: [PATCH] Make use of TimeZoneInfo.CachedData on Linux. Fix https://github.com/dotnet/corefx/issues/2497. --- src/mscorlib/src/System/TimeZoneInfo.cs | 223 ++++++++++++++------------------ 1 file changed, 94 insertions(+), 129 deletions(-) diff --git a/src/mscorlib/src/System/TimeZoneInfo.cs b/src/mscorlib/src/System/TimeZoneInfo.cs index a9b1e8e..2d475b4 100644 --- a/src/mscorlib/src/System/TimeZoneInfo.cs +++ b/src/mscorlib/src/System/TimeZoneInfo.cs @@ -225,11 +225,11 @@ namespace System { return kind; } -#if FEATURE_WIN32_REGISTRY public Dictionary m_systemTimeZones; public ReadOnlyCollection m_readOnlySystemTimeZones; public bool m_allSystemTimeZonesRead; +#if FEATURE_WIN32_REGISTRY [System.Security.SecuritySafeCritical] private static TimeZoneInfo GetCurrentOneYearLocal() { // load the data from the OS @@ -696,16 +696,11 @@ namespace System { static public DateTimeOffset ConvertTimeBySystemTimeZoneId(DateTimeOffset dateTimeOffset, String destinationTimeZoneId) { return ConvertTime(dateTimeOffset, FindSystemTimeZoneById(destinationTimeZoneId)); } -#endif // FEATURE_WIN32_REGISTRY - -#if FEATURE_WIN32_REGISTRY static public DateTime ConvertTimeBySystemTimeZoneId(DateTime dateTime, String destinationTimeZoneId) { return ConvertTime(dateTime, FindSystemTimeZoneById(destinationTimeZoneId)); } -#endif // FEATURE_WIN32_REGISTRY -#if FEATURE_WIN32_REGISTRY static public DateTime ConvertTimeBySystemTimeZoneId(DateTime dateTime, String sourceTimeZoneId, String destinationTimeZoneId) { if (dateTime.Kind == DateTimeKind.Local && String.Compare(sourceTimeZoneId, TimeZoneInfo.Local.Id, StringComparison.OrdinalIgnoreCase) == 0) { // TimeZoneInfo.Local can be cleared by another thread calling TimeZoneInfo.ClearCachedData. @@ -941,8 +936,6 @@ namespace System { return m_id.ToUpper(CultureInfo.InvariantCulture).GetHashCode(); } - -#if FEATURE_WIN32_REGISTRY // // GetSystemTimeZones - // @@ -963,29 +956,8 @@ namespace System { lock (cachedData) { if (cachedData.m_readOnlySystemTimeZones == null) { - PermissionSet permSet = new PermissionSet(PermissionState.None); - permSet.AddPermission(new RegistryPermission(RegistryPermissionAccess.Read, c_timeZonesRegistryHivePermissionList)); - permSet.Assert(); - - using (RegistryKey reg = Registry.LocalMachine.OpenSubKey( - c_timeZonesRegistryHive, -#if FEATURE_MACL - RegistryKeyPermissionCheck.Default, - System.Security.AccessControl.RegistryRights.ReadKey -#else - false -#endif - )) { - - if (reg != null) { - foreach (string keyName in reg.GetSubKeyNames()) { - TimeZoneInfo value; - Exception ex; - TryGetTimeZone(keyName, false, out value, out ex, cachedData); // populate the cache - } - } - cachedData.m_allSystemTimeZonesRead = true; - } + PopulateAllSystemTimeZones(cachedData); + cachedData.m_allSystemTimeZonesRead = true; List list; if (cachedData.m_systemTimeZones != null) { @@ -1005,8 +977,42 @@ namespace System { } return cachedData.m_readOnlySystemTimeZones; } -#endif // FEATURE_WIN32_REGISTRY + private static void PopulateAllSystemTimeZones(CachedData cachedData) + { +#if FEATURE_WIN32_REGISTRY + PermissionSet permSet = new PermissionSet(PermissionState.None); + permSet.AddPermission(new RegistryPermission(RegistryPermissionAccess.Read, c_timeZonesRegistryHivePermissionList)); + permSet.Assert(); + + using (RegistryKey reg = Registry.LocalMachine.OpenSubKey( + c_timeZonesRegistryHive, +#if FEATURE_MACL + RegistryKeyPermissionCheck.Default, + System.Security.AccessControl.RegistryRights.ReadKey +#else + false +#endif + )) { + + 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 + } // // HasSameRules - @@ -2119,7 +2125,7 @@ namespace System { // the data from the Win32 API return GetLocalTimeZoneFromWin32Data(timeZoneInformation, dstDisabled); -#else // FEATURE_WIN32_REGISTRY +#elif PLATFORM_UNIX // FEATURE_WIN32_REGISTRY // Without Registry support, create the TimeZoneInfo from a TZ file return GetLocalTimeZoneFromTzFile(); #endif // FEATURE_WIN32_REGISTRY @@ -2127,25 +2133,10 @@ namespace System { #if PLATFORM_UNIX - static public TimeZoneInfo FindSystemTimeZoneById(string id) + private static TimeZoneInfoResult TryGetTimeZoneByFile(string id, out TimeZoneInfo value, out Exception e) { - if (id == null) - { - throw new ArgumentNullException("id"); - } - else if (id.Length == 0 || id.Contains("\0")) - { - throw new TimeZoneNotFoundException(Environment.GetResourceString("TimeZoneNotFound_MissingData", id)); - } - - // Special case for Utc since there is code that expects only 1 instance of the UTC time zone info to be created. - // see CachedData.GetCorrespondingKind() - if (string.Equals(id, c_utcId, StringComparison.OrdinalIgnoreCase)) - { - return TimeZoneInfo.Utc; - } - - // UNIXTODO: Issue #2497 use CachedData + value = null; + e = null; string timeZoneDirectory = GetTimeZoneDirectory(); string timeZoneFilePath = Path.Combine(timeZoneDirectory, id); @@ -2154,64 +2145,36 @@ namespace System { { rawData = File.ReadAllBytes(timeZoneFilePath); } - catch (UnauthorizedAccessException e) + catch (UnauthorizedAccessException ex) { - throw new SecurityException(Environment.GetResourceString("Security_CannotReadFileData", id), e); - } - catch (FileNotFoundException e) - { - throw new TimeZoneNotFoundException(Environment.GetResourceString("TimeZoneNotFound_MissingData", id), e); + e = ex; + return TimeZoneInfoResult.SecurityException; } - catch (DirectoryNotFoundException e) + catch (FileNotFoundException ex) { - throw new TimeZoneNotFoundException(Environment.GetResourceString("TimeZoneNotFound_MissingData", id), e); + e = ex; + return TimeZoneInfoResult.TimeZoneNotFoundException; } - catch (IOException e) + catch (DirectoryNotFoundException ex) { - throw new InvalidTimeZoneException(Environment.GetResourceString("InvalidTimeZone_InvalidFileData", id, timeZoneFilePath), e); + e = ex; + return TimeZoneInfoResult.TimeZoneNotFoundException; } - - TimeZoneInfo result = GetTimeZoneFromTzData(rawData, id); - if (result == null) + catch (IOException ex) { - throw new InvalidTimeZoneException(Environment.GetResourceString("InvalidTimeZone_InvalidFileData", id, timeZoneFilePath)); + e = new InvalidTimeZoneException(Environment.GetResourceString("InvalidTimeZone_InvalidFileData", id, timeZoneFilePath), ex); + return TimeZoneInfoResult.InvalidTimeZoneException; } - return result; - } - - /// - /// Returns a collection of TimeZoneInfo instances containing all valid TimeZones - /// from the local machine. The entries in the collection are sorted by - /// 'DisplayName'. - /// - /// This method does *not* throw TimeZoneNotFoundException or - /// InvalidTimeZoneException, but it can throw a SecurityException. - /// - static public ReadOnlyCollection GetSystemTimeZones() - { - // UNIXTODO: Issue #2497 use CachedData - List timeZoneInfos = new List(); + value = GetTimeZoneFromTzData(rawData, id); - string timeZoneDirectory = GetTimeZoneDirectory(); - foreach (string timeZoneId in GetTimeZoneIds(timeZoneDirectory)) + if (value == null) { - if (!string.IsNullOrEmpty(timeZoneId)) - { - try - { - TimeZoneInfo timeZoneInfo = FindSystemTimeZoneById(timeZoneId); - timeZoneInfos.Add(timeZoneInfo); - } - catch (InvalidTimeZoneException) { } - catch (TimeZoneNotFoundException) { } - } + e = new InvalidTimeZoneException(Environment.GetResourceString("InvalidTimeZone_InvalidFileData", id, timeZoneFilePath)); + return TimeZoneInfoResult.InvalidTimeZoneException; } - // sort and copy the TimeZoneInfo's into a ReadOnlyCollection for the user - timeZoneInfos.Sort(new TimeZoneInfoComparer()); - - return new ReadOnlyCollection(timeZoneInfos); + return TimeZoneInfoResult.Success; } /// @@ -2495,7 +2458,7 @@ namespace System { return tzDirectory; } -#else // PLATFORM_UNIX +#elif FEATURE_WIN32_REGISTRY // PLATFORM_UNIX // // GetLocalTimeZoneFromWin32Data - @@ -2527,7 +2490,6 @@ namespace System { } #endif // PLATFORM_UNIX -#if FEATURE_WIN32_REGISTRY // // FindSystemTimeZoneById - // @@ -2550,7 +2512,7 @@ namespace System { if (id == null) { throw new ArgumentNullException("id"); } - else if (id.Length == 0 || id.Length > c_maxKeyLength || id.Contains("\0")) { + else if (!IsValidSystemTimeZoneId(id)) { throw new TimeZoneNotFoundException(Environment.GetResourceString("TimeZoneNotFound_MissingData", id)); } @@ -2569,17 +2531,37 @@ namespace System { return value; } else if (result == TimeZoneInfoResult.InvalidTimeZoneException) { +#if FEATURE_WIN32_REGISTRY throw new InvalidTimeZoneException(Environment.GetResourceString("InvalidTimeZone_InvalidRegistryData", id), e); +#elif PLATFORM_UNIX + Contract.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 <= c_maxKeyLength; #endif // FEATURE_WIN32_REGISTRY + return isValid; + } + // // GetUtcOffset - // @@ -2639,7 +2621,7 @@ namespace System { } } return baseOffset; -#else +#elif PLATFORM_UNIX // Use the standard code path for the Macintosh since there isn't a faster way of handling current-year-only time zones return GetUtcOffsetFromUtc(time, TimeZoneInfo.Local, out isDaylightSavings, out isAmbiguousLocalDst); #endif // FEATURE_WIN32_REGISTRY @@ -3037,10 +3019,7 @@ namespace System { } return true; } -#endif // FEATURE_WIN32_REGISTRY - -#if FEATURE_WIN32_REGISTRY // // TryCompareStandardDate - // @@ -3060,10 +3039,7 @@ namespace System { && timeZone.StandardDate.Second == registryTimeZoneInfo.StandardDate.Second && timeZone.StandardDate.Milliseconds == registryTimeZoneInfo.StandardDate.Milliseconds; } -#endif // FEATURE_WIN32_REGISTRY - -#if FEATURE_WIN32_REGISTRY // // TryCompareTimeZoneInformationToRegistry - // @@ -3139,10 +3115,7 @@ namespace System { PermissionSet.RevertAssert(); } } -#endif // FEATURE_WIN32_REGISTRY - -#if FEATURE_WIN32_REGISTRY // // TryGetLocalizedNameByMuiNativeResource - // @@ -3222,11 +3195,7 @@ namespace System { return String.Empty; } } -#endif // FEATURE_WIN32_REGISTRY - - -#if FEATURE_WIN32_REGISTRY // // TryGetLocalizedNameByNativeResource - // @@ -3255,10 +3224,7 @@ namespace System { } return String.Empty; } -#endif // FEATURE_WIN32_REGISTRY - -#if FEATURE_WIN32_REGISTRY // // TryGetLocalizedNamesByRegistryKey - // @@ -3309,11 +3275,7 @@ namespace System { return true; } -#endif // FEATURE_WIN32_REGISTRY - - -#if FEATURE_WIN32_REGISTRY // // TryGetTimeZoneByRegistryKey - // @@ -3436,7 +3398,6 @@ namespace System { #endif // FEATURE_WIN32_REGISTRY -#if FEATURE_WIN32_REGISTRY // // TryGetTimeZone - // @@ -3469,7 +3430,12 @@ namespace System { // fall back to reading from the local machine // when the cache is not fully populated if (!cachedData.m_allSystemTimeZonesRead) { +#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.m_systemTimeZones == null) cachedData.m_systemTimeZones = new Dictionary(); @@ -3496,7 +3462,6 @@ namespace System { return result; } -#endif // FEATURE_WIN32_REGISTRY #if PLATFORM_UNIX // TZFILE(5) BSD File Formats Manual TZFILE(5) @@ -3669,7 +3634,7 @@ namespace System { TZifType transitionType = TZif_GetEarlyDateTransitionType(transitionTypes); DateTime endTransitionDate = dts[index]; - TimeSpan transitionOffset = TZif_CalculateTranistionOffsetFromBase(transitionType.UtcOffset, timeZoneBaseUtcOffset); + TimeSpan transitionOffset = TZif_CalculateTransitionOffsetFromBase(transitionType.UtcOffset, timeZoneBaseUtcOffset); TimeSpan daylightDelta = transitionType.IsDst ? transitionOffset : TimeSpan.Zero; TimeSpan baseUtcDelta = transitionType.IsDst ? TimeSpan.Zero : transitionOffset; @@ -3690,7 +3655,7 @@ namespace System { DateTime endTransitionDate = dts[index]; - TimeSpan transitionOffset = TZif_CalculateTranistionOffsetFromBase(startTransitionType.UtcOffset, timeZoneBaseUtcOffset); + TimeSpan transitionOffset = TZif_CalculateTransitionOffsetFromBase(startTransitionType.UtcOffset, timeZoneBaseUtcOffset); TimeSpan daylightDelta = startTransitionType.IsDst ? transitionOffset : TimeSpan.Zero; TimeSpan baseUtcDelta = startTransitionType.IsDst ? TimeSpan.Zero : transitionOffset; @@ -3740,7 +3705,7 @@ namespace System { // 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_CalculateTranistionOffsetFromBase(transitionType.UtcOffset, timeZoneBaseUtcOffset); + TimeSpan transitionOffset = TZif_CalculateTransitionOffsetFromBase(transitionType.UtcOffset, timeZoneBaseUtcOffset); TimeSpan daylightDelta = transitionType.IsDst ? transitionOffset : TimeSpan.Zero; TimeSpan baseUtcDelta = transitionType.IsDst ? TimeSpan.Zero : transitionOffset; @@ -3759,7 +3724,7 @@ namespace System { index++; } - private static TimeSpan TZif_CalculateTranistionOffsetFromBase(TimeSpan transitionOffset, TimeSpan timeZoneBaseUtcOffset) + private static TimeSpan TZif_CalculateTransitionOffsetFromBase(TimeSpan transitionOffset, TimeSpan timeZoneBaseUtcOffset) { TimeSpan result = transitionOffset - timeZoneBaseUtcOffset; @@ -3829,7 +3794,7 @@ namespace System { if (parsedBaseOffset.HasValue) { TimeSpan baseOffset = parsedBaseOffset.Value.Negate(); // offsets are backwards in POSIX notation - baseOffset = TZif_CalculateTranistionOffsetFromBase(baseOffset, timeZoneBaseUtcOffset); + baseOffset = TZif_CalculateTransitionOffsetFromBase(baseOffset, timeZoneBaseUtcOffset); // having a daylightSavingsName means there is a DST rule if (!string.IsNullOrEmpty(daylightSavingsName)) @@ -3844,8 +3809,8 @@ namespace System { else { daylightSavingsTimeSpan = parsedDaylightSavings.Value.Negate(); // offsets are backwards in POSIX notation - daylightSavingsTimeSpan = TZif_CalculateTranistionOffsetFromBase(daylightSavingsTimeSpan, timeZoneBaseUtcOffset); - daylightSavingsTimeSpan = TZif_CalculateTranistionOffsetFromBase(daylightSavingsTimeSpan, baseOffset); + daylightSavingsTimeSpan = TZif_CalculateTransitionOffsetFromBase(daylightSavingsTimeSpan, timeZoneBaseUtcOffset); + daylightSavingsTimeSpan = TZif_CalculateTransitionOffsetFromBase(daylightSavingsTimeSpan, baseOffset); } TransitionTime dstStart = TZif_CreateTransitionTimeFromPosixRule(start, startTime); -- 2.7.4