TimeZone Date Formatting Issue - swift

I'm trying to get a date from a string with multiple time zones, It works without any problems if the API returns zones with abbreviations like CST or UTC but it fails if it returns EET
let timeString = "17:32 (EET)"
let formatter = DateFormatter()
formatter.dateFormat = "HH:mm (zzz)"
let time = formatter.date(from: timeString) // return nil
Any idea what is the issue might be?!

Probably because there is more than one timezone that matches the timezone abbreviation or the date formatter's default date (January 1st) doesn't match the daylight savings of the timezone abbreviation used. Not all countries uses daylight savings time as well it might change at any time. Check this link. This will probably happen for all non US timezones abbreviation as well. For example CST it is used for "China Standard Time" and "Chicago Standard Time" as well. You can workaround that issue setting your date formatter property isLenient to true. Note that this will result in a date of January 1st 2000 with a probably incorrect timezone offset. If you have control of your string input you should use the timezone identifiers instead of its abbreviations to avoid ambiguity. You should also set your date formatter's locale to "en_US_POSIX" when parsing fixed date format to avoid date formatter's reflecting the users device locale and settings:
let timeString = "17:32 (EET)"
let formatter = DateFormatter()
formatter.locale = .init(identifier: "en_US_POSIX")
formatter.dateFormat = "HH:mm (zzz)"
formatter.isLenient = true
let date = formatter.date(from: timeString) // "Jan 1, 2000 at 1:32 PM" "BRT Brazilian Standard Time"
So you should use "GMT+2" or "GMT-3" to avoid ambiguity or as I have already suggested use its identifiers "HH:mm (VV)" i.e. "Europe/Athens" or "America/Sao_Paulo"

Related

How can I ignore time zones in Swift Dates?

I wish to use Swift 5 to create a Date object with the day, month, and year I pass it. Problem is that the DateFormatter has its own ideas, and seems to be treating my Date objects as if they were UTC even whatever I set <formatter>.timeZone = ... to.
Say I want t date object with the date the first of April.
private func firstOfApril() -> String {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
return "\(formatter.date(from: "2021-04-01")!)"
}
If I do this in the AM here (GMT + 13) I get 2021-03-31 11:00:00 +0000. I expect: 2021-04-01 00:00:00
WHat I have tried:
enum TZType{
case None
case Current
case Auto
case Nil
case Default
}
private func firstOfApril(none_current:TZType) -> String {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
switch none_current {
case .None:
formatter.timeZone = .none
case .Current:
formatter.timeZone = .current
case .Nil:
formatter.timeZone = nil
case .Auto:
formatter.timeZone = .autoupdatingCurrent
case .Default:
break
}
return "\(formatter.date(from: "2021-04-01")!)"
}
print(firstOfApril(none_current: .None))
print(firstOfApril(none_current: .Current))
print(firstOfApril(none_current: .Auto))
print(firstOfApril(none_current: .Nil))
print(firstOfApril(none_current: .Default))
Outputs
2021-03-31 11:00:00 +0000
2021-03-31 11:00:00 +0000
2021-03-31 11:00:00 +0000
2021-03-31 11:00:00 +0000
There is no concept of time zones in my application. I think there should be, but I am overruled. All time is "local time", as in wall clock time, not local time zone.
Leo got it.
The last lines of that function should be:
let retDate = formatter.date(from: "2021-04-01")!
return formatter.string(from: retDate)
In total:
private func firstOfApril(none_current:TZType) -> String {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
switch none_current {
case .None:
formatter.timeZone = .none
case .Current:
formatter.timeZone = .current
case .Nil:
formatter.timeZone = nil
case .Auto:
formatter.timeZone = .autoupdatingCurrent
case .Default:
break
}
let retDate = formatter.date(from: "2021-04-01")!
return formatter.string(from: retDate)
}
You've gotten some good, but incomplete, information.
A Date object captures a moment in time, anywhere on the planet. It is devoid of a time-zone. (Internally, it's recorded by a number of seconds since the iOS/MacOS "epoch date", or "zero date". That zero date is in UTC.)
Date objects don't have a time zone, but they also don't have a fixed time or date. The same instant in time will be at different times of day in different time zones, and may even be different dates (e.g. It is about 7 PM on Monday 29 March here in the US Eastern Daylight Time timezone. It is about midnight on Tuesday 30 March in London.)
If you create a date formatter and feed it a month/day/year, it will default to creating a Date object using your local time zone. It's not the Date object that has a time zone, it's the date formatter.
You can either use the date formatter you already created to convert you dates back to strings (using the DateFormatter function string(from:), or you can use a DateFormatter class function called localizedString(from:dateStyle:timeStyle:. That function takes a date and displays it in your local time zone, using the conventions of your locale, and lets you specify short, medium, or long format dates.
If you write
let date = Date()
print(DateFormatter.localizedString(from: date, dateStyle: .medium, timeStyle: .medium))
You'll get something like this:
Mar 29, 2021 at 7:07:13 PM
That is expressed in my local time zone, using US date and time formats.
(But with the date and time at which you run it, and using the date and time formatting conventions for your locale.)
Next, it's how you display the date. If you write the code
print(Date())
What you will see is the date at the instant you run the code, but expressed in UTC. For me, that will be 4 hours ahead of my current time, so it will claim it's about 23:00 on 30 January.
2021-03-29 23:00:48 +0000
If you are in China, and I am in the US, and our phones' clocks are both synchronized exactly, and we capture the current date using Date() at the same instant, we will get the exact same Date value. If we were to both express it in UTC, it would show the same date and time. (The Date object doesn't have a time

Converting string to date with a different timezone gives the wrong date

So, this:
import Foundation
let df = DateFormatter()
df.dateFormat = "yyyy-MM-dd"
df.timeZone = TimeZone(identifier: "Australia/Currie")!
let todayString = (df.string(from: Date()))
print(todayString)
let today = df.date(from: todayString)!
print(today)
Prints:
2021-02-19
2021-02-18 13:00:00 +0000
For reference, today’s date based on my current timezone is the 18th. So it correctly prints the 19th when using an Australian timezone that moves the time ahead.
What I don't understand is why today is a day behind todayString, since it's constructed from the same DateFormatter. Ideally, they should both print the 19th, right?
Basically, what I'm trying to do is have both a date (from a different timezone) and its string representation.
As mentioned in the comments print displays Date instances always in UTC(+0000). For example check
let date = Date()
print(date)
To print the date in your current locale (and time zone) write
print(today.description(with: .current))

Date Formatter isn't showing correct day of the month when using format "DD"

I am trying to show today's date as March 26 but it is showing as "March 85" when I use this code.
let formatter = DateFormatter()
formatter.dateFormat = "MMMM DD"
let defaultTimeZoneStr = formatter.string(from: Date())
The problem is that you are using the wrong date format. D is for "day of year". The correct symbol for "day of month" is lowercased d Thats why you are getting 85instead of 26.
Another point you should consider is to set your locale fixed to "en_US_POSIX" if you don't want your date string to reflect the users settings and locale.
Note that you should use Swift native type Date instead of NSDate.
If your intent is to display it respecting the user locale and settings you should use date formatter dateStyle (short, medium, long or full) How do I get the current Date in short format in Swift
If you need a localized date format limited to
month and day only, you can use DateFormatter method dateFormat from template:
class func dateFormat(fromTemplate tmplate: String, options opts: Int, locale: Locale?) -> String?
let df = DateFormatter()
df.dateFormat = DateFormatter.dateFormat(fromTemplate: "MMMMdd", options: 0, locale: .current)
df.string(from: Date()) // "March 27"

Comparing dates in Swift ignoring current timezone

In Swift I can find the difference between two dates using Calendar.current.dateComponents
The problem is that this gives the difference relative to my current time zone.
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm XXX"
let from = zoneFormatter.date(from: "2018-03-25 00:00 +00:00")!
let to = zoneFormatter.date(from: "2018-03-26 00:00 +00:00")!
let components = Calendar.current.dateComponents([.month, .day, .hour, .minute], from: from, to: to)
// components is 1 day, 1 hour, because I am in UK and Daylight Savings started on 25 March.
How can I retrieve the absolute difference between the two instants specified in the same format which dateComponents returns?
Incorrect Date Format String
Looks like your using the incorrect format for your dates. The date format for the specified date of "2018-03-25 00:00 +00:00" is actually "yyyy-MM-dd HH:mm ZZZZZ". Give that format a try and it should fix the problem.
Why does the incorrect format not work for comparison between time zones?
This is because it was the timeZone component of your format string that is incorrect. This means the timeZone will not be accounted for in the comparison.
The timezone of Calendar.current can be changed by setting the system default timezone as follows:
NSTimeZone.default = NSTimeZone(abbreviation: "UTC")! as TimeZone

How to convert time between timezones taking daylightsavings into account?

I have the following function for converting time:
static func convert(date: String) -> String {
let formatter = DateFormatter()
formatter.dateFormat = "h:m:s a"
formatter.timeZone = TimeZone(identifier: "UTC")
let convertedDate = formatter.date(from: date)
formatter.timeZone = NSTimeZone.local
return formatter.string(from: convertedDate!)
}
Since I'm setting the new time zone based on the device's time zone I taught that daylight savings will be taken into account. But when I passed in 2:00:00 PM it returned 3:0:0 pm instead of 4.
Am I missing something, is there an automatic way to correctly convert time between time zones?
Dealing with daylight saving time only makes sense when the date is known. You don't have a date, just a time. So convertedDate will be January 1, 2001. So whatever the daylight saving rule is for the user's timezone and locale on that date will be used when converting the time.
If you want the time to be treated as "today" then you can set the date formatter's defaultDate.
formatter.defaultDate = Date()
If you want some other specific date, create a Date as needed and use that to set the defaultDate.