Calendar.current.isDate равный toGranularity: Calendar.Component.Day имеет странное поведение соответствия - PullRequest
0 голосов
/ 16 января 2019

приведенный ниже код изначально был без "Z" в поле matchingDate и печатал результаты вроде:

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: /10611959/ne-mozhet-ispolzovat-mutiruyschii-chlen-potomu-chto-dobavitcomment-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)

(примечание: swift 4.2.1)

1 Ответ

0 голосов
/ 16 января 2019

Проблема в том, что вы смешиваете часовые пояса.

Использование 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 для сравнения дат.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...