refactor(time): Centralize time handling with kotlinx-datetime#4545
Merged
Conversation
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>
Contributor
There was a problem hiding this comment.
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/modeland migrates many call sites tonowMillis/nowSeconds/nowInstant. - Refactors POSIX time zone string generation to live in
core/modeland updates consumers/tests accordingly. - Replaces various
TimeUnitconversions and magic numbers withkotlin.time.Durationand 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 + Instant→Date 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 Instant→Date interop |
| feature/node/src/main/kotlin/org/meshtastic/feature/node/metrics/MetricsViewModel.kt | Uses nowSeconds and Instant→Date 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 Instant→Date 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 |
❌ 1 Tests Failed:
View the top 1 failed test(s) by shortest run time
To view more test analytics, go to the Test Analytics Dashboard |
- 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.
# Conflicts: # app/src/main/java/com/geeksville/mesh/service/MeshMessageProcessor.kt # core/model/src/main/kotlin/org/meshtastic/core/model/DataPacket.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This commit refactors time and date handling across the codebase to use a centralized and more robust approach with the
kotlinx-datetimelibrary and custom utility functions. The primary goal is to eliminate direct calls toSystem.currentTimeMillis()andjava.util.Date, replacing them with testable and consistent time sources.Key Changes:
Introduced
TimeExtensions.ktandTimeConstants.kt:nowMillis,nowSeconds, andnowInstantfromClock.System. This makes time-related logic more explicit and easier to mock in tests.TimeConstantsfor common durations likeONE_HOURandONE_DAY, replacing magic numbers andTimeUnitconversions.Long.toInstant(),Instant.toDate(), andCountDownLatch.await(Duration).Replaced
System.currentTimeMillis()andjava.util.Date:nowMillis,nowSeconds, andnowInstantproperties. This affects timestamps for packets, logs, connection metrics, UI updates, and repository cache checks.java.util.Dateinstantiations withnowMillis.toInstant().toDate()for interoperability with Android APIs that still require it (e.g.,DateFormat,DatePickerDialog).Refactored POSIX Time Zone Logic:
toPosixStringlogic fromcore/uitocore/modelto make it a core utility.kotlinx.datetime.TimeZoneinstead ofjava.time.ZoneIdfor better multiplatform compatibility and consistency.ZoneIdExtensions.ktfromcore/ui.Dependency and Build Updates:
kotlinx-datetimelibrary tocore/model.@ExperimentalTimeopt-in at the project level.This refactoring improves code quality by enhancing testability, consistency, and readability of time-related operations.