In my Vapor 3 app, I have an Event model, which has the properties startDate: Date and endDate: Date.
Now, I'm wondering how to pass those date values in a POST request. In Postman, I tried the following in x-www-form-urlencoded:
startDate -> 2019-03-14
This returns the error below:
Could not convert to
Double: str(\"2019-03-14\")
Apparently, Date turns into Double.
So, instead, what value do I need to pass?
I know, that, in Postman, I can insert {{$timestamp}}, but 1) this doesn't answer my question when using the API outside Postman and 2) this doesn't allow me to enter a date other than now.
So the issue here is that by default, a Date instance is decoded using the time interval since Jan 1, 2001. The URL form decoder that Vapor uses doesn't support different date strategies like the JSONDecoder does at the moment, so you'll have to do the decoding a different way. Here are a couple of ideas I could come up with:
Manually implement the Event.init(from:) and .encode(to:) methods. Just to make sure you don't break the Fluent coding, you will probably have to add some extra logic, but it should work. Here's an example:
final class Event: Model {
static let formDateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.calendar = Calendar(identifier: .iso8601)
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = TimeZone(secondsFromGMT: 0)
formatter.dateFormat = "yyyy-MM-dd"
return formatter
}()
var startDate: Date
var endDate: Date
init(from decoder: Decoder)throws {
let container = try decoder.container(keyedby: CodingKeys.self)
if let start = try? container.decode(String.self, keyedBy: .startDate), let date = Event.formDateFormatter.string(from: start) {
self.startDate = date
} else {
self.startDate = try container.decode(Date.self, keyedBy: .startDate)
}
if let end = try? container.decode(String.self, keyedBy: .endDate), let date = Event.formDateFormatter.string(from: end) {
self.endDate = date
} else {
self.endDate = try container.decode(Date.self, keyedBy: .endDate)
}
}
}
I'm not sure about x-www-form-urlencoded cause I tested it and if I send a date as 0 it decodes it as 2001-01-01 00:00:00 +0000 thought it definitely should be 1970-01-01 00:00:00 +0000.
But with JSON payload you have a flexibility cause you could provide a JSONDecoder configured as needed for you.
struct Payload: Content {
var date: Date
}
router.post("check") { req throws -> Future<String> in
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .secondsSince1970 // choose it for unix-timestamp
return try req.content.decode(json: Payload.self, using: decoder).map { p in
return String(describing: p.date)
}
}
router.post("check") { req throws -> Future<String> in
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
formatter.timeZone = TimeZone(secondsFromGMT: 0)
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(formatter) // custom date formatter
return try req.content.decode(json: Payload.self, using: decoder).map { p in
return String(describing: p.date)
}
}
So for unix-timestamp you should send seconds from 1970 and e.g. 0 will be decoded to 1970-01-01 00:00:00 +0000.
And for custom format described above you should send dates like 2018-01-01 00:00:00 to decode it as 2018-01-01 00:00:00 +0000
extension ContentContainer where M: Request {
func decodeJson<D>(_ payload: D.Type) throws -> Future<D> where D: Decodable {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
formatter.timeZone = TimeZone(secondsFromGMT: 0)
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(formatter)
return try decode(json: payload, using: decoder)
}
}
so then you'll be able to decode your payload like this
router.post("check") { (req) throws -> Future<String> in
return try req.content.decodeJson(Payload.self).map { p in
return String(describing: p.date)
}
}
I realized that the Date object is returned in the following format when queried:
2021-12-31T14:29:00Z
So this is what I tried to pass and that worked! No need for any custom decoding.