I am parsing json from server. I am getting 4 values in json so I created model class
class PriceData: Mappable {
var buy: Double?
var sell: Double?
var spot_price: NSNumber?
var timestamp: String?
var timesStampDt: Date?
required init?(map: Map) {
//
}
func mapping(map: Map) {
buy <- map["buy"]
sell <- map["sell"]
spot_price <- map["spot_price"]
timestamp <- map["timestamp"]
print(String(describing: GlobalMethods.dateFormat(dt: timestamp!)))
timesStampDt <- map[String(describing: GlobalMethods.dateFormat(dt:
timestamp!))]
}
}
I am geting timestamp as string type but I need to covert into date while parsing so I converted timeStamp to date by using this method and
static func dateFormat(dt: String) -> Date{
let formatter = Foundation.DateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
let date1 = formatter.date(from: dt)
print("date:\(String(describing: date1))")
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
let resultTime = formatter.date(from: dt)
return resultTime!
}
but I when passing the converted date to "timesStampDt" in mapping func
the value of timesStampDt is nil.
You probably want to use Transform method from object mapper. Check DateTransform class and use it like follows.
timesStampDt <- (map["timestamp"], DateTransform())
Tis will transform your timestamp to Date. You can further study the transform class and make any Object Transform for yourself, This is very flexible
Why don’t you simply let JSONDecoder handle the date parsing? You can set your format on the decoder to configure in what format you get your date with so the decoder knows how to decode it.
Doing it with JSONDecoder and Codable makes parse code a lot cleaner.
Here is a Codable tutorial for you to read.
Related
How can I show the time near a message when it was sent by the user?
I have tried using TimeInterval() and DateFormatter() but I haven't been able to implement them successfully. Also I am using a xib file for the message cell.
You can use Date and DateFormatter to get current date and time.
let formatter = DateFormatter()
formatter.dateFormat = "HH:mm:ss"
formatter.string(from: Date()) // Example: 19:38:20
formatter.dateFormat = "yyyy/MM/dd"
formatter.string(from: Date()) // Example: 2019/07/07
// Or you can use dateStyle and timeStyle.
formatter.dateStyle = .long
formatter.timeStyle = .medium
What is your backend? If you are using firebase, you can add a variable to the post called "timestamp" from there you can retrieve how long since that post was made, then you can easily get the local time and minus it from the time since it was posted. Give me more information about your stack and I might be able to give you more information.
In your message data model you could store a property for the messageTime as a Date object instead of a string(fetched from server) using a DateTransfrom. Now when you want to display that message date you could convert that messageTime Date object to a string with your own format for displaying using a DateFormatter. I am using a mapper here to map the json object to my data model object
public class Message:Mappable{
public var text: String
public var messageTime: Date
public var id: String
required public init() {
super.init()
}
required convenience public init?(map: Map) { self.init() }
open override func mapping(map: Map) {
text <- map["text"]
messageTime <- (map["created_at"], DateTransform())
id <- map["id"]
}
}
Then fetch the 'message' object.
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "hh:mm a" //07:30 PM
let messageTimeString = dateFormatter.string(from: message.messageTime)
We used Decodable, and have a time: Date field, but from server came only time with "HH:mm:ss" format. Another date parsers with DateInRegion.
But this field crash app
I try do smth in decoder, but cant see any properties (.count)
and I dont now what need do
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .custom({ decoder -> Date in
do {
let value = try decoder.singleValueContainer()
let string = try value.decode(String.self)
if string.conut == 8 {
if let date = DateInRegion(
string: string,
format: .iso8601(options: .withTime),
fromRegion: Region.Local()
) {
return date.absoluteDate
} else {
throw DecodingError.nil
}
} else if let date = DateInRegion(
string: string,
format: .iso8601(options: .withInternetDateTime),
fromRegion: Region.Local()
) {
return date.absoluteDate
} else {
throw DecodingError.nil
}
} catch let error {
Not sure what you are trying to achieve, but it seems you want to handle multiple date format in a single JSON decoder.
Given the response struct is Decodable:
struct Response: Decodable {
var time: Date
var time2: Date
}
You can configure your decoder to handle all the date format you want:
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .custom({ (decoder) -> Date in
// Extract string date from the container
let container = try decoder.singleValueContainer()
let stringDate = try container.decode(String.self)
// Trying to parse the "HH:mm:ss" format
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "HH:mm:ss"
if let date = dateFormatter.date(from: stringDate) {
return date // successfully parsed "HH:mm:ss" format
}
// Trying to parse the iso8601 format
if let date = ISO8601DateFormatter().date(from: stringDate) {
return date // successfully parsed iso8601 format.
}
// Unknown date format, throw an error.
let context = DecodingError.Context(codingPath: [], debugDescription: "Unable to parse a date from '\(stringDate)'")
throw DecodingError.dataCorrupted(context)
})
Note: This is an example code and it's not very optimized (DateFormatter init each time you want to parse a date, using the brute force parsing to determine whether the date is in "HH:mm:ss" format or not, ...).
I think, the best way to optimize this, is to use multiple decoders, each configured for a specific date format, and use the right one when needed. For example, if you are trying to parse a JSON from an API, you should be able to determine whether you need to parse an iso8601 format or something else, and use the right decoder accordingly.
Here is an example of the decoder working in a playground:
let json = """
{"time": "11:05:45", "time2": "2018-09-28T16:02:55+00:00"}
"""
let response = try! decoder.decode(Response.self, from: json.data(using: .utf8)!)
response.time // Jan 1, 2000 at 11:05 AM
response.time2 // Sep 28, 2018 at 6:02 PM
I am attempting to collect the date value from a Eureka DateTimeRow to then store it into Firebase but to store it I would need it to be in a string format. I have attempted this conversion but I receive the error 'Could not cast value of type 'Foundation.Date' (0x108af27e8) to 'Swift.String' (0x1086e99f8).'
I would like to know if there is something I am missing from my conversion method.
DateTimeRow:
<<< DateTimeRow("startDate"){
$0.title = "Start Date"
$0.value = NSDate() as Date
$0.cellUpdate { (cell, row) in
cell.datePicker.minimumDate = Date()
}
$0.onChange { row in
start = row.value!
}
}
Code getting the values of the Erueka form and converting:
let valuesDictionary = form.values()
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
let formattedDate = formatter.date(from: valuesDictionary["startDate"] as! String)
Thank you all feedback welcomed.
As you want to convert from a Date (Eureka) to a String (Firebase), you should use the string(from:) method of the DateFormatter, whereas you are attempting to use the date(from:) method.
// Date to String
func string(from date: Date) -> String
// String to Date
func date(from string: String) -> Date?
When you call .decode() to decode a struct, what exactly does it return?
I have look it up on the Apple Documentation, but all it says is "native format into in-memory representations." But what does this mean? Can anyone help me?
I'm asking this because my app is crashing when I get a null value from the JSON data, from this line of code:
let plantData = try decoder.decode([Plants].self, from: data)
Here is my struct:
struct Plants: Codable {
let date: String
let monthlyAVG: String?
enum CodingKeys : String, CodingKey {
case date = "Date"
case monthlyAVG = "30_Day_MA_MMBTU"
}
}
And Here is my Parsing code:
func parseJson() {
let url = URL(string: ebr_String)
// Load the URL
URLSession.shared.dataTask(with:url!, completionHandler: {(data, response, error) in
// If there are any errors don't try to parse it, show the error
guard let data = data, error == nil else { print(error!); return }
let decoder = JSONDecoder()
do{
let plantData = try decoder.decode([Plants].self, from: data)
print(plantData)
And Here is just a snippet of the information I am getting back:
MorrowTabbedApp.Plants(date: "2018-02-22", monthlyAVG: Optional("1210.06")), MorrowTabbedApp.Plants(date: "2018-02-23", monthlyAVG: nil)]
Here is the snippet of JSON from the web:
[
{"Date":"2018-02-21","30_Day_MA_MMBTU":"1210.06"},
{"Date":"2018-02-22","30_Day_MA_MMBTU":"1210.06"},
{"Date":"2018-02-23","30_Day_MA_MMBTU":null}
]
The decode method of JSONDecoder is a "generic" method. It returns an instance of whatever type you specified in the first parameter of the method. In your case, it returns a [Plants], i.e. a Array<Plants>, i.e. a Swift array of Plants instances.
If it's crashing because of a null value in your JSON, then you have to identify what was null, whether it was appropriate to be null, and if so, make sure that any Plants properties associated with values that might be null should be optionals.
Given your updated answer with code snippets, I'd suggest:
// Personally, I'd call this `Plant` as it appears to represent a single instance
struct Plant: Codable {
let date: String
let monthlyAVG: String? // Or you can use `String!` if you don't want to manually unwrap this every time you use it
enum CodingKeys : String, CodingKey {
case date = "Date"
case monthlyAVG = "30_Day_MA_MMBTU"
}
}
And:
do {
let plantData = try JSONDecoder().decode([Plant].self, from: data)
.filter { $0.monthlyAVG != nil }
print(plantData)
} catch let parseError {
print(parseError)
}
Note the filter line which selects only those occurrences for which monthlyAVG is not nil.
A couple of other suggestions:
Personally, if you could, I'd rather see the web service designed to only return the values you want (those with an actual monthlyAVG) and then change the monthlyAVG property to not be an optional. But that's up to you.
If monthlyAVG is really a numeric average, I'd change the web service to not return it as a string at all, but as a number without quotes. And then change the property of Plant to be Double or whatever.
You could, if you wanted, change the date property to be a Date and then use dateDecodingStrategy to convert the string to a Date:
struct Plant: Codable {
let date: Date
let monthlyAVG: String?
enum CodingKeys : String, CodingKey {
case date = "Date"
case monthlyAVG = "30_Day_MA_MMBTU"
}
}
and
do {
let decoder = JSONDecoder()
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.dateFormat = "yyyy-MM-dd"
decoder.dateDecodingStrategy = .formatted(formatter)
let plantData = try decoder.decode([Plant].self, from: data)
.filter { $0.monthlyAVG != nil }
print(plantData)
} catch let parseError {
print(parseError)
}
You might do this if, for example, you wanted the x-axis of your chart to actually represent time rather than an evenly spaced set of data points.
My app calls a web api that sometimes returns json dates in this format:
"2017-01-18T10:49:00Z"
and sometimes in this format:
"2017-02-14T19:53:38.1173228Z"
I can use the following dateformat to convert the 2nd one to a Date object:
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
But of course it doesn't work for the 1st one.
I've tried utilities like https://github.com/melvitax/DateHelper to see if it will work, but I haven't found a way to convert a json date (in any format) into a Date object.
Any recommendations?
Try both formats:
let parser1 = DateFormatter()
parser1.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
let parser2 = DateFormatter()
parser2.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
func parse(_ dateString: String) -> Date? {
let parsers = [parser1, parser2]
for parser in parsers {
if let result = parser.date(from: dateString) {
return result
}
}
return nil
}
print(parse("2017-01-18T10:49:00Z"))
print(parse("2017-02-14T19:53:38.1173228Z"))
Also note that the Z in the format is not a literal value.