diff --git a/mcs/class/corlib/System/TimeZoneInfo.cs b/mcs/class/corlib/System/TimeZoneInfo.cs index 7afdbd857de2e2808e18203b0fa5176713c07b76..e77105db4996594d32f3cd16a30bf97473e50822 100644 --- a/mcs/class/corlib/System/TimeZoneInfo.cs +++ b/mcs/class/corlib/System/TimeZoneInfo.cs @@ -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; @@ -982,7 +984,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 +1235,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; @@ -1240,13 +1256,22 @@ namespace System return false; } - 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 (date >= new DateTime (tStart.Ticks + current.DaylightDelta.Ticks, DateTimeKind.Utc)) + { + offset += current.DaylightDelta; + isDst = true; + } + return true; } } diff --git a/mcs/class/corlib/Test/System/TimeZoneInfoTest.cs b/mcs/class/corlib/Test/System/TimeZoneInfoTest.cs index 25adc0c7d0a8c87e8d70d349fdfd559925890478..6b2ad9d47e06df7464107ccd0d866d4decf18219 100644 --- a/mcs/class/corlib/Test/System/TimeZoneInfoTest.cs +++ b/mcs/class/corlib/Test/System/TimeZoneInfoTest.cs @@ -55,6 +55,8 @@ namespace MonoTests.System return "GTB Standard Time"; case "US/Eastern": return "Eastern Standard Time"; + case "US/Central": + return "Central Standard Time"; case "US/Pacific": return "Pacific Standard Time"; case "Australia/Sydney": @@ -760,13 +762,18 @@ 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)))); @@ -842,6 +849,31 @@ namespace MonoTests.System dateOffset = new DateTimeOffset (date, offset); Assert.IsTrue (tzi.IsDaylightSavingTime (dateOffset)); } + + // 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)); + } } [TestFixture]