Skip to content

Commit 068c309

Browse files
authored
fix(firestore): Preserves microseconds when serializing implicit DateTime values (#18435)
* fix(firestore): Preserves microseconds when serializing implicit DateTime values * fix
1 parent 4b731ae commit 068c309

4 files changed

Lines changed: 55 additions & 6 deletions

File tree

packages/cloud_firestore/cloud_firestore/example/integration_test/timestamp_e2e.dart

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,20 @@ void runTimestampTests() {
3737
);
3838
});
3939

40+
test('implicitly converts a DateTime without losing microseconds',
41+
() async {
42+
final doc = await initializeTest('datetime-microseconds');
43+
final date = DateTime.utc(2023, 11, 1, 0, 0, 0, 999, 999);
44+
45+
await doc.set(<String, Object?>{'foo': date});
46+
47+
final snapshot = await doc.get();
48+
final timestamp = snapshot.data()!['foo'] as Timestamp;
49+
50+
expect(timestamp, Timestamp.fromDate(date));
51+
expect(timestamp.microsecondsSinceEpoch, date.microsecondsSinceEpoch);
52+
});
53+
4054
test('updates a $Timestamp & returns', () async {
4155
DocumentReference<Map<String, dynamic>> doc =
4256
await initializeTest('geo-point-update');

packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/utils/firestore_message_codec.dart

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,10 @@ class FirestoreMessageCodec extends StandardMessageCodec {
6262
@override
6363
void writeValue(WriteBuffer buffer, dynamic value) {
6464
if (value is DateTime) {
65-
buffer.putUint8(_kDateTime);
66-
buffer.putInt64(value.millisecondsSinceEpoch);
65+
final Timestamp timestamp = Timestamp.fromDate(value);
66+
buffer.putUint8(_kTimestamp);
67+
buffer.putInt64(timestamp.seconds);
68+
buffer.putInt32(timestamp.nanoseconds);
6769
} else if (value is Timestamp) {
6870
buffer.putUint8(_kTimestamp);
6971
buffer.putInt64(value.seconds);
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Copyright 2026, the Chromium project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'package:cloud_firestore_platform_interface/cloud_firestore_platform_interface.dart';
6+
import 'package:flutter_test/flutter_test.dart';
7+
8+
import 'utils/test_firestore_message_codec.dart';
9+
10+
void main() {
11+
const TestFirestoreMessageCodec codec = TestFirestoreMessageCodec();
12+
13+
test('encodes DateTime without losing microseconds', () {
14+
final dates = <DateTime>[
15+
DateTime.utc(2023, 11, 1, 0, 0, 0, 0, 1),
16+
DateTime.utc(2023, 11, 1, 0, 0, 0, 1, 1),
17+
DateTime.utc(2023, 11, 1, 0, 0, 0, 999, 999),
18+
DateTime.utc(1969, 12, 31, 23, 59, 59, 999, 999),
19+
];
20+
21+
for (final date in dates) {
22+
final encoded = codec.encodeMessage(date);
23+
final decoded = codec.decodeMessage(encoded);
24+
25+
expect(decoded, Timestamp.fromDate(date));
26+
}
27+
});
28+
}

packages/cloud_firestore/cloud_firestore_web/lib/src/interop/utils/utils.dart

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,13 +73,18 @@ JSAny? jsify(Object? dartObject) {
7373
}
7474

7575
if (dartObject is DateTime) {
76-
return TimestampJsImpl.fromMillis(dartObject.millisecondsSinceEpoch.toJS)
77-
as JSAny;
76+
final timestamp = Timestamp.fromDate(dartObject);
77+
return TimestampJsImpl(
78+
timestamp.seconds.toJS,
79+
timestamp.nanoseconds.toJS,
80+
) as JSAny;
7881
}
7982

8083
if (dartObject is Timestamp) {
81-
return TimestampJsImpl.fromMillis(dartObject.millisecondsSinceEpoch.toJS)
82-
as JSAny;
84+
return TimestampJsImpl(
85+
dartObject.seconds.toJS,
86+
dartObject.nanoseconds.toJS,
87+
) as JSAny;
8388
}
8489

8590
if (dartObject is DocumentReference) {

0 commit comments

Comments
 (0)