68

I want to convert server UTC time to local time and vice-versa. Here is my code..

var isTimeFromServer = true
var time:String!
var period:String!
let timeString = "6:59 AM" //Current UTC time

if isTimeFromServer {

    let index = timeString.index(timeString.startIndex, offsetBy: 5)
    let twelve = timeString.substring(to: index)

    var dateString:String!

    let dateFormatter = DateFormatter()
    dateFormatter.dateFormat = "H:mm"
    let date12 = dateFormatter.date(from: twelve)!

    dateFormatter.dateFormat = "h:mm a"
    let date22 = dateFormatter.string(from: date12)

    //print(date22)
    dateString = date22
    //print("dateString=\(dateString)")

    time = dateString.components(separatedBy: " ")[0]
    period = dateString.components(separatedBy: " ")[1]

}
else {
    time = timeString.components(separatedBy: " ")[0]
    period = timeString.components(separatedBy: " ")[1]
}

var hour = Int(time.components(separatedBy: ":")[0])

hour = period == "AM" ? hour : hour! + 12
let minute = Int(time.components(separatedBy: ":")[1])
let calender = NSCalendar.current
var datecomponent = DateComponents()
datecomponent.calendar = calender
datecomponent.hour = hour
datecomponent.minute = minute

if !isTimeFromServer {
    // local to UTC
    datecomponent.timeZone = TimeZone.current
}
else {
    datecomponent.timeZone = TimeZone(abbreviation: "UTC")
}

let date = datecomponent.date
let dateFormatter = DateFormatter()

if !isTimeFromServer {
    dateFormatter.dateFormat = "H:mm"
    dateFormatter.timeZone = TimeZone(abbreviation: "UTC")
    dateFormatter.string(from: date!)
}
else {
    //UTC to local
    dateFormatter.dateFormat = "h:mm a"
    dateFormatter.timeZone = TimeZone.current
    dateFormatter.string(from: date!)
}

I get the local time

o/p: "12:52 PM"

But actual local time and output time difference is 23 minutes.

Cœur
  • 37,241
  • 25
  • 195
  • 267
NiravS
  • 1,144
  • 1
  • 12
  • 24

8 Answers8

140

I don't know what's wrong with your code.
But looks too much unnecessary things are there like you're setting calendar, fetching some elements from string. Here is my small version of UTCToLocal and localToUTC function.
But for that you need to pass string in specific format. Cause I've forcly unwrapped date objects. But you can use some guard conditions to prevent crashing your app.

func localToUTC(dateStr: String) -> String? {
    let dateFormatter = DateFormatter()
    dateFormatter.dateFormat = "h:mm a"
    dateFormatter.calendar = Calendar.current
    dateFormatter.timeZone = TimeZone.current
    
    if let date = dateFormatter.date(from: dateStr) {
        dateFormatter.timeZone = TimeZone(abbreviation: "UTC")
        dateFormatter.dateFormat = "H:mm:ss"
    
        return dateFormatter.string(from: date)
    }
    return nil
}

func utcToLocal(dateStr: String) -> String? {
    let dateFormatter = DateFormatter()
    dateFormatter.dateFormat = "H:mm:ss"
    dateFormatter.timeZone = TimeZone(abbreviation: "UTC")
    
    if let date = dateFormatter.date(from: dateStr) {
        dateFormatter.timeZone = TimeZone.current
        dateFormatter.dateFormat = "h:mm a"
    
        return dateFormatter.string(from: date)
    }
    return nil
}

and call these function like below.

print(utcToLocal(dateStr: "13:07:00"))
print(localToUTC(dateStr: "06:40 PM"))
starball
  • 20,030
  • 7
  • 43
  • 238
Mrugesh Tank
  • 3,495
  • 2
  • 29
  • 59
  • 2
    It's exactly what I want.. Thanks @Mrugesh. – NiravS Mar 15 '17 at 13:29
  • 1
    the only downfall is ineffecieny. each iteration of this function takes about 0.23 milliseconds. for comparing thousands of dates this will be ineffecient – quemeful Oct 07 '17 at 16:07
  • 3
    To make this more efficient place the declaration and init of the DateFormatter() outside of these methods in a viewDidLoad or init. DateFormatter() is an expensive operation so this can allow it to be created once and then we can manipulate the dateFormat etc in the UTCToLocal methods etc... anything you only need to set once can go in the viewDidLoad or init actually. This will improve efficiency – jcpennypincher Dec 11 '17 at 22:28
  • Fatal error: Unexpectedly found nil while unwrapping an Optional value Swift 4.2 each time... – J A S K I E R Nov 14 '19 at 03:47
  • @Oleksandr, May be you've different date format then written in answer – Mrugesh Tank Nov 14 '19 at 12:54
20

Mrugesh's answer is perfect, but if someone need to use their own formats, or in some different format, I've generalised it so you can give different format or same in both parameters.

func localToUTC(date:String, fromFormat: String, toFormat: String) -> String {

    let dateFormatter = DateFormatter()
    dateFormatter.dateFormat = fromFormat
    dateFormatter.calendar = NSCalendar.current
    dateFormatter.timeZone = TimeZone.current
    dateFormatter.date
    let dt = dateFormatter.date(from: date)
    dateFormatter.timeZone = TimeZone(abbreviation: "UTC")
    dateFormatter.dateFormat = toFormat

    return dateFormatter.string(from: dt!)
}

func UTCToLocal(date:String, fromFormat: String, toFormat: String) -> String {
    let dateFormatter = DateFormatter()
    dateFormatter.dateFormat = fromFormat
    dateFormatter.timeZone = TimeZone(abbreviation: "UTC")

    let dt = dateFormatter.date(from: date)
    dateFormatter.timeZone = TimeZone.current
    dateFormatter.dateFormat = toFormat

return dateFormatter.string(from: dt!)
}

let localDateAsString = UTCToLocal(date: dateAsString!, fromFormat: "hh:mm a, dd MMM yyyy", toFormat: "hh:mm a, dd MMM yyyy")

You can use it as above. Hope it helps.

Fadi
  • 733
  • 1
  • 5
  • 17
Hardik
  • 389
  • 4
  • 9
14

By the help of Mrugesh Tank Answer,

I have updated his answer and creating the extensions for the date. So that you can easily access the functions from anywhere either from ViewController or either from cell class as well.

extension String {

  //MARK:- Convert UTC To Local Date by passing date formats value
  func UTCToLocal(incomingFormat: String, outGoingFormat: String) -> String {
    let dateFormatter = DateFormatter()
    dateFormatter.dateFormat = incomingFormat
    dateFormatter.timeZone = TimeZone(abbreviation: "UTC")

    let dt = dateFormatter.date(from: self)
    dateFormatter.timeZone = TimeZone.current
    dateFormatter.dateFormat = outGoingFormat

    return dateFormatter.string(from: dt ?? Date())
  }

  //MARK:- Convert Local To UTC Date by passing date formats value
  func localToUTC(incomingFormat: String, outGoingFormat: String) -> String {
    let dateFormatter = DateFormatter()
    dateFormatter.dateFormat = incomingFormat
    dateFormatter.calendar = NSCalendar.current
    dateFormatter.timeZone = TimeZone.current

    let dt = dateFormatter.date(from: self)
    dateFormatter.timeZone = TimeZone(abbreviation: "UTC")
    dateFormatter.dateFormat = outGoingFormat

    return dateFormatter.string(from: dt ?? Date())
  }
}

Example how to use it:-

Note:- eventStartDate is the string which you have to converted in your format like this:- "2018-07-11T16:22:00.000Z" 

let finalDate = eventStartDate.UTCToLocal(incomingFormat: "yyyy-MM-dd'T'HH:mm:ss.SSSZ", outGoingFormat: "MMM d, yyyy h:mm a")
Mandeep Singh
  • 2,810
  • 1
  • 19
  • 31
10

To convert a UTC Date to a local system date you could use the following Date extension:

extension Date {

    // Convert local time to UTC (or GMT)
    func toGlobalTime() -> Date {
        let timezone = TimeZone.current
        let seconds = -TimeInterval(timezone.secondsFromGMT(for: self))
        return Date(timeInterval: seconds, since: self)
    }

    // Convert UTC (or GMT) to local time
    func toLocalTime() -> Date {
        
        // 1) Get the current TimeZone's seconds from GMT. Since I am in Chicago this will be: 60*60*5 (18000)
        let timezoneOffset = TimeZone.current.secondsFromGMT()
        
        // 2) Get the current date (GMT) in seconds since 1970. Epoch datetime.
        let epochDate = self.timeIntervalSince1970
        
        // 3) Perform a calculation with timezoneOffset + epochDate to get the total seconds for the
        //    local date since 1970.
        //    This may look a bit strange, but since timezoneOffset is given as -18000.0, adding epochDate and timezoneOffset
        //    calculates correctly.
        let timezoneEpochOffset = (epochDate + Double(timezoneOffset))
        
        
        // 4) Finally, create a date using the seconds offset since 1970 for the local date.
        return Date(timeIntervalSince1970: timezoneEpochOffset)
    }

}
Jujuba
  • 341
  • 3
  • 8
4

For everyone using TimeZone objects. I would advise you to create your TimeZone from identifier rather than abbreviation when you have the possibility.

This prevents errors caused by daylight saving.

To illustrate my point let's take an example. You can instantiate like this let timeZone = TimeZone(identifier: "Europe/Paris") or like that let timeZone = TimeZone(abbreviation: "CEST") or "UTC +2:00"

But this is time zone for summer CEST meaning Central Europe Summer Time We have CET meaning Central Europe Time for winter which is "UTC +1:00"

You could manage daylight saving by your own with Date.isDaylightSavingsTime but this means more code and you don't have control on where your daylight saving sprang from. "indicates whether the receiver is currently using daylight saving time" from official doc

All is that is to say favour TimeZone(identifier: ...)

3

You can use in swift 4/5

  var myDate:String = "2020-02-18 14:30:57"
    var convertedLocalTime:String = ""

    let dateFormatter = DateFormatter()
    dateFormatter.dateFormat = "yyyy-MM-dd H:mm:ss"

    dateFormatter.timeZone = TimeZone(abbreviation: "UTC")

    if let dt = dateFormatter.date(from: myDate) {
        dateFormatter.timeZone = TimeZone.current
        dateFormatter.dateFormat = "yyyy-MM-dd h:mm a"
        convertedLocalTime = dateFormatter.string(from: dt)
    } else {
        print("There was an error decoding the string")
    }

    print("convertedLocalTime--",convertedLocalTime)
Enamul Haque
  • 4,789
  • 1
  • 37
  • 50
1

Please try it:

func convertUTCToLocal(timeString: String) -> String? {
    let dateFormatter = DateFormatter()
    dateFormatter.dateFormat = "h:mm a"

    dateFormatter.timeZone = TimeZone.init(abbreviation: "UTC")
    let timeUTC = dateFormatter.date(from: timeString)

    if timeUTC != nil {
        dateFormatter.timeZone = NSTimeZone.local

        let localTime = dateFormatter.string(from: timeUTC!)
        return localTime
    }

    return nil
}



func convertLocalToUTC(localTime: String) -> String? {

    let dateFormatter = DateFormatter()
    dateFormatter.dateFormat = "h:mm a"
    dateFormatter.timeZone = NSTimeZone.local
    let timeLocal = dateFormatter.date(from: localTime)

    if timeLocal != nil {
        dateFormatter.timeZone = TimeZone.init(abbreviation: "UTC")

        let timeUTC = dateFormatter.string(from: timeLocal!)
        return timeUTC
    }
    return nil
}


    var isTimeFromServer = true
    var time:String!
    var period:String!
    let timeString = "6:59 AM" //Current UTC time


    if isTimeFromServer {
        print(convertUTCToLocal(timeString: timeString))
    } else {
        print(convertLocalToUTC(localTime: timeString))
    }
javimuu
  • 1,829
  • 1
  • 18
  • 29
  • 6
    Some *explanations* would be useful for future readers: What is wrong with the code in the question? What did you change and why? – Martin R Mar 15 '17 at 08:19
1

If you need to convert timestamp you can use timezoneOffset like here:

if let dateStr = model.date, let dateInt = Int(dateStr) {
            let timezoneOffset =  TimeZone.current.secondsFromGMT()
            let localDateInt = dateInt + timezoneOffset
            let date = Date(timeIntervalSince1970: Double(localDateInt))
            cell.dateLbl.text = date.toShortDateTimeString()
        }
Dmih
  • 530
  • 6
  • 9
  • For me `TimeZone.current.secondsFromGMT()` is very important to convert UTC date. And Thanks it worked – Kudos Apr 03 '23 at 15:17