Skip to content

Android: outdated tzdata used due to missing APEX/updated path lookup #1788

@caruschalalamove

Description

@caruschalalamove

Summary

On Android 10+, chrono reads timezone data from /system/usr/share/zoneinfo/tzdata (the OS-bundled fallback), ignoring the APEX-updated tzdata at /apex/com.android.tzdata/etc/tz/tzdata. This causes chrono to use stale DST rules that diverge from the device's actual local time.

Background: How Android updates timezone data

Since Android 10, timezone data is delivered via an APEX module (com.android.tzdata) independently of full OS upgrades. The Android runtime resolves timezone data in this priority order:

  1. /apex/com.android.tzdata/etc/tz/tzdata — APEX module (Android 10+)
  2. /data/misc/zoneinfo/current/tzdata — Google Play Services update (Android 8.1–9)
  3. /system/usr/share/zoneinfo/tzdata — OS-bundled fallback

Chrono's open_android_tz_data_file() in src/offset/local/tz_data.rs currently only checks:

  1. $ANDROID_DATA/misc/zoneinfo/tzdata (typically /data/misc/zoneinfo/tzdata — note: missing the /current/ subdirectory)
  2. $ANDROID_ROOT/usr/share/zoneinfo/tzdata (typically /system/usr/share/zoneinfo/tzdata)

Neither the APEX path nor the correct /current/ subdirectory path is checked.

Reproduction

Device: Nokia 7.2, Android 11
System tzdata: tzdata2020d (/system/usr/share/zoneinfo/tzdata)
APEX tzdata: tzdata2023a (/apex/com.android.tzdata/etc/tz/tzdata)

Test case: Asia/Amman (Jordan abolished DST permanently in 2022, moving to UTC+3 year-round)

Source Offset (Dec 2025) tzdata version used
Android system (Java/APEX) +03:00 tzdata2023a
chrono +02:00 tzdata2020d
Difference 1 hour

Chrono applies the pre-2022 DST rule (winter = UTC+2) because tzdata2020d predates the abolition. The device itself correctly shows UTC+3 because it uses the APEX-provided tzdata2023a.

This affects any timezone whose rules changed between the OS-bundled version and the APEX-updated version. Other known examples:

  • Brazil (DST abolished 2019) — affects devices with pre-2019 system tzdata but updated APEX/Play Services data
  • Egypt, Palestine, etc. — various DST rule changes post-2020

Expected behavior

Chrono should check the APEX path (and the /current/ subdirectory path for older devices) with higher priority than the system fallback, matching the Android runtime's resolution order.

Suggested fix

In open_android_tz_data_file(), add the APEX and updated paths before the existing fallback:

fn open_android_tz_data_file() -> Result<File> {
    // APEX module path (Android 10+)
    if let Ok(file) = File::open("/apex/com.android.tzdata/etc/tz/tzdata") {
        return Ok(file);
    }
    // Google Play Services updated path (Android 8.1–9)
    if let Ok(env_value) = std::env::var("ANDROID_DATA") {
        if let Ok(file) = File::open(format!("{}/misc/zoneinfo/current/tzdata", env_value)) {
            return Ok(file);
        }
    }
    // Existing fallback paths
    for (env_var, path) in
        [("ANDROID_DATA", "/misc/zoneinfo"), ("ANDROID_ROOT", "/usr/share/zoneinfo")]
    {
        if let Ok(env_value) = std::env::var(env_var) {
            if let Ok(file) = File::open(format!("{}{}/tzdata", env_value, path)) {
                return Ok(file);
            }
        }
    }
    Err(Error::from(ErrorKind::NotFound))
}

References

Environment

  • chrono version: latest main
  • Android version: 11 (API 30)
  • Device: Nokia 7.2 (DDV_sprout)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions