Swift Date function do not work as Expected - swift

I am trying to find out the difference between two date in seconds using Swift 4.1. This is the code I use,
func getDurationInSeconds(date1 :Date) -> Int{
guard let durationInSeconds = Calendar.current.dateComponents([.second], from: Date(), to: date1).second else {
return 0
}
return durationInSeconds
}
Function to generate date1 from 2018-10-09T18:19:00Z
func dateFromString(stringDate:String) -> Date? {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
dateFormatter.locale = NSLocale(localeIdentifier: "en_US_POSIX") as Locale?
let date = dateFormatter.date(from: stringDate)
return date
}
The Date is always returning back an hour less than my current device time, so the calculation is not working as expected. If my current device time is 16:34 the Date() function returns it back as 15:34.
I have seen that Date() is returning back the time in UTC not based on my timezone.
At the moment if I pass in a Date 09/10/2018 14:25:00 and the current device time is 09/10/2018 14:20:00. I am expecting this function to return a value 300 which is 60 * 5 minute difference between two dates.
But I am getting back a value of 3900 which is because the date function returns the date as
09/10/2018 13:20:00 instead of 14:20
. So the duration will be 1 hour + the 300 second difference.
Including a sample output from Xcode console, my device time when I executed this code was 2018-10-09 17:56:28.565423
(lldb) po date1
▿ 2018-10-09 17:59:00 +0000
- timeIntervalSinceReferenceDate : 560800740.0
(lldb) po durationInSeconds
3731
(lldb) po Date()
▿ 2018-10-09 16:57:04 +0000
- timeIntervalSinceReferenceDate : 560797024.35021996
(lldb)
But I cant find a proper way to find the correct duration between two times based on my current time zone. How can I do it?

The issue is not with the Date() returning wrong time. Date() always returns the current time, which is not really based on your (any other) local timezone.
The problem seems to be with the dateFormatter that you are using to generate the Date object from the date string.
Please try using the following lines of code:
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
// Not necessary as the dateFormatter would take the device's timeZone to be the default.
dateFormatter.timeZone = Calendar.current.timeZone
instead of:
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
The problem with the latter is that, you are specifying 'Z' to be the zero-offset time zone (UTC). So, the difference of 1 hour from UTC in your device is causing this offset.
And, while passing in the date string, please make sure that you skip the 'Z' at the end (For example, it should be like 2018-10-09T18:19:00).
The updated code should work good for you, and return the expected difference in seconds.

Since you are using a string that represents the current time in your time zone, try this instead:
func getDurationInSeconds(date1: Date) -> Int {
return Int(-date1.timeIntervalSinceNow + TimeZone.current.daylightSavingTimeOffset(for: date1))
}
It uses this property and this method.
Or if you'd like to account for the time zone difference from UTC in dateFromString(stringDate:):
func getDurationInSeconds(date1: Date) -> Int {
return Int(-date1.timeIntervalSinceNow)
}
func dateFromString(stringDate:String) -> Date? {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
let date = dateFormatter.date(from: stringDate)! //I am force-unwrapping for brevity
let adjustedDate = date.addingTimeInterval(-TimeZone.current.daylightSavingTimeOffset(for: date))
return adjustedDate
}
Test
I am in a UTC + 1h timezone:
let str = "2018-10-09T19:50:00Z" //"2018-10-09T19:50:00Z"
let date1 = dateFromString(stringDate: str)! //"Oct 9, 2018 at 7:50 PM"
Date() //At the time of testing this it is "Oct 9, 2018 at 7:53 PM"
getDurationInSeconds(date1: date1) //213, which is 3 minutes and 33 seconds

Related

Swift get the remaining seconds of time on target string date

The current date time today was May 9, 2020 10:03 PM, and I have a target string date with the value of 2020-05-09 22:07:30 with the format of yyyy-MM-dd HH:mm:ss.
How can I get the remaining date from that 2 date and print the value with the string of 04:30 as the range of those 2 dates are 4 minutes and 30 seconds
What I can only do is convert the milliseconds to time format like
func msToTime(ms: Int) {
let seconds = ms % 60
let minutes = ms / 60000
return String(format: "%0.2d:%0.2d",minutes,seconds)
}
Output 04:30
But I don't know how to get the range of milliseconds from today's date time to target's date time.
Or if there's any other easier way to do it?
You can use Calendar and DateComponents to easily calculate differences between dates in whatever units you desire. For example, this gets the difference in minutes and seconds:
let dateformatter = DateFormatter()
dateformatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
let date = dateformatter.date(from: "2020-05-09 22:07:30")!
let now = Date()
let components = Calendar.current.dateComponents([.minute, .second], from: now, to: date)
print("difference: \(components.minute!):\(components.second!)")
A straightforward way, with no calculation of any kind needed:
let d1 = Date()
let s = "2020-05-09 22:07:30"
let f = DateFormatter()
f.dateFormat = "yyyy-MM-dd HH:mm:ss"
f.timeZone = TimeZone.current // or whatever
if let d2 = f.date(from: s) {
let f = DateComponentsFormatter()
f.unitsStyle = .positional
let result = f.string(from: d1, to: d2)
}
If you don't like the resulting string format of result, you can eliminate pieces of it. — However, note that this works only because no full days are involved. It isn't clear what the range of possible inputs might be or what the desired output would be if the second date were three months into the future, for example.

How to get a date from European time-only with DateFormatter in Swift

I try to get a date but I only have a time available.
For now, I don't care about year, month or day. All that matters is the time.
My input is in the form "hh:mm:ss" (i.e. European time format).
The problem is that any time above 12:59:59 does not work !
How can I get dates from times above 12:59:59 ????
My code looks like follows:
import UIKit
let str = "17:02:09"
let mydateFormatter = DateFormatter()
mydateFormatter.dateFormat = "hh:mm:ss"
let timezone = TimeZone(abbreviation: "CEST") ?? TimeZone.current
mydateFormatter.timeZone = timezone
mydateFormatter.locale = Locale(identifier: "de_DE") as Locale?
let date1 = mydateFormatter.date(from: str)
print(date1?.description ?? "no date available")
Output: no date available
If I set the str's hour setting to anything below or equal to 12, then it works.
For example, if I set str = "12:02:09", I get the output: 1999-12-31 23:02:09 +0000
For example, if I set str = "10:02:09, I get the output: 2000-01-01 09:02:09 +0000
For example, if I set str = "13:02:09, I get the output: no date available
How can I get dates from times above 12:59:59 ????
And ideally, how can I get dates that have a matching time with the input-time ?

How to display only the hours when minutes are zero with date formatter?

Am currently using the format MM-dd-yyyy HH:mm to display the time stamp. Is it possible to drop the minutes if the time does not have any minute component ?
Like, instead of 3:00 PM is it possible to display only 3 PM ?
EDIT:
func formatDate(_ dateFormat: String, timeInterval: TimeInterval) -> String {
let date = Date(timeIntervalSince1970: timeInterval)
let formatter = DateFormatter()
formatter.dateFormat = dateFormat
return formatter.string(from: date)
}
let formattedDate = formatDate("MM-dd-yyyy HH:mm", timeInterval: 1520422200)
print(formattedDate)
Option 1
Replace any with zero minutes on return string.
return formatter.string(from: date).replacingOccurrences(of: ":00", with: "")
Option 2
Determine if there are minutes, and adjust date format.
if (Int(timeInterval) % 3600 == 0) {
let newFormat = dateFormat.replacingOccurrences(of: ":mm", with: "")
// ...
}
You could use DateComponents to determine what the .minute component is. If it's non-zero set the date format to include minutes, and otherwise set it to leave it out.
Note if this is for a user-facing string, use setLocalizedDateFormatFromTemplate and for hours use "j" so you can respect a user's setting for whether to display 24 hour time.
Note it is probably strange for most users to see 24 hour time without the minutes (e.g. "9pm" vs "21", the latter doesn't look right and probably would be better with ":00").

Swift: get the offset value from a date object

as simple as it sounds, but it is hard to find my exact question in google.
I'm trying to ignore the UTC printed out value. I receive multiple dates, this one here is just an example: (it could be +0900, -0200, etc...)
"2017-05-01T12:30:00-0700"
once I apply it to a value using these lines:
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssxxxxx"
if let result = formatter.date(from: time) {print result}
the value of the dateTimeResult prints:
2017-05-01 19:30:00 UTC
Using swift date objects, how do I slice out the part "-0700", multiply the -7 or +7 (this example is negative) by minutes by seconds. I'll save that total as int in DB (I need it for categorizing the different timezones later). Then applying that total to the incoming date input using this line:
let output = Calendar.current.date(byAdding: .second, value: totalSecs, to: result)
The goal is to end up with this date:
"2017-05-01 12:30:00"
I already have a solution using string manipulation, but I don't think that is the ideal solution. If it must be done by string, how do you do it?
If I understand you correctly you want only the date and time portion ignoring always the time zone information.
In this case strip the time zone from the date string with regular expression
let dateString = "2017-05-01T12:30:00-0700"
let dateStringIgnoringTimeZone = dateString.replacingOccurrences(of: "[+-]\\d{4}", with: "", options: .regularExpression)
print(dateStringIgnoringTimeZone) // "2017-05-01T12:30:00"
let dateFormatter = DateFormatter()
dateFormatter.calendar = Calendar(identifier: .iso8601)
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
let date = dateFormatter.date(from: dateStringIgnoringTimeZone)!
I think you should keep the date as it is and then just use DateFormatter to display the time at that timezone
let time = "2017-05-01T12:30:00-0700"
let dateFormatter = DateFormatter()
dateFormatter.calendar = Calendar(identifier: .iso8601)
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssxxxxx"
if let result = dateFormatter.date(from: time) {
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
print(dateFormatter.string(from: result)) // "2017-05-01 16:30:00 (corresponding time at my location GMT-3)
// to display it at -0700 just set the formatter timaZone
dateFormatter.timeZone = TimeZone(secondsFromGMT: -3600 * 7)
print(dateFormatter.string(from: result)) // "2017-05-01 12:30:00\n"
}
To get the timezone offset from the string:
let hours = Int(time.suffix(5).prefix(3)) ?? 0
let minutes = Int(time.suffix(2)) ?? 0
let offset = hours * 3600 + minutes * 60
print(offset) // -25200

Converting NSDate from UTC to local gives wrong result

I am creating an NSDate from components of hour and minute. It is in GMT and prints correctly as:
0001-01-01 07:30:00 +0000
I then want to convert this to my local timezone (CET), so I set up the NSDateFormatter like so:
formatter.timeZone = NSTimeZone.localTimeZone()
This prints (using .Longstyle):
08.13.00 GMT+0.43
This is wrong—it is supposed to be GMT+1. Printing the .localTimeZone() gives the correct offset:
Local Time Zone (Europe/Oslo (CET) offset 3600)
Edit1: By adding this extension(from an answer linked in comments) to NSDate, I can offset the timezone by hand. But then I need to set the NSDateFormatter timezone to GMT, which I don't think is right.
extension NSDate {
func toLocalTime() -> NSDate {
let timeZone = NSTimeZone.localTimeZone()
let seconds : NSTimeInterval = Double(timeZone.secondsFromGMTForDate(self))
let localDate = NSDate(timeInterval: seconds, sinceDate: self)
return localDate
}
}
Edit2: I made a test project. The expected output is for the time printed to match the offset in the timezone. Instead it adds 43 minutes.
func applicationDidFinishLaunching(aNotification: NSNotification) {
let calendar = NSCalendar.currentCalendar()
let realDateComponents = calendar.components([.Hour, .Minute], fromDate: NSDate())
guard let realDate = calendar.dateFromComponents(realDateComponents)
else{fatalError("Unable to get real date.")}
let formatter = NSDateFormatter()
formatter.timeStyle = .ShortStyle
print(realDate)
print(formatter.stringFromDate(realDate))
print(NSTimeZone.localTimeZone())
}
// OUTPUT
0001-01-01 21:03:00 +0000
21.46
Local Time Zone (Europe/Oslo (CET) offset 3600)
NSDate objects encapsulate a single point in time, independent of any
particular calendrical system or time zone. Date objects are
immutable, representing an invariant time interval relative to an
absolute reference date (00:00:00 UTC on 1 January 2001).
Apparently you're creating the date from the .Hour and .Minute components, that sets the (indeterminate) year information to 01 (not 2001).
The point in time about 2000 years ago is a pretty large time interval which probably causes the weird behavior.