From: Neale Ferguson Date: Tue, 14 Feb 2017 13:47:21 +0000 (-0500) Subject: Fix DateTime.Now and CurrentTimeToUtc during DST transition time (#4172) X-Git-Url: http://wien.tomnetworks.com/gitweb/?a=commitdiff_plain;h=16edc1e552f348a5ccdcd0b6e0c191c902f97422;p=mono.git Fix DateTime.Now and CurrentTimeToUtc during DST transition time (#4172) * 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. --- diff --git a/mcs/class/corlib/System/TimeZoneInfo.cs b/mcs/class/corlib/System/TimeZoneInfo.cs index bbb5051574b..7a12080c6a8 100644 --- a/mcs/class/corlib/System/TimeZoneInfo.cs +++ b/mcs/class/corlib/System/TimeZoneInfo.cs @@ -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); diff --git a/mcs/class/corlib/Test/System/TimeZoneInfoTest.cs b/mcs/class/corlib/Test/System/TimeZoneInfoTest.cs index 27c167f0486..d25889f6de6 100644 --- a/mcs/class/corlib/Test/System/TimeZoneInfoTest.cs +++ b/mcs/class/corlib/Test/System/TimeZoneInfoTest.cs @@ -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] diff --git a/mcs/class/corlib/Test/System/TimeZoneTest.cs b/mcs/class/corlib/Test/System/TimeZoneTest.cs index 612cac79c3b..0b9897a32e6 100644 --- a/mcs/class/corlib/Test/System/TimeZoneTest.cs +++ b/mcs/class/corlib/Test/System/TimeZoneTest.cs @@ -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))))); }