Monday, March 25, 2013

Time travelling with dates and time zone conversions in .NET

Monday, March 25, 2013

Here’s a little magic trick in .NET: In ASafaWeb I have a facility to schedule scans at a certain time of day. Because I want to be all warm and fuzzy and user friendly, when people sign up to the service I ask for their time zone then whenever they schedule a scan they enter the time of day they’d like it to happen in their local time and I pull some magic tricks to make it happen.

The process has been working flawlessly since the middle of last year – until this weekend when I started getting error notifications that something was amiss. You see, the magic trick involved taking the time of day in the person’s local time zone, converting it to UTC then storing that in the database. This magic is made possible by using TimeZoneInfo.ConvertTimeToUtc and it works just like this:

var sourceTime = new DateTime(2013, 3, 31, 1, 0, 0);
var sourceTimeZone = TimeZoneInfo.FindSystemTimeZoneById("GMT Standard Time");
var utcTime = TimeZoneInfo.ConvertTimeToUtc(sourceTime, sourceTimeZone);

Create a source time, create a source time zone then convert it into UTC. This works perfectly except when it doesn’t and presents you with an ArgumentException like this one:

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.

An invalid time?! Since when is 1am on March 31 an invalid time? I mean it’s not like it’s November 31 or February 29 on a non-leap year, what an earth is wrong with this time?! And for that matter, how on earth do you get an error when converting GMT to UTC, isn’t it the same thing?!

The problem is that 1am on March 31 this year simply will not exist in the time zone above; people there will literally travel through time! The other problem is that GMT isn’t UTC – but it’s close.

Of course the issue is daylight saving and it can ping you both ways. When you wind the clock back you literally experience the same hour of the day twice. Any represented time that falls in that hour is ambiguous as you don’t know whether it was the first occurrence or the second occurrence so it could be anywhere up to an hour wrong. Then we have the problem above where that whole hour simply doesn’t exist which is why TimeZoneInfo won’t let us create it.

But hang on, doesn’t GMT align to UTC and not observe daylight saving anyway? Well firstly, there are semantic differences between the two and secondly, the “GMT Standard Time” you see above is the StandardName attribute of the TimeZoneInfo object the FindSystemTimeZoneById method ultimately returns. Here’s how the other attributes look:

DaylightName: GMT Daylight Time
DisplayName: (UTC) Dublin, Edinburgh, Lisbon, London
SupportsDaylightSavingTime: true

In short, this time zone does observe daylight saving and as you can see here, it’ll roll over at 01:00 on March 31 this year.

So what do you do? Catch the ArgumentException, add an hour an then you’ve got exactly the same time of day as you were originally after. At the time I wrote the code I didn’t consider this eventuality but fortunately the fix was simple and there’s now a nice little unit test to make sure I don’t screw it up again. Conditions like this are literally a 1 in 61,362 occurrence and in the scheme of things, ASafaWeb is a small app so this was really needle-in-a-haystack sort of stuff. We all know the mechanics of daylight saying insofar as clocks going forward and backwards and it’s something I should have gotten right the first time.

Oh, and in case you’re wondering, if you try converting 01:30 on October 27 when the UK goes out of daylight saving this year to UTC you’ll get… 01:30. You just don’t know whether it was the first one or the second one! For more enthralling time zone reading, check out Jon Skeet’s More fun with DateTime.

Tags:

comments powered by Disqus

Leaving comments is awesome, please do. All I ask is that you be nice and if in doubt, read Comments on troyhunt.com for guidance.