iosswiftxcodecllocation

Using Swift how can I get a TimeZone from a CLLocation when User is Offline?


I am trying to get timeZones for locations on a global map that a user taps on. I have only the coordinates of those points. I am able to create a CLLocation and reverseGeocodeLocation to get the placemark and timeZone, however this only works when the user is connected to the internet.

Since timeZone boundaries are fixed, was wondering if there is anyway to get the timezone for a location when offline using the Swift libraries? I am not looking for third party libraries to achieve this.

The code for online query is:

let clLocation = CLLocation(latitude: location.latitude, longitude: location.longitude)
let geocoder = CLGeocoder()

geocoder.reverseGeocodeLocation(clLocation) { placemarks, error in

    if let error = error {
        print(error.localizedDescription)

    } else {
        if let placemarks = placemarks {
            if let timeZone = placemarks.first!.timeZone {
                self.timeZone = timeZone
            }
        }
    }
}

Solution

  • Basically there are two options:

    The easy way is to get the time zone from the location. The timestamp property of CLLocation displays the current time zone identifier when called with description(with:. A regex can extract the time zone string

    import CoreLocation
    
    let currentLocation = CLLocation(latitude: 47.36667, longitude: 8.55) // Location of Zurich
    let date = currentLocation.timestamp.description(with: Locale(identifier: "en_US_POSIX"))
    let timeRange = date.firstRange(of: /(A|P)M\s/)
    let timeZone = date[timeRange!.upperBound...]
    print(String(timeZone)) // Central European Summer Time
    

    Otherwise you need a lookup table like this one, a list of time zones and their location in a text format (here CSV)

    Create a helper struct

    struct TimeZoneData {
        let name: String
        let location: CLLocation
    }
    

    map the CSV list to instances of the struct and calculate the nearest distance from the current location. timeZoneCSV represents the CSV text

    import CoreLocation
    
    let currentLocation = CLLocation(latitude: 47.36667, longitude: 8.55) // Location of Zurich
    let nearestTimeZone = timeZoneCSV
        .components(separatedBy: .newlines)
        .map { line in
            let fields = line.components(separatedBy: ";")
            return TimeZoneData(name: fields[0], location: CLLocation(latitude: Double(fields[1])!, longitude: Double(fields[2])!))
        }
        .sorted(by: {$0.location.distance(from: currentLocation) < $1.location.distance(from: currentLocation)}).first!
    
    print(nearestTimeZone) // "Europe/Zurich
    

    Note that the first option displays the long version of the time zone identifier and the second option displays the name of the time zone.

    Edit:

    There is a library APTimeZones written in Objective-C. The code is straightforward and can be used with a bridging header or can be even easily converted to Swift.

    Nevertheless all the mentioned ways to get the time zone are not reliable. For a more precise result you have to use shapes rather than coordinates, provided in TimeZone-Boundary-Builder