Fix DateTime.Now and CurrentTimeToUtc during DST transition time (#4172)
authorNeale Ferguson <neale@sinenomine.net>
Tue, 14 Feb 2017 13:47:21 +0000 (08:47 -0500)
committerMarek Safar <marek.safar@gmail.com>
Tue, 14 Feb 2017 13:47:21 +0000 (14:47 +0100)
* During the ambiguous hour .NET will return the DST version of the time whereas mono is returning the base time. Rather than use the transitions array we look up the Adjustment rules (which are created from the transition rules in the first place).

In addition, CurrentTimeToUtc() also has problems around the transition time.

Added test and fixed another.

Fixes #43805.

* Despite the tutorial on msdn about daylight saving time, .NET interprets the 'ambiguous' hour of DST as being DST rather than standard time which is what mono did.

mcs/class/corlib/System/TimeZoneInfo.cs
mcs/class/corlib/Test/System/TimeZoneInfoTest.cs
mcs/class/corlib/Test/System/TimeZoneTest.cs

index bbb5051574b32924789a8df1ca4d12a63e6812fe..7a12080c6a89b822ca68d4806c861d4785655836 100644 (file)
@@ -826,7 +826,7 @@ namespace System
                                        return tz.BaseUtcOffset;
                        }
 
-                       if (tzRule != null && tz.IsInDST (tzRule, stdUtcDateTime)) {
+                       if (tzRule != null && tz.IsInDST (tzRule, dateTime)) {
                                // Replicate what .NET does when given a time which falls into the hour which is lost when
                                // DST starts. isDST should always be true but the offset should be BaseUtcOffset without the
                                // DST delta while in that hour.
@@ -883,7 +883,7 @@ namespace System
                        AdjustmentRule rule = GetApplicableRule (dateTime);
                        if (rule != null) {
                                DateTime tpoint = TransitionPoint (rule.DaylightTransitionEnd, dateTime.Year);
-                               if (dateTime > tpoint - rule.DaylightDelta  && dateTime <= tpoint)
+                               if (dateTime > tpoint - rule.DaylightDelta && dateTime <= tpoint)
                                        return true;
                        }
                                
@@ -913,7 +913,6 @@ namespace System
                                DST_start -= BaseUtcOffset;
                                DST_end -= (BaseUtcOffset + rule.DaylightDelta);
                        }
-
                        return (dateTime >= DST_start && dateTime < DST_end);
                }
                
@@ -1199,31 +1198,16 @@ namespace System
                                        return false;
                        }
 
-                       var inDelta = false;
-                       for (var i =  transitions.Count - 1; i >= 0; i--) {
-                               var pair = transitions [i];
-                               DateTime ttime = pair.Key;
-                               TimeType ttype = pair.Value;
-
-                               var delta =  new TimeSpan (0, 0, ttype.Offset) - BaseUtcOffset;
-
-                               if ((ttime + delta) > date) {
-                                       inDelta = ttime <= date;
-                                       continue;
-                               }
-
-                               offset =  new TimeSpan (0, 0, ttype.Offset);
-                               if (inDelta) {
-                                       // Replicate what .NET does when given a time which falls into the hour which is lost when
-                                       // DST starts. isDST should be true but the offset should be the non-DST offset.
-                                       isDst = transitions [i - 1].Value.IsDst;
-                               } else {
-                                       isDst = ttype.IsDst;
+                       AdjustmentRule current = GetApplicableRule(date);
+                       if (current != null) {
+                               DateTime tStart = TransitionPoint(current.DaylightTransitionStart, date.Year);
+                               DateTime tEnd = TransitionPoint(current.DaylightTransitionEnd, date.Year);
+                               if ((date >= tStart) && (date <= tEnd)) {
+                                       offset = baseUtcOffset + current.DaylightDelta; 
+                                       isDst = true;
+                                       return true;
                                }
-
-                               return true;
                        }
-
                        return false;
                }
 
@@ -1513,7 +1497,7 @@ namespace System
 
                        if (zone.IsAmbiguousTime (time)) {
                                isAmbiguousLocalDst = true;
-                               return baseOffset;
+//                             return baseOffset;
                        }
 
                        return zone.GetUtcOffset (time, out isDaylightSavings);
index 27c167f04862d3d763272eb03a92d4e8c2bf5fa8..d25889f6de69f7c78b1f148d45a06107c212ce32 100644 (file)
@@ -300,7 +300,7 @@ namespace MonoTests.System
                        }
                
                        [Test]
-                       public void DSTTransisions ()
+                       public void DSTTransitions ()
                        {
                                DateTime beforeDST = new DateTime (2007, 03, 25, 0, 59, 59, DateTimeKind.Unspecified);
                                DateTime startDST = new DateTime (2007, 03, 25, 2, 0, 0, DateTimeKind.Unspecified);
@@ -308,12 +308,12 @@ namespace MonoTests.System
                                DateTime afterDST = new DateTime (2007, 10, 28, 2, 0, 0, DateTimeKind.Unspecified);
                                Assert.IsFalse (london.IsDaylightSavingTime (beforeDST), "Just before DST");
                                Assert.IsTrue (london.IsDaylightSavingTime (startDST), "the first seconds of DST");
-                               Assert.IsFalse (london.IsDaylightSavingTime (endDST), "The last seconds of DST");
+                               Assert.IsTrue (london.IsDaylightSavingTime (endDST), "The last seconds of DST");
                                Assert.IsFalse (london.IsDaylightSavingTime (afterDST), "Just after DST");
                        }
                
                        [Test]
-                       public void DSTTransisionsUTC ()
+                       public void DSTTransitionsUTC ()
                        {
                                DateTime beforeDST = new DateTime (2007, 03, 25, 0, 59, 59, DateTimeKind.Utc);
                                DateTime startDST = new DateTime (2007, 03, 25, 1, 0, 0, DateTimeKind.Utc);
@@ -1179,8 +1179,8 @@ namespace MonoTests.System
 
                                d = dst1End.Add (-dstOffset);
                                Assert.AreEqual(dstUtcOffset, cairo.GetUtcOffset (d.Add (new TimeSpan(0,0,0,-1))));
-                               Assert.AreEqual(baseUtcOffset, cairo.GetUtcOffset (d));
-                               Assert.AreEqual(baseUtcOffset, cairo.GetUtcOffset (d.Add (new TimeSpan(0,0,0, 1))));
+                               Assert.AreEqual(dstUtcOffset, cairo.GetUtcOffset (d));
+                               Assert.AreEqual(baseUtcOffset, cairo.GetUtcOffset (d.Add (new TimeSpan(0,1,0, 1))));
 
                                d = dst2Start.Add (dstOffset);
                                Assert.AreEqual(baseUtcOffset, cairo.GetUtcOffset (d.Add (new TimeSpan(0,0,0,-1))));
@@ -1189,8 +1189,8 @@ namespace MonoTests.System
 
                                d = dst2End.Add (-dstOffset);
                                Assert.AreEqual(dstUtcOffset, cairo.GetUtcOffset (d.Add (new TimeSpan(0,0,0,-1))));
-                               Assert.AreEqual(baseUtcOffset, cairo.GetUtcOffset (d));
-                               Assert.AreEqual(baseUtcOffset, cairo.GetUtcOffset (d.Add (new TimeSpan(0,0,0, 1))));
+                               Assert.AreEqual(dstUtcOffset, cairo.GetUtcOffset (d));
+                               Assert.AreEqual(baseUtcOffset, cairo.GetUtcOffset (d.Add (new TimeSpan(0,1,0, 1))));
                        }
 
                  [Test]
index 612cac79c3b0d60ca2a56d4c48d257273b16d770..0b9897a32e6fb9c4d7f52979921eed51f24352ad 100644 (file)
@@ -14,6 +14,7 @@ using System;
 using System.IO;
 using System.Threading;
 using System.Globalization;
+using System.Reflection;
 using System.Runtime.Serialization.Formatters.Binary;
 
 namespace MonoTests.System {
@@ -270,6 +271,47 @@ public class TimeZoneTest {
                Assert.IsTrue (tz.ToLocalTime (dst_start_utc.Add (new TimeSpan (1, 0, 0))) < tz.ToLocalTime (dst_start_utc.Add (new TimeSpan (1, 1, 0))), "0:4:00 < 0:4:01");
        }
 
+               [Test]
+               public void GetUTCNowAtDSTBoundaries ()
+               {
+                       TimeZoneInfo.TransitionTime startTransition = TimeZoneInfo.TransitionTime.CreateFloatingDateRule(new DateTime(1, 1, 1, 2, 0, 0), 3, 5, DayOfWeek.Sunday);
+
+                       TimeZoneInfo.TransitionTime endTransition = TimeZoneInfo.TransitionTime.CreateFloatingDateRule(new DateTime(1, 1, 1, 3, 0, 0), 10, 5, DayOfWeek.Sunday);
+
+                       TimeSpan delta = TimeSpan.FromMinutes(60.0);
+                       TimeZoneInfo.AdjustmentRule adjustment = TimeZoneInfo.AdjustmentRule.CreateAdjustmentRule(new DateTime(1970, 1, 1), DateTime.MaxValue.Date, delta, startTransition, endTransition);
+                       TimeZoneInfo.TransitionTime startTrans = adjustment.DaylightTransitionStart;
+                       TimeZoneInfo.TransitionTime endTrans = adjustment.DaylightTransitionEnd;
+                       TimeZoneInfo.AdjustmentRule[] adjustments = { adjustment };
+
+                       TimeZoneInfo tzInfo = TimeZoneInfo.CreateCustomTimeZone("MY Standard Time", TimeSpan.Zero, "MST", "MST", "MDT", adjustments);
+
+                       // There is no .NET API to set timezone. Use reflection to assign time zone to the TimeZoneInfo.local field.
+                       FieldInfo localTimeZone = typeof(TimeZoneInfo).GetField("local", BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance);
+                       localTimeZone.SetValue(null, tzInfo);
+
+                       DateTime st = new DateTime(2016, 3, 27, 1, 0, 0, DateTimeKind.Local);
+                       Assert.IsTrue (!tzInfo.IsDaylightSavingTime(st));       
+                       Assert.IsTrue (!tzInfo.IsAmbiguousTime(st));
+                       Assert.IsTrue ((TimeZoneInfo.ConvertTimeToUtc(st).Hour == 1));
+                       st = new DateTime(2016, 3, 27, 3, 0, 0, DateTimeKind.Local);
+                       Assert.IsTrue (tzInfo.IsDaylightSavingTime(st));        
+                       Assert.IsTrue (!tzInfo.IsAmbiguousTime(st));
+                       Assert.IsTrue ((TimeZoneInfo.ConvertTimeToUtc(st).Hour == 2));
+                       st = new DateTime(2016, 10, 30, 2, 0, 0, DateTimeKind.Local);
+                       Assert.IsTrue (tzInfo.IsDaylightSavingTime(st));        
+                       Assert.IsTrue (!tzInfo.IsAmbiguousTime(st));
+                       Assert.IsTrue ((TimeZoneInfo.ConvertTimeToUtc(st).Hour == 1));
+                       st = new DateTime(2016, 10, 30, 3, 0, 0, DateTimeKind.Local);
+                       Assert.IsTrue (!tzInfo.IsDaylightSavingTime(st));       
+                       Assert.IsTrue (tzInfo.IsAmbiguousTime(st));
+                       Assert.IsTrue ((TimeZoneInfo.ConvertTimeToUtc(st).Hour == 3));
+                       st = new DateTime(2016, 10, 30, 4, 0, 0, DateTimeKind.Local);
+                       Assert.IsTrue (!tzInfo.IsDaylightSavingTime(st));       
+                       Assert.IsTrue (!tzInfo.IsAmbiguousTime(st));
+                       Assert.IsTrue ((TimeZoneInfo.ConvertTimeToUtc(st).Hour == 4));
+               }
+
                [Test]
                public void GetUtcOffsetAtDSTBoundary ()
                {
@@ -302,11 +344,12 @@ public class TimeZoneTest {
                                Assert.Ignore (tz.StandardName + " did not observe daylight saving time during " + year + ".");
 
                        var standardOffset = tz.GetUtcOffset(daylightChanges.Start.AddMinutes(-1));
+                       var dstOffset = tz.GetUtcOffset(daylightChanges.Start.AddMinutes(61));
 
                        Assert.AreEqual(standardOffset, tz.GetUtcOffset (dst_end));
-                       Assert.AreEqual(standardOffset, tz.GetUtcOffset (dst_end.Add (daylightChanges.Delta.Negate ().Add (TimeSpan.FromSeconds(1)))));
-                       Assert.AreEqual(standardOffset, tz.GetUtcOffset (dst_end.Add(daylightChanges.Delta.Negate ())));
-                       Assert.AreNotEqual(standardOffset, tz.GetUtcOffset (dst_end.Add(daylightChanges.Delta.Negate ().Add (TimeSpan.FromSeconds(-1)))));
+                       Assert.AreEqual(dstOffset, tz.GetUtcOffset (dst_end.Add (daylightChanges.Delta.Negate ().Add (TimeSpan.FromSeconds(1)))));
+                       Assert.AreEqual(dstOffset, tz.GetUtcOffset (dst_end.Add(daylightChanges.Delta.Negate ())));
+                       Assert.AreEqual(dstOffset, tz.GetUtcOffset (dst_end.Add(daylightChanges.Delta.Negate ().Add (TimeSpan.FromSeconds(-1)))));
                }