快速转换时区之间的日期

Posted

技术标签:

【中文标题】快速转换时区之间的日期【英文标题】:Converting date between timezones swift 【发布时间】:2016-12-03 04:15:16 【问题描述】:

我的在线服务器数据库中存储了一个日期,该日期位于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

【问题讨论】:

【参考方案1】:

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

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

【讨论】:

我们如何将日期转换为dateFromString(string:) 可以接受的字符串? @ScottyBlades 这个答案适用于您有一个没有时间偏移的日期字符串的情况,就像提问者一样。如果您从Date 开始,则使用a direct Date -> Date conversion 会更有效。【参考方案2】:

更简单的版本:

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

【讨论】:

为了考虑 DST(夏令时),delta 应该使用这个公式计算:let delta = TimeInterval(timezone.secondsFromGMT(for: self) - initTimeZone.secondsFromGMT(for: self)) 此外,由于这是一个“纪元计算”,我将重命名该方法还有一些更好地描述其功能的东西epochConversion(from: to:) 您的增量方向是向后的 - 您应该交换减法中的参数,或者交换参数名称。但是,即使已修复:尝试将 2020-03-08T05:00:00Z 从 GMT 转换为 EST。结果将延迟一小时(详见my answer)。【参考方案3】:

效率提高约 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
    

【讨论】:

很抱歉,但我不能完全理解:如果您的函数名称建议转换为本地时间,则 targetOffset 始终应为 autoupdatingCurrent。我认为 targetOffset 和 LocalOffset 必须在您的代码中交换。 通过 GMT+10,它会工作...现在尝试 GMT+8,它会像其他所有 GMT 时区一样崩溃...所以是的,非常有效【参考方案4】:

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

【讨论】:

问题是我加载比 post x 更新的帖子,上面设置的日期为 (x.date) ,我需要将上面设置的日期转换回 GMT,否则用户时区中的日期将传递给服务器和整个查询毫无意义。 这就是我的观点。唯一重要的时区是用户。从用户处获取并立即将其更改为 GMT,因此无论在何处使用,它始终采用一致的内部格式。 严格按照 GMT/UTC 处理,除非向用户显示它是关键。否则你会拉尿布。【参考方案5】:

基于mukaissi's answer,但表达式中免赔额的顺序已更正。

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

【讨论】:

这种方法是否也考虑了夏令时差异? @valeCocoa 是的,因为 DST 和标准时间使用不同的时区 那么delta 应该通过datesecondsFromGMT() 来计算,否则它怎么可能知道它是否属于夏令时。此外,@mukaissi 方法中计算delta 的参数顺序是正确的。因此那将是:let delta = TimeInterval( targetTimeZone.secondsFromGMT(for: self) - initTimeZone.secondsFromGMT(for: self) ) 通过输入这些数据来测试你的方法:let gmtTZ = TimeZone(secondsFromGMT: 0)!let romeTZ = TimeZone(identifier: "Europe/Rome")!let gregCalGMT: Calendar = var cal = Calendar(identifier: .gregorian)cal.timeZone = gmtTZreturn cal()let dc = DateComponents(year: 2019, month: 8, day: 15, hour: 12, minute: 30)let date = gregCalGMT.date(from: dc)!987654336let date = gregCalGMT.date(from: dc)!987654336 print(inRome) @valeCocoa 关于夏令时的说法完全正确【参考方案6】:

在撰写本文时,大多数答案都包含接近 DST 切换时间的边缘情况错误(请参阅下面关于其他答案的说明)。如果您只想在特定时区将具有 时间偏移量 的日期 字符串 转换为 Date,Amloelxer's answer 是最好的,但为了这些人的利益对于“如何在时区之间转换Date”的问题,有两种情况:

案例一:

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

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

案例2:

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

例如东部标准时间到格林威治标准时间:2020-03-08T06:00:00-04:002020-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 接口,就像我最初的示例一样。

【讨论】:

【参考方案7】:

所以这是 mukaissi's answer 增强了 valeCocoa's suggestion 用于夏令时:

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

【讨论】:

【参考方案8】:

详情

Xcode 11.4.1 (11E503a)、Swift 5.2

解决方案 1

基于mukaissi的回答

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)
    

解决方案一的使用

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

【讨论】:

【参考方案9】:

我建议

您在dateFormatter 上设置了 GMT 时区,以直接返回 UTC 格式的 NSDate(仅使用 UTC 格式的 NSDate 是一种很好的做法) 当您需要显示它时,您可以使用另一个设置了本地时区的 NSDateFormatter(默认情况下) 当您需要向服务器发送日期时,再次使用dateFormatter 生成字符串

【讨论】:

以上是关于快速转换时区之间的日期的主要内容,如果未能解决你的问题,请参考以下文章

使用 pytz 进行日期时间时区转换

在具有正确日期的python中转换和计算日期时间对象的时区

Python时区转换

iOS Swift:当字符串包含时区缩写时,如何将特定的日期字符串格式转换为日期对象?

使用pytz的Datetime时区转换

Java - 日期时区转换