Search code examples
iostimezonensdatenscalendar

Why compareDate from NSCalendar seems to need to set an UTC timeZone to work properly?


I create two dates like the following:

let date1 = stringToDate("2015-02-12 12:29:29")!
let date2 = stringToDate("2015-02-11 19:18:49")!

func stringToDate(var dateString: String) -> NSDate? {
    let dateFormatter = NSDateFormatter()
    dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
    dateFormatter.timeZone = NSTimeZone(name: "UTC")

    return dateFormatter.dateFromString(dateString)
}

As you can see, the two date are different and are not on the same day.

To test if two dates are on the same day, I use the following method:

func isSameDayThan(date1: NSDate, date2: NSDate) -> Bool {
    let calendar = NSCalendar.currentCalendar()
    calendar.timeZone = NSTimeZone(abbreviation: "GMT+10")!
    return calendar.compareDate(date1, toDate: date2, toUnitGranularity: .DayCalendarUnit) == .OrderedSame
}

There I don't precise any timeZone in the calendar. The local TimeZone of my device is set to GMT+10.

In that case, isSameDayThan(date1, date2)-> true

If I change the timeZone to something inferior or equal to GMT+04, then I get isSameDayThan(date1, date2)-> false.

What I don't understand is that the result is different depending on the timeZone, but I am comparing two NSDate() and NSDate() has nothing to do with time zone if I'm not wrong.


Solution

  • The timezone comes into play because you compare the dates with a granularity that is timezone dependent. So you are actually comparing against the local representation of the date. The point in time model that is often used to describe NSDate doesn't know about days and weeks. From a abstract standpoint (i.e. the point in time that is the same everywhere in the universe) it actually doesn't even know about seconds.

    Anyway, if you would compare with == you would obviously not need a timezone. That's the only comparison that is truly independent from the local representation. If two points in time are exactly the same they are equal. Easy.

    Everything beyond a straight == comparison has to be converted into local units. Not only you have to use the correct calendar, but you have to use the correct timezone as well.

    Luckily there are no calendars that have days that are shorter or longer than 24 hours. And there are no timezones that differ in seconds either. Because we know that, you can actually see if dates are within the same minute with an easy calculation. e.g.:

    Int(date1.timeIntervalSince1970 / 60) == Int(date2.timeIntervalSince1970 / 60)
    

    No calendar needed because we (currently) don't have calendars that have minutes that are not 60 seconds long. No timezone needed, because we don't have timezones with offsets that differ in the number of seconds.

    But we have a few timezones that have offsets that are only fractions of an hour. For example India Time Zone which has an offset of +05:30. So starting with hours the boundaries of the granularity units are timezone dependent.

    If you have two NSDates which are set to 9:25 and 9:35 UTC, they are in the same hour if you compare in any timezone that has an offset that does not differ in the number of minutes (e.g. 00 in +x:00). They are in the same hour in UTC, they are in the same hour in UTC+5:00 and UTC-5:00.

    But if you compare in India Time Zone these two dates are actually in different hours. Because 9:25 UTC in IST is 2:55, and 9:35 UTC is 3:05 in IST.

    In your example you are comparing to the granularity of the day. Which needs to take all timezones into account. But we can still ignore the calendar, because all calendars use days that are 24 hours long.

    But if you would compare to the granularity of a week, month or year you would have to take the calendar into account as well. There are calendars that have totally different months. Just because two dates are in the same month in gregorian calendars doesn't mean that they are in the same month in hebrew calendars.


    Yes, it's complicated. And that's the reason all date calculation appear so verbose. People often try to hide the complexity behind a fancy helper function. Which often leads to problems. So be aware of creation functions like isSameDay().
    Each time you compare a date you have to make the decision what timezone and calendar to use. If you rely on helper functions you will miss the one instance where you should actually compare against UTC instead of the local timezone.

    TL;DR: If you compare with granularity you should always set the correct calendar and the correct timezone.