From 20e73d7e18763e13c204a5d71551f7c00c28e0c6 Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Tue, 16 Jun 2026 17:33:00 +0200 Subject: [PATCH 1/8] perf(android): Replace Date with unix timestamp in SentryNanotimeDate (JAVA-533) SentryNanotimeDate stored a java.util.Date but only ever read its epoch millis. Storing the millis directly avoids a Calendar allocation on every timestamp, which on Android backs every span/transaction timestamp. The default constructor now uses System.currentTimeMillis() instead of DateUtils.getCurrentDateTime() (Calendar with UTC). This is behavior- preserving: the UTC TimeZone only affects calendar field access, not the epoch-millis value the class used. BREAKING: the public SentryNanotimeDate(Date, long) constructor is replaced by SentryNanotimeDate(long unixDate, long nanos). Co-Authored-By: Claude Opus 4.8 (1M context) --- CHANGELOG.md | 2 + .../core/ActivityLifecycleIntegration.java | 5 +- .../core/SpanFrameMetricsCollector.java | 3 +- .../core/ActivityLifecycleIntegrationTest.kt | 67 +++++++++---------- .../core/NetworkBreadcrumbsIntegrationTest.kt | 6 +- .../core/SpanFrameMetricsCollectorTest.kt | 15 +++-- .../ActivityLifecycleSpanHelperTest.kt | 9 ++- .../core/performance/AppStartMetricsTest.kt | 3 +- .../apache/ApacheHttpClientTransportTest.kt | 4 +- sentry/api/sentry.api | 3 +- sentry/src/main/java/io/sentry/DateUtils.java | 11 --- .../java/io/sentry/SentryNanotimeDate.java | 21 +++--- ...efaultCompositePerformanceCollectorTest.kt | 21 ++---- .../java/io/sentry/SentryNanotimeDateTest.kt | 29 ++++---- .../test/java/io/sentry/SentryTracerTest.kt | 5 +- .../transport/AsyncHttpTransportTest.kt | 4 +- 16 files changed, 89 insertions(+), 119 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5dbcde58f10..9c1d367ab36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ ### Improvements - Reduce boxing to improve performance ([#5523](https://github.com/getsentry/sentry-java/pull/5523), [#5527](https://github.com/getsentry/sentry-java/pull/5527), [#5551](https://github.com/getsentry/sentry-java/pull/5551)) +- Replace `Date` with a unix timestamp in `SentryNanotimeDate` to avoid `Calendar` allocations during SDK init ([#XXXX](https://github.com/getsentry/sentry-java/pull/XXXX)) + - Breaking: the `SentryNanotimeDate(Date, long)` constructor is replaced by `SentryNanotimeDate(long unixDate, long nanos)`, where `unixDate` is milliseconds since the epoch. The behavior is unchanged as `Date.getTime()` already returns epoch millis. ### Dependencies diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/ActivityLifecycleIntegration.java b/sentry-android-core/src/main/java/io/sentry/android/core/ActivityLifecycleIntegration.java index 19cee7fcce5..8a891926341 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/ActivityLifecycleIntegration.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/ActivityLifecycleIntegration.java @@ -45,7 +45,6 @@ import java.io.IOException; import java.lang.ref.WeakReference; import java.util.Collections; -import java.util.Date; import java.util.Map; import java.util.WeakHashMap; import java.util.concurrent.Future; @@ -94,7 +93,7 @@ public final class ActivityLifecycleIntegration private final @NotNull WeakHashMap ttfdSpanMap = new WeakHashMap<>(); private final @NotNull WeakHashMap activitySpanHelpers = new WeakHashMap<>(); - private @NotNull SentryDate lastPausedTime = new SentryNanotimeDate(new Date(0), 0); + private @NotNull SentryDate lastPausedTime = new SentryNanotimeDate(0, 0); private @Nullable Future ttfdAutoCloseFuture = null; // WeakHashMap isn't thread safe but ActivityLifecycleCallbacks is only called from the @@ -729,7 +728,7 @@ public void onActivityDestroyed(final @NotNull Activity activity) { private void clear() { firstActivityCreated = false; - lastPausedTime = new SentryNanotimeDate(new Date(0), 0); + lastPausedTime = new SentryNanotimeDate(0, 0); activitySpanHelpers.clear(); } diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/SpanFrameMetricsCollector.java b/sentry-android-core/src/main/java/io/sentry/android/core/SpanFrameMetricsCollector.java index a83454d29b7..074a4a6ea51 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/SpanFrameMetricsCollector.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/SpanFrameMetricsCollector.java @@ -13,7 +13,6 @@ import io.sentry.android.core.internal.util.SentryFrameMetricsCollector; import io.sentry.protocol.MeasurementValue; import io.sentry.util.AutoClosableReentrantLock; -import java.util.Date; import java.util.Iterator; import java.util.SortedSet; import java.util.TreeSet; @@ -33,7 +32,7 @@ public class SpanFrameMetricsCollector // grow indefinitely in case of a long running span private static final int MAX_FRAMES_COUNT = 3600; private static final long ONE_SECOND_NANOS = TimeUnit.SECONDS.toNanos(1); - private static final SentryNanotimeDate EMPTY_NANO_TIME = new SentryNanotimeDate(new Date(0), 0); + private static final SentryNanotimeDate EMPTY_NANO_TIME = new SentryNanotimeDate(0, 0); private final boolean enabled; protected final @NotNull AutoClosableReentrantLock lock = new AutoClosableReentrantLock(); diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/ActivityLifecycleIntegrationTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/ActivityLifecycleIntegrationTest.kt index f2ffb4b4b96..19f43432bef 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/ActivityLifecycleIntegrationTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/ActivityLifecycleIntegrationTest.kt @@ -41,7 +41,6 @@ import io.sentry.protocol.SentryId import io.sentry.protocol.TransactionNameSource import io.sentry.test.DeferredExecutorService import io.sentry.test.getProperty -import java.util.Date import java.util.concurrent.Future import java.util.concurrent.TimeUnit import kotlin.test.AfterTest @@ -936,7 +935,7 @@ class ActivityLifecycleIntegrationTest { sut.register(fixture.scopes, fixture.options) sut.setFirstActivityCreated(false) - val date = SentryNanotimeDate(Date(1), 0) + val date = SentryNanotimeDate(1, 0) setAppStartTime(date) fixture.options.dateProvider = SentryDateProvider { date } @@ -961,7 +960,7 @@ class ActivityLifecycleIntegrationTest { sut.register(fixture.scopes, fixture.options) sut.setFirstActivityCreated(false) - val date = SentryNanotimeDate(Date(1), 0) + val date = SentryNanotimeDate(1, 0) setAppStartTime(date) val activity = mock() @@ -984,8 +983,8 @@ class ActivityLifecycleIntegrationTest { sut.register(fixture.scopes, fixture.options) sut.setFirstActivityCreated(false) - val date = SentryNanotimeDate(Date(1), 0) - val date2 = SentryNanotimeDate(Date(2), 2) + val date = SentryNanotimeDate(1, 0) + val date2 = SentryNanotimeDate(2, 2) setAppStartTime(date) val activity = mock() @@ -1011,7 +1010,7 @@ class ActivityLifecycleIntegrationTest { val sut = fixture.getSut { it.tracesSampleRate = 1.0 } sut.register(fixture.scopes, fixture.options) sut.setFirstActivityCreated(true) - val date = SentryNanotimeDate(Date(1), 0) + val date = SentryNanotimeDate(1, 0) setAppStartTime(date) val activity = mock() @@ -1030,8 +1029,8 @@ class ActivityLifecycleIntegrationTest { sut.setFirstActivityCreated(false) // usually set by SentryPerformanceProvider - val date = SentryNanotimeDate(Date(1), 0) - val date2 = SentryNanotimeDate(Date(2), 2) + val date = SentryNanotimeDate(1, 0) + val date2 = SentryNanotimeDate(2, 2) val activity = mock() // Activity onCreate date will be used @@ -1056,7 +1055,7 @@ class ActivityLifecycleIntegrationTest { sut.register(fixture.scopes, fixture.options) // usually set by SentryPerformanceProvider - val date = SentryNanotimeDate(Date(1), 0) + val date = SentryNanotimeDate(1, 0) setAppStartTime(date) val activity = mock() @@ -1080,7 +1079,7 @@ class ActivityLifecycleIntegrationTest { sut.register(fixture.scopes, fixture.options) // usually set by SentryPerformanceProvider - val startDate = SentryNanotimeDate(Date(1), 0) + val startDate = SentryNanotimeDate(1, 0) setAppStartTime(startDate) val appStartMetrics = AppStartMetrics.getInstance() appStartMetrics.appStartType = AppStartType.WARM @@ -1113,9 +1112,9 @@ class ActivityLifecycleIntegrationTest { it.isEnableStandaloneAppStartTracing = true } sut.register(fixture.scopes, fixture.options) - val firstFrameDate = SentryNanotimeDate(Date(1499), 0) + val firstFrameDate = SentryNanotimeDate(1499, 0) fixture.options.dateProvider = SentryDateProvider { firstFrameDate } - setAppStartTime(SentryNanotimeDate(Date(1), 0)) + setAppStartTime(SentryNanotimeDate(1, 0)) val activity = mock() sut.onActivityPreCreated(activity, fixture.bundle) @@ -1168,8 +1167,8 @@ class ActivityLifecycleIntegrationTest { it.isEnableStandaloneAppStartTracing = true } sut.register(fixture.scopes, fixture.options) - val appStartEndDate = SentryNanotimeDate(Date(499), 0) - setAppStartTime(SentryNanotimeDate(Date(1), 0), appStartEndDate) + val appStartEndDate = SentryNanotimeDate(499, 0) + setAppStartTime(SentryNanotimeDate(1, 0), appStartEndDate) val activity = mock() sut.onActivityPreCreated(activity, fixture.bundle) @@ -1230,9 +1229,9 @@ class ActivityLifecycleIntegrationTest { AppStartMetrics.getInstance().appStartSentryTraceHeader = SentryTraceHeader(storedTraceId, SpanId(), true).value // headless start ended right before the activity opens - AppStartMetrics.getInstance().appStartEndTime = SentryNanotimeDate(Date(0), 0) + AppStartMetrics.getInstance().appStartEndTime = SentryNanotimeDate(0, 0) sut.register(fixture.scopes, fixture.options) - setAppStartTime(date = SentryNanotimeDate(Date(1), 0)) + setAppStartTime(date = SentryNanotimeDate(1, 0)) val activity = mock() sut.onActivityCreated(activity, fixture.bundle) @@ -1254,9 +1253,9 @@ class ActivityLifecycleIntegrationTest { AppStartMetrics.getInstance().appStartSentryTraceHeader = SentryTraceHeader(storedTraceId, SpanId(), true).value // headless start ended at epoch, but the activity opens more than a minute later - AppStartMetrics.getInstance().appStartEndTime = SentryNanotimeDate(Date(0), 0) + AppStartMetrics.getInstance().appStartEndTime = SentryNanotimeDate(0, 0) sut.register(fixture.scopes, fixture.options) - setAppStartTime(date = SentryNanotimeDate(Date(TimeUnit.MINUTES.toMillis(2)), 0)) + setAppStartTime(date = SentryNanotimeDate(TimeUnit.MINUTES.toMillis(2), 0)) val activity = mock() sut.onActivityCreated(activity, fixture.bundle) @@ -1389,7 +1388,7 @@ class ActivityLifecycleIntegrationTest { // usually done by SentryPerformanceProvider, if disabled it's done by // SentryAndroid.init - val startDate = SentryNanotimeDate(Date(1), 0) + val startDate = SentryNanotimeDate(1, 0) setAppStartTime(startDate) AppStartMetrics.getInstance().appStartType = AppStartType.WARM @@ -1415,7 +1414,7 @@ class ActivityLifecycleIntegrationTest { sut.register(fixture.scopes, fixture.options) // usually done by SentryPerformanceProvider - val startDate = SentryNanotimeDate(Date(1), 0) + val startDate = SentryNanotimeDate(1, 0) setAppStartTime(startDate) AppStartMetrics.getInstance().appStartType = AppStartType.WARM AppStartMetrics.getInstance().sdkInitTimeSpan.setStoppedAt(1234) @@ -1439,7 +1438,7 @@ class ActivityLifecycleIntegrationTest { sut.register(fixture.scopes, fixture.options) // usually done by SentryPerformanceProvider - val startDate = SentryNanotimeDate(Date(1), 0) + val startDate = SentryNanotimeDate(1, 0) setAppStartTime(startDate) AppStartMetrics.getInstance().appStartType = AppStartType.WARM @@ -1474,7 +1473,7 @@ class ActivityLifecycleIntegrationTest { sut.register(fixture.scopes, fixture.options) sut.setFirstActivityCreated(true) - val date = SentryNanotimeDate(Date(1), 0) + val date = SentryNanotimeDate(1, 0) setAppStartTime() val activity = mock() @@ -1988,14 +1987,14 @@ class ActivityLifecycleIntegrationTest { @Test fun `When sentry is initialized mid activity lifecycle, last paused time should be used in favor of app start time`() { val sut = fixture.getSut(importance = RunningAppProcessInfo.IMPORTANCE_FOREGROUND) - val now = SentryNanotimeDate(Date(1234), 456) + val now = SentryNanotimeDate(1234, 456) fixture.options.tracesSampleRate = 1.0 fixture.options.dateProvider = SentryDateProvider { now } sut.register(fixture.scopes, fixture.options) // usually done by SentryPerformanceProvider - val startDate = SentryNanotimeDate(Date(5678), 910) + val startDate = SentryNanotimeDate(5678, 910) setAppStartTime(startDate) AppStartMetrics.getInstance().appStartType = AppStartType.COLD @@ -2019,7 +2018,7 @@ class ActivityLifecycleIntegrationTest { fixture.options.tracesSampleRate = 1.0 sut.register(fixture.scopes, fixture.options) - val date = SentryNanotimeDate(Date(1), 0) + val date = SentryNanotimeDate(1, 0) setAppStartTime(date) assertTrue(sut.activitySpanHelpers.isEmpty()) @@ -2036,8 +2035,8 @@ class ActivityLifecycleIntegrationTest { fun `Creates activity lifecycle spans`() { val sut = fixture.getSut() fixture.options.tracesSampleRate = 1.0 - val appStartDate = SentryNanotimeDate(Date(1), 0) - val startDate = SentryNanotimeDate(Date(2), 0) + val appStartDate = SentryNanotimeDate(1, 0) + val startDate = SentryNanotimeDate(2, 0) val appStartMetrics = AppStartMetrics.getInstance() val activity = mock() fixture.options.dateProvider = SentryDateProvider { startDate } @@ -2076,7 +2075,7 @@ class ActivityLifecycleIntegrationTest { fun `Creates activity lifecycle spans even when no app start span is available`() { val sut = fixture.getSut() fixture.options.tracesSampleRate = 1.0 - val startDate = SentryNanotimeDate(Date(2), 0) + val startDate = SentryNanotimeDate(2, 0) val appStartMetrics = AppStartMetrics.getInstance() val activity = mock() fixture.options.dateProvider = SentryDateProvider { startDate } @@ -2134,8 +2133,8 @@ class ActivityLifecycleIntegrationTest { fun `Creates activity lifecycle spans on API lower than 29`() { val sut = fixture.getSut(apiVersion = Build.VERSION_CODES.P) fixture.options.tracesSampleRate = 1.0 - val appStartDate = SentryNanotimeDate(Date(1), 0) - val startDate = SentryNanotimeDate(Date(2), 0) + val appStartDate = SentryNanotimeDate(1, 0) + val startDate = SentryNanotimeDate(2, 0) val appStartMetrics = AppStartMetrics.getInstance() val activity = mock() fixture.options.dateProvider = SentryDateProvider { startDate } @@ -2187,8 +2186,8 @@ class ActivityLifecycleIntegrationTest { fun `Does not add activity lifecycle spans when firstActivityCreated is true`() { val sut = fixture.getSut() fixture.options.tracesSampleRate = 1.0 - val appStartDate = SentryNanotimeDate(Date(1), 0) - val startDate = SentryNanotimeDate(Date(2), 0) + val appStartDate = SentryNanotimeDate(1, 0) + val startDate = SentryNanotimeDate(2, 0) val appStartMetrics = AppStartMetrics.getInstance() val activity = mock() fixture.options.dateProvider = SentryDateProvider { startDate } @@ -2209,7 +2208,7 @@ class ActivityLifecycleIntegrationTest { fun `When firstActivityCreated is false and app start span has stopped, restart app start to current date`() { val sut = fixture.getSut() fixture.options.tracesSampleRate = 1.0 - val appStartDate = SentryNanotimeDate(Date(1), 0) + val appStartDate = SentryNanotimeDate(1, 0) val appStartMetrics = AppStartMetrics.getInstance() val activity = mock() setAppStartTime(appStartDate) @@ -2290,7 +2289,7 @@ class ActivityLifecycleIntegrationTest { } private fun setAppStartTime( - date: SentryDate = SentryNanotimeDate(Date(1), 0), + date: SentryDate = SentryNanotimeDate(1, 0), stopDate: SentryDate? = null, ) { // set by SentryPerformanceProvider so forcing it here diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/NetworkBreadcrumbsIntegrationTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/NetworkBreadcrumbsIntegrationTest.kt index 4f6ba7fc5f0..711f5f7fe0b 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/NetworkBreadcrumbsIntegrationTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/NetworkBreadcrumbsIntegrationTest.kt @@ -5,7 +5,6 @@ import android.net.Network import android.net.NetworkCapabilities import android.os.Build import io.sentry.Breadcrumb -import io.sentry.DateUtils import io.sentry.IScopes import io.sentry.ISentryExecutorService import io.sentry.SentryDateProvider @@ -54,8 +53,9 @@ class NetworkBreadcrumbsIntegrationTest { executorService = executor isEnableNetworkEventBreadcrumbs = enableNetworkEventBreadcrumbs dateProvider = SentryDateProvider { - val nowNanos = TimeUnit.MILLISECONDS.toNanos(nowMs ?: System.currentTimeMillis()) - SentryNanotimeDate(DateUtils.nanosToDate(nowNanos), nowNanos) + val nowMillis = nowMs ?: System.currentTimeMillis() + val nowNanos = TimeUnit.MILLISECONDS.toNanos(nowMillis) + SentryNanotimeDate(nowMillis, nowNanos) } } return NetworkBreadcrumbsIntegration(context, buildInfo) diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/SpanFrameMetricsCollectorTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/SpanFrameMetricsCollectorTest.kt index e5d7349d37c..2b6f19a8d31 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/SpanFrameMetricsCollectorTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/SpanFrameMetricsCollectorTest.kt @@ -8,7 +8,6 @@ import io.sentry.SentryNanotimeDate import io.sentry.SpanContext import io.sentry.android.core.internal.util.SentryFrameMetricsCollector import io.sentry.protocol.MeasurementValue -import java.util.Date import java.util.UUID import java.util.concurrent.TimeUnit import kotlin.test.Test @@ -50,11 +49,12 @@ class SpanFrameMetricsCollectorTest { val span = mock() val spanContext = SpanContext("op.fake") whenever(span.spanContext).thenReturn(spanContext) - whenever(span.startDate).thenReturn(SentryNanotimeDate(Date(), startTimeStampNanos)) + whenever(span.startDate) + .thenReturn(SentryNanotimeDate(System.currentTimeMillis(), startTimeStampNanos)) whenever(span.finishDate) .thenReturn( if (endTimeStampNanos != null) { - SentryNanotimeDate(Date(), endTimeStampNanos) + SentryNanotimeDate(System.currentTimeMillis(), endTimeStampNanos) } else { null } @@ -69,11 +69,12 @@ class SpanFrameMetricsCollectorTest { val span = mock() val spanContext = SpanContext("op.fake") whenever(span.spanContext).thenReturn(spanContext) - whenever(span.startDate).thenReturn(SentryNanotimeDate(Date(), startTimeStampNanos)) + whenever(span.startDate) + .thenReturn(SentryNanotimeDate(System.currentTimeMillis(), startTimeStampNanos)) whenever(span.finishDate) .thenReturn( if (endTimeStampNanos != null) { - SentryNanotimeDate(Date(), endTimeStampNanos) + SentryNanotimeDate(System.currentTimeMillis(), endTimeStampNanos) } else { null } @@ -438,8 +439,8 @@ class SpanFrameMetricsCollectorTest { @Test fun `SentryNanoDate diff does nano precision`() { // having this in here, as SpanFrameMetricsCollector relies on this behavior - val a = SentryNanotimeDate(Date(1234), 567) - val b = SentryNanotimeDate(Date(1234), 0) + val a = SentryNanotimeDate(1234, 567) + val b = SentryNanotimeDate(1234, 0) assertEquals(567, a.diff(b)) } diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/performance/ActivityLifecycleSpanHelperTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/performance/ActivityLifecycleSpanHelperTest.kt index 710fc835acd..ef048978795 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/performance/ActivityLifecycleSpanHelperTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/performance/ActivityLifecycleSpanHelperTest.kt @@ -12,7 +12,6 @@ import io.sentry.SpanDataConvention import io.sentry.SpanOptions import io.sentry.TracesSamplingDecision import io.sentry.TransactionContext -import java.util.Date import java.util.concurrent.TimeUnit import kotlin.test.BeforeTest import kotlin.test.Test @@ -31,8 +30,8 @@ class ActivityLifecycleSpanHelperTest { val appStartSpan: ISpan val scopes = mock() val options = SentryOptions() - val date = SentryNanotimeDate(Date(1), 1000000) - val endDate = SentryNanotimeDate(Date(3), 3000000) + val date = SentryNanotimeDate(1, 1000000) + val endDate = SentryNanotimeDate(3, 3000000) init { whenever(scopes.options).thenReturn(options) @@ -59,7 +58,7 @@ class ActivityLifecycleSpanHelperTest { @Test fun `createAndStopOnCreateSpan creates and finishes onCreate span`() { val helper = fixture.getSut() - val date = SentryNanotimeDate(Date(1), 1) + val date = SentryNanotimeDate(1, 1) helper.setOnCreateStartTimestamp(date) helper.createAndStopOnCreateSpan(fixture.appStartSpan) @@ -99,7 +98,7 @@ class ActivityLifecycleSpanHelperTest { @Test fun `createAndStopOnStartSpan creates and finishes onStart span`() { val helper = fixture.getSut() - val date = SentryNanotimeDate(Date(1), 1) + val date = SentryNanotimeDate(1, 1) helper.setOnStartStartTimestamp(date) helper.createAndStopOnStartSpan(fixture.appStartSpan) diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/performance/AppStartMetricsTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/performance/AppStartMetricsTest.kt index ab0013a8c75..2737785349f 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/performance/AppStartMetricsTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/performance/AppStartMetricsTest.kt @@ -18,7 +18,6 @@ import io.sentry.android.core.CurrentActivityHolder import io.sentry.android.core.SentryAndroidOptions import io.sentry.android.core.SentryShadowProcess import io.sentry.protocol.SentryId -import java.util.Date import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicInteger import kotlin.test.Test @@ -639,7 +638,7 @@ class AppStartMetricsTest { @Test fun `createProcessInitSpan creates a span`() { val appStartMetrics = AppStartMetrics.getInstance() - val startDate = SentryNanotimeDate(Date(1), 1000000) + val startDate = SentryNanotimeDate(1, 1000000) appStartMetrics.classLoadedUptimeMs = 10 val startMillis = DateUtils.nanosToMillis(startDate.nanoTimestamp().toDouble()).toLong() appStartMetrics.appStartTimeSpan.setStartedAt(1) diff --git a/sentry-apache-http-client-5/src/test/kotlin/io/sentry/transport/apache/ApacheHttpClientTransportTest.kt b/sentry-apache-http-client-5/src/test/kotlin/io/sentry/transport/apache/ApacheHttpClientTransportTest.kt index 639dd4e0513..9f5c9b910ad 100644 --- a/sentry-apache-http-client-5/src/test/kotlin/io/sentry/transport/apache/ApacheHttpClientTransportTest.kt +++ b/sentry-apache-http-client-5/src/test/kotlin/io/sentry/transport/apache/ApacheHttpClientTransportTest.kt @@ -213,7 +213,7 @@ class ApacheHttpClientTransportTest { val now = Date(9001) val sut = fixture.getSut() fixture.options.dateProvider = mock() - whenever(fixture.options.dateProvider.now()).thenReturn(SentryNanotimeDate(now, 0)) + whenever(fixture.options.dateProvider.now()).thenReturn(SentryNanotimeDate(now.time, 0)) val envelope = SentryEnvelope.from(fixture.options.serializer, SentryEvent(), null) sut.send(envelope) @@ -226,7 +226,7 @@ class ApacheHttpClientTransportTest { val now = Date(9001) val sut = fixture.getSut() fixture.options.dateProvider = mock() - whenever(fixture.options.dateProvider.now()).thenReturn(SentryNanotimeDate(now, 0)) + whenever(fixture.options.dateProvider.now()).thenReturn(SentryNanotimeDate(now.time, 0)) val envelope = SentryEnvelope.from(fixture.options.serializer, SentryEvent(), null) sut.send(envelope, Hint()) diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index 22f9366f738..75bbed4a59b 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -382,7 +382,6 @@ public final class io/sentry/DataCategory : java/lang/Enum { } public final class io/sentry/DateUtils { - public static fun dateToNanos (Ljava/util/Date;)J public static fun dateToSeconds (Ljava/util/Date;)D public static fun doubleToBigDecimal (D)Ljava/math/BigDecimal; public static fun getCurrentDateTime ()Ljava/util/Date; @@ -3558,7 +3557,7 @@ public final class io/sentry/SentryMetricsEvents$JsonKeys { public final class io/sentry/SentryNanotimeDate : io/sentry/SentryDate { public fun ()V - public fun (Ljava/util/Date;J)V + public fun (JJ)V public fun compareTo (Lio/sentry/SentryDate;)I public synthetic fun compareTo (Ljava/lang/Object;)I public fun diff (Lio/sentry/SentryDate;)J diff --git a/sentry/src/main/java/io/sentry/DateUtils.java b/sentry/src/main/java/io/sentry/DateUtils.java index b86bddeaad8..e407391c394 100644 --- a/sentry/src/main/java/io/sentry/DateUtils.java +++ b/sentry/src/main/java/io/sentry/DateUtils.java @@ -151,17 +151,6 @@ public static double dateToSeconds(final @NotNull Date date) { return millisToSeconds(date.getTime()); } - /** - * Convert {@link Date} to nanoseconds represented as {@link Long}. - * - * @param date - date - * @return nanoseconds - */ - @SuppressWarnings("JavaUtilDate") - public static long dateToNanos(final @NotNull Date date) { - return millisToNanos(date.getTime()); - } - public static long secondsToNanos(final @NotNull long seconds) { return seconds * (1000L * 1000L * 1000L); } diff --git a/sentry/src/main/java/io/sentry/SentryNanotimeDate.java b/sentry/src/main/java/io/sentry/SentryNanotimeDate.java index 98c46ad5325..87fa3b96dc0 100644 --- a/sentry/src/main/java/io/sentry/SentryNanotimeDate.java +++ b/sentry/src/main/java/io/sentry/SentryNanotimeDate.java @@ -1,32 +1,31 @@ package io.sentry; -import java.util.Date; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; /** - * Uses {@link Date} in combination with System.nanoTime(). + * Uses a unix timestamp (milliseconds since the epoch) in combination with System.nanoTime(). * - *

A single date only offers millisecond precision but diff can be calculated with up to + *

The unix timestamp only offers millisecond precision but diff can be calculated with up to * nanosecond precision. This increased precision can also be used to calculate a new end date for a * transaction where start date is sent with ms precision and end date is added to it with ns * precision leading to an end timestamp with ns precision that can be used to gain ns precision * transaction durations. * *

This is a workaround for older versions of Java (before 9) and Android API (lower than 26) - * that allows for higher precision than {@link Date} alone would. + * that allows for higher precision than a millisecond timestamp alone would. */ public final class SentryNanotimeDate extends SentryDate { - private final @NotNull Date date; + private final long unixDate; private final long nanos; public SentryNanotimeDate() { - this(DateUtils.getCurrentDateTime(), System.nanoTime()); + this(System.currentTimeMillis(), System.nanoTime()); } - public SentryNanotimeDate(final @NotNull Date date, final long nanos) { - this.date = date; + public SentryNanotimeDate(final long unixDate, final long nanos) { + this.unixDate = unixDate; this.nanos = nanos; } @@ -41,7 +40,7 @@ public long diff(final @NotNull SentryDate otherDate) { @Override public long nanoTimestamp() { - return DateUtils.dateToNanos(date); + return DateUtils.millisToNanos(unixDate); } @Override @@ -63,8 +62,8 @@ public long laterDateNanosTimestampByDiff(final @Nullable SentryDate otherDate) public int compareTo(@NotNull SentryDate otherDate) { if (otherDate instanceof SentryNanotimeDate) { final @NotNull SentryNanotimeDate otherNanoDate = (SentryNanotimeDate) otherDate; - final long thisDateMillis = date.getTime(); - final long otherDateMillis = otherNanoDate.date.getTime(); + final long thisDateMillis = unixDate; + final long otherDateMillis = otherNanoDate.unixDate; if (thisDateMillis == otherDateMillis) { return Long.compare(nanos, otherNanoDate.nanos); } else { diff --git a/sentry/src/test/java/io/sentry/DefaultCompositePerformanceCollectorTest.kt b/sentry/src/test/java/io/sentry/DefaultCompositePerformanceCollectorTest.kt index ceec3571ebd..f8e3a8f9f98 100644 --- a/sentry/src/test/java/io/sentry/DefaultCompositePerformanceCollectorTest.kt +++ b/sentry/src/test/java/io/sentry/DefaultCompositePerformanceCollectorTest.kt @@ -4,7 +4,6 @@ import io.sentry.test.getCtor import io.sentry.test.getProperty import io.sentry.test.injectForField import io.sentry.util.thread.ThreadChecker -import java.util.Date import java.util.Timer import java.util.concurrent.TimeUnit import kotlin.test.Test @@ -188,14 +187,8 @@ class DefaultCompositePerformanceCollectorTest { val mockCollector = mock() val dates = listOf( - SentryNanotimeDate( - Date().apply { time = TimeUnit.SECONDS.toMillis(100) }, - TimeUnit.SECONDS.toNanos(100), - ), - SentryNanotimeDate( - Date().apply { time = TimeUnit.SECONDS.toMillis(131) }, - TimeUnit.SECONDS.toNanos(131), - ), + SentryNanotimeDate(TimeUnit.SECONDS.toMillis(100), TimeUnit.SECONDS.toNanos(100)), + SentryNanotimeDate(TimeUnit.SECONDS.toMillis(131), TimeUnit.SECONDS.toNanos(131)), ) whenever(mockDateProvider.now()).thenReturn(dates[0], dates[0], dates[0], dates[1]) val collector = @@ -226,14 +219,8 @@ class DefaultCompositePerformanceCollectorTest { val mockDateProvider = mock() val dates = listOf( - SentryNanotimeDate( - Date().apply { time = TimeUnit.SECONDS.toMillis(100) }, - TimeUnit.SECONDS.toNanos(100), - ), - SentryNanotimeDate( - Date().apply { time = TimeUnit.SECONDS.toMillis(130) }, - TimeUnit.SECONDS.toNanos(130), - ), + SentryNanotimeDate(TimeUnit.SECONDS.toMillis(100), TimeUnit.SECONDS.toNanos(100)), + SentryNanotimeDate(TimeUnit.SECONDS.toMillis(130), TimeUnit.SECONDS.toNanos(130)), ) whenever(mockDateProvider.now()).thenReturn(dates[0], dates[0], dates[0], dates[1]) val collector = fixture.getSut { it.dateProvider = mockDateProvider } diff --git a/sentry/src/test/java/io/sentry/SentryNanotimeDateTest.kt b/sentry/src/test/java/io/sentry/SentryNanotimeDateTest.kt index 86464bcedba..3f7a5dca8b6 100644 --- a/sentry/src/test/java/io/sentry/SentryNanotimeDateTest.kt +++ b/sentry/src/test/java/io/sentry/SentryNanotimeDateTest.kt @@ -1,20 +1,19 @@ package io.sentry -import java.util.Date import kotlin.test.Test import kotlin.test.assertEquals class SentryNanotimeDateTest { @Test fun `doubleValue only offers ms precision`() { - val date = SentryNanotimeDate(Date(1672742031123), 123456789) + val date = SentryNanotimeDate(1672742031123, 123456789) assertEquals(1672742031123000000L, date.nanoTimestamp()) } @Test fun `laterDateNanosByDiff offers ns precision`() { - val startDate = SentryNanotimeDate(Date(1672742031123), 456788) - val finishDate = SentryNanotimeDate(Date(1672742031123), 456789) + val startDate = SentryNanotimeDate(1672742031123, 456788) + val finishDate = SentryNanotimeDate(1672742031123, 456789) val dateInSeconds = startDate.laterDateNanosTimestampByDiff(finishDate) assertEquals(1672742031123000001L, dateInSeconds) } @@ -26,7 +25,7 @@ class SentryNanotimeDateTest { */ @Test fun `laterDateNanosByDiff with SentryLongDate gives ms precision`() { - val startDate = SentryNanotimeDate(Date(1672742031123), 456789) + val startDate = SentryNanotimeDate(1672742031123, 456789) val finishDate = SentryLongDate(61633553039) val dateInSeconds = startDate.laterDateNanosTimestampByDiff(finishDate) assertEquals(1672742031123000000L, dateInSeconds) @@ -36,36 +35,36 @@ class SentryNanotimeDateTest { @Test fun `compareTo() with equal dates returns 0`() { - val date1 = SentryNanotimeDate(Date(1672742031123), 456789) - val date2 = SentryNanotimeDate(Date(1672742031123), 456789) + val date1 = SentryNanotimeDate(1672742031123, 456789) + val date2 = SentryNanotimeDate(1672742031123, 456789) assertEquals(0, date1.compareTo(date2)) } @Test fun `compareTo() returns -1 for earlier ns`() { - val date1 = SentryNanotimeDate(Date(1672742031123), 456788) - val date2 = SentryNanotimeDate(Date(1672742031123), 456789) + val date1 = SentryNanotimeDate(1672742031123, 456788) + val date2 = SentryNanotimeDate(1672742031123, 456789) assertEquals(-1, date1.compareTo(date2)) } @Test fun `compareTo() returns 1 for later ns`() { - val date1 = SentryNanotimeDate(Date(1672742031123), 456789) - val date2 = SentryNanotimeDate(Date(1672742031123), 456788) + val date1 = SentryNanotimeDate(1672742031123, 456789) + val date2 = SentryNanotimeDate(1672742031123, 456788) assertEquals(1, date1.compareTo(date2)) } @Test fun `compareTo() returns -1 for earlier date`() { - val date1 = SentryNanotimeDate(Date(1672742030123), 456789) - val date2 = SentryNanotimeDate(Date(1672742031123), 456789) + val date1 = SentryNanotimeDate(1672742030123, 456789) + val date2 = SentryNanotimeDate(1672742031123, 456789) assertEquals(-1, date1.compareTo(date2)) } @Test fun `compareTo() returns 1 for later date`() { - val date1 = SentryNanotimeDate(Date(1672742031123), 456789) - val date2 = SentryNanotimeDate(Date(1672742030123), 456789) + val date1 = SentryNanotimeDate(1672742031123, 456789) + val date2 = SentryNanotimeDate(1672742030123, 456789) assertEquals(1, date1.compareTo(date2)) } } diff --git a/sentry/src/test/java/io/sentry/SentryTracerTest.kt b/sentry/src/test/java/io/sentry/SentryTracerTest.kt index 1ccbcf2f318..3b808dd2220 100644 --- a/sentry/src/test/java/io/sentry/SentryTracerTest.kt +++ b/sentry/src/test/java/io/sentry/SentryTracerTest.kt @@ -8,7 +8,6 @@ import io.sentry.test.getProperty import io.sentry.util.thread.IThreadChecker import java.time.LocalDateTime import java.time.ZoneOffset -import java.util.Date import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse @@ -184,7 +183,7 @@ class SentryTracerTest { val tracer = fixture.getSut() val date = SentryNanotimeDate( - Date.from(LocalDateTime.of(2022, 12, 24, 23, 59, 58, 0).toInstant(ZoneOffset.UTC)), + LocalDateTime.of(2022, 12, 24, 23, 59, 58, 0).toInstant(ZoneOffset.UTC).toEpochMilli(), 0, ) tracer.finish(SpanStatus.ABORTED, date) @@ -643,7 +642,7 @@ class SentryTracerTest { @Test fun `when startTimestamp is given, use it as startTimestamp`() { - val date = SentryNanotimeDate(Date(0), 0) + val date = SentryNanotimeDate(0, 0) val transaction = fixture.getSut(startTimestamp = date) assertSame(date, transaction.startDate) diff --git a/sentry/src/test/java/io/sentry/transport/AsyncHttpTransportTest.kt b/sentry/src/test/java/io/sentry/transport/AsyncHttpTransportTest.kt index 70092ffa7ba..6f711cfedbf 100644 --- a/sentry/src/test/java/io/sentry/transport/AsyncHttpTransportTest.kt +++ b/sentry/src/test/java/io/sentry/transport/AsyncHttpTransportTest.kt @@ -367,7 +367,7 @@ class AsyncHttpTransportTest { // given val now = Date(9001) fixture.sentryOptions.dateProvider = mock() - whenever(fixture.sentryOptions.dateProvider.now()).thenReturn(SentryNanotimeDate(now, 0)) + whenever(fixture.sentryOptions.dateProvider.now()).thenReturn(SentryNanotimeDate(now.time, 0)) val envelope = SentryEnvelope.from(fixture.sentryOptions.serializer, createSession(), null) whenever(fixture.transportGate.isConnected).thenReturn(true) @@ -387,7 +387,7 @@ class AsyncHttpTransportTest { // given val now = Date(9001) fixture.sentryOptions.dateProvider = mock() - whenever(fixture.sentryOptions.dateProvider.now()).thenReturn(SentryNanotimeDate(now, 0)) + whenever(fixture.sentryOptions.dateProvider.now()).thenReturn(SentryNanotimeDate(now.time, 0)) val envelope = SentryEnvelope.from(fixture.sentryOptions.serializer, createSession(), null) whenever(fixture.transportGate.isConnected).thenReturn(true) From e26eaf4cf6b99447c87836d8e350cd853cd38423 Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Tue, 16 Jun 2026 17:34:10 +0200 Subject: [PATCH 2/8] changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c1d367ab36..0ec832169e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ ### Improvements - Reduce boxing to improve performance ([#5523](https://github.com/getsentry/sentry-java/pull/5523), [#5527](https://github.com/getsentry/sentry-java/pull/5527), [#5551](https://github.com/getsentry/sentry-java/pull/5551)) -- Replace `Date` with a unix timestamp in `SentryNanotimeDate` to avoid `Calendar` allocations during SDK init ([#XXXX](https://github.com/getsentry/sentry-java/pull/XXXX)) +- Replace `Date` with a unix timestamp in `SentryNanotimeDate` to avoid `Calendar` allocations during SDK init ([#5550](https://github.com/getsentry/sentry-java/pull/5550)) - Breaking: the `SentryNanotimeDate(Date, long)` constructor is replaced by `SentryNanotimeDate(long unixDate, long nanos)`, where `unixDate` is milliseconds since the epoch. The behavior is unchanged as `Date.getTime()` already returns epoch millis. ### Dependencies From 86dab4c53b6972afa59b835eac67a3750487cd8f Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Tue, 16 Jun 2026 17:42:01 +0200 Subject: [PATCH 3/8] ref(android): Mark SentryNanotimeDate as @ApiStatus.Internal SentryNanotimeDate is the legacy Date+nanoTime precision workaround and is not intended for direct use by consumers. Marking it @ApiStatus.Internal signals this and means the constructor change in this PR is not a public API break per the repo's API policy. Co-Authored-By: Claude Opus 4.8 (1M context) --- CHANGELOG.md | 2 +- sentry/src/main/java/io/sentry/SentryNanotimeDate.java | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ec832169e8..2184d9f47b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,7 @@ - Reduce boxing to improve performance ([#5523](https://github.com/getsentry/sentry-java/pull/5523), [#5527](https://github.com/getsentry/sentry-java/pull/5527), [#5551](https://github.com/getsentry/sentry-java/pull/5551)) - Replace `Date` with a unix timestamp in `SentryNanotimeDate` to avoid `Calendar` allocations during SDK init ([#5550](https://github.com/getsentry/sentry-java/pull/5550)) - - Breaking: the `SentryNanotimeDate(Date, long)` constructor is replaced by `SentryNanotimeDate(long unixDate, long nanos)`, where `unixDate` is milliseconds since the epoch. The behavior is unchanged as `Date.getTime()` already returns epoch millis. + - `SentryNanotimeDate` is now marked `@ApiStatus.Internal`. Its constructor changed from `(Date, long)` to `(long unixDate, long nanos)`, where `unixDate` is milliseconds since the epoch. Behavior is unchanged as `Date.getTime()` already returns epoch millis. ### Dependencies diff --git a/sentry/src/main/java/io/sentry/SentryNanotimeDate.java b/sentry/src/main/java/io/sentry/SentryNanotimeDate.java index 87fa3b96dc0..7b11b251a20 100644 --- a/sentry/src/main/java/io/sentry/SentryNanotimeDate.java +++ b/sentry/src/main/java/io/sentry/SentryNanotimeDate.java @@ -1,5 +1,6 @@ package io.sentry; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -15,6 +16,7 @@ *

This is a workaround for older versions of Java (before 9) and Android API (lower than 26) * that allows for higher precision than a millisecond timestamp alone would. */ +@ApiStatus.Internal public final class SentryNanotimeDate extends SentryDate { private final long unixDate; From 48adb69929b14ba426eb560223a45dc74b8ace8c Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Wed, 17 Jun 2026 13:36:14 +0200 Subject: [PATCH 4/8] refactor(sentry): Rename unixDate field to unixDateMillis Name the long field for its unit so it is clear it holds the unix timestamp in milliseconds since the epoch. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../src/main/java/io/sentry/SentryNanotimeDate.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/sentry/src/main/java/io/sentry/SentryNanotimeDate.java b/sentry/src/main/java/io/sentry/SentryNanotimeDate.java index 7b11b251a20..9d4c6d22ad6 100644 --- a/sentry/src/main/java/io/sentry/SentryNanotimeDate.java +++ b/sentry/src/main/java/io/sentry/SentryNanotimeDate.java @@ -19,15 +19,15 @@ @ApiStatus.Internal public final class SentryNanotimeDate extends SentryDate { - private final long unixDate; + private final long unixDateMillis; private final long nanos; public SentryNanotimeDate() { this(System.currentTimeMillis(), System.nanoTime()); } - public SentryNanotimeDate(final long unixDate, final long nanos) { - this.unixDate = unixDate; + public SentryNanotimeDate(final long unixDateMillis, final long nanos) { + this.unixDateMillis = unixDateMillis; this.nanos = nanos; } @@ -42,7 +42,7 @@ public long diff(final @NotNull SentryDate otherDate) { @Override public long nanoTimestamp() { - return DateUtils.millisToNanos(unixDate); + return DateUtils.millisToNanos(unixDateMillis); } @Override @@ -64,8 +64,8 @@ public long laterDateNanosTimestampByDiff(final @Nullable SentryDate otherDate) public int compareTo(@NotNull SentryDate otherDate) { if (otherDate instanceof SentryNanotimeDate) { final @NotNull SentryNanotimeDate otherNanoDate = (SentryNanotimeDate) otherDate; - final long thisDateMillis = unixDate; - final long otherDateMillis = otherNanoDate.unixDate; + final long thisDateMillis = unixDateMillis; + final long otherDateMillis = otherNanoDate.unixDateMillis; if (thisDateMillis == otherDateMillis) { return Long.compare(nanos, otherNanoDate.nanos); } else { From 60e189cb27fdd5a1d7459564a799433de15ac444 Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Wed, 17 Jun 2026 13:47:21 +0200 Subject: [PATCH 5/8] docs(changelog): Reword SentryNanotimeDate entry Co-Authored-By: Claude Opus 4.8 (1M context) --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2184d9f47b6..8fc7a59ab1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ ### Improvements - Reduce boxing to improve performance ([#5523](https://github.com/getsentry/sentry-java/pull/5523), [#5527](https://github.com/getsentry/sentry-java/pull/5527), [#5551](https://github.com/getsentry/sentry-java/pull/5551)) -- Replace `Date` with a unix timestamp in `SentryNanotimeDate` to avoid `Calendar` allocations during SDK init ([#5550](https://github.com/getsentry/sentry-java/pull/5550)) +- Replace `Date` with a unix timestamp in `SentryNanotimeDate` to improve performance ([#5550](https://github.com/getsentry/sentry-java/pull/5550)) - `SentryNanotimeDate` is now marked `@ApiStatus.Internal`. Its constructor changed from `(Date, long)` to `(long unixDate, long nanos)`, where `unixDate` is milliseconds since the epoch. Behavior is unchanged as `Date.getTime()` already returns epoch millis. ### Dependencies From fa5bd00e9c36c712dbb194b78726bc500b478870 Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Wed, 17 Jun 2026 14:03:24 +0200 Subject: [PATCH 6/8] feat(sentry): Restore deprecated SentryNanotimeDate Date constructor (JAVA-533) The previous change replaced the (Date, long) constructor with a (long, long) constructor, which was a breaking API change. Add the Date constructor back, delegating to the millis-based one, and mark it deprecated to steer callers toward the new constructor. Co-Authored-By: Claude Opus 4.8 (1M context) --- CHANGELOG.md | 2 +- sentry/api/sentry.api | 1 + sentry/src/main/java/io/sentry/SentryNanotimeDate.java | 9 +++++++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8fc7a59ab1e..0de5fc72452 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,7 @@ - Reduce boxing to improve performance ([#5523](https://github.com/getsentry/sentry-java/pull/5523), [#5527](https://github.com/getsentry/sentry-java/pull/5527), [#5551](https://github.com/getsentry/sentry-java/pull/5551)) - Replace `Date` with a unix timestamp in `SentryNanotimeDate` to improve performance ([#5550](https://github.com/getsentry/sentry-java/pull/5550)) - - `SentryNanotimeDate` is now marked `@ApiStatus.Internal`. Its constructor changed from `(Date, long)` to `(long unixDate, long nanos)`, where `unixDate` is milliseconds since the epoch. Behavior is unchanged as `Date.getTime()` already returns epoch millis. + - `SentryNanotimeDate` is now marked `@ApiStatus.Internal`. A new `(long unixDateMillis, long nanos)` constructor was added, where `unixDateMillis` is milliseconds since the epoch. The existing `(Date, long)` constructor is retained but deprecated. ### Dependencies diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index 75bbed4a59b..e9083350349 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -3558,6 +3558,7 @@ public final class io/sentry/SentryMetricsEvents$JsonKeys { public final class io/sentry/SentryNanotimeDate : io/sentry/SentryDate { public fun ()V public fun (JJ)V + public fun (Ljava/util/Date;J)V public fun compareTo (Lio/sentry/SentryDate;)I public synthetic fun compareTo (Ljava/lang/Object;)I public fun diff (Lio/sentry/SentryDate;)J diff --git a/sentry/src/main/java/io/sentry/SentryNanotimeDate.java b/sentry/src/main/java/io/sentry/SentryNanotimeDate.java index 9d4c6d22ad6..ffbdb457ebd 100644 --- a/sentry/src/main/java/io/sentry/SentryNanotimeDate.java +++ b/sentry/src/main/java/io/sentry/SentryNanotimeDate.java @@ -1,5 +1,6 @@ package io.sentry; +import java.util.Date; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -26,6 +27,14 @@ public SentryNanotimeDate() { this(System.currentTimeMillis(), System.nanoTime()); } + /** + * @deprecated use {@link SentryNanotimeDate#SentryNanotimeDate(long, long)} instead. + */ + @Deprecated + public SentryNanotimeDate(final @NotNull Date date, final long nanos) { + this(date.getTime(), nanos); + } + public SentryNanotimeDate(final long unixDateMillis, final long nanos) { this.unixDateMillis = unixDateMillis; this.nanos = nanos; From db109ea83ade3daf0ba2f6c7063106704f905567 Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Wed, 17 Jun 2026 14:19:54 +0200 Subject: [PATCH 7/8] fix(sentry): Suppress InlineMeSuggester on deprecated constructor (JAVA-533) Error Prone flagged the deprecated (Date, long) constructor as inlineable, failing the build. Suppress the suggestion to match the existing convention in Sentry.java, keeping the constructor available for backwards compatibility. Co-Authored-By: Claude Opus 4.8 (1M context) --- sentry/src/main/java/io/sentry/SentryNanotimeDate.java | 1 + 1 file changed, 1 insertion(+) diff --git a/sentry/src/main/java/io/sentry/SentryNanotimeDate.java b/sentry/src/main/java/io/sentry/SentryNanotimeDate.java index ffbdb457ebd..11455b55ffa 100644 --- a/sentry/src/main/java/io/sentry/SentryNanotimeDate.java +++ b/sentry/src/main/java/io/sentry/SentryNanotimeDate.java @@ -31,6 +31,7 @@ public SentryNanotimeDate() { * @deprecated use {@link SentryNanotimeDate#SentryNanotimeDate(long, long)} instead. */ @Deprecated + @SuppressWarnings("InlineMeSuggester") public SentryNanotimeDate(final @NotNull Date date, final long nanos) { this(date.getTime(), nanos); } From 3c8154f688a0777851974666362b5d538e0234c7 Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Wed, 17 Jun 2026 14:30:01 +0200 Subject: [PATCH 8/8] fix(sentry): Suppress JavaUtilDate on deprecated constructor (JAVA-533) Error Prone's JavaUtilDate check flagged date.getTime() in the deprecated constructor, failing the build. Suppress it, matching the existing suppression used elsewhere in this class. Co-Authored-By: Claude Opus 4.8 (1M context) --- sentry/src/main/java/io/sentry/SentryNanotimeDate.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry/src/main/java/io/sentry/SentryNanotimeDate.java b/sentry/src/main/java/io/sentry/SentryNanotimeDate.java index 11455b55ffa..f3abf3518f7 100644 --- a/sentry/src/main/java/io/sentry/SentryNanotimeDate.java +++ b/sentry/src/main/java/io/sentry/SentryNanotimeDate.java @@ -31,7 +31,7 @@ public SentryNanotimeDate() { * @deprecated use {@link SentryNanotimeDate#SentryNanotimeDate(long, long)} instead. */ @Deprecated - @SuppressWarnings("InlineMeSuggester") + @SuppressWarnings({"InlineMeSuggester", "JavaUtilDate"}) public SentryNanotimeDate(final @NotNull Date date, final long nanos) { this(date.getTime(), nanos); }