I am storing a document within a collection in a Cloud Firestore database. In this document, I want a reference to when the document was stored so I am using Firestore's FieldValue object which has a serverTimeStamp() function.
I am unable to parse this FieldValue object on the client as either a Date/NSDate or String. I have spent some time reading the Firestore iOS documentation and cannot find any leads on this.
There is no issue getting the FieldValue from the database to the client, however, I am unable to cast/convert the timeStamp of type FieldValue to anything.
Attempts to convert to string and date:
let timeStampString : String = message.timeStamp
let timeStampDate = Date(timeIntervalSince1970: message.timeStamp)
Cannot assign value of type FieldValue to type String
Cannot convert value of type FieldValue to expected argument type TimeInterval (aka Double)
Edit: After reviewing Doug Stevenson's answer, the best way to handle this is by casting your timeStamp value to a TimeStamp (not FieldValue) when reading the info on the client.
let timeStamp = document["yourTimeStampKey"] as! TimeStamp
rather than
let timeStamp = document["yourTimeStampKey"] as! FieldValue
There's no parsing needed. Firestore timestamp fields are of type Timestamp, which you should use directly. It represents a point in time with nanosecond precision.
Code:
let timeStamp = document["yourTimeStampKey"] as! TimeStamp
rather than
let timeStamp = document["yourTimeStampKey"] as! FieldValue
Related
I have this code let Model = i.get("Model") as! String I am using it from my firestore database. I am trying to make it search that. I tried it with other fields and it worked just not with some that were integers and Model when model in my database is a String. I get this error Could not cast value of type '__NSCFNumber' (0x7f851d873250) to 'NSString' (0x7fff86cf7118).
I have a Vapor 3 API up on Heroku. Unfortunately, it's not handling dates correctly. Originally, I thought I could just treat dates like strings for simplicity in Vapor, like so:
struct MyModel {
var time: String?
}
But whenever I fetch MyModels from the db and return it, the time key doesn't appear at all (while other keys and values have no problems). I thought I might be able to just change time's type to Date, but that resulted in the same thing, and I've already used ContentConfig to set the JsonEncoder.dateEncodingStrategy to .iso8601 (again, no luck – perhaps because dateEncodingStrategy only supports millis on Linux, which is what Heroku uses?).
How do I convert Postgres dates to ISO8601 in json with Vapor 3 running on Heroku?
Got it working! Just changed the properties to Dates, and manually converted request query parameters to Dates as well (for use in filter calls). So, a little more by hand than most things in Vapor 3, but not terrible.
Eg my model looks like this now:
struct MyModel {
var time: Date?
}
And then when I try to filter by date I do something like this:
var builder = MyModel.query(on: req)
if let afterString: String = try? self.query.get(String.self, at: "after") {
let afterDate: Date? = DateFormatter.iso8601Full.date(from: afterString)
builder = builder.filter(\.time > afterDate)
}
where after is a url parameter, and DateFormatter.iso8601Full is my iso8601 date formatter. Then when I'm returning an array of MyModels in a response, I map the array to an array of MyModelResponseObjects which look like this:
struct MyModelResponseObject {
var time: String?
}
by doing something like this:
myModelsFuture.all().map(to: [MyModelResponseObject].self, { (myModels) -> [MyModelResponseObject] in
return myModels.map { it in
return MyModelResponseObject(time: DateFormatter.iso8601Full.string(from: it.time ?? Date.init(timeIntervalSince1970: 0)))
}
}
So basically I'm manually converting the dates into the format that I want when returning them in JSON.
I am trying to implement a dictionary data structure in swift that stores an Array of Strings. I have declared it like:
var journeyDetails = [Int: [String]]()
When I want to append an actual string to it, I do
if let journeys = fetchedData["journeys"] as? [[String: Any]]{
var nr_of_journey : Int = 0
for journey in journeys{
self.journeyDetails[nr_of_journey]?.append("The starting date and time of the journey are: "+String(describing: journey["startDateTime"]))
}
}
nr_of_journey = nr_of_journey + 1
etc etc. However, journeyDetails keeps returning nil. Should I do any other type of initialization? Why is the data not appended?
Initially there are no keys or values in journeyDetails so every use of self.journeyDetails[nr_of_journey] returns nil.
If you are using Swift 4, you can specify a default value to be used if there currently isn't a value for the given key.
Update the line:
self.journeyDetails[nr_of_journey]?.append("The starting date and time of the journey are: "+String(describing: journey["startDateTime"]))
to:
self.journeyDetails[nr_of_journey, default: []].append("The starting date and time of the journey are: "+String(describing: journey["startDateTime"]))
This provides a default empty array if there currently isn't a value for the given nr_of_journey key.
I have an object from the server that is recognized by Swift 2.1 as either NSDate or NSNull. I want to put it into a struct with a property of type NSDate.
Is that possible? If not, how should I handle this to be type safe later when I use it?
struct Data {
var completedAt: [NSDate]
var name: [String]
var gender: [Bool]
}
but sometimes completedAt comes from the server as NSNull:
completedAt = "<null>";
Any help is very much appreciated, thank you.
Based on my interpretation of the text in the question you didn't mean to declare the variables as arrays.
This is how I handle my parson and I think it works pretty neatly.
The date formatter should probable not be initiated in every iteration of the constructor. If you won't use the date regularly you might want to keep the detesting until you need to parse the date or you can have a static date formatter utility that you only instantiate once.
struct Data {
var completedAt: NSDate?
var name: String
var gender: Bool
init?(dictionary: [String:AnyObject]) {
//Guessing that you want some of the values non optional...
guard let name = dictionary["name"] as? String,
let gender = dictionary["gender"] as? String
else {
return nil
}
self.name = name
self.gender = gender
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
//safe handle of optional values
if let completedAtString = dictionary["completedAt"] as? String, completedAt = dateFormater.dateFromString(completedAtString) {
self.completedAt = completedAt
}
}
}
Take a step back. For each item that the server might provide, there is no guarantee whatsoever that you receive what you expect, since you cannot control the server. So you need to decide how to react to which input.
In the case of expecting a date for example (if your data comes in JSON, that means you likely expect a string formatted in a certain way), the actual data that you receive might be an array, dictionary, string, number, bool, null, or nothing. You might then for example decide that you want to interpret nothing or null or an empty string as nil, that you want to interpret a string containing a well-formatted date as an NSDate, and anything else a fatal error in a debug version, and as either nothing or a fatal error in a release version. On the other hand, if an NSDate is absolutely required then you might interpret anything that doesn't give an NSDate as an error.
Then you write a function that delivers exactly what you want and use it. That way you can parse complex data, with your code warning you when something isn't as it should be, and with your code either surviving any possible input, or deliberately crashing on wrong input, as you want it.
My code is:
func getTimeStamps( tablename : String) -> String {
let time_stamps = db["time_stamps"]
let t_tabelle = Expression<String?>["tabelle"]
let t_time_stamp = Expression<String?>["TIME_StAMP"]
let query = time_stamps.filter(like(tablename, t_tabelle))
return query[t_time_stamp]
}
But I get an error on conversion:
Expression<String?> is not convertible to String
How can I return a String?
Thanks
Hauke
The error refers to the fact that your function signature for getTimeStamps, String -> String, has return type String, but the value you're returning, query[t_time_stamp], is an Expression<String?>.
Query structures can be subscripted with Expressions to return a namespaced version of the expression:
let id = Expression<Int64>("id") // literally: "id"
let users = db["users"]
let users_id = users[id] // literally: "users"."id"
In your case, subscripting query with t_time_stamp is merely returning a new, namespaced version of the t_time_stamp expression (in your version, "time_stamps"."TIME_StAMP"). This is helpful for disambiguation, but unlikely your intent.
It's tough to tell from the code provided exactly what you want to return from the function, but it looks like you want to execute the query in order to extract a value. Row structures, once fetched, can be subscripted with expressions to retrieve the underlying value.
If you're looking to retrieve a single row, try the following:
if let row = time_stamps.filter(like(tablename, t_tabelle)).first {
return row[t_time_stamp]
}
Your function, however, still returns String, not String?. If it's ever possible for your query to return zero rows or for any of the rows it does return to have a NULL timestamp column, you need to handle your optionals accordingly.
If, however, a NULL timestamp would indicate a programming error/bug, you should update String? to String accordingly:
let t_time_stamp = Expression<String>["TIME_StAMP"]
// ...
return query.first![t_time_stamp]
Please note that the above will crash if you're mishandling the potential for optional values.