Skip to content

Your phone has more than one clock

Abdul-Qudus Rufai 1 min read

Nsila’s Android app has daily streaks. The requirement was simple to state: changing the phone’s date or timezone must never create or erase streak days. This worked the first time it was implemented, so there is no debugging story here. The concept behind it was just new to me.

Before this, I thought of time on a phone as one thing. Android has several clocks, and they answer different questions:

  • System.currentTimeMillis: the wall clock. It is what people read, and the user or the network can change it
  • SystemClock.elapsedRealtime: time since the phone booted, including while it is asleep. The user cannot change it
  • SystemClock.uptimeMillis: time since boot, but it stops during deep sleep

How the streak uses them

A date alone cannot be trusted, because anyone can move it forward a week. So the app counts logical days, which move forward only when real time has passed. Within one boot, the elapsed clock provides that proof.

The elapsed clock restarts when the phone reboots, so two readings can only be compared if they come from the same boot. The app stores a boot count with each reading to check that. Across a reboot it has to trust the wall clock, but a date that went backwards is never treated as a new day.

The wall clock still has a job, which is deciding where a day begins for the person using the app. A new day should start at their midnight, not 24 hours after they installed.

A different clock for a different job

Hand tracking in the same app uses uptimeMillis, because MediaPipe’s live mode only needs timestamps that always increase. The code also guarantees that no two frames ever get the same value:

val now = SystemClock.uptimeMillis().coerceAtLeast(lastTimestamp + 1)

Neither clock is better. The useful question is what you need the number to mean: a date for a person, or a duration you can rely on.

Read the Nsila case study