UTC to local time - wrong result swift - swift

I know there are a lot of threads, but I can't find a solution for my problem. Maybe I can't see the solution...
I receive a UTC Time: for example 12:50
I want convert this time to MEZ respectively to the time zone of the users device. For my example I expect 13:50, because atm is MEZ +1 to UTC.
This is my code
//transform date to string
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
dateFormatter.timeZone = TimeZone(abbreviation: "UTC")
let newDateAsString:String = dateFormatter.string(from: self)
//is 12:50 UTC
//transform date to MEZ respectively to the local device timezone
let dateFormatterToDate = DateFormatter()
dateFormatterToDate.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
let timeZone = TimeZone.autoupdatingCurrent.identifier as String
dateFormatter.timeZone = TimeZone(identifier: timeZone)
//same result with: dateFormatter.timeZone = NSTimeZone.local
//result is 11:50 but i would expect 13:50
if let result = dateFormatterToDate.date(from: newDateAsString) {
return result
}
The result 11:50 is the time now in my current timezone. But I don't understand this. I give explicitly the date, which should convert. Somebody know where is my mistake?

The conversion that you are doing is the opposite of what you intend. The string newDateAsString, which gives the time as 12:50, does not specify a timezone, because your date format string does not include formatting for a timezone. When you set dateFormatterToDate's timezone to MEZ, and pass newDateAsString to dateFormatterToDate, you are saying: give me a Date object for 12:50 in MEZ.
By default Dates are displayed as UTC, so result is displayed as 11:50, because 12:50 in MEZ is 11:50 in UTC.
To format a date as a string in the local timezone you would use code like this:
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
dateFormatter.timeZone = NSTimeZone.local
let localTimeZoneDateString = dateFormatter.string(from: self)

Related

Swift ISO8601DateFormatter TimeZone

I receive a string timestamp from JSON formatted as follows: "2022-10-06T19:10:00.000Z"
I want to convert the string to my local timezone and possibly even reformat the whole thing to something more readable like "Oct 6 2022, 3:30:45 PM". I assume that I need to use both ISO8601DateFormatter() and DateFormatter() to make that happen?
When I run the code below I get "2022-10-07 02:10:00 +0000" which is tomorrow's date so obviously not my timezone.
How do I properly format this date/time stamp to my current timezone? Thanks!
let jsonDateString = "2022-10-06T19:10:00.000Z"
let dateFormatter = ISO8601DateFormatter()
dateFormatter.timeZone = TimeZone.current
dateFormatter.formatOptions = [.withYear, .withMonth, .withDay, .withTime, .withDashSeparatorInDate, .withColonSeparatorInTime]
let formattedDate = dateFormatter.date(from: jsonDateString)
print(formattedDate!)

How to add specified duration to string date in Swift 5

So I have a function that converts a specified local time, to UTC (Time starts and ends as a string)
I need to add a duration (lets say 1.5 hours) to this time, which may end up going into the next day so I believe I need to use Calendar, and not timeInterval.
I'm a little clueless on how this is done, the documentation isn't the greatest on this and I'm not good with Swift.
Here is what I have so far.
import Foundation
func localToUTC(date:String, originTimeZone:String) -> String {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MM/dd/yyyy HH:mm"
dateFormatter.timeZone = TimeZone(identifier: originTimeZone)
let dt = dateFormatter.date(from: date)
dateFormatter.timeZone = TimeZone(abbreviation: "UTC")
dateFormatter.dateFormat = "MM/dd/yyyy HH:mm"
return dateFormatter.string(from: dt!)
}
print(localToUTC(date: "05/01/2021 15:37", originTimeZone: "America/Boise"))
Just to be clear, I am trying to add another parameter to my function (or make a new function, doesn't matter) to add a duration to the UTC time that my current function outputs.
To put things into context, let's say a flight departs at 15:37 local time (Boise, for example, which is +6 for UTC conversion).
So the flight departs at 21:37 UTC on 5/1/2021. The flight duration is 4 hours.
I would like an output of 5/2/2021 01:37 UTC.
Just add the flight duration to the resulting date object. Note also that you shouldn't force unwrap the result as it might crash your app. Make sure to return nil in case you pass an invalid string. Something like:
func localToUTC(date: String, originTimeZone: String, duration: TimeInterval) -> String? {
let dateFormatter = DateFormatter()
dateFormatter.locale = .init(identifier: "en_US_POSIX")
dateFormatter.dateFormat = "MM/dd/yyyy HH:mm"
guard let timezone = TimeZone(identifier: originTimeZone) else { return nil }
dateFormatter.timeZone = timezone
guard let dt = dateFormatter.date(from: date) else { return nil }
dateFormatter.timeZone = TimeZone(abbreviation: "UTC")
return dateFormatter.string(from: dt.addingTimeInterval(duration))
}
print(localToUTC(date: "05/01/2021 15:37", originTimeZone: "America/Boise", duration: 4 * 60 * 60) ?? "nil")
This will print
05/02/2021 01:37

Swift - date formatter returns unwanted time

If I convert "2019-01-01T00:00:00+0000" to a date, I would like the date to be the date in the string - January 1, 2019. And if I calculate the number of seconds in the time portion, I would like the result to be zero.
The problem is, when I convert my string into a date, it is stored in the UTC timezone. My locale is set to "en_US_POSIX", and my time zone is set to current. My date formatter uses "yyyy-MM-dd'T'HH:mm:ssZ". I know that the final Z means UTC, but I can't seem to figure out the correct field symbol to get the results I want.
func convertToDate(_ dateString: String) -> Date {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
dateFormatter.timeZone = .current
let date = dateFormatter.date(from: dateString)
else {
print("DATE ERROR:", dateString)
return Date()
}
return date
}
If you know for sure that the date strings you wish to parse will always end with a timezone in the form +XXXX then you can trim off that timezone from the string and then parse the remaining string as local time.
func convertToDate(_ dateString: String) -> Date? {
let minusTZ = String(dateString.dropLast(5)) // Assume the string ends with a +9999 timezone
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
let date = dateFormatter.date(from: minusTZ)
return date
}
This will give you a local date with the same date and time in the original string regardless of the timezone of the original string.

Assigning NSDateComponents to a label

I'm creating a mobile app that has a countdown to a specific date. I think I have the timer itself correct, but I'm struggling to get it into a format where I can assign it to my label. I'm getting an error "Cannot invoke initializer for type 'String' with an argument list of type '(NSDateComponents)'. This error is found at the line "var date = String(openingGavelDate)". The outlet for the label has been properly created in this file.
First step I took was creating the date variable and setting it equal to the converted value of my other variable. Second step involved trying to look through documentation but so far I haven't really found any substantial documentation that can help.
func createGavelTimer() {
let openingGavelDate = NSDateComponents()
openingGavelDate.year = 2019
openingGavelDate.month = 7
openingGavelDate.day = 16
openingGavelDate.hour = 14
openingGavelDate.minute = 00
openingGavelDate.timeZone = NSTimeZone(abbreviation: "CST")! as TimeZone
var date = String(openingGavelDate) //problem is here
countdownLabel.text = date
}
One of possible solutions:
let date = Calendar.current.date(from: openingGavelDate)
let dateFormatter = DateFormatter()
dateFormatter.timeStyle = .short
dateFormatter.dateStyle = .medium
dateFormatter.doesRelativeDateFormatting = true
dateFormatter.timeZone = TimeZone(abbreviation: "CST")!
let yourString = dateFormatter.string(from: date)
Try converting the NSDateComponents object to a Date by using Calendar.date(from:), and then converting that to a String using a DateFormatter:
let gregorianCalendar = Calendar(identifier: .gregorian)
if let date = gregorianCalendar.date(from: openingGavelDate as DateComponents) {
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .medium
dateFormatter.timeStyle = .none
countdownLabel.text = dateFormatter.string(from: date)
}
Also, as #Sh_Khan and #rmaddy have commented, you should be using DateComponents, TimeZone, etc. instead of their NS counterparts (unless you're using Swift 2 or lower).
Two things you need to do to form your date:
Set a calendar on the DateComponents instance.
Get your date by accessing the date property on your DateComponents instance.
Also, I'd recommend using time zone identifiers instead of abbreviation to specify a time zone; advantage is that identifiers will automatically apply special rules such as daylight savings as appropriate. (Below I've substituted the "America/Chicago" zone for UTC.)
Try this code in a playground:
var openingGavelDate = DateComponents()
let timeZone = TimeZone(identifier: "America/Chicago")!
openingGavelDate.year = 2019
openingGavelDate.month = 7
openingGavelDate.day = 16
openingGavelDate.hour = 14
openingGavelDate.minute = 00
openingGavelDate.calendar = Calendar.current
openingGavelDate.timeZone = timeZone
let date = openingGavelDate.date
print(date ?? "no date")
Output: 2019-07-16 19:00:00 +0000 (your date in GMT.)
This will get you a date, but notice that the Date class prints in GMT by default, because Date has no concept of timezone.
To print date in the timezone and format you want, use DateFormatter:
let f = DateFormatter()
f.dateFormat = "yyyy-MM-dd hh:mm a"
f.timeZone = TimeZone(identifier: "America/Chicago")!
print(f.string(from: date!))
Output: 2019-07-16 02:00 PM (your date & time, in CST and formatted for reading.)
DateFormatter allows you to either control the format yourself, or follow the user's system settings to determine what is in the final string. See the docs for DateFormatter to see how to get it into the format you want to display.

Getting wrong date when converting string with timezone

In Swift Playground, I run this.
let string = "2019-01-14T00:00:00+08:00"
let utcTimezone = TimeZone(abbreviation: "UTC")!
let sgtTimezone = TimeZone(abbreviation: "SGT")!
let dfs = DateFormatter()
dfs.timeZone = sgtTimezone
dfs.locale = Locale(identifier: "en_sg")
dfs.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZ"
dfs.calendar = Calendar(identifier: Calendar.Identifier.iso8601)
let date = dfs.date(from: string)!
Why is date = Jan 13, 2019 at 11:00 PM and not the accurate Jan 14, 2019 at 00:00 AM ?
Tried changing the timezone to UTC but by default the result is UTC
I am expecting Jan 14, 2019 at 00:00 AM.. or at least Jan 14
// This lets us parse a date from the server using the RFC3339 format
let rfc3339DateFormatter = DateFormatter()
rfc3339DateFormatter.locale = Locale(identifier: "en_US_POSIX")
rfc3339DateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ"
rfc3339DateFormatter.timeZone = TimeZone(secondsFromGMT: 0)
// This string is just a human readable format.
// The timezone at the end of this string does not mean your date
// will magically contain this timezone.
// It just tells the parser what timezone to use to convert this
// string into a date which is basically just seconds since epoch.
let string = "2019-01-14T00:00:00+08:00"
// At this point the date object has no timezone
let shiftDate = rfc3339DateFormatter.date(from: string)!
// If you want to keep printing in SGT, you have to give the formatter an SGT timezone.
let printFormatter = DateFormatter()
printFormatter.dateStyle = .none
printFormatter.timeStyle = .full
printFormatter.timeZone = TimeZone(abbreviation: "SGT")!
let formattedDate = printFormatter.string(from: shiftDate)
You will notice that it prints 12am. There is nothing wrong with your code. You just misunderstand the Date object. Most people do.
Edit: I used the RFC formatter found in the Apple docs here. The result is the same if you use your formatter. And yes, as rmatty said, there are a few things wrong with your formatter (I stand corrected :))