From e72c7524f6307e95d305a64ec2895b24f12ec55d Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Thu, 3 Sep 2026 17:29:04 +0200 Subject: [PATCH 1/4] ref(time): Deprecate DateUtils.getCurrentDateTime (JAVA-571) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It reads the wall clock into a java.util.Date at millisecond resolution. SentryDateProvider already owns wall time, is configurable, stubbable in tests and resolves finer, so there is no reason for new code to reach for the static. The class itself is not deprecated: its other fourteen statics are pure conversion and formatting helpers with no clock in them. Every caller keeps working and gets a suppression, because -Xlint:all -Werror turns the warning into a build failure. Each suppression carries a TODO [MAJOR] naming the replacement, since a suppressed warning is no longer a checklist entry — and nearly all of these callers are frozen until the next major anyway, as they stamp serialized timestamps. --- .../java/io/sentry/android/core/AndroidProfiler.java | 2 ++ .../android/core/AndroidTransactionProfiler.java | 2 ++ sentry/src/main/java/io/sentry/Breadcrumb.java | 6 ++++-- sentry/src/main/java/io/sentry/DateUtils.java | 6 +++++- sentry/src/main/java/io/sentry/ProfilingTraceData.java | 2 ++ sentry/src/main/java/io/sentry/SentryEvent.java | 2 ++ sentry/src/main/java/io/sentry/SentryReplayEvent.java | 2 ++ sentry/src/main/java/io/sentry/Session.java | 10 ++++++++++ .../src/main/java/io/sentry/cache/EnvelopeCache.java | 2 ++ .../io/sentry/clientreport/ClientReportRecorder.java | 2 ++ 10 files changed, 33 insertions(+), 3 deletions(-) diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidProfiler.java b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidProfiler.java index 3f569df5378..6f9501e4391 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidProfiler.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidProfiler.java @@ -117,6 +117,8 @@ public AndroidProfiler( } @SuppressLint("NewApi") + // TODO [MAJOR]: replace DateUtils.getCurrentDateTime() with options.getDateProvider().now() + @SuppressWarnings("deprecation") public @Nullable ProfileStartData start() { try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { // intervalUs is 0 only if there was a problem in the init diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidTransactionProfiler.java b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidTransactionProfiler.java index e44ed746a08..baffeb2851c 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidTransactionProfiler.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidTransactionProfiler.java @@ -89,6 +89,8 @@ public AndroidTransactionProfiler( () -> executorService); } + // TODO [MAJOR]: replace DateUtils.getCurrentDateTime() with options.getDateProvider().now() + @SuppressWarnings("deprecation") public AndroidTransactionProfiler( final @NotNull Context context, final @NotNull BuildInfoProvider buildInfoProvider, diff --git a/sentry/src/main/java/io/sentry/Breadcrumb.java b/sentry/src/main/java/io/sentry/Breadcrumb.java index fff6954ee56..dc53fa172ba 100644 --- a/sentry/src/main/java/io/sentry/Breadcrumb.java +++ b/sentry/src/main/java/io/sentry/Breadcrumb.java @@ -97,7 +97,8 @@ public Breadcrumb(final long timestamp) { * @param options - the sentry options * @return the breadcrumb */ - @SuppressWarnings("unchecked") + // TODO [MAJOR]: replace DateUtils.getCurrentDateTime() with options.getDateProvider().now() + @SuppressWarnings({"unchecked", "deprecation"}) public static Breadcrumb fromMap( @NotNull Map map, @NotNull SentryOptions options) { @@ -882,7 +883,8 @@ public void serialize(final @NotNull ObjectWriter writer, final @NotNull ILogger } public static final class Deserializer implements JsonDeserializer { - @SuppressWarnings("unchecked") + // TODO [MAJOR]: replace DateUtils.getCurrentDateTime() with options.getDateProvider().now() + @SuppressWarnings({"unchecked", "deprecation"}) @Override public @NotNull Breadcrumb deserialize(@NotNull ObjectReader reader, @NotNull ILogger logger) throws Exception { diff --git a/sentry/src/main/java/io/sentry/DateUtils.java b/sentry/src/main/java/io/sentry/DateUtils.java index fcba83fbe05..fa1c51a4726 100644 --- a/sentry/src/main/java/io/sentry/DateUtils.java +++ b/sentry/src/main/java/io/sentry/DateUtils.java @@ -19,8 +19,12 @@ private DateUtils() {} * Get the current Date (UTC) * * @return the UTC Date + * @deprecated use {@code options.getDateProvider().now()}, which is configurable and resolves + * finer than a millisecond. */ - @SuppressWarnings("JavaUtilDate") + @Deprecated + // not @InlineMe: the replacement is options.getDateProvider().now(), not this method's body + @SuppressWarnings({"JavaUtilDate", "InlineMeSuggester"}) public static @NotNull Date getCurrentDateTime() { return new Date(); } diff --git a/sentry/src/main/java/io/sentry/ProfilingTraceData.java b/sentry/src/main/java/io/sentry/ProfilingTraceData.java index aee42fa07d4..f19f9414ce3 100644 --- a/sentry/src/main/java/io/sentry/ProfilingTraceData.java +++ b/sentry/src/main/java/io/sentry/ProfilingTraceData.java @@ -75,6 +75,8 @@ private ProfilingTraceData() { this(new File("dummy"), NoOpTransaction.getInstance()); } + // TODO [MAJOR]: replace DateUtils.getCurrentDateTime() with options.getDateProvider().now() + @SuppressWarnings("deprecation") public ProfilingTraceData( final @NotNull File traceFile, final @NotNull ITransaction transaction) { this( diff --git a/sentry/src/main/java/io/sentry/SentryEvent.java b/sentry/src/main/java/io/sentry/SentryEvent.java index 8b8575fe7ec..7a1edde8cc1 100644 --- a/sentry/src/main/java/io/sentry/SentryEvent.java +++ b/sentry/src/main/java/io/sentry/SentryEvent.java @@ -103,6 +103,8 @@ public SentryEvent(final @Nullable Throwable throwable) { this.throwable = throwable; } + // TODO [MAJOR]: replace DateUtils.getCurrentDateTime() with options.getDateProvider().now() + @SuppressWarnings("deprecation") public SentryEvent() { this(new SentryId(), DateUtils.getCurrentDateTime()); } diff --git a/sentry/src/main/java/io/sentry/SentryReplayEvent.java b/sentry/src/main/java/io/sentry/SentryReplayEvent.java index 0ed9dbf72f3..87188210b05 100644 --- a/sentry/src/main/java/io/sentry/SentryReplayEvent.java +++ b/sentry/src/main/java/io/sentry/SentryReplayEvent.java @@ -52,6 +52,8 @@ public static final class Deserializer implements JsonDeserializer { private @Nullable List segmentNames; private @Nullable Map unknown; + // TODO [MAJOR]: replace DateUtils.getCurrentDateTime() with options.getDateProvider().now() + @SuppressWarnings("deprecation") public SentryReplayEvent() { super(); this.replayId = new SentryId(); diff --git a/sentry/src/main/java/io/sentry/Session.java b/sentry/src/main/java/io/sentry/Session.java index 598fe994fdc..a2296b5ebfc 100644 --- a/sentry/src/main/java/io/sentry/Session.java +++ b/sentry/src/main/java/io/sentry/Session.java @@ -112,6 +112,8 @@ public Session( this.abnormalMechanism = abnormalMechanism; } + // TODO [MAJOR]: replace DateUtils.getCurrentDateTime() with options.getDateProvider().now() + @SuppressWarnings("deprecation") public Session( @Nullable String distinctId, final @Nullable User user, @@ -221,6 +223,8 @@ public boolean hasNonTerminatingUnhandledError() { * * @return whether the session was updated, i.e. false if it had already reached a terminal state */ + // TODO [MAJOR]: replace DateUtils.getCurrentDateTime() with options.getDateProvider().now() + @SuppressWarnings("deprecation") @ApiStatus.Internal public boolean recordNonTerminatingUnhandledError() { try (final @NotNull ISentryLifecycleToken ignored = sessionLock.acquire()) { @@ -242,6 +246,8 @@ public boolean recordNonTerminatingUnhandledError() { } /** Ends a session and update its values */ + // TODO [MAJOR]: replace DateUtils.getCurrentDateTime() with options.getDateProvider().now() + @SuppressWarnings("deprecation") public void end() { end(DateUtils.getCurrentDateTime()); } @@ -251,6 +257,8 @@ public void end() { * * @param timestamp the timestamp or null */ + // TODO [MAJOR]: replace DateUtils.getCurrentDateTime() with options.getDateProvider().now() + @SuppressWarnings("deprecation") public void end(final @Nullable Date timestamp) { try (final @NotNull ISentryLifecycleToken ignored = sessionLock.acquire()) { init = null; @@ -301,6 +309,8 @@ public boolean update( * @param abnormalMechanism the mechanism which caused the session to be abnormal * @return if the session has been updated */ + // TODO [MAJOR]: replace DateUtils.getCurrentDateTime() with options.getDateProvider().now() + @SuppressWarnings("deprecation") public boolean update( final @Nullable State status, final @Nullable String userAgent, diff --git a/sentry/src/main/java/io/sentry/cache/EnvelopeCache.java b/sentry/src/main/java/io/sentry/cache/EnvelopeCache.java index d3f473cfbc9..393e29d9049 100644 --- a/sentry/src/main/java/io/sentry/cache/EnvelopeCache.java +++ b/sentry/src/main/java/io/sentry/cache/EnvelopeCache.java @@ -279,6 +279,8 @@ private void tryEndPreviousSession(final @NotNull Hint hint) { } } + // TODO [MAJOR]: replace DateUtils.getCurrentDateTime() with options.getDateProvider().now() + @SuppressWarnings("deprecation") private void writeCrashMarkerFile() { final File crashMarkerFile = new File(options.getCacheDirPath(), CRASH_MARKER_FILE); try (final OutputStream outputStream = new FileOutputStream(crashMarkerFile)) { diff --git a/sentry/src/main/java/io/sentry/clientreport/ClientReportRecorder.java b/sentry/src/main/java/io/sentry/clientreport/ClientReportRecorder.java index ccd7c5dc402..eed58893c65 100644 --- a/sentry/src/main/java/io/sentry/clientreport/ClientReportRecorder.java +++ b/sentry/src/main/java/io/sentry/clientreport/ClientReportRecorder.java @@ -164,6 +164,8 @@ private long itemCountFromHeader(final @NotNull SentryEnvelopeItem envelopeItem) return itemCount != null ? itemCount : 1L; } + // TODO [MAJOR]: replace DateUtils.getCurrentDateTime() with options.getDateProvider().now() + @SuppressWarnings("deprecation") @Nullable ClientReport resetCountsAndGenerateClientReport() { final Date currentDate = DateUtils.getCurrentDateTime(); From c0c74537e1935c898bce853f2dace37dce8b7409 Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Thu, 3 Sep 2026 17:29:04 +0200 Subject: [PATCH 2/4] ref(time): Deprecate the legacy current-date providers (JAVA-571) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit One interface carried two incompatible clocks: CurrentDateProvider is System.currentTimeMillis() and AndroidCurrentDateProvider is SystemClock.uptimeMillis(). Nothing in the name or the type said which, which is the defect JAVA-571 is about — a field declared ICurrentDateProvider accepts either, and the two disagree by however long the device has been suspended. MonotonicClock names the guarantee it gives, and SentryDateProvider covers wall time. The annotation goes on the members rather than the types: the Android modules compile at Java 8, where javac still warns on imports of a deprecated type, and an import declaration cannot carry a @SuppressWarnings. Deprecating getCurrentTimeMillis() and getInstance() warns any caller just the same, and every warning it produces lands somewhere that can be suppressed. Each suppression names the replacement the site should take at the next major. Several of these must stay on the wall clock: AnrV2Integration, TombstoneIntegration and ApplicationExitInfoHistoryDispatcher compare against epoch ApplicationExitInfo timestamps, and LifecycleWatcher against Session.getStarted(). --- .../android/core/AndroidOptionsInitializer.java | 3 +++ .../io/sentry/android/core/AnrV2Integration.java | 3 +++ .../core/AppComponentsBreadcrumbsIntegration.java | 2 ++ .../core/ApplicationExitInfoHistoryDispatcher.java | 3 +++ .../io/sentry/android/core/LifecycleWatcher.java | 9 +++++++++ .../android/core/ScreenshotEventProcessor.java | 2 ++ .../core/SystemEventsBreadcrumbsIntegration.java | 3 +++ .../sentry/android/core/TombstoneIntegration.java | 3 +++ .../android/core/ViewHierarchyEventProcessor.java | 2 ++ .../android/core/cache/AndroidEnvelopeCache.java | 5 +++++ .../internal/util/AndroidCurrentDateProvider.java | 14 ++++++++++++++ .../android/core/internal/util/Debouncer.java | 2 ++ .../io/sentry/transport/CurrentDateProvider.java | 14 ++++++++++++++ .../io/sentry/transport/ICurrentDateProvider.java | 11 ++++++++++- 14 files changed, 75 insertions(+), 1 deletion(-) diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java index 8bcd16b0965..48c517c0b13 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java @@ -401,6 +401,9 @@ private static void setupProfiler( } } + // TODO [MAJOR]: replace CurrentDateProvider (wall clock) with SentryDateProvider or + // MonotonicClock + @SuppressWarnings("deprecation") static void installDefaultIntegrations( final @NotNull Context context, final @NotNull SentryAndroidOptions options, diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AnrV2Integration.java b/sentry-android-core/src/main/java/io/sentry/android/core/AnrV2Integration.java index e28a08f7e8d..348f0008559 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AnrV2Integration.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/AnrV2Integration.java @@ -50,6 +50,9 @@ public class AnrV2Integration implements Integration, Closeable { private final @NotNull ICurrentDateProvider dateProvider; private @Nullable SentryAndroidOptions options; + // TODO [MAJOR]: replace CurrentDateProvider (wall clock) with SentryDateProvider; the epoch + // comparison it feeds must stay wall time + @SuppressWarnings("deprecation") public AnrV2Integration(final @NotNull Context context) { // using CurrentDateProvider instead of AndroidCurrentDateProvider as AppExitInfo uses // System.currentTimeMillis diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AppComponentsBreadcrumbsIntegration.java b/sentry-android-core/src/main/java/io/sentry/android/core/AppComponentsBreadcrumbsIntegration.java index 43ed3422cd9..5f0fbfb6f43 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AppComponentsBreadcrumbsIntegration.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/AppComponentsBreadcrumbsIntegration.java @@ -34,6 +34,8 @@ public final class AppComponentsBreadcrumbsIntegration private @Nullable IScopes scopes; private @Nullable SentryAndroidOptions options; + // TODO [MAJOR]: replace AndroidCurrentDateProvider with MonotonicClock + @SuppressWarnings("deprecation") private final @NotNull Debouncer trimMemoryDebouncer = new Debouncer(AndroidCurrentDateProvider.getInstance(), DEBOUNCE_WAIT_TIME_MS, 0); diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/ApplicationExitInfoHistoryDispatcher.java b/sentry-android-core/src/main/java/io/sentry/android/core/ApplicationExitInfoHistoryDispatcher.java index 155c9fab27e..f3fbbf0f1b8 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/ApplicationExitInfoHistoryDispatcher.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/ApplicationExitInfoHistoryDispatcher.java @@ -36,6 +36,9 @@ final class ApplicationExitInfoHistoryDispatcher implements Runnable { private final @NotNull ApplicationExitInfoPolicy policy; private final long threshold; + // TODO [MAJOR]: replace CurrentDateProvider (wall clock) with SentryDateProvider; the epoch + // comparison it feeds must stay wall time + @SuppressWarnings("deprecation") ApplicationExitInfoHistoryDispatcher( final @NotNull Context context, final @NotNull IScopes scopes, diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/LifecycleWatcher.java b/sentry-android-core/src/main/java/io/sentry/android/core/LifecycleWatcher.java index ca874e714e1..91a0ead7c09 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/LifecycleWatcher.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/LifecycleWatcher.java @@ -28,6 +28,9 @@ final class LifecycleWatcher implements AppState.AppStateListener { private final @NotNull ICurrentDateProvider currentDateProvider; + // TODO [MAJOR]: replace CurrentDateProvider (wall clock) with SentryDateProvider; the epoch + // comparison it feeds must stay wall time + @SuppressWarnings("deprecation") LifecycleWatcher( final @NotNull IScopes scopes, final long sessionIntervalMillis, @@ -60,6 +63,9 @@ public void onForeground() { addAppBreadcrumb("foreground"); } + // TODO [MAJOR]: replace CurrentDateProvider (wall clock) with SentryDateProvider; the epoch + // comparison it feeds must stay wall time + @SuppressWarnings("deprecation") private void startSession() { cancelTask(); @@ -90,6 +96,9 @@ private void startSession() { // App went to background and triggered this callback after 700ms // as no new screen was shown + // TODO [MAJOR]: replace CurrentDateProvider (wall clock) with SentryDateProvider; the epoch + // comparison it feeds must stay wall time + @SuppressWarnings("deprecation") @Override public void onBackground() { final long currentTimeMillis = currentDateProvider.getCurrentTimeMillis(); diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/ScreenshotEventProcessor.java b/sentry-android-core/src/main/java/io/sentry/android/core/ScreenshotEventProcessor.java index dd0e259f937..3cf54ddf151 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/ScreenshotEventProcessor.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/ScreenshotEventProcessor.java @@ -47,6 +47,8 @@ public final class ScreenshotEventProcessor implements EventProcessor { private final boolean isReplayAvailable; private final AtomicBoolean isReplayModuleAbsenceLogged = new AtomicBoolean(false); + // TODO [MAJOR]: replace AndroidCurrentDateProvider with MonotonicClock + @SuppressWarnings("deprecation") public ScreenshotEventProcessor( final @NotNull SentryAndroidOptions options, final @NotNull BuildInfoProvider buildInfoProvider, diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/SystemEventsBreadcrumbsIntegration.java b/sentry-android-core/src/main/java/io/sentry/android/core/SystemEventsBreadcrumbsIntegration.java index 18ff901b0e9..7bf35c86c85 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/SystemEventsBreadcrumbsIntegration.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/SystemEventsBreadcrumbsIntegration.java @@ -298,6 +298,9 @@ final class SystemEventsBroadcastReceiver extends BroadcastReceiver { private static final long DEBOUNCE_WAIT_TIME_MS = 60 * 1000; private final @NotNull IScopes scopes; private final @NotNull SentryAndroidOptions options; + + // TODO [MAJOR]: replace AndroidCurrentDateProvider with MonotonicClock + @SuppressWarnings("deprecation") private final @NotNull Debouncer batteryChangedDebouncer = new Debouncer(AndroidCurrentDateProvider.getInstance(), DEBOUNCE_WAIT_TIME_MS, 0); diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/TombstoneIntegration.java b/sentry-android-core/src/main/java/io/sentry/android/core/TombstoneIntegration.java index d6d6a7767c8..6ea5b42448f 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/TombstoneIntegration.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/TombstoneIntegration.java @@ -54,6 +54,9 @@ public class TombstoneIntegration implements Integration, Closeable { private final @NotNull ICurrentDateProvider dateProvider; private @Nullable SentryAndroidOptions options; + // TODO [MAJOR]: replace CurrentDateProvider (wall clock) with SentryDateProvider; the epoch + // comparison it feeds must stay wall time + @SuppressWarnings("deprecation") public TombstoneIntegration(final @NotNull Context context) { // using CurrentDateProvider instead of AndroidCurrentDateProvider as AppExitInfo uses // System.currentTimeMillis diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/ViewHierarchyEventProcessor.java b/sentry-android-core/src/main/java/io/sentry/android/core/ViewHierarchyEventProcessor.java index 7090985a38b..62d8edcfb74 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/ViewHierarchyEventProcessor.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/ViewHierarchyEventProcessor.java @@ -46,6 +46,8 @@ public final class ViewHierarchyEventProcessor implements EventProcessor { private static final long DEBOUNCE_WAIT_TIME_MS = 2000; private static final int DEBOUNCE_MAX_EXECUTIONS = 3; + // TODO [MAJOR]: replace AndroidCurrentDateProvider with MonotonicClock + @SuppressWarnings("deprecation") public ViewHierarchyEventProcessor(final @NotNull SentryAndroidOptions options) { this.options = Objects.requireNonNull(options, "SentryAndroidOptions is required"); this.debouncer = diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/cache/AndroidEnvelopeCache.java b/sentry-android-core/src/main/java/io/sentry/android/core/cache/AndroidEnvelopeCache.java index 0373c39deea..e3b24ed6551 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/cache/AndroidEnvelopeCache.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/cache/AndroidEnvelopeCache.java @@ -38,6 +38,8 @@ public final class AndroidEnvelopeCache extends EnvelopeCache { private final @NotNull ICurrentDateProvider currentDateProvider; + // TODO [MAJOR]: replace AndroidCurrentDateProvider with MonotonicClock + @SuppressWarnings("deprecation") public AndroidEnvelopeCache(final @NotNull SentryAndroidOptions options) { this(options, AndroidCurrentDateProvider.getInstance()); } @@ -63,6 +65,9 @@ public boolean storeEnvelope(@NotNull SentryEnvelope envelope, @NotNull Hint hin return storeInternalAndroid(envelope, hint); } + // TODO [MAJOR]: this reading must stay paired with AppStartMetrics.getStartUptimeMs(), + // which is SystemClock.uptimeMillis(), so MonotonicClock is not a drop-in replacement + @SuppressWarnings("deprecation") private boolean storeInternalAndroid(@NotNull SentryEnvelope envelope, @NotNull Hint hint) { final boolean didStore = super.storeEnvelope(envelope, hint); diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/AndroidCurrentDateProvider.java b/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/AndroidCurrentDateProvider.java index cdf66c319a2..3e8e68a67fa 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/AndroidCurrentDateProvider.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/AndroidCurrentDateProvider.java @@ -4,11 +4,24 @@ import io.sentry.transport.ICurrentDateProvider; import org.jetbrains.annotations.ApiStatus; +/** + * An uptime clock: {@link SystemClock#uptimeMillis()} excludes time the device spent in deep sleep, + * which the name does not say. + * + *

Superseded by {@link io.sentry.time.MonotonicClock}, which counts deep sleep and says so in + * its name. + */ @ApiStatus.Internal public final class AndroidCurrentDateProvider implements ICurrentDateProvider { + @SuppressWarnings("deprecation") private static final ICurrentDateProvider instance = new AndroidCurrentDateProvider(); + /** + * @deprecated use {@link io.sentry.time.MonotonicClock} to measure an interval. + */ + @Deprecated + @SuppressWarnings("deprecation") public static ICurrentDateProvider getInstance() { return instance; } @@ -16,6 +29,7 @@ public static ICurrentDateProvider getInstance() { private AndroidCurrentDateProvider() {} @Override + @SuppressWarnings("deprecation") public long getCurrentTimeMillis() { return SystemClock.uptimeMillis(); } diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/Debouncer.java b/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/Debouncer.java index 8808947c0cc..c0d6cd719af 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/Debouncer.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/Debouncer.java @@ -30,6 +30,8 @@ public Debouncer( * @return true if the execution should be debounced due to maxExecutions executions being made * within waitTimeMs, otherwise false. */ + // TODO [MAJOR]: replace AndroidCurrentDateProvider with MonotonicClock + @SuppressWarnings("deprecation") public boolean checkForDebounce() { final long now = timeProvider.getCurrentTimeMillis(); if (lastExecutionTime.get() == 0 || (lastExecutionTime.get() + waitTimeMs) <= now) { diff --git a/sentry/src/main/java/io/sentry/transport/CurrentDateProvider.java b/sentry/src/main/java/io/sentry/transport/CurrentDateProvider.java index 9421baac501..855d9aabdc4 100644 --- a/sentry/src/main/java/io/sentry/transport/CurrentDateProvider.java +++ b/sentry/src/main/java/io/sentry/transport/CurrentDateProvider.java @@ -2,11 +2,24 @@ import org.jetbrains.annotations.ApiStatus; +/** + * The wall clock, which jumps when the device time changes. + * + *

Superseded by {@link io.sentry.SentryDateProvider} for timestamps and {@link + * io.sentry.time.MonotonicClock} for intervals. + */ @ApiStatus.Internal public final class CurrentDateProvider implements ICurrentDateProvider { + @SuppressWarnings("deprecation") private static final ICurrentDateProvider instance = new CurrentDateProvider(); + /** + * @deprecated use {@link io.sentry.SentryDateProvider} for a timestamp, or {@link + * io.sentry.time.MonotonicClock} to measure an interval. + */ + @Deprecated + @SuppressWarnings("deprecation") public static ICurrentDateProvider getInstance() { return instance; } @@ -14,6 +27,7 @@ public static ICurrentDateProvider getInstance() { private CurrentDateProvider() {} @Override + @SuppressWarnings("deprecation") public final long getCurrentTimeMillis() { return System.currentTimeMillis(); } diff --git a/sentry/src/main/java/io/sentry/transport/ICurrentDateProvider.java b/sentry/src/main/java/io/sentry/transport/ICurrentDateProvider.java index b4f86cf2d33..2d5f1208a10 100644 --- a/sentry/src/main/java/io/sentry/transport/ICurrentDateProvider.java +++ b/sentry/src/main/java/io/sentry/transport/ICurrentDateProvider.java @@ -2,7 +2,13 @@ import org.jetbrains.annotations.ApiStatus; -/** Date Provider to make the Transport unit testable */ +/** + * Date Provider to make the Transport unit testable + * + *

Superseded by {@link io.sentry.time.MonotonicClock}. The name here does not say which clock + * the value comes from, and implementations disagreed: {@link CurrentDateProvider} returns wall + * time while {@code AndroidCurrentDateProvider} returns uptime, through this one type. + */ @ApiStatus.Internal public interface ICurrentDateProvider { @@ -10,6 +16,9 @@ public interface ICurrentDateProvider { * Returns the current time in millis * * @return the time in millis + * @deprecated use {@link io.sentry.time.MonotonicClock} to measure an interval, or {@link + * io.sentry.SentryDateProvider} for a wall-clock timestamp. */ + @Deprecated long getCurrentTimeMillis(); } From 61f9de2b9e489ced2162b6fec6ea729cc9d9cd5b Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Thu, 3 Sep 2026 17:29:05 +0200 Subject: [PATCH 3/4] ref(android): Deprecate AndroidDateUtils.getCurrentSentryDateTime (JAVA-571) Its own javadoc already told callers to prefer options.getDateProvider(); this makes the compiler say so. The static holder cannot be configured or stubbed, which is the whole reason the note was there. Annotated on the method rather than the class, for the same import reason as the current-date providers. --- .../android/core/ActivityLifecycleIntegration.java | 12 ++++++++++++ .../io/sentry/android/core/AndroidDateUtils.java | 3 +++ .../performance/ActivityLifecycleSpanHelper.java | 3 +++ 3 files changed, 18 insertions(+) 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 505bb84ea2c..a843c0a30f2 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 @@ -549,6 +549,9 @@ private void finishTransaction( } } + // TODO [MAJOR]: replace AndroidDateUtils.getCurrentSentryDateTime() with + // options.getDateProvider().now() + @SuppressWarnings("deprecation") @Override public void onActivityPreCreated( final @NotNull Activity activity, final @Nullable Bundle savedInstanceState) { @@ -604,6 +607,9 @@ public void onActivityPostCreated( } } + // TODO [MAJOR]: replace AndroidDateUtils.getCurrentSentryDateTime() with + // options.getDateProvider().now() + @SuppressWarnings("deprecation") @Override public void onActivityPreStarted(final @NotNull Activity activity) { final ActivityLifecycleSpanHelper helper = activitySpanHelpers.get(activity); @@ -672,6 +678,9 @@ public void onActivityPostResumed(@NotNull Activity activity) { // empty override, required to avoid a api-level breaking super.onActivityPostResumed() calls } + // TODO [MAJOR]: replace AndroidDateUtils.getCurrentSentryDateTime() with + // options.getDateProvider().now() + @SuppressWarnings("deprecation") @Override public void onActivityPrePaused(@NotNull Activity activity) { // only executed if API >= 29 otherwise it happens on onActivityPaused @@ -1094,6 +1103,9 @@ private void onHeadlessAppStart() { * Standalone-only: this is only registered as a listener when standalone app start tracing is * enabled. */ + // TODO [MAJOR]: replace AndroidDateUtils.getCurrentSentryDateTime() with + // options.getDateProvider().now() + @SuppressWarnings("deprecation") private @Nullable AppStartExtension.ExtendedAppStart onExtendAppStartRequested() { if (scopes == null || options == null diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidDateUtils.java b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidDateUtils.java index 55c1e2c7cec..6e8d940e21f 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidDateUtils.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidDateUtils.java @@ -17,7 +17,10 @@ public final class AndroidDateUtils { * invocations. * * @return the UTC SentryDate + * @deprecated use {@code options.getDateProvider()}. This static holder cannot be configured or + * stubbed, which is why the note above already asked callers to prefer the options. */ + @Deprecated public static @NotNull SentryDate getCurrentSentryDateTime() { return dateProvider.now(); } diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/performance/ActivityLifecycleSpanHelper.java b/sentry-android-core/src/main/java/io/sentry/android/core/performance/ActivityLifecycleSpanHelper.java index accb56db0dc..ae58370a16f 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/performance/ActivityLifecycleSpanHelper.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/performance/ActivityLifecycleSpanHelper.java @@ -69,6 +69,9 @@ public void createAndStopOnStartSpan(final @Nullable ISpan parentSpan) { return onStartStartTimestamp; } + // TODO [MAJOR]: replace AndroidDateUtils.getCurrentSentryDateTime() with + // options.getDateProvider().now() + @SuppressWarnings("deprecation") public void saveSpanToAppStartMetrics() { if (onCreateSpan == null || onStartSpan == null) { return; From 86b6e5e8c4f6b20b2791bc5991a282427cfd4fbd Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Thu, 3 Sep 2026 17:29:05 +0200 Subject: [PATCH 4/4] changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b929e7c4a10..02be2663840 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Measure HTTP rate-limit backoff on a monotonic clock instead of the wall clock, so that a device time change no longer lifts or extends an active rate limit ([#6030](https://github.com/getsentry/sentry-java/pull/6030)) - Measure check-in durations on the monotonic clock, so a cron job that spans device sleep reports the time a user would measure instead of the time the CPU was awake ([#6032](https://github.com/getsentry/sentry-java/pull/6032)) +- Deprecate `DateUtils.getCurrentDateTime()` in favour of `options.getDateProvider().now()`, which is configurable and resolves finer than a millisecond ([#6043](https://github.com/getsentry/sentry-java/pull/6043)) ### Fixes @@ -17,6 +18,7 @@ - Add internal `Timestamp`, `Timing` and `EpochClock`, separating a serialized wall-clock instant from a monotonically measured duration ([#6045](https://github.com/getsentry/sentry-java/pull/6045)) - Deprecate `RateLimiter(ICurrentDateProvider, SentryOptions)` in favour of `RateLimiter(MonotonicClock, RateLimiterConfig)` ([#6030](https://github.com/getsentry/sentry-java/pull/6030)) - Measure ANR detection thresholds on the internal `MonotonicClock`, so the clock is named by the type instead of chosen at each call site ([#6041](https://github.com/getsentry/sentry-java/pull/6041)) +- Deprecate `ICurrentDateProvider.getCurrentTimeMillis()`, `CurrentDateProvider.getInstance()`, `AndroidCurrentDateProvider.getInstance()` and `AndroidDateUtils.getCurrentSentryDateTime()` in favour of `MonotonicClock` and `SentryDateProvider` ([#6043](https://github.com/getsentry/sentry-java/pull/6043)) ## 8.55.0