Reputation: 586
When I run the code for this specific value of dt, an exception is thrown when I call the ConvertTimeToUtc Method. My local Machine timeZoneId is "GMT Standard Time"
var tzi = TimeZoneInfo.FindSystemTimeZoneById("Central Standard Time");
var dt = new DateTime(1995, 4, 2, 2, 55, 0);
var t = TimeZoneInfo.ConvertTimeToUtc(dt, tzi);
The exception is:
System.ArgumentException was unhandled
Message="The supplied DateTime represents an invalid time. For example, when the clock is adjusted forward, any time in the period that is skipped is invalid.\r\nParameter
Upvotes: 29
Views: 9955
Reputation: 9461
There are situations where you have to process an invalid time and you cannot throw an error and ask a user to fix it -- you [the programmer] need to just handle it.
If you can safely apply the time change (e.g., a lost hour) to an invalid time, then you can use this solution:
static void Main(string[] args)
{
// I hard-coded this to Mountain Time. It works for other timezones too.
TimeZoneInfo tzInfo = TimeZoneInfo.FindSystemTimeZoneById("Mountain Standard Time");
string sDate = args[0]; // get input time (string)
DateTime dtDate = DateTime.Parse(sDate); // convert to DateTime
if (tzInfo.IsInvalidTime(dtDate)) {
// If the input time is invalid, use the offset before and after time changes
// to calculate how many minutes will be lost when time does change. Then add
// that many minutes to 'dtDate'.
TimeSpan offsetYesterday = tzInfo.GetUtcOffset(dtDate.AddDays(-1)); // before time change
TimeSpan offsetTomorrow = tzInfo.GetUtcOffset(dtDate.AddDays(1)); // after time change
double minOffset = offsetTomorrow.TotalMinutes - offsetYesterday.TotalMinutes;
dtDate = dtDate.AddMinutes(minOffset);
}
string utcDate = TimeZoneInfo.ConvertTimeToUtc(dtDate, tzInfo).ToString();
Console.WriteLine("Converting {0} to UTC = {1}", sDate, utcDate);
}
Here's the output for six different input times, including two invalid times. The code adjusts the invalid times by adding the lost minutes to make them become the time they should have been if the user had remembered the time change:
Converting 03-12-2023 01:00:00 to UTC = 3/12/2023 8:00:00 AM
Converting 03-12-2023 01:30:00 to UTC = 3/12/2023 8:30:00 AM
Converting 03-12-2023 02:00:00 to UTC = 3/12/2023 9:00:00 AM // was invalid, added 60 min
Converting 03-12-2023 02:30:00 to UTC = 3/12/2023 9:30:00 AM // was invalid, added 60 min
Converting 03-12-2023 03:00:00 to UTC = 3/12/2023 9:00:00 AM
Converting 03-12-2023 03:30:00 to UTC = 3/12/2023 9:30:00 AM
Upvotes: 3
Reputation: 150148
One can test whether the time in question is invalid using
TimeZoneInfo.IsInvalidTime
or if it is ambiguous using
TimeZoneInfo.IsAmbiguousTime
If it is ambiguous, an array of times that could apply can be retrieved from
TimeZoneInfo GetAmbiguousTimeOffsets
In the case of an interactive application, the user can be prompted for clarification.
The BCL team wrote a good blog about the topic
Upvotes: 20
Reputation: 1502806
Yes, that's absolutely right. 2:55am didn't exist in Central Standard Time on April 4th 1995, as the wall clock skipped from 2am to 3am due to daylight saving transitions. The exception seems reasonably clear about this. (The use of "standard" is somewhat tricky here; it would make more sense to call it "Central Time" which would include "Central Standard Time" and "Central Daylight Time" but that's a different matter. Heck, I'd prefer Olson identifiers myself...)
At other times, a local time may be ambiguous - if the clock goes back an hour (or more!) then a local time may occur twice.
The question is: how do you want your code to behave in this situation?
It's somewhat unfortunate that the exception is just ArgumentException
- in Noda Time we're going to have an exception for this exact case, so that it's easier to spot and catch. (We'll also have something like IsAmbiguous and IsSkipped so you can check without catching an exception.)
But the basic message is that this isn't a bug in the BCL - it's deliberate.
Upvotes: 26