Mastodon

Probably Approximately Correct Location for iOS

If you’re writing an iOS app and you need to know the user’s current location, the answer is straightforward: use Core Location. That fires up device GPS (when available). Apple’s A-GPS combines this with things like local Wifi networks and IP addresses to work out the device’s location. All of this, of course, assuming that the user allows your app to know their location.

That’s great if you actually need nearly-exact location information. But what if you don’t care about that? What if you just want to know, say, what country the user is in, or even what continent? You could of course still use Core Location. But if your app doesn’t normally use location data in an obvious manner, users would reasonably be suspicious if you suddenly want to know where they are.

Here’s a sample use case, inspired by a StackOverflow post: Your app is for a country that includes customer support phone numbers in a multiple countries. You’d like to have a “call us” button that would initiate a phone call, and you’d like to automatically select a phone number in the same country as the user. But aside from that you never use location data.

Of course, there are third party APIs that you could use to work out a location from the device IP address. But it would be a lot simpler if you could just ask the device.

Time zones to the rescue for a change.

If all you need is a rough estimate of the user’s location, check out the local time zone on the device. If you’ve never looked too closely at time zones, you may be surprised at what they can tell you. You might also be surprised at how damn many zones there are.

The naive approach to time zones– one I’ve seen in way too much code– is that a time zone is defined by the difference between local time and UTC. That’s part of it, certainly. But in many places that number isn’t constant. It’s very common to have “summer” time (what the USA calls “daylight saving time”) where at specific dates they just go ahead and change the clocks. Not everyone has this, and those who do often disagree on the schedule. At a minimum then, a time zone is an offset from UTC combined with the rules for how that offset changes over the course of a year.

That makes for a lot of time zones. The de facto official standard used on iOS and many other systems is the time zone database maintained by IANA. If you’re on Mac OS X (or many other platforms) you can find the full collection in /usr/share/zoneinfo/ (on other Unices it might be /usr/local/etc/zoneinfo/ or somewhere else). The latest update has nearly 600 time zones.

The name of the local time zone might well be all you need to get an approximate location. They’re nicely descriptive, in most cases combining a continent with a the name of a city in the time zone. I live in the USA, in Colorado, so my local time zone is formally called America/Denver. A few other random examples include Europe/Berlin, Asia/Jakarta, and Africa/Dar_es_Salaam.

You can get the zone name with one line of code on iOS and Mac OS X:

NSString *name = [[NSTimeZone localTimeZone] name];

How about an actual location or country code?

You’d still need to include some kind of lookup table to get country codes or locations from the time zone name. This would probably list time zones where your app has relevant information.

IANA provides data that would allow other options. Their time zone data helpfully includes a file named zone.tab that maps time zone names to other details about the zone. This file is a simple tab-delimited file that includes the time zone name, an ISO 3166 two-letter country code, and a latitude/longitude coordinate for the city that defines the zone. For my home time zone this looks like:

US      +394421-1045903 America/Denver  Mountain Time

Those coordinates are the location of the Colorado state capitol building in Denver.

On Mac OS X (and again, other Unices) you can find this file at /usr/share/zoneinfo/zone.tab. Or you can get it from IANA to make sure you have the latest version.

Once you have this file, getting the country code or a CLLocation is just a matter of string parsing. Even better, the file is already on every iOS device and Mac, at the same path, available to your app.

Well, almost. There has to be a catch. The IANA data duplicates a number of time zones in the interest of backward compatibility with old names. But zone.tab only lists the current names. And… iOS and Mac OS X might well be using one of those names, since they still work just as well (the older names are not deprecated in the same way that API calls get deprecated, they just keep on working). If you’re on the west coast of the USA, your time zone name might come as US/Pacific even though the current official name is America/Los_Angeles. That’s fine and dandy until you try to look it up in zone.tab. Fortunately IANA also includes a file (named simply backward) that maps new names to old names, but unfortunately this file is not included with iOS. To get full coverage you’d have to get IANA’s data and include that file. The only other alternative would be the stupidly inefficient (though effective) approach of running through every known time zone looking for a match.

I wrote a category on NSTimeZone to do these lookups, which you can find at Github. I wrote the code to use zone.tab and backward exactly as IANA produces them, so that new versions could just be dropped in. The code caches the results for time zones by using Objective-C associated objects to attach them to time zone objects (even though some developers would say that this is a hack).

Caveats

I’d hope these are obvious, but you never know.

  • Getting the user’s location without permission is not a good idea. This technique doesn’t actually get their exact location (except just by chance, and you wouldn’t know if that were the case) so it’s probably OK. But consider the privacy aspects of using this idea before putting it into an app. Will your user care if you know what country they’re in even if you don’t know where they are in the country? They might.

  • It’s obviously not terribly accurate. The goal is to get a rough idea of the location, but “rough” could be off by hundreds of miles (or km). It’s probably in the same country as the user. In my case, the lat/long coordinate for America/Denver is 61.6 miles (99.1km) from my current location.

  • This approach believes the device time zone, since there’s no way to check on that without using Core Location. Most people will let the device set the time zone automatically, but it’s possible to override that on iOS. Go to Settings –> General –> Date & Time, turn off “Set Automatically” and then choose whatever time zone you want. At best, this approach is for low-stakes situations where it doesn’t matter much if you get an incorrect result.

  • The time zone location is the location of a city in the local time zone. This might not be the nearest city, it’s just one where the time of day is the same as the current location. Time zone boundaries are irregular enough that the nearest city in zone.tab could easily be in a different time zone.