I have a date stored on my online server database which is in GMT
. I load the date and convert it to the user's timezone using the following code:
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
}
Now I need to convert the date back to GMT
in order to communicate it to the server, however I'm not sure how to convert it back to GMT
.
Couldn't you just use your data formatter again with a different time zone and convert it? Such as
dateFormatter.timeZone = NSTimeZone(abbreviation: "GMT")
let gmtDate = dateFormatter.dateFromString(string: "your old date as string here")
Simpler version:
extension Date {
func convertToTimeZone(initTimeZone: TimeZone, timeZone: TimeZone) -> Date {
let delta = TimeInterval(timeZone.secondsFromGMT(for: self) - initTimeZone.secondsFromGMT(for: self))
return addingTimeInterval(delta)
}
}
about 50 times more effecient
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
}
}
Since NSDate
is always in GMT/UTC the time zone only becomes relevant when displaying it to, or getting it from, the user. Just always assume it's UTC internally, convert it for the user (by setting it on the NSDateFormatter
) as necessary, and you no longer have to worry about the problem.
Based on mukaissi's answer , but the order of deductible in the expression has been corrected.
extension Date {
func convert(from initTimeZone: TimeZone, to targetTimeZone: TimeZone) -> Date {
let delta = TimeInterval(initTimeZone.secondsFromGMT() - targetTimeZone.secondsFromGMT())
return addingTimeInterval(delta)
}
}
So this is mukaissi's answer enhanced with valeCocoa's suggestion for daylight saving time:
func convert(from initTimeZone: TimeZone, to targetTimeZone: TimeZone) -> Date {
let delta = TimeInterval(targetTimeZone.secondsFromGMT(for: self) - initTimeZone.secondsFromGMT(for: self))
return addingTimeInterval(delta)
}
At time of writing, most answers contain an edge case bug near DST switchover times (see my note about other answers below). If you just want to convert a date string with no time offset to a Date
in a particular time zone, Amloelxer's answer is best, but for the benefit of those with the question of "how to convert a Date
between timezones", there are two cases:
Convert a Date
to another time zone while preserving the day and time from the initial time zone.
Eg for GMT to EST: 2020-03-08T10:00:00Z
to 2020-03-08T10:00:00-04:00
Convert a Date
to the day and time from another time zone while preserving the initial time zone.
Eg for EST to GMT: 2020-03-08T06:00:00-04:00
to 2020-03-08T10:00:00-04:00
(because the initial Date
is 10am in GMT)
These two cases are actually the same (the example start and end Date
s are identical), except they are worded differently to swap which time zone is the "initial" and which is the "target". The two solutions below are therefore equivalent if you swap the time zones between them, so you can choose the one that conceptually fits your use case better.
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
At time of writing, most other answers assume one of the two above cases, but more importantly, they share a bug - they attempt to calculate the time difference between the time zones, where the sign of the difference determines the case:
Case 1:
initialTz.secondsFromGMT(for: initialDate) - targetTz.secondsFromGMT(for: initialDate)
Case 2:
targetTz.secondsFromGMT(for: initialDate) - initialTz.secondsFromGMT(for: initialDate)
secondsFromGMT
takes the Date
for which you want to know the offset, so in both cases the target offset should really be targetTz.secondsFromGMT(for: targetDate)
, which is a catch-22, since we don't know the target date yet. However, in most cases where the Date
s are close, as they are here, targetTz.secondsFromGMT(for: initialDate)
and targetTz.secondsFromGMT(for: targetDate)
are equal - a bug only occurs when they differ, which happens when the time offset changes between the two Date
s in the target time zone, eg for DST. Here is a bugged example for each case:
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)
}
If you adjust the example dates just a few hours forwards or backwards, the bug does not occur. Calendrical calculations are complex, and attempting to roll your own will almost always result in buggy edge cases. Since a time zone is a calendrical unit, to avoid bugs, you should use the existing Calendar
interface, as in my initial example.
The answer from dbplunkett is exactly right that daylight saving time isn't effectively handled by using secondsFromGMT(for: date)
, however their extension example is for Calendar
. The below extension is for date
which achieves the same aim:
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)!
}
}
I suggest
dateFormatter
to get back directly a NSDate in UTC (having only NSDates in UTC is a good practice)dateFormatter
again to generate a stringBased on mukaissi answer
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))
Do not forget to paste the Solution 1 code here
extension DateFormatter {
func date(from string: String, timeZoneInString: TimeZone, outputTimeZone: TimeZone = .autoupdatingCurrent) -> Date? {
date(from: string)?.to(timeZone: outputTimeZone, from: timeZoneInString)
}
}
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))
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.