未验证 提交 329b65af 编写于 作者: C cprasad-rythmos 提交者: GitHub

Merge pull request #1378 from Unity-Technologies/2019.4/fix-dst-transition-bug

2019.4: Fix Incorrect UTC offset during DST transition (case 1288231)
......@@ -805,7 +805,7 @@ namespace System
return GetUtcOffset (dateTimeOffset.UtcDateTime, out isDST);
}
private TimeSpan GetUtcOffset (DateTime dateTime, out bool isDST)
private TimeSpan GetUtcOffset (DateTime dateTime, out bool isDST, bool forOffset = false)
{
isDST = false;
......@@ -817,7 +817,7 @@ namespace System
tz = TimeZoneInfo.Local;
bool isTzDst;
var tzOffset = GetUtcOffsetHelper (dateTime, tz, out isTzDst);
var tzOffset = GetUtcOffsetHelper (dateTime, tz, out isTzDst, forOffset);
if (tz == this) {
isDST = isTzDst;
......@@ -828,11 +828,11 @@ namespace System
if (!TryAddTicks (dateTime, -tzOffset.Ticks, out utcDateTime, DateTimeKind.Utc))
return BaseUtcOffset;
return GetUtcOffsetHelper (utcDateTime, this, out isDST);
return GetUtcOffsetHelper (utcDateTime, this, out isDST, forOffset);
}
// This is an helper method used by the method above, do not use this on its own.
private static TimeSpan GetUtcOffsetHelper (DateTime dateTime, TimeZoneInfo tz, out bool isDST)
private static TimeSpan GetUtcOffsetHelper (DateTime dateTime, TimeZoneInfo tz, out bool isDST, bool forOffset = false)
{
if (dateTime.Kind == DateTimeKind.Local && tz != TimeZoneInfo.Local)
throw new Exception ();
......@@ -843,7 +843,7 @@ namespace System
return TimeSpan.Zero;
TimeSpan offset;
if (tz.TryGetTransitionOffset(dateTime, out offset, out isDST))
if (tz.TryGetTransitionOffset(dateTime, out offset, out isDST, forOffset))
return offset;
if (dateTime.Kind == DateTimeKind.Utc) {
......@@ -870,10 +870,12 @@ namespace System
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 starts. isDST should be false and the offset should be BaseUtcOffset without the
// DST delta while in that hour.
isDST = true;
if (forOffset)
isDST = true;
if (tz.IsInDST (tzRule, dstUtcDateTime)) {
isDST = true;
return tz.BaseUtcOffset + tzRule.DaylightDelta;
} else {
return tz.BaseUtcOffset;
......@@ -925,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;
}
......@@ -944,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)
......@@ -953,8 +992,9 @@ namespace System
DateTime DST_end = TransitionPoint (rule.DaylightTransitionEnd, year + ((rule.DaylightTransitionStart.Month < rule.DaylightTransitionEnd.Month) ? 0 : 1));
if (dateTime.Kind == DateTimeKind.Utc) {
DST_start -= BaseUtcOffset;
DST_end -= (BaseUtcOffset + rule.DaylightDelta);
DST_end -= BaseUtcOffset;
}
DST_end -= rule.DaylightDelta;
return (dateTime >= DST_start && dateTime < DST_end);
}
......@@ -982,7 +1022,21 @@ namespace System
public bool IsDaylightSavingTime (DateTimeOffset dateTimeOffset)
{
return IsDaylightSavingTime (dateTimeOffset.DateTime);
var dateTime = dateTimeOffset.DateTime;
if (dateTime.Kind == DateTimeKind.Local && IsInvalidTime (dateTime))
throw new ArgumentException ("dateTime is invalid and Kind is Local");
if (this == TimeZoneInfo.Utc)
return false;
if (!SupportsDaylightSavingTime)
return false;
bool isDst;
GetUtcOffset (dateTime, out isDst, true);
return isDst;
}
internal DaylightTime GetDaylightChanges (int year)
......@@ -1219,7 +1273,7 @@ namespace System
return null;
}
private bool TryGetTransitionOffset (DateTime dateTime, out TimeSpan offset,out bool isDst)
private bool TryGetTransitionOffset (DateTime dateTime, out TimeSpan offset, out bool isDst, bool forOffset = false)
{
offset = BaseUtcOffset;
isDst = false;
......@@ -1235,18 +1289,45 @@ namespace System
return false;
}
var isUtc = false;
if (dateTime.Kind != DateTimeKind.Utc) {
if (!TryAddTicks (date, -BaseUtcOffset.Ticks, out date, DateTimeKind.Utc))
return false;
}
} else
isUtc = true;
AdjustmentRule current = GetApplicableRule(date);
AdjustmentRule current = GetApplicableRule (date);
if (current != null) {
DateTime tStart = TransitionPoint(current.DaylightTransitionStart, date.Year);
DateTime tEnd = TransitionPoint(current.DaylightTransitionEnd, date.Year);
DateTime tStart = TransitionPoint (current.DaylightTransitionStart, date.Year);
DateTime tEnd = TransitionPoint (current.DaylightTransitionEnd, date.Year);
TryAddTicks (tStart, -BaseUtcOffset.Ticks, out tStart, DateTimeKind.Utc);
TryAddTicks (tEnd, -BaseUtcOffset.Ticks, out tEnd, DateTimeKind.Utc);
if ((date >= tStart) && (date <= tEnd)) {
offset = baseUtcOffset + current.DaylightDelta;
isDst = true;
if (forOffset)
isDst = true;
offset = baseUtcOffset;
if (isUtc || (date >= new DateTime (tStart.Ticks + current.DaylightDelta.Ticks, DateTimeKind.Utc)))
{
offset += current.DaylightDelta;
isDst = true;
}
if (date >= new DateTime (tEnd.Ticks - current.DaylightDelta.Ticks, DateTimeKind.Utc))
{
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;
}
}
......@@ -1255,8 +1336,11 @@ namespace System
private static DateTime TransitionPoint (TransitionTime transition, int year)
{
if (transition.IsFixedDateRule)
return new DateTime (year, transition.Month, transition.Day) + transition.TimeOfDay.TimeOfDay;
if (transition.IsFixedDateRule) {
var daysInMonth = DateTime.DaysInMonth (year, transition.Month);
var transitionDay = transition.Day <= daysInMonth ? transition.Day : daysInMonth;
return new DateTime (year, transition.Month, transitionDay) + transition.TimeOfDay.TimeOfDay;
}
DayOfWeek first = (new DateTime (year, transition.Month, 1)).DayOfWeek;
int day = 1 + (transition.Week - 1) * 7 + (transition.DayOfWeek - first + 7) % 7;
......@@ -1540,7 +1624,7 @@ namespace System
isAmbiguousLocalDst = false;
TimeSpan baseOffset = zone.BaseUtcOffset;
if (zone.IsAmbiguousTime (time)) {
if (zone.IsAmbiguousLocalDstFromUtc (time)) {
isAmbiguousLocalDst = true;
// return baseOffset;
}
......@@ -1570,4 +1654,4 @@ namespace System
}
#endif
}
}
\ No newline at end of file
}
......@@ -53,22 +53,72 @@ namespace MonoTests.System
return "New Zealand Standard Time";
case "Europe/Athens":
return "GTB Standard Time";
case "Europe/Chisinau":
return "E. Europe Standard Time";
case "America/New_York":
return "Eastern Standard Time";
case "America/Chicago":
case "US/Eastern":
return "Eastern Standard Time";
case "US/Central":
return "Central Standard Time";
case "US/Pacific":
return "Pacific Standard Time";
case "Australia/Sydney":
case "Australia/Melbourne":
return "AUS Eastern Standard Time";
case "Europe/Brussels":
case "Europe/Copenhagen":
case "Europe/Paris":
case "Europe/Madrid":
return "Romance Standard Time";
case "Africa/Kinshasa":
return "W. Central Africa Standard Time";
case "Europe/Rome":
case "Europe/Vatican":
case "Europe/Vienna":
case "Europe/Berlin":
case "Europe/Luxembourg":
case "Europe/Malta":
case "Europe/Monaco":
case "Europe/Amsterdam":
case "Europe/Oslo":
case "Europe/San_Marino":
return "W. Europe Standard Time";
case "Canada/Eastern":
return "Eastern Standard Time";
case "Asia/Tehran":
return "Iran Standard Time";
case "Europe/Guernsey":
case "Europe/Dublin":
case "Europe/Isle_of_Man":
case "Europe/Jersey":
case "Europe/Lisbon":
case "Europe/London":
return "GMT Standard Time";
case "America/Havana":
return "Cuba Standard Time";
case "America/Anchorage":
return "Alaskan Standard Time";
case "Atlantic/Azores":
return "Azores Standard Time";
case "Asia/Jerusalem":
return "Israel Standard Time";
case "Asia/Amman":
return "Jordan Standard Time";
case "Europe/Tirane":
case "Europe/Warsaw":
return "Central European Standard Time";
case "Europe/Sofia":
case "Europe/Tallinn":
case "Europe/Riga":
case "Europe/Vilnius":
case "Europe/Kiev":
return "FLE Standard Time";
case "Europe/Prague":
case "Europe/Budapest":
case "Europe/Bratislava":
return "Central Europe Standard Time";
default:
Assert.Fail ($"No mapping defined for zone id '{id}'");
return null;
......@@ -683,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");
}
......@@ -760,23 +810,28 @@ namespace MonoTests.System
[Test]
public void TestAthensDST_InDSTDelta ()
{
// In .NET GetUtcOffset() returns the BaseUtcOffset for times within the hour
// lost when DST starts but IsDaylightSavingTime() returns true.
// In .NET/.Net Core GetUtcOffset() returns the BaseUtcOffset for times within the hour
// lost when DST starts and IsDaylightSavingTime() returns false for datetime and true for datetimeoffset
TimeZoneInfo tzi = TimeZoneInfo.FindSystemTimeZoneById (MapTimeZoneId ("Europe/Athens"));
var date = new DateTime (2014, 3, 30 , 3, 0, 0);
Assert.IsTrue (tzi.IsDaylightSavingTime (date));
var date = new DateTime (2014, 3, 30 , 2, 0, 0);
Assert.IsFalse (tzi.IsDaylightSavingTime (date));
Assert.AreEqual (new TimeSpan (2, 0, 0), tzi.GetUtcOffset (date));
Assert.IsFalse (tzi.IsDaylightSavingTime (new DateTimeOffset (date, tzi.GetUtcOffset (date))));
date = new DateTime (2014, 3, 30 , 3, 0, 0);
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))));
......@@ -804,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))));
......@@ -842,6 +897,270 @@ namespace MonoTests.System
dateOffset = new DateTimeOffset (date, offset);
Assert.IsTrue (tzi.IsDaylightSavingTime (dateOffset));
}
// https://github.com/mono/mono/issues/16742
[Test]
public void Bug_16472 ()
{
var parsedTime = DateTime.Parse ("1948-02-19T23:00:00Z", CultureInfo.InvariantCulture);
var newTime = TimeZoneInfo.ConvertTime (parsedTime, TimeZoneInfo.FindSystemTimeZoneById (MapTimeZoneId ("Europe/Rome")));
Assert.AreEqual (1948, newTime.Year);
}
// https://github.com/mono/mono/issues/9664
[Test]
public void Bug_9664 ()
{
TimeZoneInfo tzi = TimeZoneInfo.FindSystemTimeZoneById (MapTimeZoneId ("US/Central"));
var date = new DateTime (2019, 3, 9, 21, 0, 0);
Assert.IsFalse (tzi.IsDaylightSavingTime (date));
Assert.AreEqual (new TimeSpan (-6, 0, 0), tzi.GetUtcOffset (date));
tzi = TimeZoneInfo.FindSystemTimeZoneById (MapTimeZoneId ("US/Central"));
date = new DateTime (2019, 3, 10, 2, 0, 0);
Assert.IsFalse (tzi.IsDaylightSavingTime (date));
Assert.AreEqual (new TimeSpan (-6, 0, 0), tzi.GetUtcOffset (date));
tzi = TimeZoneInfo.FindSystemTimeZoneById (MapTimeZoneId ("US/Central"));
date = new DateTime (2019, 3, 10, 2, 30, 0);
Assert.IsFalse (tzi.IsDaylightSavingTime (date));
Assert.AreEqual (new TimeSpan (-6, 0, 0), tzi.GetUtcOffset (date));
tzi = TimeZoneInfo.FindSystemTimeZoneById (MapTimeZoneId ("US/Central"));
date = new DateTime (2019, 3, 10, 3, 0, 0);
Assert.IsTrue (tzi.IsDaylightSavingTime (date));
Assert.AreEqual (new TimeSpan (-5, 0, 0), tzi.GetUtcOffset (date));
#if !WINAOT // https://github.com/mono/mono/issues/15439
tzi = TimeZoneInfo.FindSystemTimeZoneById (MapTimeZoneId ("Europe/Vatican"));
date = new DateTime (2018, 10, 28, 2, 15, 0);
Assert.IsFalse (tzi.IsDaylightSavingTime (date));
Assert.AreEqual (new TimeSpan (1, 0, 0), tzi.GetUtcOffset (date));
tzi = TimeZoneInfo.FindSystemTimeZoneById (MapTimeZoneId ("Asia/Tehran"));
date = new DateTime (2018, 9, 21, 23, 15, 0);
Assert.IsFalse (tzi.IsDaylightSavingTime (date));
Assert.AreEqual (new TimeSpan (3, 30, 0), tzi.GetUtcOffset (date));
// for Greenwitch Mean Time (Guernsey)
tzi = TimeZoneInfo.FindSystemTimeZoneById (MapTimeZoneId ("Europe/Guernsey"));
date = new DateTime (2019, 10, 27, 1, 15, 0);
Assert.IsFalse (tzi.IsDaylightSavingTime (date));
Assert.AreEqual (new TimeSpan (0, 0, 0), tzi.GetUtcOffset (date));
#endif
}
[Test]
public void Bug_16395 ()
{
// Cuba, Havana (Cuba Standard Time): Jumps ahead at 12:00 AM on 3/8/2020 to 1:00 AM
CheckJumpingIntoDST ("America/Havana",
new DateTime (2020, 3, 8, 0, 0, 0), new DateTime (2020, 3, 8, 0, 30, 0), new DateTime (2020, 3, 8, 1, 0, 0),
new TimeSpan (-5, 0, 0), new TimeSpan (-4, 0, 0));
// US, Kansas City, MO (US Central Time): Jumps ahead at 2:00 AM on 3/8/2020 to 3:00 AM
CheckJumpingIntoDST ("America/Chicago",
new DateTime (2020, 3, 8, 2, 0, 0), new DateTime (2020, 3, 8, 2, 30, 0), new DateTime (2020, 3, 8, 3, 0, 0),
new TimeSpan (-6, 0, 0), new TimeSpan (-5, 0, 0));
// Anchorage, AK (Alaska Time): Jumps ahead at 2:00 AM on 3/8/2020 to 3:00 AM
CheckJumpingIntoDST ("America/Anchorage",
new DateTime (2020, 3, 8, 2, 0, 0), new DateTime (2020, 3, 8, 2, 30, 0), new DateTime (2020, 3, 8, 3, 0, 0),
new TimeSpan (-9, 0, 0), new TimeSpan (-8, 0, 0));
// Azores ST (Ponta Delgada, Portugal): Jumps ahead at 12:00 AM on 3/29/2020 to 1:00 AM
CheckJumpingIntoDST ("Atlantic/Azores",
new DateTime (2020, 3, 29, 0, 0, 0), new DateTime (2020, 3, 29, 0, 30, 0), new DateTime (2020, 3, 29, 1, 0, 0),
new TimeSpan (-1, 0, 0), new TimeSpan (0, 0, 0));
// Iran, Tehran (Iran ST): Jumps ahead at 12:00 AM on 3/21/2020 to 1:00 AM
CheckJumpingIntoDST ("Asia/Tehran",
new DateTime (2020, 3, 21, 0, 0, 0), new DateTime (2020, 3, 21, 0, 30, 0), new DateTime (2020, 3, 21, 1, 0, 0),
new TimeSpan (3, 30, 0), new TimeSpan (4, 30, 0));
// Israel, Jerusalem (Israel ST): Jumps ahead at 2:00 AM on 3/27/2020 to 3:00 AM
CheckJumpingIntoDST ("Asia/Jerusalem",
new DateTime (2020, 3, 27, 2, 0, 0), new DateTime (2020, 3, 27, 2, 30, 0), new DateTime (2020, 3, 27, 3, 0, 0),
new TimeSpan (2, 0, 0), new TimeSpan (3, 0, 0));
// Jordan, Amman (Eastern European ST): Jumps ahead at 12:00 AM on 3/27/2020 to 1:00 AM
CheckJumpingIntoDST ("Asia/Amman",
new DateTime (2020, 3, 27, 0, 0, 0), new DateTime (2020, 3, 27, 0, 30, 0), new DateTime (2020, 3, 27, 1, 0, 0),
new TimeSpan (2, 0, 0), new TimeSpan (3, 0, 0));
// Albania, Tirana (Central European ST): Jumps ahead at 2:00 AM on 3/29/2020 to 3:00 AM
CheckJumpingIntoDST ("Europe/Tirane",
new DateTime (2020, 3, 29, 2, 0, 0), new DateTime (2020, 3, 29, 2, 30, 0), new DateTime (2020, 3, 29, 3, 0, 0),
new TimeSpan (1, 0, 0), new TimeSpan (2, 0, 0));
// Austria, Vienna (Central European ST): Jumps ahead at 2:00 AM on 3/29/2020 to 3:00 AM
CheckJumpingIntoDST ("Europe/Vienna",
new DateTime (2020, 3, 29, 2, 0, 0), new DateTime (2020, 3, 29, 2, 30, 0), new DateTime (2020, 3, 29, 3, 0, 0),
new TimeSpan (1, 0, 0), new TimeSpan (2, 0, 0));
// Belgium, Brussels (Central European ST): Jumps ahead at 2:00 AM on 3/29/2020 to 3:00 AM
CheckJumpingIntoDST ("Europe/Brussels",
new DateTime (2020, 3, 29, 2, 0, 0), new DateTime (2020, 3, 29, 2, 30, 0), new DateTime (2020, 3, 29, 3, 0, 0),
new TimeSpan (1, 0, 0), new TimeSpan (2, 0, 0));
// Bulgaria, Sofia (Eastern European ST): Jumps ahead at 3:00 AM on 3/29/2020 to 4:00 AM
CheckJumpingIntoDST ("Europe/Sofia",
new DateTime (2020, 3, 29, 3, 0, 0), new DateTime (2020, 3, 29, 3, 30, 0), new DateTime (2020, 3, 29, 4, 0, 0),
new TimeSpan (2, 0, 0), new TimeSpan (3, 0, 0));
// Czechia, Prague (Central European ST): Jumps ahead at 2:00 AM on 3/29/2020 to 3:00 AM
CheckJumpingIntoDST ("Europe/Prague",
new DateTime (2020, 3, 29, 2, 0, 0), new DateTime (2020, 3, 29, 2, 30, 0), new DateTime (2020, 3, 29, 3, 0, 0),
new TimeSpan (1, 0, 0), new TimeSpan (2, 0, 0));
// Denmark, Copenhagen (Central European ST): Jumps ahead at 2:00 AM on 3/29/2020 to 3:00 AM
CheckJumpingIntoDST ("Europe/Copenhagen",
new DateTime (2020, 3, 29, 2, 0, 0), new DateTime (2020, 3, 29, 2, 30, 0), new DateTime (2020, 3, 29, 3, 0, 0),
new TimeSpan (1, 0, 0), new TimeSpan (2, 0, 0));
// Estonia, Tallinn (Eastern European ST): Jumps ahead at 3:00 AM on 3/29/2020 to 4:00 AM
CheckJumpingIntoDST ("Europe/Tallinn",
new DateTime (2020, 3, 29, 3, 0, 0), new DateTime (2020, 3, 29, 3, 30, 0), new DateTime (2020, 3, 29, 4, 0, 0),
new TimeSpan (2, 0, 0), new TimeSpan (3, 0, 0));
// France, Paris (Central European ST): Jumps ahead at 2:00 AM on 3/29/2020 to 3:00 AM
CheckJumpingIntoDST ("Europe/Paris",
new DateTime (2020, 3, 29, 2, 0, 0), new DateTime (2020, 3, 29, 2, 30, 0), new DateTime (2020, 3, 29, 3, 0, 0),
new TimeSpan (1, 0, 0), new TimeSpan (2, 0, 0));
// Germany, Berlin (Central European ST): Jumps ahead at 2:00 AM on 3/29/2020 to 3:00 AM
CheckJumpingIntoDST ("Europe/Berlin",
new DateTime (2020, 3, 29, 2, 0, 0), new DateTime (2020, 3, 29, 2, 30, 0), new DateTime (2020, 3, 29, 3, 0, 0),
new TimeSpan (1, 0, 0), new TimeSpan (2, 0, 0));
// Greece, Athens (Eastern European ST): Jumps ahead at 3:00 AM on 3/29/2020 to 4:00 AM
CheckJumpingIntoDST ("Europe/Athens",
new DateTime (2020, 3, 29, 3, 0, 0), new DateTime (2020, 3, 29, 3, 30, 0), new DateTime (2020, 3, 29, 4, 0, 0),
new TimeSpan (2, 0, 0), new TimeSpan (3, 0, 0));
// Guernsey (UK) Jumps ahead at 1:00 AM on 3/29/2020 to 2:00 AM
CheckJumpingIntoDST ("Europe/Guernsey",
new DateTime (2020, 3, 29, 1, 0, 0), new DateTime (2020, 3, 29, 1, 30, 0), new DateTime (2020, 3, 29, 2, 0, 0),
new TimeSpan (0, 0, 0), new TimeSpan (1, 0, 0));
// Holy See, Vatican City (Central European ST): Jumps ahead at 2:00 AM on 3/29/2020 to 3:00 AM
CheckJumpingIntoDST ("Europe/Vatican",
new DateTime (2020, 3, 29, 2, 0, 0), new DateTime (2020, 3, 29, 2, 30, 0), new DateTime (2020, 3, 29, 3, 0, 0),
new TimeSpan (1, 0, 0), new TimeSpan (2, 0, 0));
// Hungary, Budapest (Central European ST): Jumps ahead at 2:00 AM on 3/29/2020 to 3:00 AM
CheckJumpingIntoDST ("Europe/Budapest",
new DateTime (2020, 3, 29, 2, 0, 0), new DateTime (2020, 3, 29, 2, 30, 0), new DateTime (2020, 3, 29, 3, 0, 0),
new TimeSpan (1, 0, 0), new TimeSpan (2, 0, 0));
// // Ireland, Dublin (Greenwich Mean Time -> Irish Standard Time): Jumps ahead at 1:00 AM on 3/29/2020 to 2:00 AM
// CheckJumpingIntoDST ("Europe/Dublin",
// new DateTime (2020, 3, 29, 1, 0, 0), new DateTime (2020, 3, 29, 1, 30, 0), new DateTime (2020, 3, 29, 2, 0, 0),
// new TimeSpan (0, 0, 0), new TimeSpan (1, 0, 0));
// UK, Douglas, Isle of Man (GMT+1:00): Jumps ahead at 1:00 AM on 3/29/2020 to 2:00 AM
CheckJumpingIntoDST ("Europe/Isle_of_Man",
new DateTime (2020, 3, 29, 1, 0, 0), new DateTime (2020, 3, 29, 1, 30, 0), new DateTime (2020, 3, 29, 2, 0, 0),
new TimeSpan (0, 0, 0), new TimeSpan (1, 0, 0));
// Italy, Rome (Central European ST): Jumps ahead at 2:00 AM on 3/29/2020 to 3:00 AM
CheckJumpingIntoDST ("Europe/Rome",
new DateTime (2020, 3, 29, 2, 0, 0), new DateTime (2020, 3, 29, 2, 30, 0), new DateTime (2020, 3, 29, 3, 0, 0),
new TimeSpan (1, 0, 0), new TimeSpan (2, 0, 0));
// Jersey (UK): Jumps ahead at 1:00 AM on 3/29/2020 to 2:00 AM
CheckJumpingIntoDST ("Europe/Jersey",
new DateTime (2020, 3, 29, 1, 0, 0), new DateTime (2020, 3, 29, 1, 30, 0), new DateTime (2020, 3, 29, 2, 0, 0),
new TimeSpan (0, 0, 0), new TimeSpan (1, 0, 0));
// Latvia, Riga (Eastern European ST): Jumps ahead at 3:00 AM on 3/29/2020 to 4:00 AM
CheckJumpingIntoDST ("Europe/Riga",
new DateTime (2020, 3, 29, 3, 0, 0), new DateTime (2020, 3, 29, 3, 30, 0), new DateTime (2020, 3, 29, 4, 0, 0),
new TimeSpan (2, 0, 0), new TimeSpan (3, 0, 0));
// Lithuania, Vilnius (Eastern European ST): Jumps ahead at 3:00 AM on 3/29/2020 to 4:00 AM
CheckJumpingIntoDST ("Europe/Vilnius",
new DateTime (2020, 3, 29, 3, 0, 0), new DateTime (2020, 3, 29, 3, 30, 0), new DateTime (2020, 3, 29, 4, 0, 0),
new TimeSpan (2, 0, 0), new TimeSpan (3, 0, 0));
// Luxembourg, Luxembourg (Central European ST): Jumps ahead at 2:00 AM on 3/29/2020 to 3:00 AM
CheckJumpingIntoDST ("Europe/Luxembourg",
new DateTime (2020, 3, 29, 2, 0, 0), new DateTime (2020, 3, 29, 2, 30, 0), new DateTime (2020, 3, 29, 3, 0, 0),
new TimeSpan (1, 0, 0), new TimeSpan (2, 0, 0));
// Malta, Valletta (Central European ST): Jumps ahead at 2:00 AM on 3/29/2020 to 3:00 AM
CheckJumpingIntoDST ("Europe/Malta",
new DateTime (2020, 3, 29, 2, 0, 0), new DateTime (2020, 3, 29, 2, 30, 0), new DateTime (2020, 3, 29, 3, 0, 0),
new TimeSpan (1, 0, 0), new TimeSpan (2, 0, 0));
// Moldova, Chişinău (Eastern European ST): Jumps ahead at 2:00 AM on 3/29/2020 to 3:00 AM
CheckJumpingIntoDST ("Europe/Chisinau",
new DateTime (2020, 3, 29, 2, 0, 0), new DateTime (2020, 3, 29, 2, 30, 0), new DateTime (2020, 3, 29, 3, 0, 0),
new TimeSpan (2, 0, 0), new TimeSpan (3, 0, 0));
// Monaco, Monaco (Central European ST): Jumps ahead at 2:00 AM on 3/29/2020 to 3:00 AM
CheckJumpingIntoDST ("Europe/Monaco",
new DateTime (2020, 3, 29, 2, 0, 0), new DateTime (2020, 3, 29, 2, 30, 0), new DateTime (2020, 3, 29, 3, 0, 0),
new TimeSpan (1, 0, 0), new TimeSpan (2, 0, 0));
// Netherlands, Amsterdam (Central European ST): Jumps ahead at 2:00 AM on 3/29/2020 to 3:00 AM
CheckJumpingIntoDST ("Europe/Amsterdam",
new DateTime (2020, 3, 29, 2, 0, 0), new DateTime (2020, 3, 29, 2, 30, 0), new DateTime (2020, 3, 29, 3, 0, 0),
new TimeSpan (1, 0, 0), new TimeSpan (2, 0, 0));
// Norway, Oslo (Central European ST): Jumps ahead at 2:00 AM on 3/29/2020 to 3:00 AM
CheckJumpingIntoDST ("Europe/Oslo",
new DateTime (2020, 3, 29, 2, 0, 0), new DateTime (2020, 3, 29, 2, 30, 0), new DateTime (2020, 3, 29, 3, 0, 0),
new TimeSpan (1, 0, 0), new TimeSpan (2, 0, 0));
// Poland, Warsaw (Central European ST): Jumps ahead at 2:00 AM on 3/29/2020 to 3:00 AM
CheckJumpingIntoDST ("Europe/Warsaw",
new DateTime (2020, 3, 29, 2, 0, 0), new DateTime (2020, 3, 29, 2, 30, 0), new DateTime (2020, 3, 29, 3, 0, 0),
new TimeSpan (1, 0, 0), new TimeSpan (2, 0, 0));
// Portugal, Lisbon (Western European ST): Jumps ahead at 1:00 AM on 3/29/2020 to 2:00 AM
CheckJumpingIntoDST ("Europe/Lisbon",
new DateTime (2020, 3, 29, 1, 0, 0), new DateTime (2020, 3, 29, 1, 30, 0), new DateTime (2020, 3, 29, 2, 0, 0),
new TimeSpan (0, 0, 0), new TimeSpan (1, 0, 0));
// San Marino, San Marino (Central European ST): Jumps ahead at 2:00 AM on 3/29/2020 to 3:00 AM
CheckJumpingIntoDST ("Europe/San_Marino",
new DateTime (2020, 3, 29, 2, 0, 0), new DateTime (2020, 3, 29, 2, 30, 0), new DateTime (2020, 3, 29, 3, 0, 0),
new TimeSpan (1, 0, 0), new TimeSpan (2, 0, 0));
// Slovakia, Bratislava (Central European ST): Jumps ahead at 2:00 AM on 3/29/2020 to 3:00 AM
CheckJumpingIntoDST ("Europe/Bratislava",
new DateTime (2020, 3, 29, 2, 0, 0), new DateTime (2020, 3, 29, 2, 30, 0), new DateTime (2020, 3, 29, 3, 0, 0),
new TimeSpan (1, 0, 0), new TimeSpan (2, 0, 0));
// Spain, Madrid (Central European ST): Jumps ahead at 2:00 AM on 3/29/2020 to 3:00 AM
CheckJumpingIntoDST ("Europe/Madrid",
new DateTime (2020, 3, 29, 2, 0, 0), new DateTime (2020, 3, 29, 2, 30, 0), new DateTime (2020, 3, 29, 3, 0, 0),
new TimeSpan (1, 0, 0), new TimeSpan (2, 0, 0));
// Ukraine, Kiev (Eastern European ST): Jumps ahead at 3:00 AM on 3/29/2020 to 4:00 AM
CheckJumpingIntoDST ("Europe/Kiev",
new DateTime (2020, 3, 29, 3, 0, 0), new DateTime (2020, 3, 29, 3, 30, 0), new DateTime (2020, 3, 29, 4, 0, 0),
new TimeSpan (2, 0, 0), new TimeSpan (3, 0, 0));
// United Kingdom, London (British ST): Jumps ahead at 1:00 AM on 3/29/2020 to 2:00 AM
CheckJumpingIntoDST ("Europe/London",
new DateTime (2020, 3, 29, 1, 0, 0), new DateTime (2020, 3, 29, 1, 30, 0), new DateTime (2020, 3, 29, 2, 0, 0),
new TimeSpan (0, 0, 0), new TimeSpan (1, 0, 0));
}
void CheckJumpingIntoDST (string tzId, DateTime dstDeltaStart, DateTime inDstDelta, DateTime dstDeltaEnd, TimeSpan baseOffset, TimeSpan dstOffset)
{
var tzi = TimeZoneInfo.FindSystemTimeZoneById (MapTimeZoneId (tzId));
Assert.IsFalse (tzi.IsDaylightSavingTime (dstDeltaStart), $"{tzId}: #1");
Assert.AreEqual (baseOffset, tzi.GetUtcOffset (dstDeltaStart), $"{tzId}: #2");
Assert.IsFalse (tzi.IsDaylightSavingTime (inDstDelta), $"{tzId}: #3");
Assert.AreEqual (baseOffset, tzi.GetUtcOffset (inDstDelta), $"{tzId}: #4");
Assert.IsTrue (tzi.IsDaylightSavingTime (dstDeltaEnd), $"{tzId}: #5");
Assert.AreEqual (dstOffset, tzi.GetUtcOffset (dstDeltaEnd), $"{tzId}: #6");
}
}
[TestFixture]
......@@ -1175,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
......@@ -1566,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);
......@@ -1576,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.
先完成此消息的编辑!
想要评论请 注册