diff --git a/mcs/class/corlib/System/TimeZoneInfo.cs b/mcs/class/corlib/System/TimeZoneInfo.cs index 11eec8cfa951fa469f5ac1476d095e98b108836c..3360421500a390f85ff7fe46b4e33c6e19c17554 100644 --- a/mcs/class/corlib/System/TimeZoneInfo.cs +++ b/mcs/class/corlib/System/TimeZoneInfo.cs @@ -927,7 +927,33 @@ 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; + } + + return false; + } + + private bool IsAmbiguousLocalDstFromUtc (DateTime dateTime) + { + // This method determines if a dateTime in UTC falls into the Dst side + // of the ambiguous local time (the local time that occurs twice). + + if (dateTime.Kind == DateTimeKind.Local) + return false; + + if (this == TimeZoneInfo.Utc) + return false; + + AdjustmentRule rule = GetApplicableRule (dateTime); + if (rule != null) { + DateTime tpoint = TransitionPoint (rule.DaylightTransitionEnd, dateTime.Year); + // tpoint is the local time in daylight savings time when daylight savings time will end, convert it to UTC + DateTime tpointUtc; + if (!TryAddTicks(tpoint, -(BaseUtcOffset.Ticks + rule.DaylightDelta.Ticks), out tpointUtc, DateTimeKind.Utc)) + return false; + + if (dateTime >= tpointUtc - rule.DaylightDelta && dateTime < tpointUtc) return true; } @@ -946,7 +972,18 @@ namespace System return true; // We might be in the dateTime previous year's DST period - return dateTime.Year > 1 && IsInDSTForYear (rule, dateTime, dateTime.Year - 1); + if (dateTime.Year > 1 && IsInDSTForYear(rule, dateTime, dateTime.Year - 1)) + return true; + + // If we are checking an ambiguous local time, that is the local time that occurs twice during a DST "fall back" + // check if it was marked as being in the DST side of the ambiguous time when it was created + // We need to re-check IsAmbiguousTime because the IsAmbiguousDaylightSavingTime flag is not cleared when using DateTime.Add/Subtract + if (dateTime.Kind == DateTimeKind.Local && IsAmbiguousTime(dateTime)) + { + return dateTime.IsAmbiguousDaylightSavingTime(); + } + + return false; } bool IsInDSTForYear (AdjustmentRule rule, DateTime dateTime, int year) @@ -1281,6 +1318,15 @@ namespace System offset = baseUtcOffset; isDst = false; } + + // If we are checking an ambiguous local time, that is the local time that occurs twice during a DST "fall back" + // check if it was marked as being in the DST side of the ambiguous time when it was created + // We need to re-check IsAmbiguousTime because the IsAmbiguousDaylightSavingTime flag is not cleared when using DateTime.Add/Subtract + if (!isDst && dateTime.Kind == DateTimeKind.Local && IsAmbiguousTime(dateTime) && dateTime.IsAmbiguousDaylightSavingTime()) + { + offset += current.DaylightDelta; + isDst = true; + } return true; } @@ -1578,7 +1624,7 @@ namespace System isAmbiguousLocalDst = false; TimeSpan baseOffset = zone.BaseUtcOffset; - if (zone.IsAmbiguousTime (time)) { + if (zone.IsAmbiguousLocalDstFromUtc (time)) { isAmbiguousLocalDst = true; // return baseOffset; } @@ -1608,4 +1654,4 @@ namespace System } #endif } -} \ No newline at end of file +} diff --git a/mcs/class/corlib/Test/System/TimeZoneInfoTest.cs b/mcs/class/corlib/Test/System/TimeZoneInfoTest.cs index c7425a8813745c3d4462decfc3c8dc8ff2288568..1982d7355a94bf5a008a8b0da591766ccc48fa74 100644 --- a/mcs/class/corlib/Test/System/TimeZoneInfoTest.cs +++ b/mcs/class/corlib/Test/System/TimeZoneInfoTest.cs @@ -733,7 +733,7 @@ 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.IsTrue (london.IsDaylightSavingTime (endDST), "The last seconds of DST"); + Assert.IsFalse (london.IsDaylightSavingTime (endDST), "The last seconds of DST"); Assert.IsFalse (london.IsDaylightSavingTime (afterDST), "Just after DST"); } @@ -826,12 +826,12 @@ namespace MonoTests.System 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.IsFalse (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.IsFalse (tzi.IsDaylightSavingTime (date)); Assert.AreEqual (new TimeSpan (2, 0, 0), tzi.GetUtcOffset (date)); Assert.IsTrue (tzi.IsDaylightSavingTime (new DateTimeOffset (date, tzi.GetUtcOffset (date)))); @@ -859,17 +859,17 @@ namespace MonoTests.System try { var date = new DateTime (2014, 3, 30 , 3, 0, 0); - Assert.IsTrue (tzi.IsDaylightSavingTime (date)); + Assert.IsFalse (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.IsFalse (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.IsFalse (tzi.IsDaylightSavingTime (date)); Assert.AreEqual (new TimeSpan (2, 0, 0), tzi.GetUtcOffset (date)); Assert.IsTrue (tzi.IsDaylightSavingTime (new DateTimeOffset (date, tzi.GetUtcOffset (date)))); @@ -1494,19 +1494,22 @@ namespace MonoTests.System [Test] public void AmbiguousDates () { - Assert.IsFalse (london.IsAmbiguousTime (new DateTime (2007, 10, 28, 1, 0, 0))); + Assert.IsTrue (london.IsAmbiguousTime (new DateTime (2007, 10, 28, 1, 0, 0))); Assert.IsTrue (london.IsAmbiguousTime (new DateTime (2007, 10, 28, 1, 0, 1))); - Assert.IsTrue (london.IsAmbiguousTime (new DateTime (2007, 10, 28, 2, 0, 0))); + Assert.IsFalse (london.IsAmbiguousTime (new DateTime (2007, 10, 28, 2, 0, 0))); Assert.IsFalse (london.IsAmbiguousTime (new DateTime (2007, 10, 28, 2, 0, 1))); } [Test] public void AmbiguousUTCDates () { - Assert.IsFalse (london.IsAmbiguousTime (new DateTime (2007, 10, 28, 0, 0, 0, DateTimeKind.Utc))); + Assert.IsTrue (london.IsAmbiguousTime (new DateTime (2007, 10, 28, 0, 0, 0, DateTimeKind.Utc))); Assert.IsTrue (london.IsAmbiguousTime (new DateTime (2007, 10, 28, 0, 0, 1, DateTimeKind.Utc))); Assert.IsTrue (london.IsAmbiguousTime (new DateTime (2007, 10, 28, 0, 59, 59, DateTimeKind.Utc))); - Assert.IsFalse (london.IsAmbiguousTime (new DateTime (2007, 10, 28, 1, 0, 0, DateTimeKind.Utc))); + Assert.IsTrue (london.IsAmbiguousTime (new DateTime (2007, 10, 28, 1, 0, 0, DateTimeKind.Utc))); + Assert.IsTrue (london.IsAmbiguousTime (new DateTime (2007, 10, 28, 1, 59, 59, DateTimeKind.Utc))); + Assert.IsFalse (london.IsAmbiguousTime (new DateTime (2007, 10, 28, 2, 0, 0, DateTimeKind.Utc))); + Assert.IsFalse (london.IsAmbiguousTime (new DateTime (2007, 10, 28, 2, 0, 1, DateTimeKind.Utc))); } #if SLOW_TESTS @@ -1885,7 +1888,6 @@ namespace MonoTests.System d = dst1End.Add (-dstOffset); Assert.AreEqual(dstUtcOffset, 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); @@ -1895,7 +1897,6 @@ namespace MonoTests.System d = dst2End.Add (-dstOffset); Assert.AreEqual(dstUtcOffset, 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)))); } diff --git a/mcs/class/corlib/Test/System/TimeZoneTest.cs b/mcs/class/corlib/Test/System/TimeZoneTest.cs index 5f73383f8826dd1fda1942e32917f5c349bf213c..05340de31076dd4d8cee3c94e0279db0ad24ec72 100644 --- a/mcs/class/corlib/Test/System/TimeZoneTest.cs +++ b/mcs/class/corlib/Test/System/TimeZoneTest.cs @@ -299,12 +299,12 @@ public class TimeZoneTest { 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 == 2)); + 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)); @@ -348,9 +348,29 @@ public class TimeZoneTest { var dstOffset = tz.GetUtcOffset(daylightChanges.Start.AddMinutes(61)); // Assert.AreEqual(standardOffset, tz.GetUtcOffset (dst_end)); - 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(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.AreEqual(dstOffset, tz.GetUtcOffset (dst_end.Add(daylightChanges.Delta.Negate ().Add (TimeSpan.FromSeconds(-1))))); + + // This test assumes that the DST end is a "fall back" where we go to an earlier local time + if (daylightChanges.Delta > TimeSpan.Zero) + { + // dst_end is the end time of the DST in DST time. + // It is technically an ambiguous time because the same local time occurs twice, + // once in DST and then again in standard time + // The ToUniversalTime() will assume standard time for ambiguous times, so we subtract + // the DST delta to the the UTC time corresponding to the end of DST. Then + // the ToLocalTime() will encode some extra info letting the framework know that we + // are dealing with the ambiguous local time that is in DST. + var dst_ambiguous = tz.ToUniversalTime(dst_end.Add(daylightChanges.Delta.Negate())).ToUniversalTime() + .Add(daylightChanges.Delta.Negate()).ToLocalTime(); + + Assert.AreEqual(dstOffset, tz.GetUtcOffset(dst_ambiguous)); + + // The IsAmbiguousDaylightSavingTime flag is not cleared by DateTime.Add + Assert.AreEqual(standardOffset, tz.GetUtcOffset(dst_ambiguous.Add(daylightChanges.Delta))); + Assert.AreEqual(dstOffset, tz.GetUtcOffset(dst_ambiguous.Add(daylightChanges.Delta).Subtract(daylightChanges.Delta))); + } }