Skip to content

Round-tripped Firestore Timestamp fields have altered nanoseconds #14580

Open
@natep

Description

@natep

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!

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions