From f2f19e950300b9b6b8e4306836beb4c619db88e7 Mon Sep 17 00:00:00 2001 From: Niklas Therning Date: Mon, 6 Feb 2017 13:53:53 +0100 Subject: [PATCH] Temp fix for broken TimeZoneInfo on Windows families without registry access Band aid fix until we can switch to CoreRT's implementation. --- mcs/class/corlib/System/TimeZoneInfo.WinRT.cs | 348 ++++++++++++++++++ mcs/class/corlib/System/TimeZoneInfo.cs | 25 +- mcs/class/corlib/corlib.dll.sources | 1 + 3 files changed, 369 insertions(+), 5 deletions(-) create mode 100755 mcs/class/corlib/System/TimeZoneInfo.WinRT.cs diff --git a/mcs/class/corlib/System/TimeZoneInfo.WinRT.cs b/mcs/class/corlib/System/TimeZoneInfo.WinRT.cs new file mode 100755 index 00000000000..797fe43eede --- /dev/null +++ b/mcs/class/corlib/System/TimeZoneInfo.WinRT.cs @@ -0,0 +1,348 @@ +// 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. +// +// Inspired by various parts of CoreRT, most notably TimeZoneInfo.WinRT.cs. + +#if !FULL_AOT_DESKTOP || WIN_PLATFORM + +using Microsoft.Win32; +using System; +using System.Collections; +using System.Diagnostics; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Globalization; +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; + +namespace System +{ + partial class TimeZoneInfo + { + + internal struct SYSTEMTIME + { + internal ushort wYear; + internal ushort wMonth; + internal ushort wDayOfWeek; + internal ushort wDay; + internal ushort wHour; + internal ushort wMinute; + internal ushort wSecond; + internal ushort wMilliseconds; + } + + [StructLayout (LayoutKind.Sequential, CharSet = CharSet.Unicode)] + internal struct TIME_ZONE_INFORMATION + { + internal int Bias; + [MarshalAs (UnmanagedType.ByValTStr, SizeConst=32)] + internal string StandardName; + internal SYSTEMTIME StandardDate; + internal int StandardBias; + [MarshalAs (UnmanagedType.ByValTStr, SizeConst=32)] + internal string DaylightName; + internal SYSTEMTIME DaylightDate; + internal int DaylightBias; + } + + [StructLayout (LayoutKind.Sequential, CharSet = CharSet.Unicode)] + internal struct DYNAMIC_TIME_ZONE_INFORMATION + { + internal TIME_ZONE_INFORMATION TZI; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst=128)] + internal string TimeZoneKeyName; + internal byte DynamicDaylightTimeDisabled; + } + + internal const uint TIME_ZONE_ID_INVALID = 0xffffffff; + internal const uint ERROR_NO_MORE_ITEMS = 259; + + [DllImport ("api-ms-win-core-timezone-l1-1-0.dll")] + internal extern static uint EnumDynamicTimeZoneInformation (uint dwIndex, out DYNAMIC_TIME_ZONE_INFORMATION lpTimeZoneInformation); + [DllImport ("api-ms-win-core-timezone-l1-1-0.dll")] + internal extern static uint GetDynamicTimeZoneInformation (out DYNAMIC_TIME_ZONE_INFORMATION pTimeZoneInformation); + [DllImport ("api-ms-win-core-timezone-l1-1-0.dll")] + internal extern static uint GetDynamicTimeZoneInformationEffectiveYears(ref DYNAMIC_TIME_ZONE_INFORMATION lpTimeZoneInformation, out uint FirstYear, out uint LastYear); + [DllImport ("api-ms-win-core-timezone-l1-1-0.dll")] + internal extern static bool GetTimeZoneInformationForYear(ushort wYear, ref DYNAMIC_TIME_ZONE_INFORMATION pdtzi, out TIME_ZONE_INFORMATION ptzi); + + internal static AdjustmentRule CreateAdjustmentRuleFromTimeZoneInformation (ref DYNAMIC_TIME_ZONE_INFORMATION timeZoneInformation, DateTime startDate, DateTime endDate, int defaultBaseUtcOffset) + { + bool supportsDst = (timeZoneInformation.TZI.StandardDate.wMonth != 0); + + if (!supportsDst) { + if (timeZoneInformation.TZI.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.TZI.Bias, 0)); // Bias delta is all what we need from this rule + } + + // + // Create an AdjustmentRule with TransitionTime objects + // + TransitionTime daylightTransitionStart; + if (!TransitionTimeFromTimeZoneInformation (timeZoneInformation, out daylightTransitionStart, true /* start date */)) { + return null; + } + + TransitionTime daylightTransitionEnd; + if (!TransitionTimeFromTimeZoneInformation (timeZoneInformation, out daylightTransitionEnd, false /* end date */)) { + 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.TZI.DaylightBias, 0), + (TransitionTime) daylightTransitionStart, + (TransitionTime) daylightTransitionEnd, + new TimeSpan (0, defaultBaseUtcOffset - timeZoneInformation.TZI.Bias, 0)); + } + + // + // TransitionTimeFromTimeZoneInformation - + // + // Converts a TimeZoneInformation (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 + // + private static bool TransitionTimeFromTimeZoneInformation (DYNAMIC_TIME_ZONE_INFORMATION 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.TZI.StandardDate.wMonth != 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.TZI.DaylightDate.wYear == 0) { + transitionTime = TransitionTime.CreateFloatingDateRule ( + new DateTime (1, /* year */ + 1, /* month */ + 1, /* day */ + timeZoneInformation.TZI.DaylightDate.wHour, + timeZoneInformation.TZI.DaylightDate.wMinute, + timeZoneInformation.TZI.DaylightDate.wSecond, + timeZoneInformation.TZI.DaylightDate.wMilliseconds), + timeZoneInformation.TZI.DaylightDate.wMonth, + timeZoneInformation.TZI.DaylightDate.wDay, /* Week 1-5 */ + (DayOfWeek)timeZoneInformation.TZI.DaylightDate.wDayOfWeek); + } else { + transitionTime = TransitionTime.CreateFixedDateRule ( + new DateTime (1, /* year */ + 1, /* month */ + 1, /* day */ + timeZoneInformation.TZI.DaylightDate.wHour, + timeZoneInformation.TZI.DaylightDate.wMinute, + timeZoneInformation.TZI.DaylightDate.wSecond, + timeZoneInformation.TZI.DaylightDate.wMilliseconds), + timeZoneInformation.TZI.DaylightDate.wMonth, + timeZoneInformation.TZI.DaylightDate.wDay); + } + } else { + // + // read the "daylightTransitionEnd" + // + if (timeZoneInformation.TZI.StandardDate.wYear == 0) { + transitionTime = TransitionTime.CreateFloatingDateRule ( + new DateTime (1, /* year */ + 1, /* month */ + 1, /* day */ + timeZoneInformation.TZI.StandardDate.wHour, + timeZoneInformation.TZI.StandardDate.wMinute, + timeZoneInformation.TZI.StandardDate.wSecond, + timeZoneInformation.TZI.StandardDate.wMilliseconds), + timeZoneInformation.TZI.StandardDate.wMonth, + timeZoneInformation.TZI.StandardDate.wDay, /* Week 1-5 */ + (DayOfWeek)timeZoneInformation.TZI.StandardDate.wDayOfWeek); + } else { + transitionTime = TransitionTime.CreateFixedDateRule ( + new DateTime (1, /* year */ + 1, /* month */ + 1, /* day */ + timeZoneInformation.TZI.StandardDate.wHour, + timeZoneInformation.TZI.StandardDate.wMinute, + timeZoneInformation.TZI.StandardDate.wSecond, + timeZoneInformation.TZI.StandardDate.wMilliseconds), + timeZoneInformation.TZI.StandardDate.wMonth, + timeZoneInformation.TZI.StandardDate.wDay); + } + } + + return true; + } + + internal static TimeZoneInfo TryCreateTimeZone (DYNAMIC_TIME_ZONE_INFORMATION timeZoneInformation) + { + uint firstYear = 0, lastYear = 0; + AdjustmentRule rule; + AdjustmentRule[] zoneRules = null; + int defaultBaseUtcOffset = timeZoneInformation.TZI.Bias; + + if (String.IsNullOrEmpty (timeZoneInformation.TimeZoneKeyName)) + return null; + + // + // First get the adjustment rules + // + + try { + if (GetDynamicTimeZoneInformationEffectiveYears (ref timeZoneInformation, out firstYear, out lastYear) == 0) { + firstYear = lastYear = 0; + } + } catch { + // If we don't have GetDynamicTimeZoneInformationEffectiveYears() + firstYear = lastYear = 0; + } + + if (firstYear == lastYear) { + rule = CreateAdjustmentRuleFromTimeZoneInformation (ref timeZoneInformation, DateTime.MinValue.Date, DateTime.MaxValue.Date, defaultBaseUtcOffset); + if (rule != null) + zoneRules = new AdjustmentRule [1] { rule }; + } else { + DYNAMIC_TIME_ZONE_INFORMATION dtzi = default (DYNAMIC_TIME_ZONE_INFORMATION); + List rules = new List (); + // + // First rule + // + + if (!GetTimeZoneInformationForYear ((ushort) firstYear, ref timeZoneInformation, out dtzi.TZI)) + return null; + rule = CreateAdjustmentRuleFromTimeZoneInformation (ref dtzi, DateTime.MinValue.Date, new DateTime ((int) firstYear, 12, 31), defaultBaseUtcOffset); + if (rule != null) + rules.Add (rule); + + for (uint i = firstYear + 1; i < lastYear; i++) { + if (!GetTimeZoneInformationForYear ((ushort) i, ref timeZoneInformation, out dtzi.TZI)) + return null; + rule = CreateAdjustmentRuleFromTimeZoneInformation (ref dtzi, new DateTime ((int) i, 1, 1), new DateTime ((int) i, 12, 31), defaultBaseUtcOffset); + if (rule != null) + rules.Add (rule); + } + + // + // Last rule + // + + if (!GetTimeZoneInformationForYear ((ushort) lastYear, ref timeZoneInformation, out dtzi.TZI)) + return null; + rule = CreateAdjustmentRuleFromTimeZoneInformation (ref dtzi, new DateTime ((int) lastYear, 1, 1), DateTime.MaxValue.Date, defaultBaseUtcOffset); + if (rule != null) + rules.Add (rule); + + if (rules.Count > 0) + zoneRules = rules.ToArray (); + } + + return new TimeZoneInfo ( + timeZoneInformation.TimeZoneKeyName, + new TimeSpan (0, -(timeZoneInformation.TZI.Bias), 0), + timeZoneInformation.TZI.StandardName, // we use the display name as the standared names + timeZoneInformation.TZI.StandardName, + timeZoneInformation.TZI.DaylightName, + zoneRules, + false); + } + + internal static TimeZoneInfo GetLocalTimeZoneInfoWinRTFallback () + { + try { + DYNAMIC_TIME_ZONE_INFORMATION dtzi; + var result = GetDynamicTimeZoneInformation (out dtzi); + if (result == TIME_ZONE_ID_INVALID) + return Utc; + TimeZoneInfo timeZoneInfo = TryCreateTimeZone (dtzi); + return timeZoneInfo != null ? timeZoneInfo : Utc; + } catch { + return Utc; + } + } + + internal static TimeZoneInfo FindSystemTimeZoneByIdWinRTFallback (string id) + { + foreach (var tzi in GetSystemTimeZones ()) { + if (String.Compare (id, tzi.Id, StringComparison.Ordinal) == 0) + return tzi; + } + + throw new TimeZoneNotFoundException (); + } + + internal static List GetSystemTimeZonesWinRTFallback () + { + var result = new List (); + try { + uint index = 0; + DYNAMIC_TIME_ZONE_INFORMATION dtzi; + while (EnumDynamicTimeZoneInformation (index++, out dtzi) != ERROR_NO_MORE_ITEMS) { + var timeZoneInfo = TryCreateTimeZone (dtzi); + if (timeZoneInfo != null) + result.Add (timeZoneInfo); + } + } catch { + // EnumDynamicTimeZoneInformation() might not be available. + } + + if (result.Count == 0) + result.Add (Local); + return result; + } + } +} + +#endif // !FULL_AOT_DESKTOP || WIN_PLATFORM diff --git a/mcs/class/corlib/System/TimeZoneInfo.cs b/mcs/class/corlib/System/TimeZoneInfo.cs index e6b7ae23c29..bbb5051574b 100644 --- a/mcs/class/corlib/System/TimeZoneInfo.cs +++ b/mcs/class/corlib/System/TimeZoneInfo.cs @@ -160,6 +160,8 @@ namespace System name = TrimSpecial (name); if (name != null) return TimeZoneInfo.FindSystemTimeZoneById (name); + } else if (IsWindows) { + return GetLocalTimeZoneInfoWinRTFallback (); } #endif @@ -212,6 +214,9 @@ namespace System } catch {} } + return; + } else if (IsWindows) { + systemTimeZones.AddRange (GetSystemTimeZonesWinRTFallback ()); return; } #endif @@ -310,9 +315,13 @@ namespace System if (!IsWindows) return null; - return timeZoneKey = Registry.LocalMachine.OpenSubKey ( - "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones", - false); + try { + return timeZoneKey = Registry.LocalMachine.OpenSubKey ( + "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones", + false); + } catch { + return null; + } } } @@ -325,8 +334,12 @@ namespace System if (!IsWindows) return null; - return localZoneKey = Registry.LocalMachine.OpenSubKey ( - "SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation", false); + try { + return localZoneKey = Registry.LocalMachine.OpenSubKey ( + "SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation", false); + } catch { + return null; + } } } #endif @@ -547,6 +560,8 @@ namespace System if (key == null) throw new TimeZoneNotFoundException (); return FromRegistryKey(id, key); + } else if (IsWindows) { + return FindSystemTimeZoneByIdWinRTFallback (id); } #endif // Local requires special logic that already exists in the Local property (bug #326) diff --git a/mcs/class/corlib/corlib.dll.sources b/mcs/class/corlib/corlib.dll.sources index a818782bfd2..d6faf256138 100644 --- a/mcs/class/corlib/corlib.dll.sources +++ b/mcs/class/corlib/corlib.dll.sources @@ -142,6 +142,7 @@ System/TimeZoneInfo.cs System/TimeZoneInfo.Android.cs System/TimeZoneInfo.MonoTouch.cs System/TimeZoneInfo.Serialization.cs +System/TimeZoneInfo.WinRT.cs ../../build/common/MonoTODOAttribute.cs System/TypeIdentifier.cs System/TypeSpec.cs -- 2.25.1