From 1963fd31492f31f88e5cb97042d764f4c7ab6954 Mon Sep 17 00:00:00 2001 From: Guillaume Bernos Date: Mon, 13 Jul 2026 15:17:53 +0200 Subject: [PATCH 1/2] fix(firestore): Preserves microseconds when serializing implicit DateTime values --- .../integration_test/timestamp_e2e.dart | 14 ++++++++++ .../utils/firestore_message_codec.dart | 6 ++-- .../test/firestore_message_codec_test.dart | 28 +++++++++++++++++++ .../lib/src/interop/utils/utils.dart | 13 ++++++--- 4 files changed, 55 insertions(+), 6 deletions(-) create mode 100644 packages/cloud_firestore/cloud_firestore_platform_interface/test/firestore_message_codec_test.dart diff --git a/packages/cloud_firestore/cloud_firestore/example/integration_test/timestamp_e2e.dart b/packages/cloud_firestore/cloud_firestore/example/integration_test/timestamp_e2e.dart index 8c78853df295..9fbb73dd3142 100644 --- a/packages/cloud_firestore/cloud_firestore/example/integration_test/timestamp_e2e.dart +++ b/packages/cloud_firestore/cloud_firestore/example/integration_test/timestamp_e2e.dart @@ -37,6 +37,20 @@ void runTimestampTests() { ); }); + test('implicitly converts a DateTime without losing microseconds', + () async { + final doc = await initializeTest('datetime-microseconds'); + final date = DateTime.utc(2023, 11, 1, 0, 0, 0, 999, 999); + + await doc.set({'foo': date}); + + final snapshot = await doc.get(); + final timestamp = snapshot.data()!['foo'] as Timestamp; + + expect(timestamp, Timestamp.fromDate(date)); + expect(timestamp.toDate(), date); + }); + test('updates a $Timestamp & returns', () async { DocumentReference> doc = await initializeTest('geo-point-update'); diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/utils/firestore_message_codec.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/utils/firestore_message_codec.dart index 13612b411aec..5644e75333df 100644 --- a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/utils/firestore_message_codec.dart +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/utils/firestore_message_codec.dart @@ -62,8 +62,10 @@ class FirestoreMessageCodec extends StandardMessageCodec { @override void writeValue(WriteBuffer buffer, dynamic value) { if (value is DateTime) { - buffer.putUint8(_kDateTime); - buffer.putInt64(value.millisecondsSinceEpoch); + final Timestamp timestamp = Timestamp.fromDate(value); + buffer.putUint8(_kTimestamp); + buffer.putInt64(timestamp.seconds); + buffer.putInt32(timestamp.nanoseconds); } else if (value is Timestamp) { buffer.putUint8(_kTimestamp); buffer.putInt64(value.seconds); diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/test/firestore_message_codec_test.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/test/firestore_message_codec_test.dart new file mode 100644 index 000000000000..095cd825d272 --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/test/firestore_message_codec_test.dart @@ -0,0 +1,28 @@ +// Copyright 2026, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:cloud_firestore_platform_interface/cloud_firestore_platform_interface.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'utils/test_firestore_message_codec.dart'; + +void main() { + const TestFirestoreMessageCodec codec = TestFirestoreMessageCodec(); + + test('encodes DateTime without losing microseconds', () { + final dates = [ + DateTime.utc(2023, 11, 1, 0, 0, 0, 0, 1), + DateTime.utc(2023, 11, 1, 0, 0, 0, 1, 1), + DateTime.utc(2023, 11, 1, 0, 0, 0, 999, 999), + DateTime.utc(1969, 12, 31, 23, 59, 59, 999, 999), + ]; + + for (final date in dates) { + final encoded = codec.encodeMessage(date); + final decoded = codec.decodeMessage(encoded); + + expect(decoded, Timestamp.fromDate(date)); + } + }); +} diff --git a/packages/cloud_firestore/cloud_firestore_web/lib/src/interop/utils/utils.dart b/packages/cloud_firestore/cloud_firestore_web/lib/src/interop/utils/utils.dart index 04e9beade181..f249b3113e53 100644 --- a/packages/cloud_firestore/cloud_firestore_web/lib/src/interop/utils/utils.dart +++ b/packages/cloud_firestore/cloud_firestore_web/lib/src/interop/utils/utils.dart @@ -73,13 +73,18 @@ JSAny? jsify(Object? dartObject) { } if (dartObject is DateTime) { - return TimestampJsImpl.fromMillis(dartObject.millisecondsSinceEpoch.toJS) - as JSAny; + final timestamp = Timestamp.fromDate(dartObject); + return TimestampJsImpl( + timestamp.seconds.toJS, + timestamp.nanoseconds.toJS, + ) as JSAny; } if (dartObject is Timestamp) { - return TimestampJsImpl.fromMillis(dartObject.millisecondsSinceEpoch.toJS) - as JSAny; + return TimestampJsImpl( + dartObject.seconds.toJS, + dartObject.nanoseconds.toJS, + ) as JSAny; } if (dartObject is DocumentReference) { From f66fa27a637ed27523f54a7ec3cbe16488c83b78 Mon Sep 17 00:00:00 2001 From: Guillaume Bernos Date: Mon, 13 Jul 2026 15:43:20 +0200 Subject: [PATCH 2/2] fix --- .../cloud_firestore/example/integration_test/timestamp_e2e.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cloud_firestore/cloud_firestore/example/integration_test/timestamp_e2e.dart b/packages/cloud_firestore/cloud_firestore/example/integration_test/timestamp_e2e.dart index 9fbb73dd3142..99b055558f84 100644 --- a/packages/cloud_firestore/cloud_firestore/example/integration_test/timestamp_e2e.dart +++ b/packages/cloud_firestore/cloud_firestore/example/integration_test/timestamp_e2e.dart @@ -48,7 +48,7 @@ void runTimestampTests() { final timestamp = snapshot.data()!['foo'] as Timestamp; expect(timestamp, Timestamp.fromDate(date)); - expect(timestamp.toDate(), date); + expect(timestamp.microsecondsSinceEpoch, date.microsecondsSinceEpoch); }); test('updates a $Timestamp & returns', () async {