Scott Hanselman

Back to Basics: Daylight Savings Time bugs strike again with SetLastModified

November 06, 2011 Comment on this post [32] Posted in ASP.NET | Back to Basics | Learning .NET
Sponsored By

CC BY-NC 2.0 Creative Commons Clock Photo via Flickr ©Thomas Hawk No matter how well you know a topic, or a codebase, it's never to late (or early) to get nailed by a latest bug a over a half-decade old.

DasBlog, the ASP.NET 2 blog engine that powers this blog, is done. It's not dead, but it's done. It's very stable. We had some commits last year, and I committed a bug fix in February, but it's really well understood and very baked. My blog hasn't been down for traffic spike reasons in literally years as DasBlog scales nicely on a single machine.

It was 10:51pm PDT (that's Pacific Daylight Time) and I was writing a blog post about the clocks in my house, given that PST (that's Pacific Standard Time) was switching over soon. I wrote it up in Windows Live Writer, posted it to my blog, then hit to check it out.

Bam. 404.

What? 404? Nonsense. Refresh.


*heart in chest* Have I been hacked? What's going on? OK, to the logs!

l2    time    2011-11-06T05:36:31    code    1    message    Error:System.ArgumentOutOfRangeException: Specified argument was out of the range of valid values.
Parameter name: utcDate
at System.Web.HttpCacnhePolicy.UtcSetLastModified(DateTime utcDate)
at System.Web.HttpCachePolicy.SetLastModified(DateTime date)
at newtelligence.DasBlog.Web.Core.SiteUtilities.GetStatusNotModified(DateTime latest) in C:\dev\DasBlog\source\newtelligence.DasBlog.Web.Core\SiteUtilities.cs:line 1253
at newtelligence.DasBlog.Web.Core.SharedBasePage.NotModified(EntryCollection entryCollection) in C:\dev\DasBlog\source\newtelligence.DasBlog.Web.Core\SharedBasePage.cs:line 1182
at newtelligence.DasBlog.Web.Core.SharedBasePage.Page_Load(Object sender, EventArgs e) in C:\dev\DasBlog\source\newtelligence.DasBlog.Web.Core\SharedBasePage.cs:line 1213
at System.EventHandler.Invoke(Object sender, EventArgs e)
at System.Web.UI.Control.OnLoad(EventArgs e)
at System.Web.UI.Control.LoadRecursive()
at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) while processing

What's going on? Out of range? What's out of range. Ok, my site is down for the first time in years. I must have messed it up with my clock post. I'll delete that. OK, delete. Whew.




Logs, same error, now the file is a meg and growing, as this messages is happening of hundreds of times a minute. OK, to the code!

UtcSetLastModified is used for setting cache-specific HTTP headers and for controlling the ASP.NET page output cache. It lets me tell HTTP that something hasn't been modified since a certain time. I've got a utility that figures out which post was the last modified or most recently had comments modified, then I tell the home page, then the browser, so everyone can decide if there is fresh content or not.

public DateTime GetLatestModifedEntryDateTime(IBlogDataService dataService, EntryCollection entries)
//figure out if send a 304 Not Modified or not...
return latest //the lastTime anything interesting happened.

In the BasePage we ask ourselves, can we avoid work and give a 304?

//Can we get away with an "if-not-modified" header?
if (SiteUtilities.GetStatusNotModified(SiteUtilities.GetLatestModifedEntryDateTime(dataService, entryCollection)))

However, note that I'm have to call SetLastModified though. Seems that UtcSetLastModified is private. (Why?) When I call SetLastModified it does this:

public void SetLastModified(DateTime date)
DateTime utcDate = DateTimeUtil.ConvertToUniversalTime(date);

Um, OK. Lame. So that means I have to work in local time. I retrieve dates and convert them ToLocalTime().

At this point, you might say, Oh, I get it, he's called ToLocalTime() too many times and double converted his times. That's what I thought. However, after .NET 2 that is possible.

The value returned by the conversion is a DateTime whose Kind property always returns Local. Consequently, a valid result is returned even if ToLocalTime is applied repeatedly to the same DateTime.

But. We originally wrote DasBlog in .NET 1.1 first and MOVED it to .NET 2 some years later. I suspect that I'm actually counting on some incorrect behavior deep in own our (Clemens Vasters and mine) TimeZone and Data Access code that worked with that latent incorrect behavior (overconverting DateTimes to local time) and now that's not happening. And hasn't been happening for four years.

Hopefully you can see where this is going.

It seems a comment came in around 5:36am GMT or 10:36pm PDT which is 1:36am EST. That become the new Last Modified Date. At some point we an hour was added in conversion as PDT wasn't PST yet but EDT was EST.

Your brain exploded yet? Hate Daylight Saving Time? Ya, me too.

Anyway, that DateTime became 2:36am EST rather than 1:36am. Problem is, 2:36am EST is/was the future as 6:46 GMT hadn't happened yet.

A sloppy 5 year old bug that has been happening for an hour each year that was likely always there but counted on 10 year old framework code that was fixed 7 years ago. Got Unit Tests for DST? I don't.

My server is in the future, but actually not as far in the future as it usually is. My server in on the East Coast and it was 1:51am. However, the reasons my posts sometimes look like they are from the future, is I store everything in the neutral UTC/GMT zone, so it was 5:51am the next day on my file system.

Moral of the story?

I need to confirm that my server is on GMT time and that none of my storage code is affected my Daylight Saving Time.

Phrased differently, don't use DateTime.Now for ANY date calculations or to store anything. Use DateTime.UTCNow and be aware that some methods will freak out if you send them future dates, as they should. Avoid doing ANYTHING in local time until that last second when you show the DateTime to the user.

In my case, in the nine minutes it took to debug this, it resolved itself. The future became the present and the future last modified DateTime became valid. Is there a bug? There sure it, at least, there is for an hour, once a year. Now the real question is, do I fix it and possibly break something that works the other 8759 hours in a year. Hm, that IS still four 9's of uptime. (Ya, I know I need to fix it.)

"My code has no bugs, it runs exactly as it was written." - Some famous programmer

Until next year. ;)

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Hosting By
Hosted in an Azure App Service
November 06, 2011 11:50
I can't actually figure out what causes this *and only this* behaviour, the only thing I can think of is that a DateTime tagged with Local created before the switchover survived, in a cache or (raw, not SQL) serialization. Any other case of Kind switching should result in really obvious half of the times being an hour off (depending on if they are in the same TZ as now).
November 06, 2011 12:12
Good rule of thumb: use UTC/GMT for all calculations and storage, only convert to local time for display purposes (i.e. do what Scott is suggesting). A bit tricky to do in ASP.NET, so if you're not sure, here is an approach for Web Forms (should't be hard to do the same in MVC): It’s About Time: Localizing Time in ASP.NET Applications, or PDF version with correct figures and library for automatic to/from local time conversion based on browser's time zone. Have been using this approach for quite a while.
November 06, 2011 13:38
Maybe this was the time to excercise some TDD?
November 06, 2011 14:21
@Mladen: Actually, bugs like this are why I'm not convinced of TDD's utility: if you even knew this was a thing you should test then you didn't need the tests to tell you it's something you should be checking! Not to mention creating the tests for it is a pain in the ass (esp. in "classical" frameworks like ASP.NET 1.0!). Now I'm not saying don't write your tests first, let alone don't write tests(!), but catching weird edgecases like this is their *weak* point, not their strong point.
November 06, 2011 15:15
About tests, I have to agree with Simon. Tests would only really help with regression bugs in this case.
November 06, 2011 16:01
Yeah, it can be frustrating. Not just DST, but in general when your server is not in the same country / timezone as your development, or other related servers, etc..

That's why, in my current VPS server, I have followed Tatham Oddie's advice, and set the server local time to UTC, as in:
November 06, 2011 16:52
I agree with Alek, Mohamed and Tatham. UTC everywhere but really late in the presentation layer.

Where I work, we have a mainframe that is set to local time. Because of that, techs have to shut it down to manually switch it to DST and back. They also have to keep it offline for an hour in order to avoid timestamp issues when turning back the clock.
November 06, 2011 17:51
Scott, it's slightly worse than that: you have been using GMT and UTC interchangeably, but they aren't the same: during the summer, GMT is UTC+1, whereas UTC does not change for DST, only for leap seconds.
November 06, 2011 18:25
Or change the 404 message to read.

One hour of the year this happens: The local times and daylight saving time changing can affect the programming that lies behind the website pages. If you experience this problem, come back on the next hour. XX:00 and all will be well.

Thus proving that some programmers can be practical and not anal-retentive.
November 06, 2011 18:46
If you're starting a new project and are using at least .NET 3.5 and SQL Server 2008, by *far* the better answer (IMHO) is to use DateTimeOffset, which has a matching sql data type 'datetimeoffset'. The DateTimeKind helps a little in .NET 2.0, but you still get into lots of situations where it's set to Unspecified (for instance, reading a datetime column from the database).
Having a type that actually keeps the offset around on its own instead of doing it yourself (typically by keeping everything in UTC, so you're effectively keeping around an offset of 0) makes things so much simpler and prevents entire classes of bugs, especially around DST.

BCL type:

SQL type:

The BCL team's blog post discussing the new type:

The one caveat is that for others using WCF RIA Services, support for DateTimeOffset is coming in 1.0 SP2.
November 06, 2011 18:54

GMT never changes and is, to within fractions of a second I believe, the same as UTC. In the UK our 'daylight' time is British Summer Time (BST) which is GMT/UTC+1.
November 06, 2011 19:01
@Gareth: that's right - I had made a comment that said the same with a couple wikipedia links, but the blog engine yelled at me with "Your comment has been received and is under review for potential violation of site guidelines. Do not re-submit." :)
November 06, 2011 20:42
Scott ... FWIW ... it's Daylight Saving Time not "Savings".

DST <==> not DST bites in many ways. Example: Neudesic has not reset one of more servers and appears to have the auto reset turned off so replies are getting messed up ... worst case setting is replies during the 01:00 to 02:00 time frame where DST overlaps with not DST giving the impression that members have psychic ability and can answer questions before they have been asked.

Use your extra hour wisely!! B-)

regards ~~ gerry (lowry)
November 06, 2011 20:50
1) Why don't you use a service like Pingdom to check if your web site is up?
If you find out about downtime by manually checking the web -means you had more downtime than should have.

2) We keep all calculations in UTC too. That helps a lot.

3) Switching to Daylight Savings time every year is plain wrong.

4) My personal preference would be not only stop switch to/from Daylight Savings Time, but use UTC everywhere.
So instead of thinking about timezones, we would think that West Coast start their work day at 17:00 as opposing to East Coast starting work day at 14:00.
I understand that society is not ready for that yet. But may be in ~50 years that would happen.
November 06, 2011 21:03
Scott wrote, in part, "do I fix it and possibly break something that works the other 8759 hours in a year"?

Answer: no, you "fix it without breaking something that works the other [8783] 8759 hours in a [leap] year."

You've an extra day because 2012 is a leap year ... so now you have something to do for next February 29th!

The devil is in the details; the solution is simplistic: you try/catch System.ArgumentOutOfRangeException and in your catch exception check for the demon DST overlap and then decide how you can gracefully ignore it.

or, an even better and simpler solution that requires zero code changes ... during the TWO hour time frame that occurs during than annual DST ==> not DST switch, shut your site down for TWO hours of maintenance.

TIMTOWTDI =. there is more than one way to do it

regards, gerry (lowry)
November 06, 2011 22:30
Hah... I wrote about this subject one week ago, my post title is "Time travel issue..." ;) - check it:
November 07, 2011 0:37
Dennis - I use BasicState to check my site. Problem was, it WAS up. Just the default.aspx was down and was sending a 404, not a 500. I need to update my monitoring tools.
November 07, 2011 1:15
Yup, i got burned by this as well last night while working on some CSS... All of a sudden all of my styles stopped working so you can imagine that I initially thought it was something crazy i had added... But my brain told me... how could a few new lines of css completely screw up my entire css from loading... was frustrating for about 20 minutes or so. Nice detailed explanation of what's going on.
November 07, 2011 3:20
DasBlog sounds interesting. I clicked on the link that took me to and then on the 'Demo Site Blog' link (demo.dasblog.Info) => 404
So I went to the home page ( Thought I would have a look at the forums via the link at the top of the page =>404.
Don't worry too much about an error for one hour of the year, there are more prosaic problems.
I couldn't see a link to report site problems, hence the post in this forum.
November 07, 2011 4:46
@Mohamed: Setting local server time to GMT/UTC is not the same as using GMT/UTC in time calculations. These can be complementing options, but just relying on server settings, is prone to errors, e.g. if your sysadmin forgets to adjust time zone (which is even more likely in pre-prod environments). Using UTC/GMT in time calculations is a less error prone option (and, as I said, these are not mutually exclusive). One problem with having server's time zone set to non-local time, is that it makes more difficult to trace events (via event log or log files), since you would always have to adjust time, e.g. during troubleshooting. I deal with this problem quite frequently, so when I get a log file from some server outside of our organization that makes calls to our server and I have no idea in which time zone it is (in most cases it is in local time and our server is in GMT), finding logical sequence of events is always a pain.
November 07, 2011 12:54
Good plan to keep things in UTC/GMT. I think I need to check with a specialist database company now to determine if their product can return that to our web app instead of local.
November 07, 2011 21:17
Ukraine solved the problem.. They just don't do DST anymore.
November 07, 2011 22:08
Wow... sounds like a fun evening. Good heads up reminder with DateTime.UTCNow... I'll keep my eye on that one.
November 08, 2011 3:55
I got burned also double-encoding UTC times to the database as the Datetime.Kind property was auto-set to "Local" for every return from Entity Framework. I spoke to one of the EF team members at MIX 11 about my solution, to modify the return with a T4 template so every datetime comes back out of the DB as UTC by default and he said it's probably the best way to go for the moment.

My block post on retrieving DateTime as UTC by default from Entity Framework:

Would love to see a global config setting in ASP.NET that defaults EF to DateTime.Kind = UTC
November 08, 2011 9:03
Is there a reason you're not using Orchard?
November 08, 2011 13:38
Man, these types of HTTP errors are just the source of future heart attacks, I'm sure. 404 is just fine man. I got 500 range, because of a simple comparison between two strings (and they were the same to my eyes. Content = content). Just because I had forgotten to change both of them into lowercase.

Oh, also what you mentioned in this article, is my very reason to almost build everything from scratch.

November 09, 2011 0:08
I'd love to see an example of how to properly handle storage of UTC datetimes and presentation of local datetimes when using ASP.NET MVC and Entity Framework.

Anyone up for writing a short tutorial?
November 09, 2011 3:47
To be more specific with my ASP.NET MVC & EF request above, would you use a view model with properties for your date containing entity and additional properties for corresponding local times? Would you add methods to your view model to update UTC and local time fields for post and get operations, respectively, or would you do this in the controller?

I'm interested in the architecture people would suggest, and why.
June 03, 2012 18:22
Well, I wanted to understand presentation of local datetimes when using ASP.NET MVC and Entity Framework and also global settings in ASP.NET also
June 03, 2012 18:25
Guys, you are right about global settings but in MVC the default is always UTC. DateTime.UTCNOW is just a way also you can use other features more dig pls.
September 27, 2012 17:34
that kind of HTTP errors can be the source of future headaches.I just got yesterday 500 range, the reason was a simple comparison between two strings, so gotta be careful about the real reason of the problem..
November 02, 2012 2:33
Just a general question,
Lets say I have a mvc app hosted on a server in the East Coast and used all over the world.

But, the application never takes the local time from any of the client machines and never uses the local time from the client to store, update, cache, log ,etc.
The application always uses the server's local time, why would I have a problem ?

Comments are closed.

Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.