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..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 @@ -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.microsecondsSinceEpoch, date.microsecondsSinceEpoch); + }); + 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) {