Skip to content

refactor(time): Centralize time handling with kotlinx-datetime#4545

Merged
jamesarich merged 3 commits into
mainfrom
feat/kotlintime
Feb 14, 2026
Merged

refactor(time): Centralize time handling with kotlinx-datetime#4545
jamesarich merged 3 commits into
mainfrom
feat/kotlintime

Conversation

@jamesarich

Copy link
Copy Markdown
Collaborator

This commit refactors time and date handling across the codebase to use a centralized and more robust approach with the kotlinx-datetime library and custom utility functions. The primary goal is to eliminate direct calls to System.currentTimeMillis() and java.util.Date, replacing them with testable and consistent time sources.

Key Changes:

  • Introduced TimeExtensions.kt and TimeConstants.kt:

    • Centralized access to the current time via nowMillis, nowSeconds, and nowInstant from Clock.System. This makes time-related logic more explicit and easier to mock in tests.
    • Added TimeConstants for common durations like ONE_HOUR and ONE_DAY, replacing magic numbers and TimeUnit conversions.
    • Provided convenient extension functions like Long.toInstant(), Instant.toDate(), and CountDownLatch.await(Duration).
  • Replaced System.currentTimeMillis() and java.util.Date:

    • Updated numerous files to use the new nowMillis, nowSeconds, and nowInstant properties. This affects timestamps for packets, logs, connection metrics, UI updates, and repository cache checks.
    • Replaced java.util.Date instantiations with nowMillis.toInstant().toDate() for interoperability with Android APIs that still require it (e.g., DateFormat, DatePickerDialog).
  • Refactored POSIX Time Zone Logic:

    • Moved the toPosixString logic from core/ui to core/model to make it a core utility.
    • Updated the implementation to use kotlinx.datetime.TimeZone instead of java.time.ZoneId for better multiplatform compatibility and consistency.
    • Removed the now-redundant ZoneIdExtensions.kt from core/ui.
  • Dependency and Build Updates:

    • Added the kotlinx-datetime library to core/model.
    • Enabled @ExperimentalTime opt-in at the project level.

This refactoring improves code quality by enhancing testability, consistency, and readability of time-related operations.

This commit refactors time and date handling across the codebase to use a centralized and more robust approach with the `kotlinx-datetime` library and custom utility functions. The primary goal is to eliminate direct calls to `System.currentTimeMillis()` and `java.util.Date`, replacing them with testable and consistent time sources.

### Key Changes:

-   **Introduced `TimeExtensions.kt` and `TimeConstants.kt`:**
    -   Centralized access to the current time via `nowMillis`, `nowSeconds`, and `nowInstant` from `Clock.System`. This makes time-related logic more explicit and easier to mock in tests.
    -   Added `TimeConstants` for common durations like `ONE_HOUR` and `ONE_DAY`, replacing magic numbers and `TimeUnit` conversions.
    -   Provided convenient extension functions like `Long.toInstant()`, `Instant.toDate()`, and `CountDownLatch.await(Duration)`.

-   **Replaced `System.currentTimeMillis()` and `java.util.Date`:**
    -   Updated numerous files to use the new `nowMillis`, `nowSeconds`, and `nowInstant` properties. This affects timestamps for packets, logs, connection metrics, UI updates, and repository cache checks.
    -   Replaced `java.util.Date` instantiations with `nowMillis.toInstant().toDate()` for interoperability with Android APIs that still require it (e.g., `DateFormat`, `DatePickerDialog`).

-   **Refactored POSIX Time Zone Logic:**
    -   Moved the `toPosixString` logic from `core/ui` to `core/model` to make it a core utility.
    -   Updated the implementation to use `kotlinx.datetime.TimeZone` instead of `java.time.ZoneId` for better multiplatform compatibility and consistency.
    -   Removed the now-redundant `ZoneIdExtensions.kt` from `core/ui`.

-   **Dependency and Build Updates:**
    -   Added the `kotlinx-datetime` library to `core/model`.
    -   Enabled `@ExperimentalTime` opt-in at the project level.

This refactoring improves code quality by enhancing testability, consistency, and readability of time-related operations.

Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Refactors time/date handling across the Android app to route “current time”, duration constants, and legacy Date interop through shared utilities, aiming to reduce scattered System.currentTimeMillis()/Date() usage and improve consistency.

Changes:

  • Introduces centralized time utilities/constants in core/model and migrates many call sites to nowMillis / nowSeconds / nowInstant.
  • Refactors POSIX time zone string generation to live in core/model and updates consumers/tests accordingly.
  • Replaces various TimeUnit conversions and magic numbers with kotlin.time.Duration and shared constants.

Reviewed changes

Copilot reviewed 86 out of 86 changed files in this pull request and generated 14 comments.

Show a summary per file
File Description
mesh_service_example/src/main/kotlin/com/meshtastic/android/meshserviceexample/MeshServiceViewModel.kt Uses shared nowMillis + InstantDate interop for logging/packets
gradle/libs.versions.toml Adds kotlinx-datetime version; changes Kotlin version
feature/settings/src/main/kotlin/org/meshtastic/feature/settings/util/FixedUpdateIntervals.kt Replaces TimeUnit conversions with Duration/TimeConstants
feature/settings/src/main/kotlin/org/meshtastic/feature/settings/radio/component/SecurityConfigItemList.kt Uses nowMillis for exported filename timestamp
feature/settings/src/main/kotlin/org/meshtastic/feature/settings/radio/component/DeviceConfigItemList.kt Switches POSIX TZ formatting to core/model utility
feature/settings/src/main/kotlin/org/meshtastic/feature/settings/radio/RadioConfigViewModel.kt Replaces timestamp source with nowMillis
feature/settings/src/main/kotlin/org/meshtastic/feature/settings/radio/CleanNodeDatabaseViewModel.kt Uses nowSeconds for node aging calculations
feature/settings/src/main/kotlin/org/meshtastic/feature/settings/debugging/DebugViewModel.kt Uses nowInstant/interop for formatting dates
feature/settings/src/main/kotlin/org/meshtastic/feature/settings/debugging/Debug.kt Uses shared time utilities for export filename timestamp
feature/settings/src/main/kotlin/org/meshtastic/feature/settings/SettingsScreen.kt Uses shared time utilities for export filenames
feature/node/src/test/kotlin/org/meshtastic/feature/node/metrics/EnvironmentMetricsStateTest.kt Replaces System.currentTimeMillis() with nowSeconds
feature/node/src/main/kotlin/org/meshtastic/feature/node/model/TimeFrame.kt Uses nowSeconds as default “now” source
feature/node/src/main/kotlin/org/meshtastic/feature/node/metrics/TracerouteLog.kt Uses nowMillis for preview timestamp formatting
feature/node/src/main/kotlin/org/meshtastic/feature/node/metrics/PositionLog.kt Uses nowSeconds for preview/test position time
feature/node/src/main/kotlin/org/meshtastic/feature/node/metrics/PaxMetrics.kt Uses shared ms/sec constant and InstantDate interop
feature/node/src/main/kotlin/org/meshtastic/feature/node/metrics/MetricsViewModel.kt Uses nowSeconds and InstantDate interop for export
feature/node/src/main/kotlin/org/meshtastic/feature/node/metrics/HostMetricsLog.kt Fixes time math types; uses nowSeconds in preview
feature/node/src/main/kotlin/org/meshtastic/feature/node/metrics/EnvironmentMetrics.kt Uses nowSeconds in preview telemetry
feature/node/src/main/kotlin/org/meshtastic/feature/node/metrics/DeviceMetrics.kt Uses nowSeconds in previews
feature/node/src/main/kotlin/org/meshtastic/feature/node/metrics/CommonCharts.kt Uses TimeConstants/Duration and InstantDate interop
feature/node/src/main/kotlin/org/meshtastic/feature/node/detail/NodeRequestActions.kt Uses nowMillis for cooldown bookkeeping
feature/node/src/main/kotlin/org/meshtastic/feature/node/component/LastHeardInfo.kt Adds showLabel; uses nowSeconds in preview
feature/node/src/main/kotlin/org/meshtastic/feature/node/component/CooldownOutlinedIconButton.kt Uses nowMillis for cooldown calculations
feature/node/src/main/kotlin/org/meshtastic/feature/node/compass/CompassViewModel.kt Uses shared time utilities; removes local millis/second constant
feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/component/MessageItem.kt Uses nowMillis in previews
feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/MessageViewModel.kt Refactors unread/filtered metadata queries into StateFlows
feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/Message.kt Uses new ViewModel StateFlows instead of per-call flows
feature/messaging/src/androidTest/kotlin/org/meshtastic/feature/messaging/component/MessageItemTest.kt Uses nowMillis in UI tests
feature/map/src/main/kotlin/org/meshtastic/feature/map/BaseMapViewModel.kt Uses TimeConstants/nowSeconds; adds nodes-with-position flow
feature/map/src/google/kotlin/org/meshtastic/feature/map/component/PulsingNodeChip.kt Uses nowSeconds for last-heard comparisons
feature/map/src/google/kotlin/org/meshtastic/feature/map/component/EditWaypointDialog.kt Refactors expiry date/time logic using Instant/TimeZone utilities
feature/map/src/google/kotlin/org/meshtastic/feature/map/MapView.kt Uses nodes-with-position flow; replaces time calls; adjusts cluster click behavior
feature/map/src/fdroid/kotlin/org/meshtastic/feature/map/component/EditWaypointDialog.kt Refactors expiry date/time logic using Instant/TimeZone utilities
feature/map/src/fdroid/kotlin/org/meshtastic/feature/map/SqlTileWriterExt.kt Uses nowMillis for expiry queries
feature/map/src/fdroid/kotlin/org/meshtastic/feature/map/MapView.kt Uses nowMillis for waypoint expiry UI
feature/firmware/src/main/kotlin/org/meshtastic/feature/firmware/ota/WifiOtaTransport.kt Uses nowMillis for timeout loop
feature/firmware/src/main/kotlin/org/meshtastic/feature/firmware/ota/Esp32OtaUpdateHandler.kt Uses nowMillis for progress elapsed time
core/ui/src/test/kotlin/org/meshtastic/core/ui/timezone/ZoneIdExtensionsTest.kt Updates test to kotlinx.datetime.TimeZone + moved utility
core/ui/src/main/kotlin/org/meshtastic/core/ui/util/ProtoExtensions.kt Uses nowMillis for age formatting
core/ui/src/main/kotlin/org/meshtastic/core/ui/util/FormatAgo.kt Uses nowMillis and removes direct currentTimeMillis import
core/ui/src/main/kotlin/org/meshtastic/core/ui/timezone/ZoneIdExtensions.kt Deprecates UI-layer TZ conversion in favor of model-layer utility
core/ui/src/main/kotlin/org/meshtastic/core/ui/component/TimeTickWithLifecycle.kt Uses nowMillis for lifecycle-driven time ticks
core/ui/src/main/kotlin/org/meshtastic/core/ui/component/LastHeardInfo.kt Uses nowSeconds in preview
core/model/src/test/kotlin/org/meshtastic/core/model/util/TimeExtensionsTest.kt Adds tests for new time utilities
core/model/src/main/kotlin/org/meshtastic/core/model/util/TimeExtensions.kt Adds shared now/timezone/interop extensions
core/model/src/main/kotlin/org/meshtastic/core/model/util/TimeConstants.kt Adds shared duration constants
core/model/src/main/kotlin/org/meshtastic/core/model/util/PosixTimeZoneUtils.kt Moves POSIX TZ conversion to core/model
core/model/src/main/kotlin/org/meshtastic/core/model/util/DateTimeUtils.kt Refactors date/time formatting and uptime/mute duration math
core/model/src/main/kotlin/org/meshtastic/core/model/NodeInfo.kt Uses nowSeconds for “currentTime” helpers
core/model/src/main/kotlin/org/meshtastic/core/model/DataPacket.kt Uses nowMillis as default packet timestamp; preserves nullable replyId
core/model/build.gradle.kts Adds kotlinx-datetime dependency
core/database/src/test/kotlin/org/meshtastic/core/database/dao/MigrationTest.kt Uses nowMillis in test data
core/database/src/main/kotlin/org/meshtastic/core/database/entity/Packet.kt Uses nowMillis for mute checks
core/database/src/main/kotlin/org/meshtastic/core/database/entity/NodeEntity.kt Uses nowMillis/nowSeconds for timestamps
core/database/src/main/kotlin/org/meshtastic/core/database/entity/FirmwareReleaseEntity.kt Uses nowMillis for cache timestamps
core/database/src/main/kotlin/org/meshtastic/core/database/entity/DeviceHardwareEntity.kt Uses nowMillis for cache timestamps
core/database/src/main/kotlin/org/meshtastic/core/database/dao/PacketDao.kt Uses nowMillis for mute-until calculations
core/database/src/main/kotlin/org/meshtastic/core/database/DatabaseManager.kt Uses nowMillis for “last used” tracking
core/database/src/androidTest/kotlin/org/meshtastic/core/database/dao/PacketDaoTest.kt Uses nowMillis in android tests
core/data/src/test/kotlin/org/meshtastic/core/data/repository/MeshLogRepositoryTest.kt Uses nowMillis in repository tests
core/data/src/test/kotlin/org/meshtastic/core/data/repository/DeviceHardwareRepositoryTest.kt Uses nowMillis in repository tests
core/data/src/main/kotlin/org/meshtastic/core/data/repository/MeshLogRepository.kt Uses TimeConstants/nowMillis for retention cutoffs
core/data/src/main/kotlin/org/meshtastic/core/data/repository/FirmwareReleaseRepository.kt Uses TimeConstants/nowMillis for staleness checks
core/data/src/main/kotlin/org/meshtastic/core/data/repository/DeviceHardwareRepository.kt Uses TimeConstants/nowMillis for staleness checks
build-logic/convention/src/main/kotlin/org/meshtastic/buildlogic/KotlinAndroid.kt Enables ExperimentalTime opt-in globally
app/src/main/java/com/geeksville/mesh/ui/contact/Contacts.kt Uses TimeConstants/nowMillis; uses mutableIntStateOf
app/src/main/java/com/geeksville/mesh/service/ReactionReceiver.kt Refactors reaction handling to ServiceRepository action
app/src/main/java/com/geeksville/mesh/service/PacketHandler.kt Uses Duration timeouts; uses nowMillis; changes logging
app/src/main/java/com/geeksville/mesh/service/MeshTracerouteHandler.kt Uses nowMillis for elapsed time
app/src/main/java/com/geeksville/mesh/service/MeshServiceNotificationsImpl.kt Uses nowMillis; changes stats interval constants; adjusts reaction extras
app/src/main/java/com/geeksville/mesh/service/MeshNodeManager.kt Uses nowMillis default time for positions
app/src/main/java/com/geeksville/mesh/service/MeshNeighborInfoHandler.kt Uses nowMillis for elapsed time
app/src/main/java/com/geeksville/mesh/service/MeshMessageProcessor.kt Uses nowMillis/nowSeconds for timestamps
app/src/main/java/com/geeksville/mesh/service/MeshDataHandler.kt Uses nowMillis/nowSeconds; switches node ID mapping calls
app/src/main/java/com/geeksville/mesh/service/MeshConnectionManager.kt Uses nowMillis/nowSeconds; uses Duration conversions
app/src/main/java/com/geeksville/mesh/service/MeshCommandSender.kt Uses nowMillis/nowSeconds for packet timestamps and start times
app/src/main/java/com/geeksville/mesh/service/MeshActionHandler.kt Uses nowMillis for reaction timestamps
app/src/main/java/com/geeksville/mesh/service/MarkAsReadReceiver.kt Uses nowMillis for read markers
app/src/main/java/com/geeksville/mesh/repository/usb/SerialConnectionImpl.kt Uses CountDownLatch.await(Duration) extension
app/src/main/java/com/geeksville/mesh/repository/radio/TCPInterface.kt Uses nowMillis for connection timing
app/src/main/java/com/geeksville/mesh/repository/radio/SerialInterface.kt Uses nowMillis for connection timing
app/src/main/java/com/geeksville/mesh/repository/radio/RadioInterfaceService.kt Uses nowMillis as default keepAlive time
app/src/main/java/com/geeksville/mesh/repository/radio/NordicBleInterface.kt Uses nowMillis for timing measurements
app/src/main/java/com/geeksville/mesh/repository/radio/MockInterface.kt Uses nowSeconds for mock packet timestamps
app/src/main/java/com/geeksville/mesh/concurrent/SyncContinuation.kt Uses nowMillis for timeout arithmetic
app/src/main/java/com/geeksville/mesh/MeshUtilApplication.kt Uses Duration-based WorkManager periodic interval

Comment thread feature/map/src/google/kotlin/org/meshtastic/feature/map/MapView.kt
Comment thread app/src/main/java/com/geeksville/mesh/service/ReactionReceiver.kt
Comment thread gradle/libs.versions.toml
@codecov

codecov Bot commented Feb 12, 2026

Copy link
Copy Markdown

❌ 1 Tests Failed:

Tests completed Failed Passed Skipped
242 1 241 1
View the top 1 failed test(s) by shortest run time
org.meshtastic.feature.firmware.ota.BleOtaTransportNordicMockTest::full ota flow with nordic mocks
Stack Traces | 0.147s run time
java.lang.IllegalStateException: Peripheral not connected
	at no.nordicsemi.kotlin.ble.client.mock.PeripheralSpec.estimateTransferDuration-3nIYWDw$client_mock(PeripheralSpec.kt:275)
	at no.nordicsemi.kotlin.ble.client.mock.PeripheralSpec$simulateValueUpdate$1.invokeSuspend(PeripheralSpec.kt:535)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:34)
	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:100)
	at kotlinx.coroutines.internal.LimitedDispatcher$Worker.run(LimitedDispatcher.kt:124)
	at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:89)
	at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:586)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:820)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:717)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:704)

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

- Updates Kotlin to v2.3.10
- Replaces deprecated `kotlin.time.Clock` with `kotlinx.datetime.Clock`.
- Replaces deprecated `ldt.dayOfMonth` with `ldt.day`.
- Adds a guard clause to `ReactionReceiver.onReceive` to exit early if the action is not `REACT_ACTION`.
- Removes redundant `kotlin.time.Instant` import.
- Defines and uses a `DAY_DURATION` constant for clarity.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 86 out of 86 changed files in this pull request and generated 7 comments.

Comment thread app/src/main/java/com/geeksville/mesh/service/PacketHandler.kt
Comment thread feature/map/src/google/kotlin/org/meshtastic/feature/map/MapView.kt
Comment thread app/src/main/java/com/geeksville/mesh/service/ReactionReceiver.kt
# Conflicts:
#	app/src/main/java/com/geeksville/mesh/service/MeshMessageProcessor.kt
#	core/model/src/main/kotlin/org/meshtastic/core/model/DataPacket.kt
@jamesarich jamesarich added this pull request to the merge queue Feb 14, 2026
Merged via the queue into main with commit 5ca2ab4 Feb 14, 2026
7 checks passed
@jamesarich jamesarich deleted the feat/kotlintime branch February 14, 2026 02:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants