在撰写本文时,大多数答案都包含接近 DST 切换时间的边缘情况错误(请参阅下面关于其他答案的说明)。如果您只想在特定时区将具有 时间偏移量 的日期 字符串 转换为 Date,Amloelxer's answer 是最好的,但为了这些人的利益对于“如何在时区之间转换Date”的问题,有两种情况:
案例一:
将Date 转换为另一个时区,同时保留初始时区的日期和时间。
例如对于 GMT 到 EST:2020-03-08T10:00:00Z 到 2020-03-08T10:00:00-04:00
案例2:
将Date 转换为另一个时区的日期和时间,同时保留初始时区。
例如东部标准时间到格林威治标准时间:2020-03-08T06:00:00-04:00 到 2020-03-08T10:00:00-04:00(因为最初的 Date 是格林威治标准时间上午 10 点)
这两种情况实际上是相同的(示例 start 和 end Dates 是相同的),只是它们的措辞不同以交换哪个时区是“初始”而哪个是“目标”。因此,如果您交换它们之间的时区,以下两种解决方案是等效的,因此您可以选择在概念上更适合您的用例的一种。
extension Calendar {
// case 1
func dateBySetting(timeZone: TimeZone, of date: Date) -> Date? {
var components = dateComponents(in: self.timeZone, from: date)
components.timeZone = timeZone
return self.date(from: components)
}
// case 2
func dateBySettingTimeFrom(timeZone: TimeZone, of date: Date) -> Date? {
var components = dateComponents(in: timeZone, from: date)
components.timeZone = self.timeZone
return self.date(from: components)
}
}
// example values
let initTz = TimeZone(abbreviation: "GMT")!
let targetTz = TimeZone(abbreviation: "EST")!
let initDate = Calendar.current.date(from: .init(timeZone: initTz, year: 2020, month: 3, day: 8, hour: 4))!
// usage
var calendar = Calendar.current
calendar.timeZone = initTz
let case1TargetDate = calendar.dateBySetting(timeZone: targetTz, of: initDate)!
let case2TargetDate = calendar.dateBySettingTimeFrom(timeZone: targetTz, of: initDate)!
// print results
let formatter = ISO8601DateFormatter()
formatter.timeZone = targetTz // case 1 is concerned with what the `Date` looks like in the target time zone
print(formatter.string(from: case1TargetDate)) // 2020-03-08T04:00:00-04:00
// for case 2, find the initial `Date`'s time in the target time zone
print(formatter.string(from: initDate)) // 2020-03-07T23:00:00-05:00 (the target date should have this same time)
formatter.timeZone = initTz // case 2 is concerned with what the `Date` looks like in the initial time zone
print(formatter.string(from: case2TargetDate)) // 2020-03-07T23:00:00Z
关于其他答案的说明
在撰写本文时,大多数其他答案都假设上述两种情况之一,但更重要的是,它们都有一个错误 - 他们试图计算时区之间的时差,其中差异的符号决定了情况:
案例一:
initialTz.secondsFromGMT(for: initialDate) - targetTz.secondsFromGMT(for: initialDate)
案例 2:
targetTz.secondsFromGMT(for: initialDate) - initialTz.secondsFromGMT(for: initialDate)
secondsFromGMT 采用您想知道偏移量的Date,因此在这两种情况下,目标偏移量都应该是targetTz.secondsFromGMT(for: targetDate),这是一个catch-22,因为我们不知道目标日期然而。但是,在大多数情况下,Dates 很接近,因为它们在这里,targetTz.secondsFromGMT(for: initialDate) 和 targetTz.secondsFromGMT(for: targetDate) 是相等的 - 只有当它们不同时才会出现错误,当时间偏移时会发生这种情况目标时区的两个Dates 之间的变化,例如夏令时。以下是每种情况的错误示例:
extension Date {
// case 1 (bugged)
func converting(from initTz: TimeZone, to targetTz: TimeZone) -> Date {
return self + Double(initTz.secondsFromGMT(for: self) - targetTz.secondsFromGMT(for: self))
}
// case 2 (bugged)
func convertingTime(from initTz: TimeZone, to targetTz: TimeZone) -> Date {
return self + Double(targetTz.secondsFromGMT(for: self) - initTz.secondsFromGMT(for: self))
}
}
let formatter = ISO8601DateFormatter()
// case 1
do {
// example values
let initTz = TimeZone(abbreviation: "GMT")!
let targetTz = TimeZone(abbreviation: "EST")!
let initDate = Calendar.current.date(from: .init(timeZone: initTz, year: 2020, month: 3, day: 8, hour: 4))!
// usage
let targetDate = initDate.converting(from: initTz, to: targetTz)
// print results
formatter.timeZone = targetTz // case 1 is concerned with what the `Date` looks like in the target time zone
print(formatter.string(from: targetDate)) // 2020-03-08T05:00:00-04:00 (should be 4am)
}
// case 2
do {
// example values
let initTz = TimeZone(abbreviation: "EST")!
let targetTz = TimeZone(abbreviation: "GMT")!
let initDate = Calendar.current.date(from: .init(timeZone: initTz, year: 2020, month: 3, day: 8, hour: 1))!
// usage
let targetDate = initDate.convertingTime(from: initTz, to: targetTz)
// print results
formatter.timeZone = targetTz // for case 2, find the initial `Date`'s time in the target time zone
print(formatter.string(from: initDate)) // 2020-03-08T06:00:00Z (the target date should have this same time)
formatter.timeZone = initTz // case 2 is concerned with what the `Date` looks like in the initial time zone
print(formatter.string(from: targetDate)) // 2020-03-08T07:00:00-04:00 (should be 6am)
}
如果您将示例日期向前或向后调整几个小时,则不会出现错误。日历计算很复杂,尝试自己动手几乎总是会导致错误的边缘情况。由于时区是一个日历单位,为了避免错误,您应该使用现有的Calendar 接口,就像我最初的示例一样。