提交 e1b8b13d 编写于 作者: S Scott Ferguson 提交者: cprasad-rythmos

Fix Incorrect UTC offset during DST transition (case 1288231)

Corrects an issue where for the hour after the DST transition, the
local UTC offset was listed.  The UTC offset was the DST offset
instead of the standard time offset.

The runtime library captures this an ambiguous time.  That is
the local time that occurs twice - once in DST then once in standard
time.  If DST is an extra 1:00 a.m. offset and ends at 2:00 a.m., 1:00 a.m.
to 1:59:59.9999.... occurs twice.  First in DST then again in standard
time.

The classlibs had this incorrect - they did not consider 1:00 a.m. an
ambiguous time, and considered 2:00 a.m. ambiguous.  However it should
be reversed.  1:00 a.m. occurs twice, but 2:00 a.m. only occurs once.
The instance we would hit 2:00 a.m. DST, we instantaneous switch to
1:00 a.m. standard.

The classlibs were also not recording enough information to record
which side of DST a local time was.  When converting a time from UTC,
or using DateTime.Now an internal flag, IsAmbiguousDaylightSavingTime,
should be set if the time is an ambiguous local time that is on the
DST side of the transition.  The classlibs were calling
TimeZone.IsAmbigousTime which has a wider defintion for ambiguous
time that the IsAmbiguousDaylightSavingTime should have.  It returns
true for local times on either side of DST.  So a new method
IsAmbiguousLocalDstFromUtc was added to check this case.

The classlibs were also not checking the IsAmbiguousLocalDstFromUtc
flag when getting the UTC offset for a local time.  So a check
was inserted in two locations to correct for that.

Some tests has to be updated to reflect these new definitions of when
DST starts and ends and which times are ambiguous.  These also account
for some test changes required by cherry-picked changes to
TimeZoneInfo.cs where the corresponding test changes were not
cherry-picked.  Some of those changes where in PR's that updated to
the CoreFx TimeZoneInfo class.

All these changes have been verified against the behavior of the
.Net Framework and they match.

Fix case 1288231:
Mono: Fix incorrect UTC offset during daylight savings time transitions
上级 eb81d705
......@@ -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
}
......@@ -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))));
}
......
......@@ -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)));
}
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册