Skip to content

Commit 8a8298d

Browse files
authored
feat: Instant support (#6235)
1 parent 1654b31 commit 8a8298d

File tree

3 files changed

+95
-4
lines changed

3 files changed

+95
-4
lines changed

firebase-firestore/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
[#7388](//github.com/firebase/firebase-android-sdk/pull/7388)
1010
- [changed] Improve query performance by using an unsorted HashMap instead of a sorted TreeMap.
1111
[#7389](//github.com/firebase/firebase-android-sdk/pull/7389)
12+
- [changed] Add `java.time.Instant` support to `DocumentSnapshot.toObject()`,
13+
`DocumentReference.set()`, `DocumentReference.update()`, and similar.
14+
[#6235](//github.com/firebase/firebase-android-sdk/pull/6235)
1215

1316
# 26.0.1
1417

firebase-firestore/src/main/java/com/google/firebase/firestore/util/CustomClassMapper.java

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
import static com.google.firebase.firestore.util.ApiUtil.newInstance;
1919

2020
import android.net.Uri;
21+
import android.os.Build;
22+
import androidx.annotation.RequiresApi;
2123
import com.google.firebase.Timestamp;
2224
import com.google.firebase.firestore.Blob;
2325
import com.google.firebase.firestore.DocumentId;
@@ -42,6 +44,7 @@
4244
import java.lang.reflect.WildcardType;
4345
import java.net.URI;
4446
import java.net.URL;
47+
import java.time.Instant;
4548
import java.util.ArrayList;
4649
import java.util.Collection;
4750
import java.util.Collections;
@@ -177,6 +180,9 @@ private static <T> Object serialize(T o, ErrorPath path) {
177180
|| o instanceof FieldValue
178181
|| o instanceof VectorValue) {
179182
return o;
183+
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && o instanceof Instant) {
184+
Instant instant = (Instant) o;
185+
return new Timestamp(instant.getEpochSecond(), instant.getNano());
180186
} else if (o instanceof Uri || o instanceof URI || o instanceof URL) {
181187
return o.toString();
182188
} else {
@@ -237,6 +243,9 @@ private static <T> T deserializeToClass(Object o, Class<T> clazz, DeserializeCon
237243
return (T) convertDate(o, context);
238244
} else if (Timestamp.class.isAssignableFrom(clazz)) {
239245
return (T) convertTimestamp(o, context);
246+
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
247+
&& Instant.class.isAssignableFrom(clazz)) {
248+
return (T) convertInstant(o, context);
240249
} else if (Blob.class.isAssignableFrom(clazz)) {
241250
return (T) convertBlob(o, context);
242251
} else if (GeoPoint.class.isAssignableFrom(clazz)) {
@@ -512,6 +521,20 @@ private static Timestamp convertTimestamp(Object o, DeserializeContext context)
512521
}
513522
}
514523

524+
@RequiresApi(api = Build.VERSION_CODES.O)
525+
private static Instant convertInstant(Object o, DeserializeContext context) {
526+
if (o instanceof Timestamp) {
527+
Timestamp timestamp = (Timestamp) o;
528+
return Instant.ofEpochSecond(timestamp.getSeconds(), timestamp.getNanoseconds());
529+
} else if (o instanceof Date) {
530+
return Instant.ofEpochMilli(((Date) o).getTime());
531+
} else {
532+
throw deserializeError(
533+
context.errorPath,
534+
"Failed to convert value of type " + o.getClass().getName() + " to Instant");
535+
}
536+
}
537+
515538
private static Blob convertBlob(Object o, DeserializeContext context) {
516539
if (o instanceof Blob) {
517540
return (Blob) o;
@@ -933,13 +956,15 @@ Map<String, Object> serialize(T object, ErrorPath path) {
933956
private void applyFieldAnnotations(Field field) {
934957
if (field.isAnnotationPresent(ServerTimestamp.class)) {
935958
Class<?> fieldType = field.getType();
936-
if (fieldType != Date.class && fieldType != Timestamp.class) {
959+
if (fieldType != Date.class
960+
&& fieldType != Timestamp.class
961+
&& !(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && fieldType == Instant.class)) {
937962
throw new IllegalArgumentException(
938963
"Field "
939964
+ field.getName()
940965
+ " is annotated with @ServerTimestamp but is "
941966
+ fieldType
942-
+ " instead of Date or Timestamp.");
967+
+ " instead of Date, Timestamp, or Instant.");
943968
}
944969
serverTimestamps.add(propertyName(field));
945970
}
@@ -954,13 +979,15 @@ private void applyFieldAnnotations(Field field) {
954979
private void applyGetterAnnotations(Method method) {
955980
if (method.isAnnotationPresent(ServerTimestamp.class)) {
956981
Class<?> returnType = method.getReturnType();
957-
if (returnType != Date.class && returnType != Timestamp.class) {
982+
if (returnType != Date.class
983+
&& returnType != Timestamp.class
984+
&& !(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && returnType == Instant.class)) {
958985
throw new IllegalArgumentException(
959986
"Method "
960987
+ method.getName()
961988
+ " is annotated with @ServerTimestamp but returns "
962989
+ returnType
963-
+ " instead of Date or Timestamp.");
990+
+ " instead of Date, Timestamp, or Instant.");
964991
}
965992
serverTimestamps.add(propertyName(method));
966993
}

firebase-firestore/src/test/java/com/google/firebase/firestore/util/MapperTest.java

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,15 @@
2222
import static org.junit.Assert.fail;
2323

2424
import androidx.annotation.Nullable;
25+
import com.google.firebase.Timestamp;
2526
import com.google.firebase.firestore.DocumentId;
2627
import com.google.firebase.firestore.DocumentReference;
2728
import com.google.firebase.firestore.Exclude;
2829
import com.google.firebase.firestore.PropertyName;
2930
import com.google.firebase.firestore.TestUtil;
3031
import com.google.firebase.firestore.ThrowOnExtraProperties;
3132
import java.io.Serializable;
33+
import java.time.Instant;
3234
import java.util.ArrayList;
3335
import java.util.Arrays;
3436
import java.util.Collection;
@@ -37,6 +39,7 @@
3739
import java.util.HashMap;
3840
import java.util.List;
3941
import java.util.Map;
42+
import java.util.Objects;
4043
import java.util.Set;
4144
import org.junit.Test;
4245
import org.robolectric.annotation.Config;
@@ -95,6 +98,40 @@ public boolean isValue() {
9598
}
9699
}
97100

101+
private static class TimeBean {
102+
public Timestamp timestamp;
103+
public Date date;
104+
public Instant instant;
105+
106+
@Override
107+
public boolean equals(Object o) {
108+
if (o == null || getClass() != o.getClass()) {
109+
return false;
110+
}
111+
TimeBean timeBean = (TimeBean) o;
112+
return Objects.equals(timestamp, timeBean.timestamp)
113+
&& Objects.equals(date, timeBean.date)
114+
&& Objects.equals(instant, timeBean.instant);
115+
}
116+
117+
@Override
118+
public int hashCode() {
119+
return Objects.hash(timestamp, date, instant);
120+
}
121+
122+
@Override
123+
public String toString() {
124+
return "TimeBean{"
125+
+ "_date="
126+
+ date
127+
+ ", _timestamp="
128+
+ timestamp
129+
+ ", _instant="
130+
+ instant
131+
+ '}';
132+
}
133+
}
134+
98135
private static class ShortBean {
99136
private short value;
100137

@@ -1476,6 +1513,30 @@ public void serializeBooleanBean() {
14761513
assertJson("{'value': true}", serialize(bean));
14771514
}
14781515

1516+
@Test
1517+
public void serializeTimeBean() {
1518+
TimeBean bean = new TimeBean();
1519+
bean.instant = Instant.ofEpochSecond(1234, 5678);
1520+
bean.timestamp = new Timestamp(bean.instant);
1521+
bean.date = new Date(1234);
1522+
assertEquals(
1523+
Map.of("timestamp", bean.timestamp, "date", bean.date, "instant", bean.timestamp),
1524+
serialize(bean));
1525+
}
1526+
1527+
@Test
1528+
public void deserializeTimeBean() {
1529+
TimeBean bean = new TimeBean();
1530+
bean.instant = Instant.ofEpochSecond(1234, 5678);
1531+
bean.timestamp = new Timestamp(bean.instant);
1532+
bean.date = new Date(1234);
1533+
assertEquals(
1534+
bean,
1535+
convertToCustomClass(
1536+
Map.of("timestamp", bean.timestamp, "date", bean.date, "instant", bean.timestamp),
1537+
TimeBean.class));
1538+
}
1539+
14791540
@Test
14801541
public void serializeFloatBean() {
14811542
FloatBean bean = new FloatBean();

0 commit comments

Comments
 (0)