Lately I've been doing a good amount of work on a website feature that requires the output of a relative date and time. For example, a entry written on a user's wall (the website has social aspects) may be annoted with "written by John Smith about two hours ago." It is a relatively straight forward task to accomplish this - but I thought I'd write a few words about some pitfalls you may come across.
Even if you have only every written the most trivial of applications you are likely to have used the DateTime structure in .NET (if you've written .NET apps, that is). And you're very likely to have used DateTime.Now; Let's take a closer look at the DateTime structure. According to
Microsoft's documentation a DateTime "Represents an instant in time, typically expressed as a date and time of day." And, DateTime.Now "Gets a DateTime object that is set to the current date and time on this computer, expressed as the local time."
Cool. DateTime.Now is really handy for getting a handle on the current time, and we've all used it. But now I'm going to tell you that you shouldn't.
DateTime.Now returns a DateTime object that represents "the current date and time on this computer, expressed as the local time." As convenient as this may be, it's a potential source of trouble. "This computer" is the computer where the code executes. It could be your desktop machine, a development server, a production server, or a mobile device. The time on that machine depends on where the machine is located (which timezone it is in) and whether or not the machine is affected by daylight savings. So why is this a problem?
The problem is that if you cannot guarantee that all the machines in your infrastructure are all in the same timezone and are equally affected (or not affected) by daylight savings, using DateTime.Now in your code will potentially yield different timestamps on different machines even if the call to DateTime.Now was made at exactly the same time on the machines in question. 1pm in Oslo, Norway, on October 5th 2011 is not the same time as 1pm in London, UK, on the same date. "But that's just silly", I hear you say. "All our infrastructure is in the same data centre in one place." OK. Fine. That may very well be the case. But what about your users. Where are they? Are all of them in the same time zone as your servers? And is it likely that you'll never grow beyond having only local users and having only one data centre?
Even if your answer to the above questions is "we'll never scale beyond one data centre and all our users are in the same place and always will be" I think you should keep reading. It might just make your life a little simpler down the road. Just in case.
The problem with DateTime.Now is that it always represents the local time of the machine on which the code executes, and you don't really want to worry about where that machine is, because doing so makes life as a developer painful. What you want to do instead is use DateTime.UtcNow which returns an instance of DateTime representing the Coordinated Universal Time (UTC) of
now. UTC is the local time of the server
less any timezone differences and
less any daylight savings difference. If you only ever store and use UTC DateTimes then none of your DateTime comparisons will ever have to take into consideration any time differences caused by time zones or daylight savings.
The only thing you now have to worry about is the thing you
should worry about, which is displaying the correct date to your end users. You'll have to adjust for the timezone they're in because a date for an event at 12pm in London should be rendered as 1pm for a user in Oslo (they're always an hour ahead of London time).
Regardless of whether you use UTC or not, you'll always have to consider time zones and daylight savings when rendering dates for a user. Using UTC, that's all you'll have to worry about. If you use DateTime.Now, however, you'll also have to ensure that you know what the time offset of that DateTime instance is if you're going to compare it to another date, or if you're going to render it to a user. Pain in the arse (PITA).
I reckon that you should
always use UTC times in your applications regardless of what your user base might look like. It makes life simpler from the start, and if you ever need to support users across different timezones you'll be a step ahead.
So, as a rule, this is what I do:
- Always call DateTime.UtcNow and never DateTime.Now. (In fact, I don't use DateTime... I use DomainTime which is a wrapper I've created around DateTime. I'll write more about that in a later post).
- Always treat DateTime stored in a database as UTC. This means that when I read that DateTime out of the database I specify that it is a UTC DateTime using the DateTime.SpecifyKind() method. This is very important, because the machine will by default treat any DateTime as local time.
Note that if you're using an ORM such as NHibernate you need to tell the ORM that the date should be treated as UTC. With FluentNHibernate this is really simple:
Map(x => x.DateCreated).CustomType().Not.Nullable();
- Lastly, before displaying the date to the user, I apply the time difference between UTC and the user's location. There are several ways of doing this. For example you can have your users tell you which time zone they're in and you can apply the offset. Or, if your users are web users you can use Javascript's Date.getTimezoneOffset() method and apply the difference (in minutes) to your UTC date. Check out this StackOverflow question for some specific pitfalls of that particular method.