0

I'm using this code to convert HH:mm string to GMT time string HH:mm:

func convertToGMT(timeString: String) -> String {
    let dateFormatter = DateFormatter()
    dateFormatter.dateFormat = "HH:mm"
    
    // Assuming the input time is in the user's local time zone
    dateFormatter.timeZone = TimeZone.current
    
    // Parse the input time string into a Date object
    guard let date = dateFormatter.date(from: timeString) else {
        return "" // Return nil if the input format is invalid
    }
    
    // Convert the date to GMT (UTC) time zone
    dateFormatter.timeZone = TimeZone(abbreviation: "GMT")
    let gmtTimeString = dateFormatter.string(from: date)
    
    return gmtTimeString
}

The problem is that in Jerusalem GMT -3 I get for 12:00 -> 10:00 instead of 09:00.

What is the problem?

HangarRash
  • 7,314
  • 5
  • 5
  • 32
YosiFZ
  • 7,792
  • 21
  • 114
  • 221

3 Answers3

2

The problem is how your converting the date, when you're doing the conversion to date using the date formatter

    // Parse the input time string into a Date object
    guard let date = dateFormatter.date(from: timeString) else {
        return "" // Return nil if the input format is invalid
    }
    

It is producing the following date 2000-01-01 10:00:00 +0000.

Upon looking on a time zone converter website the result produced below shows that the conversion being done on your current code is correct. enter image description here

The conversion shown below using current date indicates a difference of 3 hours, which is also the difference you expected your code to produce. This difference is explained by JeremyP in the comments:

In July, Israel is in daylight savings time. In January it is not. In January of any year including 2000, Israel is two hours ahead of GMT. In July it is three hours ahead.

enter image description here

A possible solution is to create the date using DateComponents:

func convertToGMT(timeString: String) -> String {
    var dateComponents = Calendar.current.dateComponents(
        in: TimeZone.current,
        from: Date()
    )
    
    let dateParts = timeString.split(separator: ":")
    dateComponents.hour = Int(dateParts[0]) // TODO: validate existance on array
    dateComponents.minute = Int(dateParts[1]) // TODO: validate existance on array
    
    // Parse the input time string into a Date object
    guard let date = Calendar.current.date(from: dateComponents) else {
        return "" // Return nil if the input format is invalid
    }
    
    let dateFormatter = DateFormatter()
    dateFormatter.dateFormat = "HH:mm"
    // Convert the date to GMT (UTC) time zone
    dateFormatter.timeZone = TimeZone(abbreviation: "GMT")
    let gmtTimeString = dateFormatter.string(from: date)
    
    return gmtTimeString
}
Claudio
  • 5,078
  • 1
  • 22
  • 33
  • 1
    "Some adjustments might have been made over the years" It's simpler than that. In July, Israel is in daylight savings time. In January it is not. In January of any year including 2000, Israel is two hours ahead of GMT. In July it is three hours ahead. – JeremyP Jul 30 '23 at 10:21
  • The main difference which you haven't clearly stated is that the 'correct' conversion is `IDT` to `GMT` and not `IST`. But as noted in the comments on the question if the OP wants `UTC` they should say that and not conflate `GMT` (a timezone) with `UTC` (a time standard) – pilchard Jul 30 '23 at 10:23
  • @pilchard that would be taken into account because the user's current time zone takes daylight savings into account. The conversion is clever enough to figure out that in the iOS Israel default time zone, Jan 1st 2000 was in IST whereas today is IDT. – JeremyP Jul 30 '23 at 10:27
  • I understand that it is 'clever enough' but in an explanation such as this, especially when the OP is already conflating terminology, making the clear distinction is important – pilchard Jul 30 '23 at 10:27
1

As mentioned in Claudio's answer the actual time difference depends on the date portion, and the confusion occurs because DateFormatter sets the date to Jan 1, 2000 for a pure time string.

You have to calculate the time difference from the time in today to consider daylight saving time.

This is an approach which extracts hour and minute with Regular Expression and sets the hour and minute of the current date with date(bySettingHour:minute:second:of: of Calendar.

The code works in any locale and throws errors for a format mismatch and if the digits are not in 0..<24 (hour) and 0..<60 (minute).

enum TimeConversionError: Error {
    case formatMismatch, outOfRange
}

func convertToGMT(timeString: String) throws -> String {
    guard let hhmm = timeString.firstMatch(of: /(\d{1,2}):(\d{2})/) else { throw TimeConversionError.formatMismatch }
    guard let currentDate = Calendar.current.date(bySettingHour: Int(hhmm.1)!, minute: Int(hhmm.2)!, second: 0, of: .now) else { throw TimeConversionError.outOfRange }
    let dateFormatter = DateFormatter()
    dateFormatter.timeZone = TimeZone(secondsFromGMT: 0)
    dateFormatter.dateFormat = "HH:mm"
    return dateFormatter.string(from: currentDate)
}

let string = "12:00"
do {
    let gmtDateString = try convertToGMT(timeString: string)
} catch { print(error) }
vadian
  • 274,689
  • 30
  • 353
  • 361
0

The problem is that you need to set the dateFormater’s defaultDate to startOfDay for .now. Make sure to set the dateFormatter calendar property as well.

func convertToUTC(timeString: String) -> String? {
    let calendar = Calendar(identifier: .iso8601)
    let locale = Locale(identifier: "en_US_POSIX")
    let defaultDate = calendar.startOfDay(for: .now)
    let dateFormatter = DateFormatter()
    dateFormatter.calendar = calendar
    dateFormatter.locale = locale
    // Assuming the input time is in the user's local time zone which is the default. This line can be omitted
    dateFormatter.timeZone = .current
    dateFormatter.defaultDate = defaultDate
    dateFormatter.dateFormat = "HH:mm"
    // Parse the input time string into a Date object
    guard let date = dateFormatter.date(from: timeString) else {
        return nil // Return nil if the input format is invalid
    }
    // Convert the date to UTC time zone
    dateFormatter.timeZone = TimeZone(abbreviation: "UTC")
    return dateFormatter.string(from: date)
}
convertToUTC(timeString: "09:00")  // "12:00" my timezone is -3hs
Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
  • I haven't messed much with `DateFormatter`'s `defaultDate` property. What effect does setting it have? – Duncan C Jul 30 '23 at 21:17
  • This will provide the components not found at the date string when parsing it. The startOfDay is necessary to zero the seconds as well – Leo Dabus Jul 31 '23 at 00:39
  • The calendar shouldn’t be an issue here but I rather be safe than sorry. It is really necessary when you have a date string without time. Check this post https://stackoverflow.com/a/32408916/2303865 – Leo Dabus Jul 31 '23 at 00:42