There’s an old adage: if you don’t hate time zones, you aren’t a real programmer. Time zones are a famous source of software bugs. iPhones have had problems in the past with alarms not going off at the right time when switching to and from Daylight Saving time. I personally have fixed several bugs with displayed date/time data being off by several hours because people try to handle time zones in ways they don’t have to.
And now I’d like to explain to you how to avoid such bugs in your applications.
Never Store Local Time
I once worked on an ASP.NET web app that did event tracking. At first, when I saved the date and time of an event, I stored the value of
DateTime.Now to a
datetime column in SQL Server. It worked great, until I had events happening in other time zones, and users looking at them from yet other time zones. I could have just kept storing
DateTime.Now and converted that from my time zone to whatever time zone I needed to display in – there’s an API for that in .NET. But
DateTime.Now was based on the set time zone of the server my code ran on, and I didn’t want my app to break if it had to run in a data center in a different location.
As it turns out, it’s pretty standard practice to store times in UTC. So I updated my code to assume the database had all its event data in UTC and convert as needed for display, and then ran an update query to convert all the historical event data to UTC. That was a bit of a pain – I had to move all the timestamps by different times depending on whether they fell within DST or not – but it could have been worse.
The lesson I learned was never to store local time. If you have to store a moment in time, do it in UTC. In C#,
DateTime.UtcNow is your friend. Even better is to store it as a UNIX timestamp, which is the number of seconds since it was midnight on January 1, 1970 UTC. Java, Obj-C/Swift and .NET can all deal with this pretty easily. This is nice because it presents no illusions about what you’re storing. A timestamp at its base level is just an amount of time since another point in time.
Never do your own temporal math
I say “temporal math” so it sounds complicated and scary. What I mean is adding or subtracting hours from UTC on your own.
If you find yourself adding or subtracting hours from a time to display it, you’re doing it wrong. There are tons of edge cases, and there are tons of really smart people who have handled them for you (and even they make mistakes sometimes – remember the iPhone bug?). Let the system you’re working with handle it for you if at all possible.
Here’s how to read and display a date in Objective-C. Note that
NSDateFormatter has a
timeZone property, but you almost certainly don’t need to set it – it defaults to the device’s current time zone.
NSTimeInterval timestamp = [MyDataSource getTimestamp];
NSDate *date = [NSDate dateWithTimeIntervalSince1970:timestamp];
NSDateFormatter *format = [[NSDateFormatter alloc]init];
format.dateStyle = NSDateFormatterStyleLong;
format.timeStyle = NSDateFormatterStyleLong;
NSString *dateString = [format stringFromDate:date];
self.timeLabel.text = dateString;
Note that you don’t have to do any kind of conversion on the date you give the formatter – it just assumes it’s getting a UTC date and converts it for display.
So, in summary: always store dates in UTC, and try to only deal with time zones when displaying dates. And try to use system APIs as much as possible. If you’re working with Apple tech, you should also be aware of
NSDateComponents. There’s even a great NSHipster article!