Sometimes getting nil while converting UTC date string to timestamp - ios

Sometimes I am getting nil while converting date string to timestamp.
Here is my code:
class func createTimeStampFromDateString(dateString : String) -> Double? {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
let convertedDate = dateFormatter.date(from: dateString)
let timeIntervalsince1970 = convertedDate?.timeIntervalSince1970
return timeIntervalsince1970
}
Could you please let me know what is wrong in this code.
Thanks in advance!

Based on your comments, the bug is not because of this function (it is working as expected). The bug (the crash) you're experiencing is because you're force unwrapping a date value that is nil, and that is because the string used to create that date isn't in the valid format. And this is the nature of working with dates and strings from sources outside of your app—the strings aren't guaranteed to be in the right format. And this is why your function, as it should be, returns an optional value.
By the way, you could simplify your method and take advantage of the ISO8601DateFormatter:
func timestamp(from string: String) -> Double? {
let df = ISO8601DateFormatter()
df.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
return df.date(from: string)?.timeIntervalSince1970
}
And to use it properly, use it conditionally without force unwrapping:
if let interval = timestamp(from: "2020-06-02T13:38:31.814Z") {
print(interval)
}
To correct your bug:
Don't force unwrap when working with date/string conversions. If the string is ever not in the correct format, consider discarding the result, providing a default value, displaying something like "unknown"—anything but forcing the app to crash.
Determine why the string isn't in the right format and see if that's correctable.

When using the standard DateFormatter for an ISO8601 date, you should set the locale to en_US_POSIX (dateFormatter.locale = Locale(identifier: "en_US_POSIX")) before converting from string.
Also when converting a string to a date it can always happen that the string is in the wrong format (missing or wrong number/character). There are many things that can be wrong here.
But for ISO8601 dates there is a dedicated ISO8601DateFormatter. It can handle multiple variants of ISO8601 formatted dates. But it is only available since iOS 10.

Related

How to get only time from date in swift

This question is not asked for the first time, but no solution works for me. I am getting time in String from Api response in the following format "14:45".
I want to compare this time with the current time, for this purpose I need to convert this string in Time formate(swift sports)
I always get nil after conversion
I have tried multiple ways but none of them worked for me and one is given for reference, I don't know what am I missing here
Thanks for any response
func stringToTime(str:String) -> Date{ // 14:45 passed here in str
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "hh:mm a"
print(str)
//here time string prints
let date = dateFormatter.date(from: (str))
print(date)
//date is nil here, should be 02:45 pm
return date!
}
If the time you get from the API is in 24h format you can do a string comparison
let formatter = DateFormatter()
formatter.dateFormat = "HH:mm"
let currentTime = formatter.string(from: Date())
let compare = currentTime.compare("14:45")
You might need to set the time zone for the DateFormatter to make sure it uses the same as the API
It seems like you want to transform a time string in one format to another format. Your method signature should look like this:
func changeFormat(str:String) -> String {
Note that you should not output a Date here, because Dates don't have formats. They will always be printed in the same way. What you need to do in this method is 2 things:
parse str to a Date using a DateFormatter, specifying the format HH:mm. You seem to assume that DateFormatter can automatically work this format out. It can't :(
format the Date object you just got using a DateFormatter, specifying the format hh:mm a. This produces a string, not a date.
(You could also consider having the method return a Date (then it would be called parseTime), and do the second step just before you show the date to the screen.)
func changeFormat(str:String) -> String {
let dateFormatter = DateFormatter()
// step 1
dateFormatter.dateFormat = "HH:mm" // input format
let date = dateFormatter.date(from: str)!
// step 2
dateFormatter.dateFormat = "hh:mm a" // output format
let string = dateFormatter.string(from: date)
return string
}

Why does NSDateFormatter not return nil when given empty string ("")

I have a quick question.
According to apple official documentation
https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSDateFormatter_Class/#//apple_ref/occ/instm/NSDateFormatter/dateFromString:
NSDateFormatter.dateFromString(String) would return
A date representation of string interpreted using the receiver’s current settings. If dateFromString: can not parse the string, returns nil.
Then I when I give it an empty string "", should it return nil?
When I do this in the playground
let date = ""
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "yyyyMMdd"
print(dateFormatter.dateFromString(date))
It gave me
"Optional(1999-12-31 15:00:00 +0000)\n"
Is this happen to be a normal behaviour?
Or am I doing something wrong?
Thank you
If dateFromString: can not parse the string, returns nil
The simple answer is, the dateFromString does manage to parse an empty string. If you try and put wrong data in the string, say "0" then you will get nil.
Why is it happening? I assume that it is just they way the class is written, since the date formatter does skip missing data and try to complete it. There is probably not a case for "all is missing".
Exactly. If it's not possible to parse the string it will return nil but if you use an empty string it will return that date by default.
Change the "" for " " or anything to fill it if it's nil what you want to obtain.

Swift 2.1 xcode 7 fatal error: when i change string to date but is working in swift 2.0

I am simply string convert in nsdate but its always return me error "fatal error: unexpectedly found nil while unwrapping an Optional value" Line number 6 let startDate:NSDate = dateFormatter.dateFromString(start)!
Here is my code. Please suggest a solution, because this code working in swift 2.0 version but is't crash on swift 2.1
func doSomething(getdate:String) -> String {
let start = getdate
print("start==\(start)")
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "yyyy-­MM-­dd HH:mm:ss"
let startDate:NSDate = dateFormatter.dateFromString(start)!
let date:String? = (self.timeAgoSinceDate(startDate, numericDates: Bool()))
return date!
}
when I print a start variable is look like this start==2015-09-15 06:35:53
The reasons for your error is the forced unwrapping of a nil return from method .dateFromString() method (class method for NSDateFormatter). You code is, in it's current form, unsafe, and just happened to pass without runtime errors in your previous runs.
From the language reference for class NSDateFormatter method .dateFromString(), we have
...
Returns a date representation of a given string interpreted using the
receiver’s current settings. A date representation of string
interpreted using the receiver’s current settings. If dateFromString:
can not parse the string, returns nil.
To begin with, we should make sure the behaviour of your code is well-defined by avoiding forced unwrapping of optional, by using if-let clauses (or guard-let, or nil coalescing operator ??) instead of forced unwrapping (!).
Now, I don't have the context of your code, but consider fixing the forced unwrapping in you function to change it into a form like
func doSomething(getdate:String) -> String {
let start = getdate
print("start==\(start)")
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "yyyy-­MM-­dd HH:mm:ss"
var date : String?
if let startDate = dateFormatter.dateFromString(start) {
date = (self.timeAgoSinceDate(startDate, numericDates: Bool()))
}
return date ?? ""
}
With this out of the way, we can proceed to examine why .dateFromString(start) fails to parse the string.
Based on the following threads
NSDateFormatter returns nil in swift and iOS SDK 8.0,
Swift: NSDateFormatter dateFromString returns nil on devices,
possibly, you need to specify the .locale property of the dateFormatter. Try to add one of the two following line
dateFormatter.locale = NSLocale(localeIdentifier: "us")
dateFormatter.locale = NSLocale(localeIdentifier: "en_US_POSIX")
right after you've defined the dateFormatter.dateFormat.
Note, however, that the most probable cause as to why .dateFromString(start) fails to parse the string, and hence returns nil, is that your string start simply does not comply to dateFormat. It does look, given your printed output of start, that it does, but please have an extra look to see if this is really always the case, for all possible calls to your function. Neither .locale or .timeZone should affect whether .dateFromString(...) can parse its argument or not. Only .dateFormat controls this success or non-success of string parsing.
import Foundation
let str = "2015-09-15 06:35:53"
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
guard let startDate = dateFormatter.dateFromString(str) else {
exit(-1)
}
print(startDate) // 2015-09-15 04:35:53 +0000
dateFormatter.timeZone = NSTimeZone(abbreviation: "UTC")
guard let startDateUTC = dateFormatter.dateFromString(str) else {
exit(-1)
}
print(startDateUTC) // 2015-09-15 06:35:53 +0000
Please, follow dfri instructions about binding the result of dateFromString()
If there in no specification about timezone in your string, use "UTC" as timeZone. Then you have some 'reference' date in very known format and you can compare it or make something else ... If you know the timeZone of the source of your string, you can use that timeZone directly.
dateFormatter.locale = NSLocale(localeIdentifier: "en_US_POSIX")
will NOT change the timeZone settings, so check the resulting NSDate before use!
For your reference I recommend you this reading

NSDate and string without timezone information

I am parsing some XML that is returned by a web service. The string: 2015-12-24T12:00:00 is found in the fromTimestamp with no timezone. The "geniuses" (I don't mean that) keeps the timezone information in a own field in the XML in minutes. So 300 means GMT+05:00.
I have been reading a lot about NSDate and all this timezone stuff and I know that NSDate don't care about timezones, thats NSDateFormatter job.
However, in order to "convert" the timestamp without the timezone so that the value in NSDate represents a GMT time I add the GMT+05:00 to the string so it becomes 2015-12-24T12:00:00 +05:00. This is how it must be done right? Without adding the timezone when you convert from string to date the NSDate thinks the value is the GMT time? Thats the part I don't understand about it. It would be wrong to just convert the string without that timezone information because NSDate wouldn't be able to subtract 5 hours from the value inside NSDate? Is that correct? I am having a hard time explaining it.
Your assessment is correct and your solution is one of two possible solutions.
To ensure the date string is properly converted to an NSDate, you can do one of two things:
You need to ensure the date string has timezone information included and the date formatter's format string includes the proper format specifier for the timezone. This is what you describe in your question.
You leave the date string as you have it, without timezone information. But you set a specific timezone on the date formatter based on the timezone field you get from the XML.
Either approach will give you the proper NSDate.
Update: My second approach is shown in the answer by Martin R.
It may be simpler to set the time zone of the date formatter
explicitly. Example (error checking omitted for brevity):
let fromTimestamp = "2015-12-24T12:00:00"
let timeZoneInfo = "300"
let fmt = NSDateFormatter()
fmt.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
let secondsFromGMT = Int(timeZoneInfo)! * 60
fmt.timeZone = NSTimeZone(forSecondsFromGMT: secondsFromGMT)
let date = fmt.dateFromString(fromTimestamp)
I found it more convenient to wrap Martin's approach as an extension of the String class. It also prepends it with current timestamp and writes text ta a debugger output.
Swift 5:
"Hello world".log()
2019-09-05 12:18:10 Hello world
extension String {
func log() {
let fmt = DateFormatter()
fmt.dateFormat = "yyyy-MM-dd' 'HH:mm:ss"
let formattedString = "\(fmt.string(from: Date())) \(self)"
print(formattedString)
let log = URL(fileURLWithPath: "log.txt")
do {
let handle = try FileHandle(forWritingTo: log)
handle.seekToEndOfFile()
handle.write((formattedString+"\n").data(using: .utf8)!)
handle.closeFile()
} catch {
print(error.localizedDescription)
do {
try self.data(using: .utf8)?.write(to: log)
} catch {
print(error.localizedDescription)
}
}
}
}

Swift - extracting hours from NSDate, parsing string issue

I receive a string in Json and first of all I have to do is to convert it into NSDate. The problem is, none of string formats I used is valid. the code goes as follows:
let formatter = NSDateFormatter()
formatter.dateFormat = "yyyy-MM-dd hh:mm:ss"
var output = formatter.dateFromString("2000-01-01T00:00:00.000Z")
let timeString = formatter.stringFromDate(output)
as far as I know, if I want to retrieve hours from NSData, I have to call formatter once more
formatter.dateFormat = "hh"
and call it on NSDate obtained from string. Am I right?
My first question is: how to make the determine proper date format so the output will not be evaluated to nil? The second question is: Do I get it right or there is a simpler method or generally way to retrieve the hours from the following string: "2000-01-01T00:00:00.000Z" ? I know I can do it via dealing with mere string without involving dateFormatter and NSDate, but won't such solution be vulnerable? Please advice me what's the simplest(and robust) way to deal with this.
Thanks in advance
First of all, you have your formatter wrong...
let formatter = NSDateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'hh:mm:ss.SSS'Z'"
var output = formatter.dateFromString("2000-01-01T00:00:00.000Z")
After that, you can get the hour component with
if let date = output {
var hours = NSCalendar.currentCalendar().component(.HourCalendarUnit, fromDate: date)
}

Resources