iOS, Swift

Time Zone fun with NSDate

The other day I had to build a booking feature where users can select a date to book a facility with a calendar.

It worked well enough, when the dates were clicked an NSDate was produced. That NSDate could then be sent to our Rails backend to store the booking.

I noticed however, that the NSDate received from the calendar gave a date from the UTC timezone.

Now, a slight segue about NSDates. According to the Apple docs

NSDate objects encapsulate a single point in time, independent of any particular calendrical system or time zone. Date objects are immutable, representing an invariant time interval relative to an absolute reference date (00:00:00 UTC on 1 January 2001).

Basically it means that an NSDate is a certain amount of integers (I assume its seconds but the Apple Docs don’t say anything about that) away from 00:00:00 UTC on 1 January 2001.

So this makes the idea of a timezone for NSDate pretty meaningless.

Thankfully, we can use NSDateFormatter to create string versions of NSDates. The NSDateFormatter has a property called timeZone that can be used to convert a NSDate to a String with the set timezone. The way I used it was as below

    func convertTimeZoneDateToString(date: String) -> String {
        let dateString = String(date)
        let dateFormatter = NSDateFormatter()
        dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss zzzz"
        let dateObj = dateFormatter.dateFromString(dateString)
        var formattedDateString = dateFormatter.stringFromDate(dateObj!)
        dateFormatter.dateFormat = "dd-MM-yy"
        dateFormatter.timeZone = NSTimeZone(name: "MYC")
        formattedDateString = dateFormatter.stringFromDate(dateObj!)
        return formattedDateString
    }

So this will return a date with the format “dd-MM-yy” and follows the Malaysian timezone.

So thats now all well and good, but then I got a feature request where we only want to limit booking dates to 3-5 days ahead.

It would be pretty easy to compare NSDates together, we could use something like this.

    func differenceBetweenDate(currentDate: NSDate, selectedDate: NSDate) -> Int {
        let userCalendar = NSCalendar.currentCalendar()
        let dayCalendarUnit: NSCalendarUnit = [.Day]
        let dayDifference = userCalendar.components(
            dayCalendarUnit,
            fromDate: currentDate,
            toDate: selectedDate,
            options: [])
        return dayDifference.day
    }

This will return an integer of the difference in days.

Sadly,since the calendar returns a NSDate in UTC timezone, it can cause some predictable bugs since the app is supposed to live in Malaysia.

Using the earlier method wont cut it as it returns a String. So how?

After giving it some thought, I decided on doing using the string from the convertTimeZoneDateToString method and use regex to parse out the day/month/year from it. From there we can use NSDateComponents to create NSDates with the correct timezone.

Below is the method used to parse the dates with regex

    func matchesForRegexInText(regex: String!, text: String!) -> [String] {
        
        do {
            let regex = try NSRegularExpression(pattern: regex, options: [])
            let nsString = text as NSString
            let results = regex.matchesInString(text,
                                                options: [], range: NSMakeRange(0, nsString.length))
            return results.map { nsString.substringWithRange($0.range)}
        } catch let error as NSError {
            print("invalid regex: \(error.localizedDescription)")
            return []
        }
    }

An example for using this method will be:

  let date = "22-03-16"
  let dateArray = matchesForRegexInText("[0-9][0-9]", text: date)
  //dateArray = ["22","03","16"]

So then we can use NSDateComponents to create a new date. An example is:

  let date = "22-03-16"
  let calendar = NSCalendar.currentCalendar()
  let components = NSDateComponents()
  let dateArray = matchesForRegexInText("[0-9][0-9]", text: date)
  components.day = Int(dateArray[0])!
  components.month = Int(dateArray[1])!
  components.year = Int(dateArray[2])!
     
  let actualDate = calendar.dateFromComponents(components)
  return actualDate!

So now you that you have the correct date with the correct timezone. So then you can use the differenceBetweenDate method to get the difference without any unpredictable bugs.

This may be a roundabout brittle way to do things. I’m open to a better way of doing it…please.

Leave a comment