Skip old time zones adjustments with offsets higher than 14h (#18305) (#18874)
authorKrzysztof Wicher <mordotymoja@gmail.com>
Fri, 13 Jul 2018 03:24:07 +0000 (20:24 -0700)
committerGitHub <noreply@github.com>
Fri, 13 Jul 2018 03:24:07 +0000 (20:24 -0700)
* Skip old time zones with offsets higher than 14h

* apply offline feedback

* fix braces

src/mscorlib/shared/System/TimeZoneInfo.Unix.cs
src/mscorlib/shared/System/TimeZoneInfo.cs

index cc386a1..2be5002 100644 (file)
@@ -595,6 +595,7 @@ namespace System
                 }
                 catch (ArgumentException) { }
                 catch (InvalidTimeZoneException) { }
+
                 try
                 {
                     return new TimeZoneInfo(rawData, id, dstDisabled: true); // create a TimeZoneInfo instance from the TZif data w/o DST support
@@ -602,7 +603,6 @@ namespace System
                 catch (ArgumentException) { }
                 catch (InvalidTimeZoneException) { }
             }
-
             return null;
         }
 
@@ -866,7 +866,7 @@ namespace System
                 index++;
             }
 
-            if (index == 0)
+            if (rulesList.Count == 0 && index < dts.Length)
             {
                 TZifType transitionType = TZif_GetEarlyDateTransitionType(transitionTypes);
                 DateTime endTransitionDate = dts[index];
@@ -883,6 +883,12 @@ namespace System
                         default(TransitionTime),
                         baseUtcDelta,
                         noDaylightTransitions: true);
+
+                if (!IsValidAdjustmentRuleOffest(timeZoneBaseUtcOffset, r))
+                {
+                    NormalizeAdjustmentRuleOffset(timeZoneBaseUtcOffset, ref r);
+                }
+
                 rulesList.Add(r);
             }
             else if (index < dts.Length)
@@ -920,6 +926,12 @@ namespace System
                         default(TransitionTime),
                         baseUtcDelta,
                         noDaylightTransitions: true);
+
+                if (!IsValidAdjustmentRuleOffest(timeZoneBaseUtcOffset, r))
+                {
+                    NormalizeAdjustmentRuleOffset(timeZoneBaseUtcOffset, ref r);
+                }
+
                 rulesList.Add(r);
             }
             else
@@ -932,8 +944,14 @@ namespace System
                 if (!string.IsNullOrEmpty(futureTransitionsPosixFormat))
                 {
                     AdjustmentRule r = TZif_CreateAdjustmentRuleForPosixFormat(futureTransitionsPosixFormat, startTransitionDate, timeZoneBaseUtcOffset);
+
                     if (r != null)
                     {
+                        if (!IsValidAdjustmentRuleOffest(timeZoneBaseUtcOffset, r))
+                        {
+                            NormalizeAdjustmentRuleOffset(timeZoneBaseUtcOffset, ref r);
+                        }
+
                         rulesList.Add(r);
                     }
                 }
@@ -954,6 +972,12 @@ namespace System
                         default(TransitionTime),
                         baseUtcDelta,
                         noDaylightTransitions: true);
+
+                    if (!IsValidAdjustmentRuleOffest(timeZoneBaseUtcOffset, r))
+                    {
+                        NormalizeAdjustmentRuleOffset(timeZoneBaseUtcOffset, ref r);
+                    }
+
                     rulesList.Add(r);
                 }
             }
index 6e27376..e95321d 100644 (file)
@@ -1918,12 +1918,6 @@ namespace System
         }
 
         /// <summary>
-        /// Helper function that validates the TimeSpan is within +/- 14.0 hours
-        /// </summary>
-        internal static bool UtcOffsetOutOfRange(TimeSpan offset) =>
-            offset.TotalHours < -14.0 || offset.TotalHours > 14.0;
-
-        /// <summary>
         /// Helper function that performs all of the validation checks for the
         /// factory methods and deserialization callback.
         /// </summary>
@@ -1972,11 +1966,7 @@ namespace System
                         throw new InvalidTimeZoneException(SR.Argument_AdjustmentRulesNoNulls);
                     }
 
-                    // FUTURE: check to see if this rule supports Daylight Saving Time
-                    // adjustmentRulesSupportDst = adjustmentRulesSupportDst || current.SupportsDaylightSavingTime;
-                    // FUTURE: test baseUtcOffset + current.StandardDelta
-
-                    if (UtcOffsetOutOfRange(baseUtcOffset + current.DaylightDelta))
+                    if (!IsValidAdjustmentRuleOffest(baseUtcOffset, current))
                     {
                         throw new InvalidTimeZoneException(SR.ArgumentOutOfRange_UtcOffsetAndDaylightDelta);
                     }
@@ -1989,5 +1979,82 @@ namespace System
                 }
             }
         }
+
+        private static readonly TimeSpan MaxOffset = TimeSpan.FromHours(14.0);
+        private static readonly TimeSpan MinOffset = -MaxOffset;
+        
+        /// <summary>
+        /// Helper function that validates the TimeSpan is within +/- 14.0 hours
+        /// </summary>
+        internal static bool UtcOffsetOutOfRange(TimeSpan offset) =>
+            offset < MinOffset || offset > MaxOffset;
+
+        private static TimeSpan GetUtcOffset(TimeSpan baseUtcOffset, AdjustmentRule adjustmentRule)
+        {
+            return baseUtcOffset
+                + adjustmentRule.BaseUtcOffsetDelta
+                + (adjustmentRule.HasDaylightSaving ? adjustmentRule.DaylightDelta : TimeSpan.Zero);
+        }
+        
+        /// <summary>
+        /// Helper function that performs adjustment rule validation
+        /// </summary>
+        private static bool IsValidAdjustmentRuleOffest(TimeSpan baseUtcOffset, AdjustmentRule adjustmentRule)
+        {
+            TimeSpan utcOffset = GetUtcOffset(baseUtcOffset, adjustmentRule);
+            return !UtcOffsetOutOfRange(utcOffset);
+        }
+
+        /// <summary>
+        /// Normalize adjustment rule offset so that it is within valid range
+        /// This method should not be called at all but is here in case something changes in the future
+        /// or if really old time zones are present on the OS (no combination is known at the moment)
+        /// </summary>
+        private static void NormalizeAdjustmentRuleOffset(TimeSpan baseUtcOffset, ref AdjustmentRule adjustmentRule)
+        {
+            // Certain time zones such as:
+            //       Time Zone  start date  end date    offset
+            // -----------------------------------------------------
+            // America/Yakutat  0001-01-01  1867-10-18   14:41:00
+            // America/Yakutat  1867-10-18  1900-08-20   14:41:00
+            // America/Sitka    0001-01-01  1867-10-18   14:58:00
+            // America/Sitka    1867-10-18  1900-08-20   14:58:00
+            // Asia/Manila      0001-01-01  1844-12-31  -15:56:00
+            // Pacific/Guam     0001-01-01  1845-01-01  -14:21:00
+            // Pacific/Saipan   0001-01-01  1845-01-01  -14:21:00
+            //
+            // have larger offset than currently supported by framework.
+            // If for whatever reason we find that time zone exceeding max
+            // offset of 14h this function will truncate it to the max valid offset.
+            // Updating max offset may cause problems with interacting with SQL server
+            // which uses SQL DATETIMEOFFSET field type which was originally designed to be
+            // bit-for-bit compatible with DateTimeOffset.
+
+            TimeSpan utcOffset = GetUtcOffset(baseUtcOffset, adjustmentRule);
+
+            // utc base offset delta increment
+            TimeSpan adjustment = TimeSpan.Zero;
+
+            if (utcOffset > MaxOffset)
+            {
+                adjustment = MaxOffset - utcOffset;
+            }
+            else if (utcOffset < MinOffset)
+            {
+                adjustment = MinOffset - utcOffset;
+            }
+
+            if (adjustment != TimeSpan.Zero)
+            {
+                adjustmentRule = AdjustmentRule.CreateAdjustmentRule(
+                    adjustmentRule.DateStart,
+                    adjustmentRule.DateEnd,
+                    adjustmentRule.DaylightDelta,
+                    adjustmentRule.DaylightTransitionStart,
+                    adjustmentRule.DaylightTransitionEnd,
+                    adjustmentRule.BaseUtcOffsetDelta + adjustment,
+                    adjustmentRule.NoDaylightTransitions);
+            }
+        }
     }
 }