Temp fix for broken TimeZoneInfo on Windows families without registry access
authorNiklas Therning <niklas@therning.org>
Mon, 6 Feb 2017 12:53:53 +0000 (13:53 +0100)
committerMarek Safar <marek.safar@gmail.com>
Tue, 7 Feb 2017 08:22:43 +0000 (09:22 +0100)
Band aid fix until we can switch to CoreRT's implementation.

mcs/class/corlib/System/TimeZoneInfo.WinRT.cs [new file with mode: 0755]
mcs/class/corlib/System/TimeZoneInfo.cs
mcs/class/corlib/corlib.dll.sources

diff --git a/mcs/class/corlib/System/TimeZoneInfo.WinRT.cs b/mcs/class/corlib/System/TimeZoneInfo.WinRT.cs
new file mode 100755 (executable)
index 0000000..797fe43
--- /dev/null
@@ -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<AdjustmentRule> rules = new List<AdjustmentRule> ();
+                               //
+                               // 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<TimeZoneInfo> GetSystemTimeZonesWinRTFallback ()
+               {
+                       var result = new List<TimeZoneInfo> ();
+                       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
index e6b7ae23c29c2f6c4a252d2666903a2e1a5ed88a..bbb5051574b32924789a8df1ca4d12a63e6812fe 100644 (file)
@@ -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)
index a818782bfd258487fd336e1022add1bc9a73a4fa..d6faf256138acae5d82f61e2c00af59494b0f65f 100644 (file)
@@ -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