Skip to content

Car Service & Framework

Time, clocks and time zones

Getting the time right in a vehicle is genuinely hard — three different clocks, no network for weeks, a driver who crosses borders, and a timestamp field that fails silently if you pick the wrong one.

Intermediate6 minTime · Clocks · Framework

Time looks like a solved problem until you work on a vehicle. Then you discover that a car can sit unpowered for six weeks, cross three time zones in an afternoon, and have no network connection for any of it.

Three clocks, and picking the wrong one is a silent bug#

Android has several notions of "now", and they are not interchangeable.

ClockWhat it measuresSurvives rebootCan jump
System.currentTimeMillis()Wall clock — 1970 epochYesYes
SystemClock.elapsedRealtime()Since boot, including sleepNoNo
SystemClock.uptimeMillis()Since boot, excluding sleepNoNo

The VHAL timestamp, again#

This is why the topics keep insisting on it. A VehiclePropValue timestamp is CLOCK_BOOTTIME in nanoseconds — the native equivalent of elapsedRealtime().

Correct
.timestamp = elapsedRealtimeNano(),
Silently wrong
.timestamp = std::chrono::system_clock::now().time_since_epoch().count(),

Where the vehicle gets the actual time#

A phone asks the network. A vehicle often cannot.

The sources, in rough order of preference:

. Satellites broadcast extremely accurate time, and a vehicle usually has a receiver. This is the best source and needs no network at all.

Network time. If the vehicle is connected, NTP works as it does anywhere.

The . A small battery-backed clock that keeps running while the car is off. It drifts — seconds per week — but it means the vehicle knows roughly what time it is the instant it powers on.

The vehicle network. Some vehicles distribute time from a gateway ECU.

Time zones and a moving vehicle#

A phone changes time zone when its network does. A vehicle drives across borders.

Reacting to a time zone change
context.registerReceiver(
    object : BroadcastReceiver() {
        override fun onReceive(context: Context, intent: Intent) {
            // Everything you have formatted and cached is now wrong.
            reformatAllDisplayedTimes()
        }
    },
    IntentFilter(Intent.ACTION_TIMEZONE_CHANGED),
)

Whether a vehicle should follow the local zone automatically is a product decision with genuine arguments on both sides — a driver near a border may not want the clock flipping back and forth. Check what your programme decided rather than assuming.

Scheduling across sleep#

The head unit suspends. An alarm set for 3am has to survive that.

Alarms that fire while the vehicle is asleep
val alarmManager = context.getSystemService(AlarmManager::class.java)
 
// RTC_WAKEUP wakes the device at a wall-clock time
alarmManager.setExactAndAllowWhileIdle(
    AlarmManager.RTC_WAKEUP,
    triggerAtMillis,
    pendingIntent,
)
 
// ELAPSED_REALTIME_WAKEUP wakes it after a duration, immune to clock jumps
alarmManager.setExactAndAllowWhileIdle(
    AlarmManager.ELAPSED_REALTIME_WAKEUP,
    SystemClock.elapsedRealtime() + delayMillis,
    pendingIntent,
)

Logs, and why timestamps disagree#

Which clock is each log using?
adb shell dmesg | head -3          # seconds since boot
adb logcat -v threadtime | head -3 # wall clock
adb shell bootstat --print         # milliseconds since boot

A checklist#

  • Measuring a duration? elapsedRealtime(), never wall clock.
  • Writing a VHAL timestamp? CLOCK_BOOTTIME nanoseconds.
  • Storing a moment for later display? Store the instant, format at render.
  • Handling ACTION_TIMEZONE_CHANGED? Reformat, do not assume.
  • Setting an alarm? ELAPSED_REALTIME_WAKEUP unless it is genuinely a wall-clock moment.
  • Correlating logs? logcat -v uptime.

Next#

Accessibility — a surface that is easy to forget and increasingly required.

References & further reading

Code links target the main branch on cs.android.com. AOSP moves — if a path 404s, search the symbol instead.