Search code examples
iosswifttimezonenstimezone

How to correctly extend TimeZone in Swift to initialize a random element?


I'm trying to extend TimeZone in Swift so that I can initialize it to give me a TimeZone at random. I've landed on the below, but there's a couple of things that seem wrong.

   init?(random: Bool = false) {
    if random {
        let randomIdentifier = TimeZone.knownTimeZoneIdentifiers.randomElement()!
        self.init(identifier: String(randomIdentifier))
    } else {
        return nil
    }
}

Firstly, when I initialize a random element and print the result, it seems to have '(fixed)' added on to the end - what would be causing that please?

Secondly, is this even the correct approach to this problem? It seems odd that I'd even give the option to initialize with 'false', as other initializers offer all the other non-random initializations.

Any thoughts would be much appreciated!


Solution

  • Time zones are tricky because there is no definitive standard, even though it may seem like there is. I came across this problem a while back and produced this list from the Swift library:

    "HST": "Pacific/Honolulu", -10
    "AKST": "America/Juneau", -9
    "AKDT": "America/Juneau", -9
    "PDT": "America/Los_Angeles", -8
    "PST": "America/Los_Angeles", -8
    "MST": "America/Phoenix", -7
    "MDT": "America/Denver", -7
    "CDT": "America/Chicago", -6
    "CST": "America/Chicago", -6
    "PET": "America/Lima", -5
    "COT": "America/Bogota", -5
    "EDT": "America/New_York", -5
    "EST": "America/New_York", -5
    "AST": "America/Halifax", -4
    "ADT": "America/Halifax", -4
    "NST": "America/St_Johns", -330
    "NDT": "America/St_Johns", -330
    "CLST": "America/Santiago", -3
    "ART": "America/Argentina/Buenos_Aires", -3
    "CLT": "America/Santiago", -3
    "BRT": "America/Sao_Paulo", -2
    "BRST": "America/Sao_Paulo", -2
    "GMT": "GMT", 0
    "UTC": "UTC", 0
    "WET": "Europe/Lisbon", 0
    "BST": "Europe/London", 0
    "WEST": "Europe/Lisbon", 0
    "CEST": "Europe/Paris", +1
    "CET": "Europe/Paris", +1
    "WAT": "Africa/Lagos", +1
    "EEST": "Europe/Athens", +2
    "CAT": "Africa/Harare", +2
    "EET": "Europe/Athens", +2
    "MSK": "Europe/Moscow", +3
    "MSD": "Europe/Moscow", +3
    "TRT": "Europe/Istanbul", +3
    "EAT": "Africa/Addis_Ababa", +3
    "IRST": "Asia/Tehran", +330
    "GST": "Asia/Dubai", +4
    "PKT": "Asia/Karachi", +5
    "IST": "Asia/Kolkata", +530
    "BDT": "Asia/Dhaka", +6
    "ICT": "Asia/Bangkok", +7
    "WIT": "Asia/Jakarta", +7
    "SGT": "Asia/Singapore", +8
    "PHT": "Asia/Manila", +8
    "HKT": "Asia/Hong_Kong", +8
    "KST": "Asia/Seoul", +9
    "JST": "Asia/Tokyo”, +9
    "NZDT": "Pacific/Auckland", +13
    "NZST": "Pacific/Auckland", +13
    

    The duplicates show you just how "standard" this stuff is. You can curate your own list based on what you consider correct and produce a legitimate randomizer from that set.

    The problem with Jack's answer is that time zone identifiers in Swift are uneven; some zones are overrepresented and others underrepresented based on how many regions Swift considers popular in that zone. Australia has 12 identifiers even though the country only has 3 time zones, for instance. To save time, you could just randomly generate a number between -12 hours and +14 hours in seconds and return a time zone using seconds from GMT.

    extension TimeZone {
        static var random: TimeZone? {
            return TimeZone(secondsFromGMT: Int.random(in: -43200...50400))
        }
    }
    
    if let t = TimeZone.random {
        print(t)
    }
    

    If you want a neat division of time zones, just divide them into blocks and randomly select from that set. Just realize that there are half and quarter time zones too; not every time zone is a clean plus or minus 1 hour from its neighbor. If you do this approach, your array will have more than 24 integers, which should not worry you.