Comparing dates in Swift ignoring current timezone - swift

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

Related

How to parse date in "2022-03-04T10:30:00-08:00" format correctly in swift?

I have date coming from API shown below. These dates are for different countries.
dateTime = "2022-03-04T14:30:00-08:00"
I need to convert this use it both as Date and as String. But I do not know if date and time I am converting are correct. I am using following code:
To Convert String from API to Date:
extension String {
var CommonDateFormat: Date? {
get {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
return dateFormatter.date(from: self)
}
}
}
To Convert Date to String:
extension Date {
func getDateAccoringTo(format: DateFormat ) -> String {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = format.rawValue
dateFormatter.timeZone = .current
dateFormatter.timeZone = TimeZone.init(identifier: "UTC")
return dateFormatter.string(from: self)
}
}
enum DateFormat: String {
case ddmmyyyy = "dd/MM/yyyy"
case mmddyyyy = "MM/dd/yyyy"
case mmmd_yyyy = "MMM d, yyyy"
case llll_yyyy = "LLLL ,yyyy"
case TIME = "HH:mm:ss"
case day = "dd"
}
When I try to get day from date it always gives next day date. For example if date = "2022-03-04T14:30:00-08:00" then if I try to get day using code below
date?.getDateAccoringTo(format: .day)
This returns 5 not 4
date?.getDateAccoringTo(format: .TIME)
This returns time 00:00:00
Am I missing something important which is leading to these values?
Also, if I am missing something in my question kindly let me know so that I can improve it.
The given string
let dateTime = "2022-03-04T14:30:00-08:00"`
is a standard ISO8601 formatted date string. It can be converted to Date with
let formatter = ISO8601DateFormatter()
let date = formatter.date(from: dateTime)!
At this specific point in time it is
14:30 on Friday, March 4 in Denver, CO, USA
22:30 on Friday, March 4 in London, UK
06:30 on Saturday, March 5 in Tokyo, Japan
Now let's see how Xcode displays dates.
print displays Date instances always in UTC indicated by +0000 which is the London time zone unless you print(date.description(with: .current), this displays the date in the local time zone.
In a Xcode Playground the result area displays Date instances in the local time zone except in print lines.
Last point to consider is that DateFormatter converts Date to String in the local time zone if no time zone is specified.
Keeping this behavior in mind you get the next day if you convert the date to string with DateFormatter but without specifying the time zone and your local time zone is greater than or equal to +01:30.
And you get the time 00:00 if you convert the date to string with DateFormatter but without specifying the time zone and your local time zone is exactly +01:30 which is a pretty unusual time zone by the way.

Swift 5 Datetime Conversion triple timezone cast (???)

I have a DateTime that I'm reading from an API that is in GMT.
I want to force cast it to an EST date object.
The original object is a string that I then cast to a Date to do some time comparison. Unfortunately, I don't think I did this right which made me do this unholy abomination:
extension String {
//this force casts from our original data being in GMT to EST
func getDateTimeTZ() -> Date{
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm"
dateFormatter.timeZone = TimeZone(abbreviation: "GMT")
if let date = dateFormatter.date(from: self) {
dateFormatter.timeZone = TimeZone(abbreviation: "EST")
let localTime = dateFormatter.string(from: date)
dateFormatter.timeZone = TimeZone(abbreviation: "GMT")
return dateFormatter.date(from: localTime)!
}
return dateFormatter.date(from: self)!
}
}
For whatever reason, I seem to have to flip the dateformatter 3 times to get the correct EST string output. Is there a better way to do this?
A Date is not associated with a time zone. From the Date reference:
A specific point in time, independent of any calendar or time zone.
...
A Date value encapsulate a single point in time, independent of any particular calendrical system or time zone. Date values represent a time interval relative to an absolute reference date.
So there is no such thing as “an EST date object” in the iOS SDK.
Time zones are relevant when converting to or from strings and when manipulating the calendrical components (year, month, day, hour, minute, second, etc.) of a Date.
The code you posted computes a new Date that is offset from the original date by the time offset between GMT and EST, but the new Date is not “an EST date object”.
(Incidentally, "EST" means Eastern Standard Time, and will not switch to daylight saving time during the appropriate part of the year. Use "US/Eastern" to get the appropriate time conversion depending on the day of year.)
You say you're doing this adjustment “to do some time comparison”, but you don't say what you're comparing to. It would help for you to describe what comparison you're performing.
For example, let's you want to check whether the Date is in the range 8:30 AM to 3:00 PM in the US/Eastern time zone, regardless of the day. If the input string is GMT, then you should parse it that way, as you do in your code:
let parser = DateFormatter()
parser.timeZone = TimeZone.init(identifier: "GMT")
parser.dateFormat = "yyyy-MM-dd HH:mm"
let date = parser.date(from: "2021-03-26 19:55")!
Then, use a Calendar set to the "US/Eastern" TimeZone to compute the hour and minute components of the Date:
var calendar = Calendar.current
calendar.timeZone = TimeZone(identifier: "US/Eastern")!
let components = calendar.dateComponents([.hour, .minute], from: date)
let hhmm = components.hour! * 100 + components.minute!
if (08_30 ..< 15_00).contains(hhmm) {
print("school time")
} else {
print("play 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))

TimeZone Date Formatting Issue

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"

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.