From c4d13249b74aba4d827ede243b3e3de9968ff4d6 Mon Sep 17 00:00:00 2001 From: Didier Garcia Date: Tue, 18 Apr 2023 08:57:43 -0400 Subject: [PATCH 1/6] Remove use of Instant.now() replace with older API. --- .../analytics/kotlin/core/Analytics.kt | 1 + .../segment/analytics/kotlin/core/Events.kt | 24 ++-------- .../kotlin/core/utilities/DateTimeUtils.kt | 11 +++++ .../core/utilities/EventsFileManager.kt | 7 ++- .../kotlin/core/DateSerializerTests.kt | 47 ------------------- .../kotlin/core/compat/JavaAnalyticsTest.kt | 3 ++ .../core/utilities/EventsFileManagerTest.kt | 3 ++ 7 files changed, 28 insertions(+), 68 deletions(-) create mode 100644 core/src/main/java/com/segment/analytics/kotlin/core/utilities/DateTimeUtils.kt delete mode 100644 core/src/test/kotlin/com/segment/analytics/kotlin/core/DateSerializerTests.kt diff --git a/core/src/main/java/com/segment/analytics/kotlin/core/Analytics.kt b/core/src/main/java/com/segment/analytics/kotlin/core/Analytics.kt index 1247f8b4..4ed00b96 100644 --- a/core/src/main/java/com/segment/analytics/kotlin/core/Analytics.kt +++ b/core/src/main/java/com/segment/analytics/kotlin/core/Analytics.kt @@ -89,6 +89,7 @@ open class Analytics protected constructor( Analytics.segmentLog( "Caught Exception in Analytics Scope: ${t}" ) + t.printStackTrace() } override val analyticsScope = CoroutineScope(SupervisorJob() + exceptionHandler) override val analyticsDispatcher : CloseableCoroutineDispatcher = diff --git a/core/src/main/java/com/segment/analytics/kotlin/core/Events.kt b/core/src/main/java/com/segment/analytics/kotlin/core/Events.kt index cc1e0d55..4c63b9a2 100644 --- a/core/src/main/java/com/segment/analytics/kotlin/core/Events.kt +++ b/core/src/main/java/com/segment/analytics/kotlin/core/Events.kt @@ -1,16 +1,14 @@ package com.segment.analytics.kotlin.core +//import kotlinx.datetime.Instant +//import kotlinx.datetime.LocalDate +import com.segment.analytics.kotlin.core.utilities.dateTimeNowString import kotlinx.serialization.DeserializationStrategy -import kotlinx.serialization.KSerializer import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable -import kotlinx.serialization.descriptors.PrimitiveKind -import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder import kotlinx.serialization.json.* import sovran.kotlin.Store -import java.time.Instant +import java.text.SimpleDateFormat import java.util.* typealias AnalyticsContext = JsonObject @@ -21,18 +19,6 @@ typealias Traits = JsonObject val emptyJsonObject = JsonObject(emptyMap()) val emptyJsonArray = JsonArray(emptyList()) -class DateSerializer : KSerializer { - override val descriptor = PrimitiveSerialDescriptor("Instant", PrimitiveKind.STRING) - - override fun deserialize(decoder: Decoder): Instant { - return Instant.parse(decoder.decodeString()) - } - - override fun serialize(encoder: Encoder, value: Instant) { - encoder.encodeString(value.toString()) - } -} - @Serializable data class DestinationMetadata( var bundled: List? = emptyList(), @@ -97,7 +83,7 @@ sealed class BaseEvent { } internal fun applyBaseData() { - this.timestamp = Instant.now().toString() + this.timestamp = dateTimeNowString() this.context = emptyJsonObject this.messageId = UUID.randomUUID().toString() } diff --git a/core/src/main/java/com/segment/analytics/kotlin/core/utilities/DateTimeUtils.kt b/core/src/main/java/com/segment/analytics/kotlin/core/utilities/DateTimeUtils.kt new file mode 100644 index 00000000..6bca9457 --- /dev/null +++ b/core/src/main/java/com/segment/analytics/kotlin/core/utilities/DateTimeUtils.kt @@ -0,0 +1,11 @@ +package com.segment.analytics.kotlin.core.utilities + +import java.text.SimpleDateFormat +import java.util.* + +fun dateTimeNowString(): String { + val sdf = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:sszzz") + val utc = TimeZone.getTimeZone("UTC"); + sdf.timeZone = utc; + return sdf.format(Date()).replace("UTC", "Z") +} \ No newline at end of file diff --git a/core/src/main/java/com/segment/analytics/kotlin/core/utilities/EventsFileManager.kt b/core/src/main/java/com/segment/analytics/kotlin/core/utilities/EventsFileManager.kt index a810b19f..f1db624e 100644 --- a/core/src/main/java/com/segment/analytics/kotlin/core/utilities/EventsFileManager.kt +++ b/core/src/main/java/com/segment/analytics/kotlin/core/utilities/EventsFileManager.kt @@ -3,7 +3,8 @@ package com.segment.analytics.kotlin.core.utilities import kotlinx.coroutines.sync.Semaphore import java.io.File import java.io.FileOutputStream -import java.time.Instant +import java.text.SimpleDateFormat +import java.util.* /** * Responsible for storing events in a batch payload style @@ -134,7 +135,9 @@ class EventsFileManager( return } // close events array and batch object - val contents = """],"sentAt":"${Instant.now()}","writeKey":"$writeKey"}""" + + + val contents = """],"sentAt":"${dateTimeNowString()}","writeKey":"$writeKey"}""" writeToFile(contents.toByteArray(), file) file.renameTo(File(directory, file.nameWithoutExtension)) os?.close() diff --git a/core/src/test/kotlin/com/segment/analytics/kotlin/core/DateSerializerTests.kt b/core/src/test/kotlin/com/segment/analytics/kotlin/core/DateSerializerTests.kt deleted file mode 100644 index 96858e2c..00000000 --- a/core/src/test/kotlin/com/segment/analytics/kotlin/core/DateSerializerTests.kt +++ /dev/null @@ -1,47 +0,0 @@ -package com.segment.analytics.kotlin.core - -import kotlinx.serialization.json.Json -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.TestInstance -import java.time.Instant - -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -class DateSerializerTests { - @Test - fun `dates are properly serialized`() { - val d1 = Instant.EPOCH - val d2 = Instant.ofEpochSecond(1615918845) - val d3 = Instant.parse("2021-03-16T18:20:45Z") - assertEquals("1970-01-01T00:00:00Z".jsonified(), d1.serialize()) - assertEquals("2021-03-16T18:20:45Z".jsonified(), d2.serialize()) - assertEquals("2021-03-16T18:20:45Z".jsonified(), d3.serialize()) - } - - @Test - fun `dates are properly de-serialized`() { - val d1 = "1970-01-01T00:00:00Z".jsonified() - val d2 = "2021-03-16T18:20:45Z".jsonified() - val d3 = "2021-03-16T18:20:45.000Z".jsonified() - val d4 = "2021-03-16T18:20:45.000000000Z".jsonified() -// val d5 = "2021-03-16T20:20:45+02:00".jsonified() // Need to support offset date time - val d2Expected = Instant.ofEpochSecond(1615918845) - assertEquals(Instant.EPOCH, d1.deserialize()) - assertEquals(d2Expected, d2.deserialize()) - assertEquals(d2Expected, d3.deserialize()) - assertEquals(d2Expected, d4.deserialize()) - } - - private fun Instant.serialize(): String { - return Json.encodeToString(DateSerializer(), this) - } - - private fun String.deserialize(): Instant { - return Json.decodeFromString(DateSerializer(), this) - } - - private fun String.jsonified(): String { - return "\"$this\"" - } - -} diff --git a/core/src/test/kotlin/com/segment/analytics/kotlin/core/compat/JavaAnalyticsTest.kt b/core/src/test/kotlin/com/segment/analytics/kotlin/core/compat/JavaAnalyticsTest.kt index 6559809e..a58fc90e 100644 --- a/core/src/test/kotlin/com/segment/analytics/kotlin/core/compat/JavaAnalyticsTest.kt +++ b/core/src/test/kotlin/com/segment/analytics/kotlin/core/compat/JavaAnalyticsTest.kt @@ -15,6 +15,7 @@ import com.segment.analytics.kotlin.core.emptyJsonObject import com.segment.analytics.kotlin.core.platform.DestinationPlugin import com.segment.analytics.kotlin.core.platform.Plugin import com.segment.analytics.kotlin.core.platform.plugins.ContextPlugin +import com.segment.analytics.kotlin.core.utilities.dateTimeNowString import com.segment.analytics.kotlin.core.utils.StubPlugin import com.segment.analytics.kotlin.core.utils.TestRunPlugin import com.segment.analytics.kotlin.core.utils.mockHTTPClient @@ -62,6 +63,8 @@ internal class JavaAnalyticsTest { init { mockkStatic(Instant::class) every { Instant.now() } returns Date(0).toInstant() + mockkStatic(::dateTimeNowString) + every { dateTimeNowString() } returns Date(0).toInstant().toString() mockkStatic(UUID::class) every { UUID.randomUUID().toString() } returns "qwerty-qwerty-123" mockHTTPClient() diff --git a/core/src/test/kotlin/com/segment/analytics/kotlin/core/utilities/EventsFileManagerTest.kt b/core/src/test/kotlin/com/segment/analytics/kotlin/core/utilities/EventsFileManagerTest.kt index b0df419d..8843f3ca 100644 --- a/core/src/test/kotlin/com/segment/analytics/kotlin/core/utilities/EventsFileManagerTest.kt +++ b/core/src/test/kotlin/com/segment/analytics/kotlin/core/utilities/EventsFileManagerTest.kt @@ -26,6 +26,9 @@ internal class EventsFileManagerTest{ init { mockkStatic(Instant::class) every { Instant.now() } returns Date(0).toInstant() + + mockkStatic(::dateTimeNowString) + every { dateTimeNowString() } returns Date(0).toInstant().toString() } @BeforeEach From 6c80bd95a9cf1e48d473e5e5dd669d9c210270c9 Mon Sep 17 00:00:00 2001 From: Didier Garcia Date: Tue, 18 Apr 2023 23:57:25 -0400 Subject: [PATCH 2/6] Fix Android unittest that relied on Instant.now() --- .../com/segment/analytics/kotlin/android/EventsFileTests.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/android/src/test/java/com/segment/analytics/kotlin/android/EventsFileTests.kt b/android/src/test/java/com/segment/analytics/kotlin/android/EventsFileTests.kt index 076dc135..6b39311f 100644 --- a/android/src/test/java/com/segment/analytics/kotlin/android/EventsFileTests.kt +++ b/android/src/test/java/com/segment/analytics/kotlin/android/EventsFileTests.kt @@ -6,6 +6,7 @@ import com.segment.analytics.kotlin.core.TrackEvent import com.segment.analytics.kotlin.core.emptyJsonObject import com.segment.analytics.kotlin.core.utilities.EncodeDefaultsJson import com.segment.analytics.kotlin.core.utilities.EventsFileManager +import com.segment.analytics.kotlin.core.utilities.dateTimeNowString import io.mockk.every import io.mockk.mockkStatic import kotlinx.coroutines.test.runTest @@ -32,6 +33,8 @@ class EventsFileTests { init { mockkStatic(Instant::class) + mockkStatic(::dateTimeNowString) + every { dateTimeNowString() } returns Date(0).toInstant().toString() every { Instant.now() } returns Date(0).toInstant() } From 64d142d64699b8aff5d85123ccebbf3db92e604c Mon Sep 17 00:00:00 2001 From: Didier Garcia Date: Wed, 19 Apr 2023 00:01:19 -0400 Subject: [PATCH 3/6] Clean up imports. --- core/src/main/java/com/segment/analytics/kotlin/core/Events.kt | 3 --- .../analytics/kotlin/core/utilities/EventsFileManager.kt | 2 -- 2 files changed, 5 deletions(-) diff --git a/core/src/main/java/com/segment/analytics/kotlin/core/Events.kt b/core/src/main/java/com/segment/analytics/kotlin/core/Events.kt index 4c63b9a2..86594a0a 100644 --- a/core/src/main/java/com/segment/analytics/kotlin/core/Events.kt +++ b/core/src/main/java/com/segment/analytics/kotlin/core/Events.kt @@ -1,14 +1,11 @@ package com.segment.analytics.kotlin.core -//import kotlinx.datetime.Instant -//import kotlinx.datetime.LocalDate import com.segment.analytics.kotlin.core.utilities.dateTimeNowString import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.json.* import sovran.kotlin.Store -import java.text.SimpleDateFormat import java.util.* typealias AnalyticsContext = JsonObject diff --git a/core/src/main/java/com/segment/analytics/kotlin/core/utilities/EventsFileManager.kt b/core/src/main/java/com/segment/analytics/kotlin/core/utilities/EventsFileManager.kt index f1db624e..db758e54 100644 --- a/core/src/main/java/com/segment/analytics/kotlin/core/utilities/EventsFileManager.kt +++ b/core/src/main/java/com/segment/analytics/kotlin/core/utilities/EventsFileManager.kt @@ -3,8 +3,6 @@ package com.segment.analytics.kotlin.core.utilities import kotlinx.coroutines.sync.Semaphore import java.io.File import java.io.FileOutputStream -import java.text.SimpleDateFormat -import java.util.* /** * Responsible for storing events in a batch payload style From 7dca34c73e40a6073507f9c49580a16c468b1b7f Mon Sep 17 00:00:00 2001 From: Didier Garcia Date: Wed, 19 Apr 2023 00:24:02 -0400 Subject: [PATCH 4/6] Add milliseconds to date time string and add documenation. --- .../analytics/kotlin/core/utilities/DateTimeUtils.kt | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/com/segment/analytics/kotlin/core/utilities/DateTimeUtils.kt b/core/src/main/java/com/segment/analytics/kotlin/core/utilities/DateTimeUtils.kt index 6bca9457..16d32f59 100644 --- a/core/src/main/java/com/segment/analytics/kotlin/core/utilities/DateTimeUtils.kt +++ b/core/src/main/java/com/segment/analytics/kotlin/core/utilities/DateTimeUtils.kt @@ -3,8 +3,16 @@ package com.segment.analytics.kotlin.core.utilities import java.text.SimpleDateFormat import java.util.* +/** + * This function is a replacement for Instant.now().toString(). It produces strings in a + * compatible format. + * + * Ex: + * Instant.now(): 2023-04-19T04:03:46.880969Z + * dateTimeNowString(): 2023-04-19T04:03:46.880Z + */ fun dateTimeNowString(): String { - val sdf = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:sszzz") + val sdf = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'.'Szzz") val utc = TimeZone.getTimeZone("UTC"); sdf.timeZone = utc; return sdf.format(Date()).replace("UTC", "Z") From fd91bd2924946b97265e168bb9d1e90f0fac5cdc Mon Sep 17 00:00:00 2001 From: Didier Garcia Date: Wed, 19 Apr 2023 00:35:27 -0400 Subject: [PATCH 5/6] Add mockk static to AnalyticsTest that was failing CI. --- .../kotlin/com/segment/analytics/kotlin/core/AnalyticsTests.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/src/test/kotlin/com/segment/analytics/kotlin/core/AnalyticsTests.kt b/core/src/test/kotlin/com/segment/analytics/kotlin/core/AnalyticsTests.kt index 445ce2cf..ced41071 100644 --- a/core/src/test/kotlin/com/segment/analytics/kotlin/core/AnalyticsTests.kt +++ b/core/src/test/kotlin/com/segment/analytics/kotlin/core/AnalyticsTests.kt @@ -3,6 +3,7 @@ package com.segment.analytics.kotlin.core import com.segment.analytics.kotlin.core.platform.DestinationPlugin import com.segment.analytics.kotlin.core.platform.Plugin import com.segment.analytics.kotlin.core.platform.plugins.ContextPlugin +import com.segment.analytics.kotlin.core.utilities.dateTimeNowString import com.segment.analytics.kotlin.core.utils.StubPlugin import com.segment.analytics.kotlin.core.utils.TestRunPlugin import com.segment.analytics.kotlin.core.utils.clearPersistentStorage @@ -56,6 +57,8 @@ class AnalyticsTests { init { mockkStatic(Instant::class) every { Instant.now() } returns Date(0).toInstant() + mockkStatic(::dateTimeNowString) + every { dateTimeNowString() } returns Date(0).toInstant().toString() mockkStatic(UUID::class) every { UUID.randomUUID().toString() } returns "qwerty-qwerty-123" } From 1d54abd9c591f96b00333cd6507afd7ccb3c8dad Mon Sep 17 00:00:00 2001 From: Didier Garcia Date: Thu, 20 Apr 2023 09:55:36 -0400 Subject: [PATCH 6/6] Remove desugaring from android build.gradle and cleanup. --- android/build.gradle | 4 ---- .../main/java/com/segment/analytics/kotlin/core/Analytics.kt | 1 - 2 files changed, 5 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index fbee74a9..d82b6a5b 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -32,9 +32,6 @@ android { } } compileOptions { - // Flag to enable support for the new language APIs - coreLibraryDesugaringEnabled true - sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } @@ -59,7 +56,6 @@ dependencies { implementation 'androidx.lifecycle:lifecycle-process:2.4.0' implementation 'androidx.lifecycle:lifecycle-common-java8:2.4.0' - coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' // TESTING testImplementation 'org.junit.jupiter:junit-jupiter:5.7.2' diff --git a/core/src/main/java/com/segment/analytics/kotlin/core/Analytics.kt b/core/src/main/java/com/segment/analytics/kotlin/core/Analytics.kt index 4ed00b96..1247f8b4 100644 --- a/core/src/main/java/com/segment/analytics/kotlin/core/Analytics.kt +++ b/core/src/main/java/com/segment/analytics/kotlin/core/Analytics.kt @@ -89,7 +89,6 @@ open class Analytics protected constructor( Analytics.segmentLog( "Caught Exception in Analytics Scope: ${t}" ) - t.printStackTrace() } override val analyticsScope = CoroutineScope(SupervisorJob() + exceptionHandler) override val analyticsDispatcher : CloseableCoroutineDispatcher =