At OpenSignal, each datapoint we collect has two timestamps: the time the reading was taken and the time the reading was inserted into our server. Because we make extensive use of SQLite cacheing on devices, these times can be far apart - sometimes up to the order of weeks. The app reading time, however, should always be before the server insert time, otherwise you have an apparent violation of causality. When we noticed this happening occasionally, we took a closer look and found widespread difficulties getting accurate time readings.
We uncovered various causes of these Android time discrepancies: a bug in Android's setting of time via GPS which produces a 15s error; disagreements of one hour caused by Time Zone edge cases and perhaps also by a system bug in the application of timezones; and deliberate manual adjustments to the system clock time in order to fool games.
We'll focus on the time difference between the timestamps of speedtests taken in-app, and the timestamps for when they arrive at the server database.
Time difference = App time - Server time
If all phone clocks and our server clock were synchronised this would always be negative and usually quite small, since the app will attempt to upload a speedtest immediately after it has run. If the upload fails, it will wait until the next speedtest is run.
Looking at the last million speedtests run, this is the distribution found when restricting the dataset to just one minute either side of zero (density = frequency density).
Drawing out the salient features, we see some behaviour that is to be expected and some that is rather curious:
- The peak is at -5s, implying that the most common scenario is a speedtest being taken and 5s later appearing in the database on the server. That makes sense, the app needs to do a little bit of work, connect to the server, and send that data. So if an upload is attempted immediately after a test, a 5s delay is reasonable.
- The distribution is skewed to the left. That's good: rows are more likely to suffer a longer delay than 5s (since they hang around in the database longer, waiting for a chance to upload) than a shorter delay.
- But there are positive time differences … and in particular a small sub-peak at about 13s. We think this may be the bug the Verge reported on here.
- tl;dr: Android clocks set by the system are exactly 15s too fast. So the devices with this bug should be 15s to the right of our -5s peak. or at 10s.
Some noise to the right is expected: while GPS is occasionally used to synchronize the clocks, the time is generally maintained on the device by its internal clock which can drift.
Stepping back to look 100 minutes either side of the origin reveals another anomaly: peaks at +/- one hour:
This could be caused by the following:
- The user moved to another timezone one hour away
- Location based services were turned off, so the system couldn't update the timezone
- The user changed the time manually (not the timezone)
The time displayed to the user will seem correct, if they go to France from the UK, they'll set the time one hour in advance. The system time, however, will be wrong - as the device still thinks it's in the UK timezone. In this case we'd expect the peaks at plus and minus one hour to be roughly the same height. But the right peak is about 20% higher than the left peak. This difference may be because users are deliberately mis-setting times.
It may not be only moving between countries or states that causes timezone problems, but daylight saving adjustments going awry on the device, as the following chart suggests:
This shows the weekly total number of devices submitting data found to be more than one hour in the future (actually we look at 59 minutes to catch minor fluctuations of a clock set one hour ahead). To give some idea of statistical significance, speedtests are currently run on about 100k distinct devices each week, though this figure was lower in the past.
Note the peak in November 2011, when the clocks go back. Consider clocks in Arizona, which does not obey DST unlike surrounding states, if for some reason the system thinks it does observe DST the clock will be set back one hour, Android users will react by setting it forward one hour - thus the system clock will be one hour in the future. There are many reports to be found of this in forums. In particular this may happen where users live in a border area, between a timezone that uses DST and one not using DST, if "use network time" is enabled in the app settings they may be erroneously placed in the wrong timezone, for half of the year this will provide correct times, but when DST comes into play users will find their phone an hour out and will manually adjust the displayed time, instead of chossing the correct timezone.
The fact that this peak occured just one year suggests the cause may have been exacerbated by an Android bug that was later fixed.
Coin Dozer is styled as an old-style fair/arcade game in which coins are pushed onto a table where they can bump other coins off into a collection tray. The user starts with a finite number of coins, more coins can be won to continue playing, additionally every 30s a coin is generated. If all coins run out by falling off the wrong side, the user must wait for a coin to be generated. This is a cunning technique to encourage users to keep the app open, which will increase ad revenue for the creators.
A cheat was discovered on Android and iOS that involves manually advancing the system clock to fool Coin Dozer and gain coins more rapidly. Since the system time on both iOS and Android is altered by scrolling through numbers, the quickest adjustment is to alter the clock by one hour, one day, or one minute. The cheats generally recommend an hour or a day in order to generate a full complement of coins.
On Android this cheat now requires rooting the phone. Coin Dozer are probably now using elapsedRealtime(), which returns the number of milliseconds the device has been on, and does not change when the clock is altered. However there are probably other apps that are still relying on currentTimeMillis, the field we've been measuring, which is susceptible to alteration.
What this cheat (and others like it) gives us is a possible explanation for why the peak on the right had side is higher than the left. It shows that that there is an incentive for people to deliberately mis-set their clocks an hour forward, which could go some way towards explaining the aysmmetry of the two peaks.
Examining the last million speedtests, 1.9% of devices are one hour or more in the future. As noted above, we can't be sure from this dataset how many devices have their clock set one hour in the past - since a negative value for difference could be because the reading is cached - however since the peak at plus one hour as about 20% higher than the peak at minus one hour we'll say the total is: 1.9 + 1.9*(1/1.2) = 3.5%
Looking at the number of devices having times set more than one day in the future, we find the numbers much smaller but have recently been growing:
For any app collecting data and cacheing it, incorrect clocks are a problem. For crowdsourcing apps like ours, it's a source of noise in timestamped data. For example it can cause problems when doing things like measuring cell breathing, whereby cell towers lose coverage at particular times of day as network traffic in that area increases.
Pretty much any app whose functionality in some way depends on how you are moving will use an offline timesource, so this includes apps like Waze or Nike+. Even if these apps include a large real-time element, like Waze, they will likely also collect absolute time. For example, Waze might want to use data that was cached on a device (since the device was in a tunnel or for some other reason could not immediately upload data) to calculate peak traffic hours.
Ensuring time accuracy requires the app to receive time notifications from a server (a special protocol, NTP, has been designed for this), when an update is received cache the value (call it T0), also cache the value returned by elapsedRealtime() (call it E0), to get the time at some later point when elapsedRealtime() = E1, use:
T1= E1 - E0 + T0
This solution is not ideal. It's cumbersome and, since elapsedRealtime() resets on boot, it requires the app to contact a server on every boot which will not be possible in many situations.
Unfortunately there are no silver bullets for this problem.
This article has been republished with permission from Open Signal.
OpenSignal are the world's largest source of crowdsourced coverage information. Their Signal-finder app for Android and iOS helps people get a better connection, and lets them contribute to the project of creating impartial coverage maps - used to help consumers work out which network is best for them.