Skip to content

Commit a13deae

Browse files
authored
fix(cloud_firestore): correct nanoseconds calculation for pre-1970 dates (#17195)
* fix(cloud_firestore): correct nanoseconds calculation for pre-1970 dates * chore: add test to ensure Timestamp.fromDate handles pre-1970 dates correctly * chore: add native implementation reference * chore: add more tests
1 parent 59d902c commit a13deae

File tree

3 files changed

+55
-2
lines changed

3 files changed

+55
-2
lines changed

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,5 +53,22 @@ void runTimestampTests() {
5353
equals(date.millisecondsSinceEpoch),
5454
);
5555
});
56+
57+
test('set pre-1970 $Timestamp and return', () async {
58+
DocumentReference<Map<String, dynamic>> doc =
59+
await initializeTest('timestamp');
60+
final date = DateTime(1969, 06, 22, 0, 0, 0, 123);
61+
final localTimestamp = Timestamp.fromDate(date);
62+
63+
await doc.set({'foo': localTimestamp});
64+
65+
DocumentSnapshot<Map<String, dynamic>> snapshot = await doc.get();
66+
Timestamp retievedTimestamp = snapshot.data()!['foo'];
67+
expect(retievedTimestamp, isA<Timestamp>());
68+
expect(
69+
retievedTimestamp,
70+
equals(localTimestamp),
71+
);
72+
});
5673
});
5774
}

packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/timestamp.dart

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,15 @@ class Timestamp implements Comparable<Timestamp> {
4040

4141
/// Create a [Timestamp] fromMicrosecondsSinceEpoch
4242
factory Timestamp.fromMicrosecondsSinceEpoch(int microseconds) {
43-
final int seconds = microseconds ~/ _kMillion;
44-
final int nanoseconds = (microseconds - seconds * _kMillion) * _kThousand;
43+
int seconds = microseconds ~/ _kMillion;
44+
int nanoseconds = (microseconds - seconds * _kMillion) * _kThousand;
45+
46+
// Matches implementation in Android SDK:
47+
// https://github.com/firebase/firebase-android-sdk/blob/master/firebase-common/src/main/java/com/google/firebase/Timestamp.kt#L114-L121
48+
if (nanoseconds < 0) {
49+
seconds -= 1;
50+
nanoseconds += _kBillion;
51+
}
4552
return Timestamp(seconds, nanoseconds);
4653
}
4754

packages/cloud_firestore/cloud_firestore_platform_interface/test/timestamp_test.dart

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,5 +80,34 @@ void main() {
8080

8181
expect(epoch, equals(-9999999999));
8282
});
83+
84+
test('Timestamp should not throw for dates before 1970', () {
85+
final dates = [
86+
DateTime(1969, 06, 22, 0, 0, 0, 123),
87+
DateTime(1969, 12, 31, 23, 59, 59, 999),
88+
DateTime(1900, 01, 01, 12, 30, 45, 500),
89+
DateTime(1800, 07, 04, 18, 15, 30, 250),
90+
DateTime(0001, 01, 01, 00, 00, 00, 001),
91+
];
92+
93+
for (final date in dates) {
94+
try {
95+
final timestamp = Timestamp.fromDate(date);
96+
expect(timestamp, isA<Timestamp>());
97+
} catch (e) {
98+
fail('Timestamp.fromDate threw an error: $e');
99+
}
100+
}
101+
});
102+
103+
test(
104+
'pre-1970 Timestamps should match the original DateTime after conversion',
105+
() {
106+
final date = DateTime(1969, 06, 22, 0, 0, 0, 123);
107+
final timestamp = Timestamp.fromDate(date);
108+
final timestampAsDateTime = timestamp.toDate();
109+
110+
expect(date, equals(timestampAsDateTime));
111+
});
83112
});
84113
}

0 commit comments

Comments
 (0)