Skip to content

Commit 80019ca

Browse files
authored
dataconnect: LocalDate and LocalDateSerializer added (#6434)
1 parent 93640ce commit 80019ca

File tree

4 files changed

+290
-0
lines changed

4 files changed

+290
-0
lines changed

firebase-dataconnect/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@
2020
errors related to using these experimental APIs.
2121
([#6424](https://github.com/firebase/firebase-android-sdk/pull/6424)) and
2222
([#6433](https://github.com/firebase/firebase-android-sdk/pull/6433))
23+
* [changed] Replaced java.util.Date with
24+
com.google.firebase.dataconnect.LocalDate.
25+
([#6434](https://github.com/firebase/firebase-android-sdk/pull/6434))
2326

2427
# 16.0.0-beta02
2528
* [changed] Updated protobuf dependency to `3.25.5` to fix

firebase-dataconnect/api.txt

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,24 @@ package com.google.firebase.dataconnect {
116116
method public static void setLogLevel(@NonNull com.google.firebase.dataconnect.FirebaseDataConnect.Companion, @NonNull com.google.firebase.dataconnect.LogLevel);
117117
}
118118

119+
@kotlinx.serialization.Serializable(with=LocalDateSerializer::class) public final class LocalDate {
120+
ctor public LocalDate(int year, int month, int day);
121+
method public int getDay();
122+
method public int getMonth();
123+
method public int getYear();
124+
property public final int day;
125+
property public final int month;
126+
property public final int year;
127+
}
128+
129+
public final class LocalDateKt {
130+
method @NonNull public static com.google.firebase.dataconnect.LocalDate copy(@NonNull com.google.firebase.dataconnect.LocalDate, int year = year, int month = month, int day = day);
131+
method @NonNull public static com.google.firebase.dataconnect.LocalDate toDataConnectLocalDate(@NonNull java.time.LocalDate);
132+
method @NonNull public static com.google.firebase.dataconnect.LocalDate toDataConnectLocalDate(@NonNull kotlinx.datetime.LocalDate);
133+
method @NonNull public static java.time.LocalDate toJavaLocalDate(@NonNull com.google.firebase.dataconnect.LocalDate);
134+
method @NonNull public static kotlinx.datetime.LocalDate toKotlinxLocalDate(@NonNull com.google.firebase.dataconnect.LocalDate);
135+
}
136+
119137
public enum LogLevel {
120138
method @NonNull public static com.google.firebase.dataconnect.LogLevel valueOf(@NonNull String name) throws java.lang.IllegalArgumentException;
121139
method @NonNull public static com.google.firebase.dataconnect.LogLevel[] values();
@@ -300,6 +318,14 @@ package com.google.firebase.dataconnect.serializers {
300318
field @NonNull public static final com.google.firebase.dataconnect.serializers.DateSerializer INSTANCE;
301319
}
302320

321+
public final class LocalDateSerializer implements kotlinx.serialization.KSerializer<com.google.firebase.dataconnect.LocalDate> {
322+
method @NonNull public com.google.firebase.dataconnect.LocalDate deserialize(@NonNull kotlinx.serialization.encoding.Decoder decoder);
323+
method @NonNull public kotlinx.serialization.descriptors.SerialDescriptor getDescriptor();
324+
method public void serialize(@NonNull kotlinx.serialization.encoding.Encoder encoder, @NonNull com.google.firebase.dataconnect.LocalDate value);
325+
property @NonNull public kotlinx.serialization.descriptors.SerialDescriptor descriptor;
326+
field @NonNull public static final com.google.firebase.dataconnect.serializers.LocalDateSerializer INSTANCE;
327+
}
328+
303329
public final class TimestampSerializer implements kotlinx.serialization.KSerializer<com.google.firebase.Timestamp> {
304330
method @NonNull public com.google.firebase.Timestamp deserialize(@NonNull kotlinx.serialization.encoding.Decoder decoder);
305331
method @NonNull public kotlinx.serialization.descriptors.SerialDescriptor getDescriptor();
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
/*
2+
* Copyright 2024 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.firebase.dataconnect
18+
19+
import android.annotation.SuppressLint
20+
import com.google.firebase.dataconnect.serializers.LocalDateSerializer
21+
import java.util.Objects
22+
import kotlinx.serialization.Serializable
23+
24+
/**
25+
* A date without a time-zone in the ISO-8601 calendar system, such as {@code 2007-12-03}. This is
26+
* the default Kotlin type used to represent a `Date` GraphQL custom scalar in Firebase Data
27+
* Connect.
28+
*
29+
* ### Description (adapted from [java.time.LocalDate])
30+
*
31+
* [LocalDate] is an immutable date-time object that represents a date, often viewed as
32+
* year-month-day. For example, the value "2nd October 2007" can be stored in a [LocalDate].
33+
*
34+
* This class does not store or represent a time or time-zone. Instead, it is a description of the
35+
* date, as used for birthdays. It cannot represent an instant on the time-line without additional
36+
* information such as an offset or time-zone.
37+
*
38+
* ### Relationship to [java.time.LocalDate] and [kotlinx.datetime.LocalDate]
39+
*
40+
* This class exists solely to fill the gap for a "day-month-year" data type in Android API versions
41+
* less than 26. When the Firebase Android SDK updates its `minSdkVersion` to 26 or later, then this
42+
* class will be marked as "deprecated" and eventually removed.
43+
*
44+
* The [java.time.LocalDate] class was added in Android API 26 and should be used if it's available
45+
* instead of this class. If [java.time.LocalDate] is available then [kotlinx.datetime.LocalDate] is
46+
* a completely valid option as well, if it's desirable to take a dependency on
47+
* https://github.com/Kotlin/kotlinx-datetime.
48+
*
49+
* Alternately, if your application has its `minSdkVersion` set to a value _less than_ 26, you can
50+
* use "desugaring" (https://developer.android.com/studio/write/java8-support-table) to get access
51+
* [java.time.LocalDate] class regardless of the API version used at runtime.
52+
*
53+
* ### Using [java.time.LocalDate] and [kotlinx.datetime.LocalDate] in code generation.
54+
*
55+
* By default, the Firebase Data Connect code generation will use this class when generating code
56+
* for Kotlin. If, however, you want to use the preferable [java.time.LocalDate] or
57+
* [kotlinx.datetime.LocalDate] classes, add a `dateClass` entry in your `connector.yaml` set to the
58+
* fully-qualified class name that you'd like to use. For example,
59+
*
60+
* ```
61+
* connectorId: demo
62+
* authMode: PUBLIC
63+
* generate:
64+
* kotlinSdk:
65+
* outputDir: ../../.generated/demo
66+
* dateClass: java.time.LocalDate # or kotlinx.datetime.LocalDate
67+
* ```
68+
*
69+
* ### Safe for Concurrent Use
70+
*
71+
* All methods and properties of [FirebaseDataConnect] are thread-safe and may be safely called
72+
* and/or accessed concurrently from multiple threads and/or coroutines.
73+
*
74+
* @property year The year. The valid range is between 1583 and 9999, inclusive; however, this is
75+
* _not_ checked or prevented by this class. Values less than 1583 are not forbidden; however, their
76+
* interpretation by the Data Connect backend is unspecified. See
77+
* https://en.wikipedia.org/wiki/ISO_8601#Years for more details.
78+
* @property month The month. The valid range is between 1 and 12, inclusive; however, this is _not_
79+
* checked or prevented by this class.
80+
* @property day The day of the month. The valid range is between 1 and 31, inclusive; however, this
81+
* is _not_ checked or prevented by this class.
82+
*/
83+
@Serializable(with = LocalDateSerializer::class)
84+
public class LocalDate(public val year: Int, public val month: Int, public val day: Int) {
85+
86+
/**
87+
* Compares this object with another object for equality.
88+
*
89+
* @param other The object to compare to this for equality.
90+
* @return true if, and only if, the other object is an instance of [LocalDate] and has the same
91+
* values for [year], [month], and [day] as this object, respectively.
92+
*/
93+
override fun equals(other: Any?): Boolean =
94+
other is LocalDate && other.year == year && other.month == month && other.day == day
95+
96+
/**
97+
* Calculates and returns the hash code for this object.
98+
*
99+
* The hash code is _not_ guaranteed to be stable across application restarts.
100+
*
101+
* @return the hash code for this object, that incorporates the values of this object's public
102+
* properties.
103+
*/
104+
override fun hashCode(): Int = Objects.hash(LocalDate::class, year, month, day)
105+
106+
/**
107+
* Returns a string representation of this object, useful for debugging.
108+
*
109+
* The string representation is _not_ guaranteed to be stable and may change without notice at any
110+
* time. Therefore, the only recommended usage of the returned string is debugging and/or logging.
111+
* Namely, parsing the returned string or storing the returned string in non-volatile storage
112+
* should generally be avoided in order to be robust in case that the string representation
113+
* changes.
114+
*
115+
* @return a string representation of this object, which includes the class name and the values of
116+
* all public properties.
117+
*/
118+
override fun toString(): String = "LocalDate(year=$year, month=$month, day=$day)"
119+
}
120+
121+
/**
122+
* Creates and returns a [java.time.LocalDate] object that represents the same date as this object.
123+
*
124+
* Be sure to _only_ call this method if [java.time.LocalDate] is available; otherwise the behavior
125+
* is undefined. If your application's `minSdkVersion` is greater than or equal to `26`, or if you
126+
* have configured "desugaring" (https://developer.android.com/studio/write/java8-support-table)
127+
* then it is guaranteed to be available. Otherwise, check [android.os.Build.VERSION.SDK_INT] at
128+
* runtime and verify that its value is at least [android.os.Build.VERSION_CODES.O] before calling
129+
* this method.
130+
*
131+
* @see java.time.LocalDate.toDataConnectLocalDate
132+
* @see kotlinx.datetime.LocalDate.toDataConnectLocalDate
133+
* @see toKotlinxLocalDate
134+
*/
135+
@SuppressLint("NewApi")
136+
public fun LocalDate.toJavaLocalDate(): java.time.LocalDate =
137+
java.time.LocalDate.of(year, month, day)
138+
139+
/**
140+
* Creates and returns a [LocalDate] object that represents the same date as this
141+
* [java.time.LocalDate] object. This is the inverse operation of [LocalDate.toJavaLocalDate].
142+
*
143+
* Be sure to _only_ call this method if [java.time.LocalDate] is available. See the documentation
144+
* for [LocalDate.toJavaLocalDate] for details.
145+
*
146+
* @see toJavaLocalDate
147+
* @see toKotlinxLocalDate
148+
* @see kotlinx.datetime.LocalDate.toDataConnectLocalDate
149+
*/
150+
@SuppressLint("NewApi")
151+
public fun java.time.LocalDate.toDataConnectLocalDate(): LocalDate =
152+
LocalDate(year = year, month = monthValue, day = dayOfMonth)
153+
154+
/**
155+
* Creates and returns a [kotlinx.datetime.LocalDate] object that represents the same date as this
156+
* object.
157+
*
158+
* Be sure to _only_ call this method if your application has a dependency on
159+
* `org.jetbrains.kotlinx:kotlinx-datetime`; otherwise, the behavior of this method is undefined. If
160+
* your `minSdkVersion` is less than `26` then you _may_ also need to configure "desugaring"
161+
* (https://developer.android.com/studio/write/java8-support-table).
162+
*
163+
* @see kotlinx.datetime.LocalDate.toDataConnectLocalDate
164+
* @see java.time.LocalDate.toDataConnectLocalDate
165+
* @see toJavaLocalDate
166+
*/
167+
public fun LocalDate.toKotlinxLocalDate(): kotlinx.datetime.LocalDate =
168+
kotlinx.datetime.LocalDate(year = year, monthNumber = month, dayOfMonth = day)
169+
170+
/**
171+
* Creates and returns a [LocalDate] object that represents the same date as the given
172+
* [kotlinx.datetime.LocalDate] object. This is the inverse operation of [toKotlinxLocalDate].
173+
*
174+
* Be sure to _only_ call this method if your application has a dependency on
175+
* `org.jetbrains.kotlinx:kotlinx-datetime`. See the documentation for [toKotlinxLocalDate] for
176+
* details.
177+
*
178+
* @see toKotlinxLocalDate
179+
* @see toJavaLocalDate
180+
* @see java.time.LocalDate.toDataConnectLocalDate
181+
*/
182+
public fun kotlinx.datetime.LocalDate.toDataConnectLocalDate(): LocalDate =
183+
LocalDate(year = year, month = monthNumber, day = dayOfMonth)
184+
185+
/** Creates and returns a new [LocalDate] instance with the given property values. */
186+
public fun LocalDate.copy(
187+
year: Int = this.year,
188+
month: Int = this.month,
189+
day: Int = this.day,
190+
): LocalDate = LocalDate(year = year, month = month, day = day)
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* Copyright 2024 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.firebase.dataconnect.serializers
18+
19+
import com.google.firebase.dataconnect.LocalDate
20+
import java.util.regex.Matcher
21+
import java.util.regex.Pattern
22+
import kotlinx.serialization.KSerializer
23+
import kotlinx.serialization.descriptors.PrimitiveKind
24+
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
25+
import kotlinx.serialization.descriptors.SerialDescriptor
26+
import kotlinx.serialization.encoding.Decoder
27+
import kotlinx.serialization.encoding.Encoder
28+
29+
/**
30+
* An implementation of [KSerializer] for serializing and deserializing [LocalDate] objects in the
31+
* wire format expected by the Firebase Data Connect backend.
32+
*/
33+
public object LocalDateSerializer : KSerializer<LocalDate> {
34+
35+
override val descriptor: SerialDescriptor =
36+
PrimitiveSerialDescriptor("com.google.firebase.dataconnect.LocalDate", PrimitiveKind.STRING)
37+
38+
override fun serialize(encoder: Encoder, value: LocalDate) {
39+
value.run {
40+
require(year >= 0) { "invalid value: $value (year must be non-negative)" }
41+
require(month >= 0) { "invalid value: $value (month must be non-negative)" }
42+
require(day >= 0) { "invalid value: $value (day must be non-negative)" }
43+
}
44+
val serializedDate =
45+
"${value.year}".padStart(4, '0') +
46+
'-' +
47+
"${value.month}".padStart(2, '0') +
48+
'-' +
49+
"${value.day}".padStart(2, '0')
50+
encoder.encodeString(serializedDate)
51+
}
52+
53+
override fun deserialize(decoder: Decoder): LocalDate {
54+
val decodedString = decoder.decodeString()
55+
val matcher = Pattern.compile("^(\\d+)-(\\d+)-(\\d+)$").matcher(decodedString)
56+
require(matcher.matches()) {
57+
"date \"$decodedString\" does not match regular expression: ${matcher.pattern()}"
58+
}
59+
60+
fun Matcher.groupToIntIgnoringLeadingZeroes(index: Int): Int {
61+
val groupText = group(index)!!.trimStart('0')
62+
return if (groupText.isEmpty()) 0 else groupText.toInt()
63+
}
64+
65+
val year = matcher.groupToIntIgnoringLeadingZeroes(1)
66+
val month = matcher.groupToIntIgnoringLeadingZeroes(2)
67+
val day = matcher.groupToIntIgnoringLeadingZeroes(3)
68+
69+
return LocalDate(year = year, month = month, day = day)
70+
}
71+
}

0 commit comments

Comments
 (0)