A Fast, Accurate Way to Handle Dates from Servers
The Importance of Showing Accurate Dates and Times
In FlightTrack, it is critical that we show accurate dates and times to all of our users, no matter what the cost. If, for some reason, we told a user that their flight was going to take off at 10:00am when it was actually taking off at 9:00am, they could miss their flight and be charged a painful airline rebooking fee.
The importance of showing accurate dates and times extends beyond just flight tracking apps. Any application that needs to display dates and times to its users should consider the ramifications of possibly showing inaccurate information, especially those that download dates from a server.
Downloading and Displaying Dates and Times from Servers
Take the following JSON data that a server API could send to a mobile client:
This is a perfectly valid ISO 8601 date string that references an exact point in time. While at first glance it may look like it contains time zone information, don’t be fooled.
-06:00 refers to the UTC offset of that date string. It does not refer to an actual time zone. That date may be associated with an event in the
America/Mexico_City time zones (among others). What is the difference? For one, two similar time zones may have very different rules concerning daylight savings time (DST).
Daylight savings rules can change often. In 2007, the Unites States extended daylight savings time due to the Energy Policy Act of 2005. And things can get even more complicated when daylight savings time is not involved. In 2011, for example, the Pacific island of Samoa jumped forward in time by an entire day.
All of this time zone information (both current and historical) is contained in the tz database, which ships with most operating systems. So, if I wanted to display a time like “10:00am CDT” to a user using the
America/Chicago timezone, I could do the following in Objective-C (which makes use of the system tz database):
Clients Can Show Inaccurate Dates and Times
What happens if the system has an old version of the tz database? Well, your apps might start showing incorrect dates and times to your users. In addition, the time zone abbreviation strings might be incorrect. Thus, the above code could incorrectly show “11:00am CST” instead of “10:00am CDT”. In the world of mobile apps, having an outdated tz database might not be as uncommon as you think. The tz database is updated with the operating system. Some users don’t update their devices. Others can’t due to carrier lockdown or because they are using an older device model that can’t run the latest and greatest version of its operating system. Unfortunately, these devices can very possibly have outdated tz databases and thus display incorrect dates and times to your users.
So what can we do to fix this? Your gut reaction might be to have your app download the latest version of the tz database whenever it changes. This is certainly something you could do, but it has interesting quirks. As far as I can tell, there is no way in Java or Objective-C to take a downloaded tz database and tell the system frameworks to start using it. This means that you would have to write (or find) code to understand and parse the tz database and then use that information to create explicit
NSTimeZone objects using offsets from UTC.
A slightly different approach (that we opted for) was to send time zone information along with each date from our server:
While quite a bit more verbose than just the ISO 8601 date string, this extra data gives us the ability to display the proper date, time, and time zone strings no matter how old and inaccurate the tz database. This would then change our Objective-C code to the following:
This code is not that much more complicated that the original code. The biggest change for our application was replacing many of our NSDate objects with a different object that stores the extra time zone information associated with each date. This works great when the dates and times are associated with a particular time zone (in our application, the take off and landing dates of flights are associated with the airport where that event takes place). This solution doesn’t work as well if you have to support arbitrary time zones with each and every date. In this scenario, you may be better off opting for the “parse an updated version of the tz database” solution.
Parsing Dates Can Be Slow
SimpleDateFormat are slow. Really slow. In FlightTrack, we have to download many dates from our data providers. In fact, a single request to get data for 100 flights can result in the application downloading and parsing as many as 1,400 date strings. Parsing these ISO 8601 date strings using
NSDateFormatter takes a non-trivial amount of time on a mobile device (approximately 350ms on my iPhone 4S).
Now we could use a faster date parser written in C (especially since we only have to support the ISO 8601 format). However, since we are already adding so much extra information to each date in the response, why not add a little bit more to make it absolutely trivial for apps to parse this date? We can do this by also sending integers representing the number of seconds since the unix epoch.1
This gives us the following data structure:
At this point, all you have to do to parse the date is call
[NSDate dateWithTimeIntervalSince1970:unixTime], which takes effectively no processor time. If all your clients start using the
unixTime property to construct their date objects, the server doesn’t even need to send the ISO 8601 date string anymore. However, servers are free to send one data type, the other, or even both (this behavior could depend on a request parameter sent by the client). I try not to worry about the increase in response size since servers should all be sending their JSON strings gzipped, anyway.
Depending on the range of dates your application supports this may or may not be an easy thing to do. For example, if you have dates ranging from 200 million BC to 200 million AD, this can be tricky and you have to make sure that you use types that can hold this range without overflowing. ↩