let theDate:NSDate = dateFor.dateFromString(date)! provides the error
unexpectadly found nil while unwrapping an optional value, theDate returns as uninitialized and as nil.
if let date = review.createdAt {
let dateFor:NSDateFormatter = NSDateFormatter()
dateFor.timeZone = NSTimeZone(abbreviation: "UTC")
dateFor.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
let theDate:NSDate = dateFor.dateFromString(date)!
let tempoDate = Tempo(date: theDate)
let timeStamp = tempoDate.timeAgoNow()
if let reviewString = review.description {
let review = NSMutableAttributedString(string: reviewString + " - ")
let x = NSAttributedString(string: timeStamp, attributes: [NSForegroundColorAttributeName : UIColor.lightGrayColor()])
review.appendAttributedString(x)
vivrcell.reviewDescription.attributedText = review
vivrcell.reviewDescription.sizeToFit()
}
}
Why is this happening how do i fix it? this worked in swift 1.2
dateFor.dateFromString(date)!
returns nil because 'date' contains a wrongly formatted string which can not be converted to a date. But you are force unwrapping it, that creates the error. Instead of force unwrapping it with ! do it with an if let:
if let parsedDate = dateFor.dateFromString(date) { ... }
to check if the parsing works.
Related
I have an issue. I have a dictionary type [String: Any]
my code that works is
dict["start"] = "\(start.hour!):\(start.minute!)"
if let end = end {
dict["end"] = "\(end.hour!):\(end.minute!)"
}
But as I use swiftlint it throws me an error for force unwrapping. Value must be saved so if let is not good here :)
that is mostly a semantic issue, but you could do something like this:
if let startHour = start.hour,
let startMinute = start.minute {
dict["start"] = "\(startHour):\(startMinute)"
if let end = end,
let endHour = end.hour,
let endMinute = end.minute {
dict["end"] = "\(endHour):\(endMinute)"
}
}
...or something similar – as there are various ways in Swift to safely unwrap an optional.
You can try the following demo code. I hope it will help you.
import Foundation
let calendar = Calendar.current
let dateComponents = DateComponents(hour: 12, minute: 20, second: 55)
func getHoursMinutesFrom(time: DateComponents) -> (hour: Int,minute: Int) {
switch (time.hour, time.minute) {
case let (.some(hour), .some(minutes)):
return (hour,minutes)
default:
return (0,0)
}
}
print(getHoursMinutesFrom(time: dateComponents))
My data source is
{"events": [{"name":"event
foo","date":"2018-07-21","time":"7:00","am_or_pm":"PM","day":"Saturday","description":"test
"}, {"name":"event
bar","date":"2018-07-21","time":"7:00","am_or_pm":"PM","day":"Saturday","description":"test2"},
{"name":"event
foobar","date":"2018-07-21","time":"11:00","am_or_pm":"PM","day":"Saturday","description":"test3"}]}
I have tried dictionary/arrays, but not really getting close to my wanted result.
Pulling out data into an array:
var times = ["9:00","9:00","11:00"]
var names = ["event foo","event bar","event foobar"]
Desired output:
["9:00", "11:00"]
[["event foo", "event bar"], ["event foobar"]]
Any pointers to do this in Swift is appreciated. My end result is to hope to section a uitableview grouped by time.
If using Swift 4, you can use reduce(into:) and the default value for subscript operator:
guard
let json = (try? JSONSerialization.jsonObject(with: data)) as? [String: [Any]],
let events = json["events"] as? [[String: String]] else {
return
}
let results = events.reduce(into: [String: [String]]()) { result, value in
guard let time = value["time"], let name = value["name"] else { return }
result[time, default: []].append(name)
}
That results in a dictionary:
["11:00": ["event foobar"], "7:00": ["event foo", "event bar"]]
Or, as Vadian suggested, you can use Dictionary(grouping:,by:), but then you have to map it if you only want your name values, resulting in an array of tuples:
let results = Dictionary(grouping: events, by: { $0["time"]! })
.map { ($0.key, $0.value.map { $0["name"]! })}
[("11:00", ["event foobar"]), ("7:00", ["event foo", "event bar"])]
Personally, like Vadian suggested, I'd be inclined to combine date, time, and am_or_pm to build a full Date object and use one of the above patterns. E.g.:
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd h:mm a"
formatter.locale = Locale(identifier: "en_US_POSIX")
// if date/time values are in GMT, uncomment the following line:
//
// formatter.timeZone = TimeZone(secondsFromGMT: 0)
let results = events.reduce(into: [Date: [String]]()) { result, value in
guard
let timeString = value["time"],
let dateString = value["date"],
let amPm = value["am_or_pm"],
let date = formatter.date(from: dateString + " " + timeString + " " + amPm),
let name = value["name"] else { return }
result[date, default: []].append(name)
}
or
let results = Dictionary(grouping: events, by: { dictionary -> Date in
let string = dictionary["date"]! + " " + dictionary["time"]! + " " + dictionary["am_or_pm"]!
return formatter.date(from: string)!
})
.map { ($0.key, $0.value.map { $0["name"]! })}
Or, if the web service returned a single ISO 8601/RFC 3339 string representation of the date, time, and am/pm in the JSON, this could be simplified further.
I recommend to decode the JSON with Decodable and create a full date from the components. Then use Dictionary(grouping:by:) to group the array.
First create a DateFormatter (uncomment the time zone line if you need absolute UTC dates)
let dateFormatter : DateFormatter = {
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
// formatter.timeZone = TimeZone(secondsFromGMT: 0)!
formatter.dateFormat = "yyyy-MM-dd hh:mm a"
return formatter
}()
Create two structs for the root element and the events array. A custom initializer creates the Date instance
struct Root : Decodable {
let events : [Event]
}
struct Event : Decodable {
let date : Date
let name, description : String
private enum CodingKeys: String, CodingKey { case name, date, time, am_or_pm, description}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decode(String.self, forKey: .name)
description = try container.decode(String.self, forKey: .description)
let datePortion = try container.decode(String.self, forKey: .date)
let timePortion = try container.decode(String.self, forKey: .time)
let ampm = try container.decode(String.self, forKey: .am_or_pm)
let dateString = "\(datePortion) \(timePortion) \(ampm)"
guard let fullDate = dateFormatter.date(from: dateString) else {
throw DecodingError.dataCorruptedError(forKey: .date,
in: container,
debugDescription: "Date cannot be created")
}
date = fullDate
}
}
Decode the JSON and group the array
let jsonString = """
{"events": [{"name":"event foo","date":"2018-07-21","time":"7:00","am_or_pm":"PM","day":"Saturday","description":"test "}, {"name":"event bar","date":"2018-07-21","time":"7:00","am_or_pm":"PM","day":"Saturday","description":"test2"}, {"name":"event foobar","date":"2018-07-21","time":"11:00","am_or_pm":"PM","day":"Saturday","description":"test3"}]}
"""
do {
let data = Data(jsonString.utf8)
let decoder = JSONDecoder()
let result = try decoder.decode(Root.self, from: data)
let grouped = Dictionary(grouping: result.events, by: { $0.date})
print(grouped)
} catch {
print("error: ", error)
}
Looked at similar SO questions and found that there are no answers in Swift, there are plenty of answers exclusively for date formatting but not in combination with iteration.
I have an array of date strings like the below:
let dateStrings = ["2016-12-22T08:00:00-08:00", "2016-12-22T08:15:00-08:00", "2016-12-22T08:30:00-08:00"]
I would like to convert them to an array of local dateObjects like the below
var dateObjects = [2016-12-22 21:30:00 +0530, 2016-12-22 21:45:00 +0530, 2016-12-22 22:00:00 +0530]
I've tried iterating through the array of date strings but I get this error :
"fatal error: unexpectedly found nil while unwrapping an Optional value"
So I tried with optional binding without success. Please advice where I could be going wrong.
var dateObjects = [Date]()
let dateFormatter = DateFormatter()
for date in dateStrings{
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ"
let dateObject = dateFormatter.date(from: date)
self.dateObjects.append(dateObject!) // ERROR LINE
}
When I try with optional binding its going through else statement printing "Unable to convert to date object"
for date in dateStrings{
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ"
if let dateObject = dateFormatter.date(from: date){
self.dateObjects.append(dateObject!) // ERROR LINE
}
else{
print("Unable to convert to date object")
}
}
In second case you are force wrapping non-optional value there is no need of that and you are writing self with dateObjects var that is not declare as a instance property, also you can simply use flatMap to reduce your code and create array of Date from array of String.
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
let dateObjects = dateStrings.flatMap { dateFormatter.date(from: $0) }
Try this: date object is local variable so remove self. if you want use self then make it global
let dateStrings = ["2016-12-22T08:00:00-08:00", "2016-12-22T08:15:00-08:00", "2016-12-22T08:30:00-08:00"]
var dateObjects = [Date]()
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ"
for date in dateStrings{
let dateObject = dateFormatter.date(from: date)
dateObjects.append(dateObject!)
}
print(dateObjects)
Simply remove self from dateObjects.
dateObjects is local variable. So don't use self with dateObjects.
var dateObjects = [Date]()
let dateFormatter = DateFormatter()
for date in dateStrings{
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ"
let dateObject = dateFormatter.date(from: date)
dateObjects.append(dateObject!)
}
What I did was that I mapped over the the array and made each item into a new Date object.
let dateObjects = _.map(dateStrings, (dateString) => new Date(dateString)
It's that easy. In my case, I was trying to compare the dates to another array of dates so after doing new Date(dateString) I converted them back into a ISOString by doing
let dateObjects = _.map(dateStrings, (dateString) => new Date(dateString).toISOString()
Note:
You don't need to use _.map, you could just use .map
Try This..
Swift 3
let dateStrings = ["2016-12-22T08:00:00-08:00", "2016-12-22T08:15:00-08:00", "2016-12-22T08:30:00-08:00"]
func convertDate(str: String) -> Date {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssxxx"
let dateObject = dateFormatter.date(from: str)
return(dateObject!)
}
var stringDates : [Date] = []
for date in dateStrings {
stringDates.append(convertDate(str: dateStrings[0]))
}
HI guys getting a well known error while unwrapping an optional error. Ran in debugger mode and it falls down at the line below. Im using an implicit unwrap which I thought would be fine here as I am definitely selecting a date. Works in the simulator but not on the iPhone.
im guessing I can use an if let statement to be on the safe side, just wondered how I would implement this and am looking for some assistance.
Hope someone can help
Many thanks
if startDateTextField.text == "" || endDateTextField == "" {
let alert2 = UIAlertController(title: "Oops!", message: "Please Select an End Date!", preferredStyle: UIAlertControllerStyle.Alert)
alert2.addAction(UIAlertAction(title: "Close", style: UIAlertActionStyle.Default, handler: nil))
self.presentViewController(alert2, animated: true, completion: nil)
}
else {
let start = String(startDateTextField.text)
let end = String(endDateTextField.text)
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "MM-dd-yyyy"
guard let startDate = dateFormatter.dateFromString(start), endDate = dateFormatter.dateFromString(end) else {
print("error")
return
}
let calendar = NSCalendar.currentCalendar()
let components = calendar.components([.Day], fromDate: startDate, toDate: endDate, options: [])
let days = components.day
let weeks = components.day / 7
let weeksanddays = days % 7
let newString = "\(weeks) weeks + \(weeksanddays) days"
resultWeeksAndDays.text = newString
You are using the ! on a task that may produce errors. You can't just believe that transforming any string from the input (that user gives!) will be the transformed to NSDate(). You can even have something lacking in the data format or even in input constraints. Just use if let instead of explicitly unwrapping the optionals, or even better, use guard. What to do exactly?
Instead of :
let startDate: NSDate = dateFormatter.dateFromString(start)!
let endDate: NSDate = dateFormatter.dateFromString(end)!
Do:
guard let startDat = dateFormatter.dateFromString(start), endDate = dateFormatter.dateFromString(end) else {
// You don't have dates, show error, do no nothing - your choice.
return
}
And after the block you are 100% sure you get the objects. Are they correct? Probably yes if the formatter you specified is correct. But you have objects, and thats the fact. Now you can proceed with your operations.
To be honest, you should also do the same checks with the string getters from UI objects (these two below), because you may delete one from the view and bam..
let start = String(startDateTextField.text!)
let end = String(endDateTextField.text!)
I'm trying to make a simple clock app and I'm running into an issue.
#IBAction func toggle(sender: UISwitch) {
func formatADate() {
var dateFormatter = NSDateFormatter()
dateFormatter.dateStyle = .ShortStyle
dateFormatter.dateFormat = "hh:mm:ss a"
let date = NSDate()
let output = dateFormatter.stringFromDate(date)
println(output)
}
let clockString: String = formatADate()
clockFace.hidden = false
clockFace.text = clockString
}
But I keep getting the error () is not convertible to String. Any idea why is this happening?
The formatADate function is declared as taking no parameters and returning void (i.e. nothing), whereas in this line
let clockString: String = formatADate()
you are assigning its return value (void) to a string.
You just have to declare that function as returning a string:
func formatADate() -> String {
var dateFormatter = NSDateFormatter()
dateFormatter.dateStyle = .ShortStyle
dateFormatter.dateFormat = "hh:mm:ss a"
let date = NSDate()
let output = dateFormatter.stringFromDate(date)
println(output)
return output
}
I'm making the assumption that output is what you want it to return - if not, change accordingly.
Your formatADate function should be defined as returning a String
func formatADate()-> String { // use -> to show returned type
var dateFormatter = NSDateFormatter()
dateFormatter.dateStyle = .ShortStyle
dateFormatter.dateFormat = "hh:mm:ss a"
let date = NSDate()
let output = dateFormatter.stringFromDate(date)
return output // return string type
}
let clockString: String = formatADate()