Kronos-Android: Easy NTP

Based on a sampling of over 5 million sessions, over 3% of Lyft’s Android users have device clocks skewed by over one minute. That’s more than twice as many as the 1% we see on iOS.

Think about what that means for our systems and users if we depended on device timestamps for analytics, offline events, locations or displayed times. Data loses order and meaning; drivers get dispatched for rides that no longer make sense geographically; users might see estimated arrival times that look like they’re in the past.

Introducing Kronos

We are announcing Kronos-Android, an open source Network Time Protocol (NTP) synchronization library, which provides a trusted clock on the JVM. This post about Kronos for iOS goes into detail about our decision to derive a “sane time” from NTP.

Unlike the system’s clock, the time reported by Kronos is unaffected when the local time is changed while your app is running. Instead, Kronos stores accurate NTP time along with a delta between the NTP time and the system uptime. Since uptime increases monotonically, Kronos isn’t affected by device time changes. CallingKronosClock.getCurrentTimeMs() will return the local time based on the last known accurate time + delta since last sync.

Getting Started

Include the following in your build.gradle file:

implementation "com.lyft.kronos:kronos-android:$latest_version"

Obtain a Kronos clock instance that is synchronized with NTP servers.

class YourApplication : Application() {

lateinit var kronosClock: KronosClock

override fun onCreate() {
super.onCreate()

kronosClock = AndroidClockFactory.createKronosClock(applicationContext)
kronosClock.syncInBackground()
}
}

Replace usages of

System.currentTimeMillis()

with

kronosClock.getCurrentTimeMs()

If the NTP server cannot be reached or Kronos has not yet been synced, getCurrentTimeMs() will return time from the device or other fallback clock and trigger syncInBackground(). If you’d rather control the fallback, you can use getCurrentNtpTimeMs(), which returns null instead of falling back.

Since it relies on system uptime, Kronos detects and requires a new sync after each reboot.

For your use case

Kronos comes with a set of default configurations that have worked well for us. You can customize the configuration by using AndroidClockFactory.createKronosClock() with a set of optional parameters described in the README.

With or without Android

Kronos provides access to the Kotlin-only base library, Kronos-Java, for usage with non-Android modules. Kronos-Java depends on an externally provided local clock and cache. The Android library abstracts away the creation of the clock by extracting the Android system clock from a provided Context. It abstracts away the creation of the cache by using SharedPreferences.

To use Kronos-Java, include the following in your build.gradle file:

implementation "com.lyft.kronos:kronos-java:$latest_version"

Welcome to sane time

Check out the library on Github.

Lyft is hiring! If you’re interested in working on projects like this and shaping the future of transportation, apply to join our team.

Please comment with your favorite quote about time — I missed the opportunity to include any in this blog post. I was entertained by many of these.

Thanks to all the contributors, including Leo Kwong, Geoff Hackett, Artem Zinnatullin, Ryan Rhee, Colin Rafferty, Martín Conte MacDonell, and Alexey Zakharov. ?


Kronos-Android: Easy NTP was originally published in Lyft Engineering on Medium, where people are continuing the conversation by highlighting and responding to this story.

Source: Lyft