为什么 NSCalendar 的 compareDate 似乎需要设置 UTC 时区才能正常工作?

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

我创建两个日期如下:

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)
}

如您所见,这两个日期不同,不是同一天。

为了测试两个日期是否在同一天,我使用以下方法:

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
}

我没有在日历中精确指定任何时区。我设备的本地时区设置为 GMT+10。

在那种情况下,isSameDayThan(date1, date2)-> true

如果我将时区更改为低于或等于 GMT+04 的时区,则会得到 isSameDayThan(date1, date2)-> false。

我不明白的是,结果因时区而异,但我比较的是两个 NSDate() 和 NSDate() 如果我没记错的话,与时区无关。

这两个日期是 UTC 时区中的不同日期。但在 GMT+10 时区,它们都是同一天 - 2 月 12 日。

2015-02-12 12:29:29 UTC = 2015-02-12 22:29:29 UTC+10
2015-02-11 19:18:49 UTC = 2015-02-12 05:18:49 UTC+10

默认情况下,比较是在本地时区完成的,但您的日期对象是专门在 UTC 时区创建的。

如果您使用默认时区从字符串创建 NSDate 对象并使用默认时区比较它们,那么日期将有两个不同的日期。

时区发挥作用,因为您将日期与时区相关的粒度进行比较。所以你实际上是在与日期的本地表示进行比较。通常用于描述 NSDate 的时间点模型不知道天数和周数。从抽象的角度来看(即宇宙中到处都是相同的时间点),它实际上甚至不知道秒。

无论如何,如果您要与 == 进行比较,您显然不需要时区。这是唯一真正独立于本地表示的比较。如果两个时间点完全相同,则它们相等。简单。

超出直接 == 比较的所有内容都必须转换为本地单位。您不仅必须使用正确的日历,还必须使用正确的时区。

幸运的是,没有日历的天数短于或长于 24 小时。而且也没有以秒为单位的时区差异。因为我们知道,您实际上可以通过简单的计算来查看日期是否在同一分钟内。例如:

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

不需要日历,因为我们(目前)没有分钟不超过 60 秒的日历。不需要时区,因为我们没有偏移量在秒数上不同的时区。

但是我们有几个时区的时差只有几分之一小时。例如,印度时区的偏移量为 +05:30。因此,从小时开始,粒度单位的边界取决于时区。

如果你有两个设置为 9:25 和 9:35 UTC 的 NSDates,如果你在任何时区进行比较,并且偏移量在数字上没有差异,则它们处于同一小时分钟数(例如 00 in +x:00)。它们在 UTC 中处于同一小时,它们在 UTC+5:00 和 UTC-5:00 中处于同一小时。

但是如果你在印度时区比较这两个日期实际上是在不同的时间。因为 9:25 UTC 在 IST 中是 2:55,而 9:35 UTC 在 IST 中是 3:05。

在您的示例中,您正在与一天的粒度进行比较。这需要考虑所有时区。但是我们仍然可以忽略日历,因为所有日历都使用 24 小时制的天。

但是,如果您要比较周、月或年的粒度,则还必须考虑日历。有些日历的月份完全不同。仅仅因为两个日期在公历中处于同一月并不意味着它们在希伯来历中处于同一月。


是的,这很复杂。这就是所有日期计算显得如此冗长的原因。人们经常试图隐藏花哨的辅助函数背后的复杂性。这通常会导致问题。所以要注意像 isSameDay().
这样的创建函数 每次比较日期时,您都必须决定使用哪个时区和日历。如果您依赖辅助函数,您将错过一个实际应该与 UTC 而不是本地时区进行比较的实例。

TL;DR: 如果您比较粒度,您应该始终设置正确的日历和正确的时区。