快速转换时区之间的日期

Alk*_*Alk 12 timezone nsdate gmt ios 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
 }
Run Code Online (Sandbox Code Playgroud)

现在我需要将日期转换回来GMT以便将它传递给服务器,但是我不知道如何将其转换回来GMT.

Aml*_*xer 14

难道你不能再使用不同时区的数据格式化器并进行转换吗?如

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

  • 我们如何将日期转换为“dateFromString(string:)”可接受的字符串? (2认同)

Dav*_*rry 9

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


que*_*ful 9

效率提高约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
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 我很抱歉,但我不能完全遵循:如果你的函数名称建议转换为TopLocalTime,则targetOffset总是应该是autoupdatingCurrent.我认为必须在代码中交换targetOffset和LocalOffset. (3认同)
  • 通过GMT + 10,它将起作用...现在尝试GMT + 8,它将像其他所有GMT时区一样崩溃...所以,非常有效 (2认同)

Nik*_*ner 7

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

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

  • 那么应该通过将“date”传递给“secondsFromGMT()”来计算“delta”,否则它如何知道它是否属于夏令时期间。此外,@mukaissi 方法中计算“delta”的参数顺序是正确的。因此,这将是: `let delta = TimeInterval( targetTimeZone.secondsFromGMT(for: self) - initTimeZone.secondsFromGMT(for: self) )` (4认同)
  • @valeCocoa 关于夏令时的说法绝对正确 (2认同)

Leo*_*eon 7

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)!
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 谢谢。苹果似乎更喜欢用户明确地使用日历进行日期操作,这迫使他们选择合适的日历,并且因为日期是“哑巴”,所以让他们执行日历逻辑有点神奇。所以我试图与这一点保持一致,尽管我的方式确实比较冗长。合理的折衷方案可能是允许将 Calendar 指定为 Date 方法的参数。 (2认同)
  • 同意,是的,在我们的项目中,我们使用“当前”日历的日期,因此这个扩展和“日期”上的其他扩展使我们的事情变得更容易。感谢您的全面回答。 (2认同)

muk*_*ssi 5

更简单的版本:

extension Date {
    func convertToTimeZone(initTimeZone: TimeZone, timeZone: TimeZone) -> Date {
         let delta = TimeInterval(timeZone.secondsFromGMT() - initTimeZone.secondsFromGMT())
         return addingTimeInterval(delta)
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 为了考虑 DST(夏令时),应使用以下公式计算“delta”:“let delta = TimeInterval(timezone.secondsFromGMT(for: self) - initTimeZone.secondsFromGMT(for: self))”此外,因为这是一个“纪元计算”,我也会将该方法重命名为“epochConversion(from: to:)”,以更好地描述其功能 (3认同)

dbp*_*ett 5

在撰写本文时,大多数答案都包含 DST 切换时间附近的边缘案例错误(请参阅我关于下面其他答案的注释)。如果你只是想一个日期转换字符串没有时间偏移Date在特定的时间段,Amloelxer的回答是最好的,但对于那些以“如何转换的问题的利益Date时区之间”,有两种情况:

情况1:

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

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

案例2:

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

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

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

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
Run Code Online (Sandbox Code Playgroud)

关于其他答案的说明

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

情况1:

initialTz.secondsFromGMT(for: initialDate) - targetTz.secondsFromGMT(for: initialDate)
Run Code Online (Sandbox Code Playgroud)

案例2:

targetTz.secondsFromGMT(for: initialDate) - initialTz.secondsFromGMT(for: initialDate)
Run Code Online (Sandbox Code Playgroud)

secondsFromGMTDate你想知道偏移量的 ,所以在这两种情况下,目标偏移量应该真的是targetTz.secondsFromGMT(for: targetDate),这是一个 catch-22,因为我们还不知道目标日期。然而,在大多数情况下,Dates 接近,因为它们在这里targetTz.secondsFromGMT(for: initialDate)并且targetTz.secondsFromGMT(for: targetDate) 相等- 只有当它们不同时才会发生错误,当Date目标时区中的两个s之间的时间偏移发生变化时,例如 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)
}
Run Code Online (Sandbox Code Playgroud)

如果您将示例日期向前或向后调整几个小时,则不会发生该错误。日历计算很复杂,尝试自己进行计算几乎总是会导致错误的边缘情况。由于时区是一个日历单位,为了避免错误,您应该使用现有的Calendar界面,就像我最初的例子一样。


mbi*_*mbi 5

因此,这是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)
}
Run Code Online (Sandbox Code Playgroud)