From 779ae9b0a13ba1db7722204a8ea04c21bbc73033 Mon Sep 17 00:00:00 2001 From: Wenxi Zeng Date: Wed, 29 Nov 2023 11:59:51 -0800 Subject: [PATCH 1/2] fix date format issue --- .../kotlin/core/utilities/DateTimeUtils.kt | 41 +++++++++++++------ 1 file changed, 28 insertions(+), 13 deletions(-) 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 649a487a..39508c4b 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,19 +3,34 @@ 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 { +object SegmentInstant { + // Note, we should specify locale = Locale.ROOT, otherwise the timestamp returned will use // the default locale, which may not be what we want. - val sdf = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'.'Szzz", Locale.ROOT) - val utc = TimeZone.getTimeZone("UTC"); - sdf.timeZone = utc; - return sdf.format(Date()).replace("UTC", "Z") + private val formatter: SimpleDateFormat = + SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'.'SSSzzz", Locale.ROOT).apply { + timeZone = TimeZone.getTimeZone("UTC") + } + + /** + * 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 now(): String { + return from(Date()) + } + + internal fun from(date: Date): String { + // internal use only. for testing purpose. + return formatter.format(date).replace("UTC", "Z") + } } + +@Deprecated("Please use SegmentInstant.now() instead", ReplaceWith("SegmentInstant.now()")) +fun dateTimeNowString(): String { + return SegmentInstant.now() +} \ No newline at end of file From 9e5982cf6c993602ee6f6e8ff95041d9e4b20fc1 Mon Sep 17 00:00:00 2001 From: Wenxi Zeng Date: Wed, 29 Nov 2023 12:00:20 -0800 Subject: [PATCH 2/2] fix unit tests --- .../analytics/kotlin/android/EventsFileTests.kt | 10 ++++------ .../com/segment/analytics/kotlin/core/Events.kt | 4 ++-- .../kotlin/core/utilities/EventsFileManager.kt | 2 +- .../analytics/kotlin/core/AnalyticsTests.kt | 9 +++------ .../kotlin/core/compat/JavaAnalyticsTest.kt | 9 +++------ .../kotlin/core/utilities/DateTimeUtilsTest.kt | 14 +++++++++++++- .../kotlin/core/utilities/EventsFileManagerTest.kt | 8 +++----- 7 files changed, 29 insertions(+), 27 deletions(-) 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 6b39311f..68262e42 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,9 +6,9 @@ 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 com.segment.analytics.kotlin.core.utilities.SegmentInstant import io.mockk.every -import io.mockk.mockkStatic +import io.mockk.mockkObject import kotlinx.coroutines.test.runTest import kotlinx.serialization.encodeToString import kotlinx.serialization.json.buildJsonObject @@ -32,10 +32,8 @@ class EventsFileTests { private val directory = File("/tmp/analytics-android-test/") init { - mockkStatic(Instant::class) - mockkStatic(::dateTimeNowString) - every { dateTimeNowString() } returns Date(0).toInstant().toString() - every { Instant.now() } returns Date(0).toInstant() + mockkObject(SegmentInstant) + every { SegmentInstant.now() } returns Date(0).toInstant().toString() } @BeforeEach 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 86594a0a..0b9de45f 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,6 +1,6 @@ package com.segment.analytics.kotlin.core -import com.segment.analytics.kotlin.core.utilities.dateTimeNowString +import com.segment.analytics.kotlin.core.utilities.SegmentInstant import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @@ -80,7 +80,7 @@ sealed class BaseEvent { } internal fun applyBaseData() { - this.timestamp = dateTimeNowString() + this.timestamp = SegmentInstant.now() this.context = emptyJsonObject this.messageId = UUID.randomUUID().toString() } 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 db758e54..196e9dbd 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 @@ -135,7 +135,7 @@ class EventsFileManager( // close events array and batch object - val contents = """],"sentAt":"${dateTimeNowString()}","writeKey":"$writeKey"}""" + val contents = """],"sentAt":"${SegmentInstant.now()}","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/AnalyticsTests.kt b/core/src/test/kotlin/com/segment/analytics/kotlin/core/AnalyticsTests.kt index 60ece38f..ace1e5ea 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 @@ -4,7 +4,7 @@ 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.platform.plugins.SegmentDestination -import com.segment.analytics.kotlin.core.utilities.dateTimeNowString +import com.segment.analytics.kotlin.core.utilities.SegmentInstant import com.segment.analytics.kotlin.core.utils.StubPlugin import com.segment.analytics.kotlin.core.utils.TestRunPlugin import com.segment.analytics.kotlin.core.utils.clearPersistentStorage @@ -28,7 +28,6 @@ import org.junit.jupiter.api.TestInstance import org.junit.jupiter.api.assertThrows import java.io.ByteArrayInputStream import java.net.HttpURLConnection -import java.time.Instant import java.util.Date import java.util.UUID @@ -50,10 +49,8 @@ class AnalyticsTests { private val testScope = TestScope(testDispatcher) init { - mockkStatic(Instant::class) - every { Instant.now() } returns Date(0).toInstant() - mockkStatic(::dateTimeNowString) - every { dateTimeNowString() } returns Date(0).toInstant().toString() + mockkObject(SegmentInstant) + every { SegmentInstant.now() } returns Date(0).toInstant().toString() mockkStatic(UUID::class) every { UUID.randomUUID().toString() } returns "qwerty-qwerty-123" } 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 16b998cc..ab96ae5b 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 @@ -5,7 +5,7 @@ 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.platform.plugins.SegmentDestination -import com.segment.analytics.kotlin.core.utilities.dateTimeNowString +import com.segment.analytics.kotlin.core.utilities.SegmentInstant import com.segment.analytics.kotlin.core.utils.StubPlugin import com.segment.analytics.kotlin.core.utils.TestRunPlugin import com.segment.analytics.kotlin.core.utils.mockHTTPClient @@ -21,7 +21,6 @@ import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test -import java.time.Instant import java.util.* import java.util.function.Consumer @@ -43,10 +42,8 @@ internal class JavaAnalyticsTest { private val testScope = TestScope(testDispatcher) init { - mockkStatic(Instant::class) - every { Instant.now() } returns Date(0).toInstant() - mockkStatic(::dateTimeNowString) - every { dateTimeNowString() } returns Date(0).toInstant().toString() + mockkObject(SegmentInstant) + every { SegmentInstant.now() } 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/DateTimeUtilsTest.kt b/core/src/test/kotlin/com/segment/analytics/kotlin/core/utilities/DateTimeUtilsTest.kt index 4a9c3124..5f8e8eda 100644 --- a/core/src/test/kotlin/com/segment/analytics/kotlin/core/utilities/DateTimeUtilsTest.kt +++ b/core/src/test/kotlin/com/segment/analytics/kotlin/core/utilities/DateTimeUtilsTest.kt @@ -1,15 +1,27 @@ package com.segment.analytics.kotlin.core.utilities +import io.mockk.every +import io.mockk.mockkConstructor +import io.mockk.mockkObject +import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertNotNull import org.junit.jupiter.api.Test import java.time.format.DateTimeFormatter.ISO_DATE_TIME +import java.util.* class DateTimeUtilsTest { @Test fun `dateTimeNowString() produces a string in the correct ISO8601 format`() { - val dateTimeNowString = dateTimeNowString() + val dateTimeNowString = SegmentInstant.now() val date = ISO_DATE_TIME.parse(dateTimeNowString) assertNotNull(date) } + + @Test + fun `dateTimeNowString() returns three digit seconds`() { + val date = Date(1700617928023L) + val dateTimeNowString = SegmentInstant.from(date) + assertEquals("2023-11-22T01:52:08.023Z", dateTimeNowString) + } } 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 8843f3ca..7b5baec5 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 @@ -3,6 +3,7 @@ package com.segment.analytics.kotlin.core.utilities import com.segment.analytics.kotlin.core.TrackEvent import com.segment.analytics.kotlin.core.emptyJsonObject import io.mockk.every +import io.mockk.mockkObject import io.mockk.mockkStatic import kotlinx.coroutines.test.runTest import kotlinx.serialization.encodeToString @@ -24,11 +25,8 @@ internal class EventsFileManagerTest{ private val kvStore = PropertiesFile(directory.parentFile, "123") init { - mockkStatic(Instant::class) - every { Instant.now() } returns Date(0).toInstant() - - mockkStatic(::dateTimeNowString) - every { dateTimeNowString() } returns Date(0).toInstant().toString() + mockkObject(SegmentInstant) + every { SegmentInstant.now() } returns Date(0).toInstant().toString() } @BeforeEach