Thursday, October 27, 2016

What is time?

Тhe intricacies of System.DateTime - а .NET essay in three parts

Part 1: You're doing it wrong - don't use DateTime.Now

I was tempted to make this the shortest blog post I've ever written: Always use DateTime.UtcNow instead of DateTime.Now! It would be a magnificently prosaic follow-up to a deep-sounding title.
The takeaway point is really that simple. However, developers have inquiring minds and would demand to know more. Therefore, I did the exact opposite - wrote way more than anyone would want to read on the topic :-) so, if you're only after the reasoning behind the above recommendation - head straight to paragraph 4. If, on the other hand, you'd like to generally enlighten yourself about how dates are handled by humans in general and in .NET programming in particular then pour yourself a cup of coffee and plough straight through.

First, let's get one thing out of the way - dates are awkward. I don't mean data types representing dates - just dates in general. We live on an awkwardly shaped object that revolves around its parent star at slightly irregular intervals and to make matters worse - it's populated by beings obsessed with measuring time precisely*; we have roughly 365 1/4 days to the year and even if we ignore the "roughly 1/4" part of it we're still stuck with 365, which is an unfortunate number - we can't have months of equal numbers of days (unless having five months of 73 days sounds appealing to you) yet 360 is so close. If only we had 360 days in the year we would have had neat half years, third years, quarter years, 6-th years, 8-th years, 10-th, 15-th, 24-th... and 12 neat months of 30 days each. Alas, it was not meant to be - this, in my opinion, is one of the strongest arguments against a benevolent creator of the universe - what kind of god would do this to us? Apparently he didn't design this place with developers in mind.

Before developers entered the picture though there were all kinds of folk who liked order and measuring things precisely and this bugged the hell out of all of them. How could we divide time in sensible small-scale units for everyday use that stack nicely into larger units and eventually - into the cycles of our home planet? Civilizations experimented with all kinds of arrangements until we settled on the present one with weeks and months. We owe the concept of the month to the romans, but theirs were initially quite different than ours - they had just 10, with the winter not counting in the calendar at all and had three kinda-sorta-weeks of uneven length per month; the famous Mayan calendar that hit its own y2k** problem in 2012 on the other hand had two parallel cycles of 260 and 365 days - try converting that to System.DateTime! Our modern day calendar may be somewhat saner but it still has its leap seconds, daylight saving time and other weirdness.

I hope that you're now convinced that dates are awkward and messy, which may or may not alleviate the stress you're experiencing when troubleshooting programming issues related to dates. Indeed, some of these issues are due to the convoluted nature of our calendar and cannot be helped; others though can be easily resolved with this little hack - use UtcNow instead of Now. What's the difference? Well, DateTime.Now returns the current time in whatever timezone the computer it's executing on runs while UtcNow converts the current time to the UTC time zone. "Why would I care about UTC time zone, I live in San Francisco", some of you might be thinking. It doesn't matter where you're based, where your customers are or where the server is located - UTC is a fine choice of universal time that will be the same on every computer where the code might be executed. Why is this important, then? Consider the following scenario:
You are building a web app that allows users to manage documents. You have a functionality that shows users the documents they've opened in the last day. You might have a stored procedure like this one:
SELECT * FROM Documents WHERE DateOpened > DateAdd(DAY, -1, getutcdate()) 

DateOpened < getutcdate()
Nice and simple. And when you open a document, you run:
UPDATE Documents SET DateOpened = @d WHERE DocumentId=@id

and you pass DateTime.Now as the value of the @d parameter. You've done similar things, haven't you, retrieving the data for the last X days or hours? Maybe not just like that, maybe you use Entity Framework instead of stored procedures - it doesn't matter, the concept is the same. The bug is the same, too. See, if your SQL server resides in a different timezone than your webserver this is not going to work; in the best case it will show old documents (when the SQL server is behind), or it might not work at all if you're showing docs for the last hour instead of day.
In case you're thinking that all your code is going to run on the same server I regret to inform you that you're still doing it wrong and are just desperately trying to find an excuse to not change your wicked ways. The situations when it's safe to use DateTime.Now are a tiny minority and in most cases you can't be sure that you're in one of them at the time of writing the code and no, you're not going to come back and change it if the requirements change. Just quit your mumbling and get into the habit of always using DateTime.UtcNow instead of DateTime.Now. You can even go further. Do you have a coding standards document in your company? If you do - go ahead and edit it right now, mandating that all developers do it. If anyone objects - tell them that they are wrong; let Sheldon Cooper be your inspiration for dealing with ignoramuses who have yet to see the light of day. 

* - contrary to Aristotle's advice that "it is the mark of an educated mind to rest satisfied with the degree of precision which the nature of the subject admits"
** - many think of the y2k bug as an embarrassment for software developers; "duh", the general population thinks, "didn't these programmers figure out that the year won't fit in two digits, aren't they supposed to be like super smart - this sounds like a pretty dumb mistake to me". However, it's actually a compliment - the programs that the industry pioneers cobbled together to fix some problem there and then ended up solving millions of other problems for decades on end. The y2k panic is a hats off for software built to last.

No comments: