Closed justin closed 1 month ago
Everything in the content state is not parsed by us, we just we just deliver it to the OS and the OS will automatically parse it for you using the default encodable/decodable behaviors. You will want to look at the default behavior of how a Date is decoded using JSONDecoder. I believe it should be seconds, the docs on this are pretty bad though - https://developer.apple.com/documentation/foundation/jsondecoder/2895216-datedecodingstrategy
I am not sure if you can set a custom date decoding strategy, the one you want here is https://developer.apple.com/documentation/foundation/jsondecoder/datedecodingstrategy/secondssince1970
I am pretty sure you can decode this yourself though, but it means you have to decode everything in the struct. It might be better to decode to an UInt and converting it to a date in a property wrapper.
I dug a bit on this to figure it out, this test is passing:
struct TestStruct: Codable {
let expiration: Date
enum CodingKeys: String, CodingKey {
case expiration
}
}
func testDateDecoding() throws {
let payload = """
{
"expiration": 1721840555
}
"""
let decoded = try JSONDecoder().decode(TestStruct.self, from: payload.data(using: .utf8)!)
XCTAssertEqual(Date(timeIntervalSinceReferenceDate: 1721840555), decoded.expiration)
}
Its timeIntervalSinceReferenceDate
, which is Returns a Date initialized relative to 00:00:00 UTC on 1 January 2001 by a given number of seconds.
I would recommend using an iso 8601 timestamp instead
This works if you want to use the values you have without having to figure out if you can give a widget a custom decoding strategy:
struct TestStruct: Codable {
let expirationSeconds: TimeInterval
var expirationDate: Date {
Date(timeIntervalSince1970: expirationSeconds)
}
enum CodingKeys: String, CodingKey {
case expirationSeconds = "expiration"
}
}
func testDateDecoding() throws {
let payload = """
{
"expiration": 1721840555
}
"""
let decoded = try JSONDecoder().decode(TestStruct.self, from: payload.data(using: .utf8)!)
XCTAssertEqual(Date(timeIntervalSince1970: 1721840555), decoded.expirationDate)
}
And here is the custom decoding handled in the struct:
struct TestStruct: Codable {
let expiration: Date
enum CodingKeys: String, CodingKey {
case expiration
}
init(from decoder: any Decoder) throws {
let container: KeyedDecodingContainer<CodingKeys> = try decoder.container(keyedBy: CodingKeys.self)
self.expiration = Date(
timeIntervalSince1970: try container.decode(TimeInterval.self, forKey: .expiration)
)
}
func encode(to encoder: any Encoder) throws {
var container = encoder.container(keyedBy: AirshipDateFormatterTest.TestStruct.CodingKeys.self)
try container.encode(self.expiration.timeIntervalSince1970, forKey: .expiration)
}
}
func testDateDecoding() throws {
let payload = """
{
"expiration": 1721840555
}
"""
let decoded = try JSONDecoder().decode(TestStruct.self, from: payload.data(using: .utf8)!)
XCTAssertEqual(Date(timeIntervalSince1970: 1721840555), decoded.expiration)
}
Thank you, @rlepinski! I'll give it a whirl.
I have one more for you, you can just add your own codable date structure that handles seconds from epoch:
struct Expiration: Codable {
let date: Date
init(from decoder: any Decoder) throws {
let container = try decoder.singleValueContainer()
self.date = Date(
timeIntervalSince1970: try container.decode(TimeInterval.self)
)
}
func encode(to encoder: any Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(self.date.timeIntervalSince1970)
}
}
struct TestStruct: Codable {
let expiration: Expiration
enum CodingKeys: String, CodingKey {
case expiration
}
}
func testDateDecoding() throws {
let payload = """
{
"expiration": 1721840555
}
"""
let decoded = try JSONDecoder().decode(TestStruct.self, from: payload.data(using: .utf8)!)
XCTAssertEqual(Date(timeIntervalSince1970: 1721840555), decoded.expiration.date)
}
This contains the custom encoding/decoding to a single struct so you dont have to decode/encode everything in your content state
Preliminary Info
What Airship dependencies are you using?
We are using 18.6.0
What are the versions of any relevant development tools you are using?
Report
What unexpected behavior are you seeing?
Scenario: We are implementing Live Activities that are started and updated via push notifications.
When using Live Activities I cannot figure out how the
ActivityAttributes.ContentState
is being parsed by Airship's SDK. It seems like it's trying to parse the JSON response into milliseconds instead of seconds, but I can't confirm or reject that thesis.I am trying to make a progress countdown from
Date.now
to a date value defined in theActivityAttributes.ContentState
and passed as epoch time.It seems like the
expiration
field is trying to parse as a Date from milliseconds instead of just seconds because the time difference between that value andDate.now
is so strikingly different.What is the expected behavior?
I'm expecting to see around a 1 minute countdown, not 11,000 days or so.
What are the steps to reproduce the unexpected behavior?
This is a truncated version of our JSON with non-relevant fields omitted.
The relevant fields from the Swift type that is parsing this.
Do you have logging for the issue?
I cannot see anything of relevance, but am happy to toggle and send anything. Running the device through Proxyman to see what payloads are sent or received doesn't show anything related to my payloads, but I did notice how every Airship related value being sent seems to be in milliseconds.