13

I've been working with the DateTime and TimeZoneInfo classes and I ran into an interesting result with the following code:

var dstStart = new DateTime(2013, 3, 10, 2, 0, 0, DateTimeKind.Local);
var result = TimeZoneInfo.Local.IsDaylightSavingTime(dstStart);

The result of this is False. I actually would have thought it would be True (DST starts on March 10th at 2:00 AM)

Then I tried similar code using FindSystemTimeZoneById instead:

var myTimeZone = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
var result = myTimeZone.IsDaylightSavingTime(dstStart);

The result of this is surprisingly True.

I then checked to see that these objects are both representing the same time zone:

myTimeZone.Id == TimeZoneInfo.Local.Id // returns True (Both are "Eastern Standard Time")

My question is: why are these results different, and more importantly how can I make them the same?

My computer is definitely in the Eastern Standard Time time zone

More information:

I resynced my computer's clock, and I ran a number of tests to compare the TimeZoneInfo object that was returned by each of the above methods. Here is my test program

var timeZoneFromLookup = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");

var dstStart = new DateTime(2013, 3, 10, 2, 0, 0, DateTimeKind.Local);

// -- The following return true --

Console.WriteLine("Equal? {0}", TimeZoneInfo.Local.Equals(timeZoneFromLookup));

Console.WriteLine("Has Same Rules? {0}", TimeZoneInfo.Local.HasSameRules(timeZoneFromLookup));

Console.WriteLine("Same Id? {0}", TimeZoneInfo.Local.Id == timeZoneFromLookup.Id);

Console.WriteLine("Same Base UTC Offset? {0}", TimeZoneInfo.Local.BaseUtcOffset == timeZoneFromLookup.BaseUtcOffset);

Console.WriteLine("Same Daylight Name? {0}", TimeZoneInfo.Local.DaylightName == timeZoneFromLookup.DaylightName);

Console.WriteLine("Same Display Name? {0}", TimeZoneInfo.Local.DisplayName == timeZoneFromLookup.DisplayName);

Console.WriteLine("Same Standard Name? {0}", TimeZoneInfo.Local.StandardName == timeZoneFromLookup.StandardName);

Console.WriteLine("Same Support For DST? {0}", 
    TimeZoneInfo.Local.SupportsDaylightSavingTime == timeZoneFromLookup.SupportsDaylightSavingTime
);

Console.WriteLine("Same result as to whether date/time is ambiguous? {0}", 
    timeZoneFromLookup.IsAmbiguousTime(dstStart) == TimeZoneInfo.Local.IsAmbiguousTime(dstStart)
);

// -- The following return false --


Console.WriteLine("Same utc offset result? {0}", 
    timeZoneFromLookup.GetUtcOffset(dstStart) == TimeZoneInfo.Local.GetUtcOffset(dstStart)

);
Console.WriteLine("Same Conversion to UTC? {0}", 
    TimeZoneInfo.Local.GetUtcOffset(dstStart) == timeZoneFromLookup.GetUtcOffset(dstStart)
);

Console.WriteLine("Same result as to whether date/time is invalid? {0}", 
    timeZoneFromLookup.IsInvalidTime(dstStart) == TimeZoneInfo.Local.IsInvalidTime(dstStart)
);

Console.WriteLine("Same result as to whether date/time is DST? {0}", 
    timeZoneFromLookup.IsDaylightSavingTime(dstStart) == TimeZoneInfo.Local.IsDaylightSavingTime(dstStart)
);
4

2 回答 2

8

I did a little bit of reflecting and I believe the inconsistency stems from how System.TimeZoneInfo+CachedData.GetCorrespondingKind(TimeZoneInfo timeZone) returns DateTimeKind.Local only in the case where timeZone == this.m_localTimeZone (ie, when the argument was the same instance as the TimeZoneInfo.Local property is based on).

In the case where you pass this other TimeZoneInfo instance you got from TimeZoneInfo.FindSystemTimeZoneById I expect that it returns DateTimeKind.Unspecified.

This will (probably among other things) affect System.TimeZoneInfo.IsDaylightSavingTime(DateTime dateTime) where, in the case where dateTime.Kind is local, it does a conversion between essentially TimeZoneInfo.Local and your TimeZoneInfo instance and bases the conversion on what GetCorrespondingKind says for the source and target timezones (the conversion returns the original datetime in the case where source and target are both local).

于 2013-02-13T21:57:20.820 回答
5

The difference in behavior when using the non-Local TimeZoneInfo is defined in the MSDN documentation.

The reason the first result is False is because the DateTime object you have created is technically ambiguous. There is no 2:00 am on March 10 2013 in the EST local time zone.

The doc states that the IsDaylightSavingTime() method is "affected by the relationship between the time zone represented by the TimeZoneInfo object and the Kind property of the dateTime parameter". The table in the Remarks section provides a description of each of the possible combinations.

When specifying a time zone by explicitly calling FindSystemTimeZoneById, the "Local Kind" DateTime argument is first converted from Local to the specified time zone. During this step, the ambiguous time is resolved into a legitimate value.

Try adding this to your test:

var dstStart = new DateTime(2013, 3, 10, 2, 0, 0, DateTimeKind.Local);

dstStart = dstStart.ToUniversalTime();
dstStart = TimeZoneInfo.ConvertTime(dstStart, TimeZoneInfo.Utc, myTimeZone);

The final value of dstStart becomes '3/10/2013 3:00:00 AM' (assuming your machine is still in EST). The same kind of conversion is happening within IsDaylightSavingTime() on the local kind parameter, which illustrates why it returns True.

The real surprise here is that the IsDaylightSavingTime() method doesn't raise an ArgumentException in either case. The documentation says that it will throw an exception when given an invalid DateTime parameter whose kind is DateTimeKind.Local, but clearly this does not happen.

Edit:

After looking through the source code and comments for the TimeZoneInfo class, I've come to the conclusion that the IsDaylightSavingTime() method is not meant to throw exceptions. This is a mistake in the documentation.

于 2013-05-08T04:52:14.893 回答