iosswifttimezonenstimezone

Trouble Initializing TimeZone from TimeZone.abbreviation()


I am storing user birth dates on my backend via storing a date component dictionary. It looks something like this:

{
    "day": 1,
    "month": 1,
    "year": 1970,
    "timeZone": "GMT"
}

To store this object, it grabs the user's birth day, month, and year from user input. The user time zone, however, is gathered via TimeZone.current.abbreviation().

Now, some of my user birthdate objects on my backend have their "timeZone" formatted as "CST", "BST", or "PDT". "timeZone"s that are formatted this way successfully initialize a TimeZone on the front end via let timeZone = TimeZone(abbreviation: "CST")!, let timeZone = TimeZone(abbreviation: "BST")!, or let timeZone = TimeZone(abbreviation: "PDT")!, respectively.

The problem is, other user birthdate objects on my backend have their "timeZone" formatted as "GMT+8". When trying to initialize "timeZone"s formatted like this via let timeZone = TimeZone(abbreviation: "GMT+8")!, the initialization returns nil. I also tried let timeZone = TimeZone(identifier: "GMT+8")!, but this returns nil as well.

Is there a way to initialize a TimeZone when it is formatted with respect to its offset to GMT as opposed to its unique abbreviation? I've seen a TimeZone initializer that is TimeZone(secondsFromGMT: Int). Could I simply take the 8 from "GMT+8" and multiply it by 3600 (the number of seconds in an hour) and pass this result to TimeZone(secondsFromGMT: Int)?


Solution

  • I ended up writing code that adapts my application to account for these unexpected fringe cases where a TimeZone's abbreviation is formatted like "GMT+8" rather than "SGT". I created an extension to TimeZone:

    extension TimeZone {
        static func timeZone(from string: String) -> TimeZone {
            //The string format passed into this function should always be similar to "GMT+8" or "GMT-3:30"
    
            if string.contains("±") {
                //This case should always be "GMT±00:00", or simply GMT
                return TimeZone(secondsFromGMT: 0)!
            } else {
    
                //If the string doesn't contain "±", then there should be some offset. We will split the string into timeZone components. "GMT+8" would split into ["GMT", "8"]. "GMT-3:30" would split int ["GMT","3","30"]
                let timeZoneComponents = string.components(separatedBy: CharacterSet(charactersIn: "+-:"))
                var isAheadOfGMT: Bool!
    
                //Check if the string contains "+". This will dictate if we add or subtract seconds from GMT
                if string.contains("+") {
                    isAheadOfGMT = true
                } else {
                    isAheadOfGMT = false
                }
    
                //Grab the second element in timeZoneElements. This represents the offset in hours
                let offsetInHours = Int(timeZoneComponents[1])!
    
                //Convert these hours into seconds
                var offsetInSeconds: Int!
                if isAheadOfGMT {
                    offsetInSeconds = offsetInHours * 3600
                } else {
                    offsetInSeconds = offsetInHours * -3600
                }
    
                //Check if there is a colon in the passed string. If it does, then there are additional minutes we need to account for
                if string.contains(":") {
                    let additionalMinutes = Int(timeZoneComponents[2])!
                    let additionalSeconds = additionalMinutes * 60
                    offsetInSeconds += additionalSeconds
                }
    
                //Create a TimeZone from this calculated offset in seconds
                let timeZoneFromOffset = TimeZone(secondsFromGMT: offsetInSeconds)!
    
                //Return this value
                return timeZoneFromOffset
            }
        }
    }
    

    It is used like so:

    let json: [String:String] = ["timeZone":"GMT+8"]
    let timeZone = json["timeZone"]
    let birthDate: BirthDate!
    if let timeZoneFromAbbrev = TimeZone(abbreviation: timeZone) {
        birthDate = BirthDate(day: birthDay, month: birthMonth, year: birthYear, timeZone: timeZoneFromAbbrev)
    } else {       
        let timeZoneFromOffset = TimeZone.timeZone(from: timeZone)
        print(timeZoneFromOffset.abbreviation())
        //Prints "GMT+8"
    
        birthDate = BirthDate(day: birthDay, month: birthMonth, year: birthYear, timeZone: timeZoneFromOffset)
    }
    

    My BirthDate class for context:

    class BirthDate {
        var day: Int
        var month: Int
        var year: Int
        var timeZone: TimeZone
    
        init(day: Int, month: Int, year: Int, timeZone: TimeZone) {
            self.day = day
            self.month = month
            self.year = year
            self.timeZone = timeZone
        }
    }
    

    Time zones are funny things to work with. If anybody sees issue with the TimeZone extension above, please let me know. I think I've accounted for all scenarios, but could be mistaken.