In many mobile applications, it is necessary to perform certain actions at a specific moment in time or after a certain time interval. All of this requires having reliable information about the current time. This article will discuss a method for obtaining the current time, independent of device and system settings.
Use Case
Let's consider the time synchronization process using the following example. Suppose your application implements a special offer (e.g., free access to premium features) for users who meet certain conditions. One of these conditions is regular use of the application (e.g., at least once every 12 hours) over several days. At the same time, no day can be skipped, otherwise the offer is canceled. Each time the user opens the application, their progress should be displayed. However, the application does not have its own backend to handle validation, meaning the application can only rely on itself and third-party services (of course, any interaction with them should preferably be minimized or eliminated). Leaving aside possible technical implementation options for the checks, let's focus on the following:
How to check that enough time has passed since the last mark of fulfilling the next part of the condition (in our case, 12 hours)?
How to check that no day has been skipped? To solve these issues, it is necessary to ensure a reliable mechanism for obtaining accurate and maximally independent timestamps of when certain events occur.
Why is this important?
- Independence from user settings:
Devices may have incorrectly set time zones or intentionally altered time settings, which can cause application malfunctions.
Some users disable automatic time synchronization, which can create problems for applications requiring precision.
Global users: Applications used worldwide face different time zones, time formats, and daylight saving time rules.
Critical accuracy: Timestamps for transactions, events, and data synchronization must be accurate, especially in banking, logistics, and financial applications.
Fraud prevention: Changing the device's time can be used to bypass restrictions, such as during free trial periods or promotions.
Technical Implementation
For Android devices, the only way to obtain time independently of device/system settings and other factors that can be directly influenced during application use is to rely on external systems. Applications can obtain time from servers via protocols such as NTP (Network Time Protocol) or APIs. Data sources can be specialized time servers (e.g., services offered by Google, Microsoft, etc.) or national services. Applications can connect to the internet to obtain time, ignoring local device settings. Another option is GPS integration: GPS receivers provide precise time as part of the satellite system (typically used in navigation applications). Since it is preferable to minimize the use of external systems, we will use the NTP protocol.
NTP (Network Time Protocol) is a protocol for synchronizing time between devices on a network. It allows obtaining accurate time from special servers that use atomic clocks or GPS.
Time Synchronization Using NTP
The general workflow with NTP is quite simple:
The device sends a request to an NTP server.
The server returns the current precise time.
The device compares it with its own time and adjusts if necessary. Thus, using NTP ensures the same time across all devices on the network, provides accuracy, and prevents errors due to desynchronization.
Considering the specifics outlined in the example, the following should be taken into account:
There is no need to contact the NTP server every time precise time is required—it can be done once, and then the obtained value and the time elapsed since its retrieval can be used. For this, Android provides the API
long SystemClock.elapsedRealtime()
, which returns the time elapsed since the device was booted (this cannot be influenced except by restarting the device—see point 2 for a solution to this issue).The
elapsedRealtime
value represents the time elapsed since the device was booted and, therefore, resets with each reboot. This must be considered when determining passed time interval.
Thus, for the correct operation of the time synchronizer, it is necessary to respond to three events:
Start of the scenario (in our example, this point
X
is the user accepting the special offer). At this moment, a request to the NTP server should be made. Let's denote the server's response astrueTimeStart
. Save this value in persistent memory (e.g., in SharedPreferences). This value will also be used astrueTimeOnReboot
in case the device is restarted. At the same time, determine and save two device-related values: 1) the elapsed real time value at the start of the scenario (denoted aselapsedRealtimeStart
and 2) the local time at the start of the scenario (denoted aslocalTimeStart
). These actions are performed within the application code upon the occurrence of the specific event (start of the scenario—in our example, when the user clicks the "Accept Offer" button).Device reboot (point
Y
). Similarly, the device's time and the actual NTP time need to be synchronized. For this, a request to the NTP server is made, saving its response astrueTimeOnReboot
. At the same time, update the Telapsed value. From the Android system's perspective, to receive a notification about the device booting, a BroadcastReceiver with the filterIntent.ACTION_BOOT_COMPLETED
and/orIntent.ACTION_LOCKED_BOOT_COMPLETED
should be implemented (receiving one or both of these notifications depends on the specifics of the firmware implementation).
Therefore, the next time the current time is needed (and there can be many such situations), no new requests to the server will be required—it will be sufficient to use the saved time marks:
val delta0 = trueTimeOnReboot - trueTimeStart
val delta1 = SystemClock.elapsedRealtime() - elapsedRealtimeStart
val localTimeNow = localTimeStart + delta0 + delta1
Here:
delta0
is the time elapsed since the start of the scenario until the last device boot. This ensures accounting for the time when the device was off.delta1
is the time elapsed since the last device boot.localTimeNow
is the desired timestamp. Taking the device's time at the start of the scenario (localTimeStart) as the reference point (point X), we add to it all the time elapsed until the last device boot (point Y), as well as the device's uptime (until the current moment). Thus, we obtain an accurate time value that accounts only for the actual elapsed time and is independent of other factors, such as manual changes to the date/time in system settings.
The full code can be viewed on GitHub.
Conclusion
This article discussed a mechanism for obtaining precise time independently of device settings. This algorithm helps make applications more reliable and secure. It uses data from NTP servers and simple calculations to always display accurate time, even if the user changes settings or the device operates incorrectly. This is especially important for banking applications, online games, booking systems, and other services where time accuracy affects functionality. This approach not only eliminates errors but also protects against attempts to cheat, such as changing the time to extend a trial period.
Top comments (0)