Calendar.current.isDate等于粒度:Calendar.Component.Day具有奇怪的匹配行为

时间:2019-01-16 18:12:35

标签: swift

下面的代码最初在matchingDate字段中没有“ Z”,并显示如下结果:

true comparing 2017-08-28 13:06:54 +0000 to matching 2017-08-28 05:00:00 +0000
false comparing 2017-08-28 04:22:42 +0000 to matching 2017-08-28 05:00:00 +0000
false comparing 2017-08-28 00:00:01 +0000 to matching 2017-08-28 05:00:00 +0000
true comparing 2017-08-28 20:24:00 +0000 to matching 2017-08-28 05:00:00 +0000

所以我认为我的问题是UTC。但是,更正后(如下所示),它会打印:

false comparing 2017-08-28 13:06:54 +0000 to matching 2017-08-28 00:00:00 +0000
true comparing 2017-08-28 04:22:42 +0000 to matching 2017-08-28 00:00:00 +0000
true comparing 2017-08-28 00:00:01 +0000 to matching 2017-08-28 00:00:00 +0000
false comparing 2017-08-28 20:24:00 +0000 to matching 2017-08-28 00:00:00 +0000

这是意外的(所有4个都应匹配)。怎么了?

import Foundation

extension Array {
    // src: https://stackoverflow.com/questions/54217704/cannot-use-mutating-member-because-append#comment-95266763
    func appending<S: Sequence>(contentsOf newElements: S) -> Array where S.Element == Element {
        return self + Array(newElements)
    }
} 

let dfCandidate = DateFormatter()
dfCandidate.dateFormat = "yyyy-MM-dd HH:mm:ssZ"

let dfMatching = DateFormatter()
dfMatching.dateFormat = "yyyy-MM-ddZ"
guard let matchingDate = dfMatching.date(from: "2017-08-28Z") else {
    preconditionFailure()
}

let dates1 = [
    "2017-08-28 13:06:54",
    "2017-08-28 04:22:42"
]
let dates2 = [
    "2017-08-28 00:00:01",
    "2017-08-28 20:24:00"
]

let matchingDates: [Date] = dates1
.map { candidateDate in
      guard let date = dfCandidate.date(from: candidateDate + "Z") else {
          return nil
      }

      let isInDate = Calendar.current.isDate(date, equalTo: matchingDate, toGranularity: Calendar.Component.day)

      print("\(isInDate) comparing \(String(describing: date)) to matching \(String(describing: matchingDate))")

      return isInDate ? date : nil
     }
.appending(contentsOf: dates2.map { candidateDate in
                                   guard let date = dfCandidate.date(from: candidateDate + "Z") else {
                                       return nil
                                   }

                                   let isInDate = Calendar.current.isDate(date, equalTo: matchingDate, toGranularity: Calendar.Component.day)

                                   print("\(isInDate) comparing \(String(describing: date)) to matching \(String(describing: matchingDate))")

                                   return isInDate ? date : nil
                                  })
.compactMap { $0 }

print(matchingDates)

(注意:迅速4.2.1)

1 个答案:

答案 0 :(得分:2)

问题在于您正在混合时区。

您在日期格式和您解析的日期字符串中使用Z意味着这些日期字符串被视为UTC时区。那可能是您想要的,也可能不是。

您使用Calendar.current.isDate意味着这两个日期是使用您自己的当前时区而非UTC时区进行比较的。因此,根据您的住所以及给定日期与午夜之间的距离,这两个日期可能在一个时区中是同一天,而在另一个时区中是两天。

您的代码可能完全正确,因为一旦您了解“奇怪”的结果实际上在给定的时区基础上是正确的。

您需要确定日期/时间字符串代表哪个时区。然后,您需要确定要与哪个时区进行比较。

示例(针对居住在美国东部,现在是UTC-5的人)

您解析字符串2017-08-28 13:06:54Z。那是UTC时区的时间。您可以在2017-08-28 13:06:54 +0000的输出中看到这一点。

您还解析了2017-08-28Z。这被视为UTC午夜时间。打印此Date将显示2017-08-28 00:00:00 +0000

在UTC时间中,这两个日期在同一天。

但是,当您使用Calendar.current时,它会以您自己的本地时间(在本示例中为UTC-5)查看日期。

这意味着本地时间的第一个日期为2017-08-28 08:06:54 -0500,本地时间的第二个日期为2017-08-27 19:00:00 -0500

在当地时间,这两个日期不在同一天。

解决方案:

如果您希望将所有日期都视为UTC日期,并且想要比较UTC时区中的每组日期,则应该将代码更新为以下内容:

let utc = TimeZone(secondsFromGMT: 0)!

let dfCandidate = DateFormatter()
dfCandidate.timeZone = utc
dfCandidate.dateFormat = "yyyy-MM-dd HH:mm:ss"

let dfMatching = DateFormatter()
dfMatching.timeZone = utc
dfMatching.dateFormat = "yyyy-MM-dd"
guard let matchingDate = dfMatching.date(from: "2017-08-28") else {
    preconditionFailure()
}

let dates1 = [
    "2017-08-28 13:06:54",
    "2017-08-28 04:22:42"
]
let dates2 = [
    "2017-08-28 00:00:01",
    "2017-08-28 20:24:00"
]

var utcCalendar = Calendar.current
utcCalendar.timeZone = utc

let matchingDates: [Date] = dates1
    .map { candidateDate in
        guard let date = dfCandidate.date(from: candidateDate) else {
            return nil
        }

        let isInDate = utcCalendar.isDate(date, equalTo: matchingDate, toGranularity: Calendar.Component.day)

        print("\(isInDate) comparing \(String(describing: date)) to matching \(String(describing: matchingDate))")

        return isInDate ? date : nil
    }
    .appending(contentsOf: dates2.map { candidateDate in
        guard let date = dfCandidate.date(from: candidateDate + "Z") else {
            return nil
        }

        let isInDate = utcCalendar.isDate(date, equalTo: matchingDate, toGranularity: Calendar.Component.day)

        print("\(isInDate) comparing \(String(describing: date)) to matching \(String(describing: matchingDate))")

        return isInDate ? date : nil
    })
    .compactMap { $0 }

print(matchingDates)

这将创建一个UTC时区并将其与两个日期格式符一起使用,并在UTC时区中创建一个日历集以进行日期比较。