Replicate .NET's TimeZoneInfo.IsDaylightSavingTime() and GetUtcOffset()
authorNiklas Therning <niklas@therning.org>
Wed, 14 Sep 2016 10:00:07 +0000 (12:00 +0200)
committerNiklas Therning <niklas@therning.org>
Wed, 14 Sep 2016 10:00:07 +0000 (12:00 +0200)
behaviour when passed times within the lost period at DST start

When passed a time within this period .NET's IsDaylightSavingTime() returns
true while GetUtcOffset() returns the BaseUtcOffset without the DST delta
added. This patch changes the behaviour in Mono to do what .NET does.

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

index 592f1d2f300adcf2d98d8c8712f1448ee240705d..96a1150368cb076b8b0c29edf6ce9575e6720eb6 100644 (file)
@@ -811,9 +811,16 @@ namespace System
                                        return tz.BaseUtcOffset;
                        }
 
-                       if (tzRule != null && tz.IsInDST (tzRule, stdUtcDateTime) && tz.IsInDST (tzRule, dstUtcDateTime)) {
+                       if (tzRule != null && tz.IsInDST (tzRule, stdUtcDateTime)) {
+                               // 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.
                                isDST = true;
-                               return tz.BaseUtcOffset + tzRule.DaylightDelta;
+                               if (tz.IsInDST (tzRule, dstUtcDateTime)) {
+                                       return tz.BaseUtcOffset + tzRule.DaylightDelta;
+                               } else {
+                                       return tz.BaseUtcOffset;
+                               }
                        }
 
                        return tz.BaseUtcOffset;
@@ -1172,16 +1179,27 @@ 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;
 
-                               if (ttime > date)
+                               var delta =  new TimeSpan (0, 0, ttype.Offset) - BaseUtcOffset;
+
+                               if ((ttime + delta) > date) {
+                                       inDelta = ttime <= date;
                                        continue;
+                               }
 
                                offset =  new TimeSpan (0, 0, ttype.Offset);
-                               isDst = ttype.IsDst;
+                               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;
+                               }
 
                                return true;
                        }
index 5e3811bca0019d8a6964049b9387ea5943157eff..b124eb64a44647cdec7dad6c98895a5b314f38ca 100644 (file)
@@ -360,6 +360,77 @@ namespace MonoTests.System
                                Assert.AreEqual (new TimeSpan (2,0,0), tzi.GetUtcOffset (date));
                        }
 
+                       [Test]
+                       public void TestAthensDST_InDSTDelta ()
+                       {
+                               // In .NET GetUtcOffset() returns the BaseUtcOffset for times within the hour
+                               // lost when DST starts but IsDaylightSavingTime() returns true.
+
+                               TimeZoneInfo tzi = TimeZoneInfo.FindSystemTimeZoneById ("Europe/Athens");
+
+                               var date = new DateTime (2014, 3, 30 , 3, 0, 0);
+                               Assert.IsTrue (tzi.IsDaylightSavingTime (date));
+                               Assert.AreEqual (new TimeSpan (2, 0, 0), tzi.GetUtcOffset (date));
+                               Assert.IsTrue (tzi.IsDaylightSavingTime (new DateTimeOffset (date, tzi.GetUtcOffset (date))));
+
+                               date = new DateTime (2014, 3, 30 , 3, 1, 0);
+                               Assert.IsTrue (tzi.IsDaylightSavingTime (date));
+                               Assert.AreEqual (new TimeSpan (2, 0, 0), tzi.GetUtcOffset (date));
+                               Assert.IsTrue (tzi.IsDaylightSavingTime (new DateTimeOffset (date, tzi.GetUtcOffset (date))));
+
+                               date = new DateTime (2014, 3, 30 , 3, 59, 0);
+                               Assert.IsTrue (tzi.IsDaylightSavingTime (date));
+                               Assert.AreEqual (new TimeSpan (2, 0, 0), tzi.GetUtcOffset (date));
+                               Assert.IsTrue (tzi.IsDaylightSavingTime (new DateTimeOffset (date, tzi.GetUtcOffset (date))));
+
+                               date = new DateTime (2014, 3, 30 , 4, 0, 0);
+                               Assert.IsTrue (tzi.IsDaylightSavingTime (date));
+                               Assert.AreEqual (new TimeSpan (3, 0, 0), tzi.GetUtcOffset (date));
+                               Assert.IsTrue (tzi.IsDaylightSavingTime (new DateTimeOffset (date, tzi.GetUtcOffset (date))));
+                       }
+
+                       [Test]
+                       public void TestAthensDST_InDSTDelta_NoTransitions ()
+                       {
+                               if (Environment.OSVersion.Platform != PlatformID.Unix)
+                                       Assert.Ignore ("TimeZoneInfo on Mono on Windows and .NET has no transitions");
+
+                               // Repeat the previous test but this time force using AdjustmentRules by nulling out TimeZoneInfo.transitions
+
+                               TimeZoneInfo tzi = TimeZoneInfo.FindSystemTimeZoneById ("Europe/Athens");
+
+                               var transitionsField = typeof (TimeZoneInfo).GetField ("transitions", BindingFlags.Instance | BindingFlags.NonPublic);
+                               var transitions = transitionsField.GetValue (tzi);
+                               Assert.IsNotNull (transitions, "Expected Athens TimeZoneInfo.transitions to be non-null");
+                               transitionsField.SetValue (tzi, null);
+
+                               try {
+
+                                       var date = new DateTime (2014, 3, 30 , 3, 0, 0);
+                                       Assert.IsTrue (tzi.IsDaylightSavingTime (date));
+                                       Assert.AreEqual (new TimeSpan (2, 0, 0), tzi.GetUtcOffset (date));
+                                       Assert.IsTrue (tzi.IsDaylightSavingTime (new DateTimeOffset (date, tzi.GetUtcOffset (date))));
+
+                                       date = new DateTime (2014, 3, 30 , 3, 1, 0);
+                                       Assert.IsTrue (tzi.IsDaylightSavingTime (date));
+                                       Assert.AreEqual (new TimeSpan (2, 0, 0), tzi.GetUtcOffset (date));
+                                       Assert.IsTrue (tzi.IsDaylightSavingTime (new DateTimeOffset (date, tzi.GetUtcOffset (date))));
+
+                                       date = new DateTime (2014, 3, 30 , 3, 59, 0);
+                                       Assert.IsTrue (tzi.IsDaylightSavingTime (date));
+                                       Assert.AreEqual (new TimeSpan (2, 0, 0), tzi.GetUtcOffset (date));
+                                       Assert.IsTrue (tzi.IsDaylightSavingTime (new DateTimeOffset (date, tzi.GetUtcOffset (date))));
+
+                                       date = new DateTime (2014, 3, 30 , 4, 0, 0);
+                                       Assert.IsTrue (tzi.IsDaylightSavingTime (date));
+                                       Assert.AreEqual (new TimeSpan (3, 0, 0), tzi.GetUtcOffset (date));
+                                       Assert.IsTrue (tzi.IsDaylightSavingTime (new DateTimeOffset (date, tzi.GetUtcOffset (date))));
+
+                               } finally {
+                                       transitionsField.SetValue (tzi, transitions);
+                               }
+                       }
+
                        [Test] //Covers #41349
                        public void TestIsDST_DateTimeOffset ()
                        {