Doing it Right

Recently I decided to make Sunrise free - a decision I discussed here. Unsurprisingly, this drastically expanded the app’s exposure. Of course, more users means more potential for bugs to be uncovered. Sure enough, a new customer reported to me that in his location, the sun rise and set times were all exactly two hours off.

My gut told me this was a time zone issue, and indeed my gut was correct. This customer is in Hokkaido, Japan. Hokkaido is in the Asia/Tokyo time zone, which is GMT+9. However, the library I use for getting the time zone for a location (APTimeZones) was returning Asia/Sakhalin, which is GMT+11. APTimeZones uses a relatively naive algorithm to determine the time zone a location is within. Basically it maintains a collection of locations and their respective time zones, then finds which the query location is closest to. The results can be improved somewhat by providing the country code your location is within - and, indeed, that would have fixed this particular customer’s problem.

But, I decided that wasn’t good enough. There are still going to be locations where this algorithm fails. In fact, checking the APTimeZones issues reveals at least a couple other locations which fail. The only way to be absolutely certain you get the correct time zone for a location is to use the time zone shapes, not a distance-to-points algorithm (even that is not perfect as localities may change their time zone or not have well defined shapes).

Fairly recent time zone shape files are available from a handful of locations. There also exist libraries to interpret these files and do the appropriate sort of queries on them to accomplish my goals. There are even a few open source libraries for doing just this thing, though most of them are either in a scripting language like Python or are released under licenses I don’t feel comfortable using in an iOS app (I’m looking at you, LGPL). Going this route it appears I would have to implement it myself, which is not necessarily a bad thing.

An alternative is to use an online service to gather this data. There are a handful of choices for this, including Yahoo, Google, and others. Google’s offering allows 2,500 requests per 24 hour period for free, while the others either have no free usage level or allow free access to only certain classes of developers. If I went this route, Google was the obvious choice.

Sunrise is almost entirely an offline app. All of the calculations are done locally on your phone. I liked the idea of keeping it that way, which made the first option seem very attractive. I always enjoy the challenge of implementing something new. Plus, I could almost certainly isolate the functionality into a library and release it as open source. I am happy to contribute to the community any time I can.

But, the one time Sunrise does use a network connection (when adding new locations) also happens to be exactly when it needs to perform a time zone lookup. That code is already arranged to run asynchronously, so adding another step to the execution chain would be relatively trivial. And while performing an HTTP request is nowhere near as challenging as doing geospatial queries, I haven’t yet had the opportunity to work with NSURLSession at all. The simple requirements of using Google’s Time Zone API would be a nice, very basic introduction to NSURLSession.

With all of that in mind I decided that, for now, I would go with Google’s Time Zone API. The API involves a single URL request per location, and returns query results as JSON data, which can be dealt with trivially on iOS. Getting it integrated was fairly easy, and the benefits were immediately clear when I tested against a few locations APTimeZones has trouble with. In the off chance that querying Google fails, Sunrise will fall back on APTimeZones. With that, I could have easily called it a day.

But, I didn’t. While Google’s limit on calls to the Time Zone API are very generous, and I have difficulty imagining Sunrise going over it, I figured minimizing calls to it could only be a good thing. Dealing with this for saved locations is trivial, I just store the time zone name along with the location. But Sunrise also keeps track of the phone’s current location (if you allow it to). For a number of reasons, including user settings, the phone’s reported time zone does not necessarily reflect the time zone the phone is currently in. For this reason, whenever the phone undergoes a significant location change it performs a time zone lookup. This also happens every time it is launched.

So I decided to cache time zone location lookups. I could have easily done this with a plist file, but decided to go back to my old friend SQLite. Sunrise now creates a simple SQLite database in the application’s caches folder. Each entry consists of a latitude, longitude, and the time zone name for that point. Whenever a time zone is requested for a location, the cache is consulted first. I wanted to avoid the problems APTimeZones encounters, so I kept the cache resolution fairly small - 1 km. Dave Addey posted on his blog a great SQLite distance function that is very useful for just this sort of query. I use FMDB which has a nice blocks-based facility for adding functions to SQLite, so I adapted Dave’s code to work with this API. The result is a fast, lightweight store for the time zones cache.

These changes will probably only affect a small number of people. APTimeZones does return the correct time zone in the vast majority of cases. But it would be a disservice to my customers to not handle those cases where APTimeZones fails. These changes, which have been submitted for review to the App Store, make sure that Sunrise is Doing It Right. Regardless of any financial benefit Sunrise might bring me, this knowledge is plenty reward for me.