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:
/apex/com.android.tzdata/etc/tz/tzdata — APEX module (Android 10+)
/data/misc/zoneinfo/current/tzdata — Google Play Services update (Android 8.1–9)
/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:
$ANDROID_DATA/misc/zoneinfo/tzdata (typically /data/misc/zoneinfo/tzdata — note: missing the /current/ subdirectory)
$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)
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:/apex/com.android.tzdata/etc/tz/tzdata— APEX module (Android 10+)/data/misc/zoneinfo/current/tzdata— Google Play Services update (Android 8.1–9)/system/usr/share/zoneinfo/tzdata— OS-bundled fallbackChrono's
open_android_tz_data_file()insrc/offset/local/tz_data.rscurrently only checks:$ANDROID_DATA/misc/zoneinfo/tzdata(typically/data/misc/zoneinfo/tzdata— note: missing the/current/subdirectory)$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)
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:
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:References
__bionic_open_tzdata()in bionic.cpp — the canonical Android C runtime tzdata lookup showing APEX-first priority/data/misc/zoneinfo/current/Environment