Description
Description
Expected Result:
When round-tripping a Timestamp
through Firestore, it is exactly the same before and after.
Actual Result:
The nanosecond field of the Timestamp
is consistently different.
Why Is This A Problem:
It makes data consistency checking very difficult. It may result in spurious change notifications.
Reproducing the issue
This simple XCTest demonstrates the problem:
struct TestStruct: Codable, Hashable {
let timestamp: Timestamp
}
func testTimestampRoundtrip() async throws {
let id = UUID().uuidString
let initial = TestStruct(timestamp: .init(date: Date.now))
try myCollection.document(id).setData(from: initial)
let roundTripped = try await myCollection.document(id).getDocument(as: TestStruct.self)
XCTAssertEqual(initial, roundTripped)
}
The assert will fail with an error like:
XCTAssertEqual failed:
("TestStruct(timestamp: <FIRTimestamp: seconds=1741964292 nanoseconds=868173122>)")
is not equal to
("TestStruct(timestamp: <FIRTimestamp: seconds=1741964292 nanoseconds=868173000>)")
Note that my test framework is configured to hit a local Firebase emulator instead of the real infrastructure - I don't know if that makes a difference. I also tried specifying cache
as the source for getDocument(as:)
, but that didn't make a difference.
This does not appear to be a problem with encoding and decoding, because this test works just fine:
func testTimestampCoding() throws {
let initial = TestStruct(timestamp: .init(date: Date.now))
let encoder = Firestore.Encoder()
let encoded = try encoder.encode(initial)
let decoder = Firestore.Decoder()
let decoded = try decoder.decode(TestStruct.self, from: encoded)
XCTAssertEqual(initial, decoded)
}
Firebase SDK Version
11.7.0
Xcode Version
16.2
Installation Method
Swift Package Manager
Firebase Product(s)
Firestore
Targeted Platforms
iOS
Relevant Log Output
If using Swift Package Manager, the project's Package.resolved
Expand Package.resolved
snippet
{
"originHash" : "279a5cbd0a3b875be5bd9e97c69521ee23b546261f86798aaaf0e3bdbbebfc53",
"pins" : [
{
"identity" : "abseil-cpp-binary",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/abseil-cpp-binary.git",
"state" : {
"revision" : "194a6706acbd25e4ef639bcaddea16e8758a3e27",
"version" : "1.2024011602.0"
}
},
{
"identity" : "app-check",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/app-check.git",
"state" : {
"revision" : "61b85103a1aeed8218f17c794687781505fbbef5",
"version" : "11.2.0"
}
},
{
"identity" : "cocoalumberjack",
"kind" : "remoteSourceControl",
"location" : "https://github.com/CocoaLumberjack/CocoaLumberjack.git",
"state" : {
"revision" : "4b8714a7fb84d42393314ce897127b3939885ec3",
"version" : "3.8.5"
}
},
{
"identity" : "firebase-ios-sdk",
"kind" : "remoteSourceControl",
"location" : "https://github.com/firebase/firebase-ios-sdk.git",
"state" : {
"revision" : "0d885d28250fb1196b614bc9455079b75c531f72",
"version" : "11.7.0"
}
},
{
"identity" : "googleappmeasurement",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/GoogleAppMeasurement.git",
"state" : {
"revision" : "be0881ff728eca210ccb628092af400c086abda3",
"version" : "11.7.0"
}
},
{
"identity" : "googledatatransport",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/GoogleDataTransport.git",
"state" : {
"revision" : "617af071af9aa1d6a091d59a202910ac482128f9",
"version" : "10.1.0"
}
},
{
"identity" : "googleutilities",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/GoogleUtilities.git",
"state" : {
"revision" : "53156c7ec267db846e6b64c9f4c4e31ba4cf75eb",
"version" : "8.0.2"
}
},
{
"identity" : "grpc-binary",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/grpc-binary.git",
"state" : {
"revision" : "f56d8fc3162de9a498377c7b6cea43431f4f5083",
"version" : "1.65.1"
}
},
{
"identity" : "gtm-session-fetcher",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/gtm-session-fetcher.git",
"state" : {
"revision" : "85b7b231882c3c472b8bda4fb495324d3f19bab6",
"version" : "4.2.0"
}
},
{
"identity" : "interop-ios-for-google-sdks",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/interop-ios-for-google-sdks.git",
"state" : {
"revision" : "2d12673670417654f08f5f90fdd62926dc3a2648",
"version" : "100.0.0"
}
},
{
"identity" : "leveldb",
"kind" : "remoteSourceControl",
"location" : "https://github.com/firebase/leveldb.git",
"state" : {
"revision" : "a0bc79961d7be727d258d33d5a6b2f1023270ba1",
"version" : "1.22.5"
}
},
{
"identity" : "nanopb",
"kind" : "remoteSourceControl",
"location" : "https://github.com/firebase/nanopb.git",
"state" : {
"revision" : "b7e1104502eca3a213b46303391ca4d3bc8ddec1",
"version" : "2.30910.0"
}
},
{
"identity" : "promises",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/promises.git",
"state" : {
"revision" : "540318ecedd63d883069ae7f1ed811a2df00b6ac",
"version" : "2.4.0"
}
},
{
"identity" : "swift-log",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-log",
"state" : {
"revision" : "96a2f8a0fa41e9e09af4585e2724c4e825410b91",
"version" : "1.6.2"
}
},
{
"identity" : "swift-protobuf",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-protobuf.git",
"state" : {
"revision" : "ebc7251dd5b37f627c93698e4374084d98409633",
"version" : "1.28.2"
}
}
],
"version" : 3
}
If using CocoaPods, the project's Podfile.lock
Expand Podfile.lock
snippet
Replace this line with the contents of your Podfile.lock!