繁体   English   中英

在时区之间转换日期 swift

[英]Converting date between timezones swift

我有一个日期存储在我的在线服务器数据库中,它是GMT 我使用以下代码加载日期并将其转换为用户的时区:

 if let messagedate = oneitem["timestamp"] as? String {
     let dateFormatter = NSDateFormatter()
     dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
     let date = dateFormatter.dateFromString(messagedate)
     let source_timezone = NSTimeZone(abbreviation: "GMT")
     let local_timezone = NSTimeZone.systemTimeZone()
     let source_EDT_offset = source_timezone?.secondsFromGMTForDate(date!)
     let destination_EDT_offset = local_timezone.secondsFromGMTForDate(date!)
     let time_interval : NSTimeInterval = Double(destination_EDT_offset - source_EDT_offset!)


     let final_date = NSDate(timeInterval: time_interval, sinceDate: date!)
     curr_item.date = final_date
 }

现在我需要将日期转换回GMT以便将其传达给服务器,但是我不确定如何将其转换回GMT

您不能再次使用具有不同时区的数据格式化程序并进行转换吗? 比如

dateFormatter.timeZone = NSTimeZone(abbreviation: "GMT")
let gmtDate = dateFormatter.dateFromString(string: "your old date as string here")

更简单的版本:

extension Date {
    func convertToTimeZone(initTimeZone: TimeZone, timeZone: TimeZone) -> Date {
         let delta = TimeInterval(timeZone.secondsFromGMT(for: self) - initTimeZone.secondsFromGMT(for: self))
         return addingTimeInterval(delta)
    }
}

效率提高约 50 倍

extension Date {
    func convertToLocalTime(fromTimeZone timeZoneAbbreviation: String) -> Date? {
        if let timeZone = TimeZone(abbreviation: timeZoneAbbreviation) {
            let targetOffset = TimeInterval(timeZone.secondsFromGMT(for: self))
            let localOffeset = TimeInterval(TimeZone.autoupdatingCurrent.secondsFromGMT(for: self))

            return self.addingTimeInterval(targetOffset - localOffeset)
        }

        return nil
    }
}

由于NSDate始终采用 GMT/UTC,因此时区仅在向用户显示或从用户获取时才变得相关。 只要始终在内部假设它是 UTC,根据需要为用户转换它(通过在NSDateFormatter上设置它),您就不必再担心这个问题了。

基于mukaissi 的回答,但已更正表达式中的免赔额顺序。

extension Date {    
    func convert(from initTimeZone: TimeZone, to targetTimeZone: TimeZone) -> Date {
        let delta = TimeInterval(initTimeZone.secondsFromGMT() - targetTimeZone.secondsFromGMT())
        return addingTimeInterval(delta)
    }    
}

因此,这是mukaissi 的回答,根据 valeCocoa 的夏令时建议进行了增强:

func convert(from initTimeZone: TimeZone, to targetTimeZone: TimeZone) -> Date {
    let delta = TimeInterval(targetTimeZone.secondsFromGMT(for: self) - initTimeZone.secondsFromGMT(for: self))
    return addingTimeInterval(delta)
}

在撰写本文时,大多数答案都包含 DST 切换时间附近的边缘案例错误(请参阅我关于下面其他答案的说明)。 如果您只想将没有时间偏移的日期字符串转换为特定时区中的DateAmloelxer 的答案是最好的,但为了那些有“如何在时区之间转换Date ”问题的人的利益,有两个案例:

案例1:

Date转换为另一个时区,同时保留初始时区的日期和时间。

例如 GMT 到 EST: 2020-03-08T10:00:00Z2020-03-08T10:00:00-04:00

案例2:

Date转换为另一个时区的Date和时间,同时保留初始时区。

例如,对于 EST 到 GMT: 2020-03-08T06:00:00-04:002020-03-08T10:00:00-04:00 (因为初始Date是格林威治标准时间上午 10 点)

这两种情况实际上是相同的(示例 start 和 end Date是相同的),除了它们的措辞不同以交换哪个时区是“初始”和哪个时区是“目标”。 因此,如果您在它们之间交换时区,则下面的两种解决方案是等效的,因此您可以选择在概念上更适合您的用例的一种。

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

关于其他答案的说明

在撰写本文时,大多数其他答案都假设上述两种情况之一,但更重要的是,它们共享一个错误 - 他们尝试计算时区之间的时差,其中差异的符号决定了这种情况:

案例1:

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,因为我们还不知道目标日期。 然而,在大多数情况下Date s 接近,因为它们在这里, targetTz.secondsFromGMT(for: initialDate)targetTz.secondsFromGMT(for: targetDate)是相等的- 只有当它们不同时才会发生错误,这发生在时间目标时区中两个Date之间的偏移量变化,例如 DST。 以下是每种情况的错误示例:

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界面,就像我最初的例子一样。

dbplunkett 的答案是完全正确的,即使用secondsFromGMT(for: date)不能有效地处理夏令时,但是它们的扩展示例适用于Calendar 以下扩展适用于实现相同目标的date

extension Date {

    func convert(from timeZone: TimeZone, to destinationTimeZone: TimeZone) -> Date {
        let calendar = Calendar.current
        var components = calendar.dateComponents(in: timeZone, from: self)
        components.timeZone = destinationTimeZone
        return calendar.date(from: components)!
    }
}

我建议

  • 您在dateFormatter上设置 GMT 时区以直接返回 UTC 中的 NSDate(在 UTC 中只有 NSDates 是一个好习惯)
  • 当您需要显示它时,您可以使用另一个 NSDateFormatter 并在其上设置本地时区(默认情况下)
  • 当需要向服务器发送日期时,再次使用dateFormatter生成字符串

详情

  • Xcode 11.4.1 (11E503a),Swift 5.2

解决方案1

基于穆凯西的回答

import Foundation
extension Date {
    func to(timeZone outputTimeZone: TimeZone, from inputTimeZone: TimeZone) -> Date {
         let delta = TimeInterval(outputTimeZone.secondsFromGMT(for: self) - inputTimeZone.secondsFromGMT(for: self))
         return addingTimeInterval(delta)
    }
}

解决方案1的使用

let utcTimeZone = TimeZone(abbreviation: "UTC")!
let dateString = "2020-06-03T01:43:44.888Z"
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
let date = dateFormatter.date(from: dateString)

print(date)
print(date?.to(timeZone: .autoupdatingCurrent, from: utcTimeZone))
print(date?.to(timeZone: .current, from: utcTimeZone))
print(date?.to(timeZone: TimeZone(abbreviation: "PDT")!, from: utcTimeZone))

解决方案2

不要忘记在此处粘贴解决方案 1 代码

extension DateFormatter {
    func date(from string: String, timeZoneInString: TimeZone, outputTimeZone: TimeZone = .autoupdatingCurrent) -> Date? {
        date(from: string)?.to(timeZone: outputTimeZone, from: timeZoneInString)
    }
}

解决方案2的使用

let utcTimeZone = TimeZone(abbreviation: "UTC")!
let pdtTimeZone = TimeZone(abbreviation: "PDT")!
let dateString = "2020-06-03T01:43:44.888Z"
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"

print(dateFormatter.date(from: dateString))
print(dateFormatter.date(from: dateString, timeZoneInString: utcTimeZone))
print(dateFormatter.date(from: dateString, timeZoneInString: utcTimeZone, outputTimeZone: pdtTimeZone))

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM