Skip to content

Android Timeline Analysis: ALEAPP, Epochs and UTC

Build an Android device timeline: every timestamp format on the phone, converting to UTC, ALEAPP and Plaso output, merging sources, and pivoting from anchors.

Published on 8 min read

A phone produces tens of thousands of timestamped records a day, in at least six different formats, written by dozens of components that disagree slightly about what "now" is. A device timeline turns that into one ordered sequence of events in UTC, each traceable to its source. This guide covers the formats you will meet on Android, how to convert them, how to use ALEAPP and Plaso output, how to merge sources without losing provenance, and how to pivot from an anchor event.

Timestamp formats on Android

FormatExample valueWhereConvert
Unix milliseconds (UTC)1789400592120SMS date, call log, WhatsApp, Signal, Google Messages, Digital Wellbeing, notification history, appops/1000, then Unix seconds
Unix seconds (UTC)1789400592MMS date, Telegram, MediaStore date_added, Bluetooth TimestampDirect
Hex milliseconds1a0a0962ef8packages.xml it, ut, ftHex to decimal, then /1000
Offset from a basebase 1789344000000 + offset 56592120usagestats eventsAdd to the file name value
WebKit / Chrome time13433874192120000Chrome, Samsung Internet/1000000 - 11644473600
ISO 8601 with offset2026-09-14T17:43:12.000+02:00Timeline.jsonParse with offset
Local wall-clock09-14 17:43:12.120logcat, battery history anchors, some dumpsAdd year and zone
EXIF local2026:09:14 17:43:12 + OffsetTimeOriginalphotosApply offset or device zone
Uptime[56592.120394]Kernel logAdd boot time

Two quick sanity checks catch most errors: a Unix millisecond value for 2026 has 13 digits and starts with 17; a WebKit value has 17 digits and starts with 13. Dates in 1970 mean a missing base or a seconds/milliseconds mix-up; dates in 1601 mean a WebKit value read as zero-based.

# Unix ms
python3 -c "import datetime as d; print(d.datetime.fromtimestamp(1789400592120/1000, d.UTC))"
# WebKit
python3 -c "import datetime as d; print(d.datetime(1601,1,1,tzinfo=d.UTC)+d.timedelta(microseconds=13433874192120000))"
# packages.xml hex ms
python3 -c "import datetime as d; print(d.datetime.fromtimestamp(int('1a0a0962ef8',16)/1000, d.UTC))"
-- SQLite equivalents
SELECT datetime(1789400592120/1000, 'unixepoch');
SELECT datetime(13433874192120000/1000000 - 11644473600, 'unixepoch');

DCode and CyberChef decode single values interactively when you need to confirm an unfamiliar format.

Establish the clock before anything else

Every timeline depends on knowing how the device clock relates to real time. At acquisition:

date -u +%FT%T.%NZ; adb shell date -u +%FT%T; adb shell getprop persist.sys.timezone
adb shell settings get global auto_time
adb shell settings get global auto_time_zone
  • Offset. Difference between the device clock and your reference, recorded in the acquisition notes. Apply it to device-generated times if it is significant, and say so.
  • Automatic time. If auto_time was off, the user could have set any time. Look for clock changes: battery history prints TIME: anchors after a change, and logcat and usagestats show jumps.
  • Time zone history. The configured zone at acquisition is not necessarily the zone at the time of the event. Travel changes it automatically. UTC-native sources are unaffected; local-time sources are.

Start with ALEAPP

ALEAPP (Android Logs Events And Protobuf Parser) processes a full file system extraction (tar, zip or folder) and produces an HTML report, TSV files per artifact and a timeline database.

python3 aleapp.py -t tar -i case0142_ffs.tar -o case0142/aleapp/
ls case0142/aleapp/ALEAPP_Reports_*/_Timeline/     # timeline database
ls case0142/aleapp/ALEAPP_Reports_*/_TSV\ Exports/

ALEAPP converts most timestamps to a readable form and keeps the source file path for each artifact. Before relying on a row:

  • Check the module supports your Android and app version (ALEAPP modules are community-maintained and vary in age).
  • Confirm the time zone ALEAPP used for display; set it explicitly if the tool offers an option, and keep UTC for merging.
  • Spot-check critical rows against the raw database with the queries on the artifact pages.

Add Plaso for breadth

Plaso (log2timeline.py and psort.py) extracts events from many file types, including SQLite databases it recognises and file system metadata, into a single storage file. It is useful for file system times and for sources ALEAPP does not cover:

log2timeline.py --storage-file case0142.plaso case0142_ffs/
psort.py -o l2tcsv -w case0142_plaso.csv case0142.plaso

Its Android coverage is narrower than ALEAPP's, so treat it as a complement, not a replacement.

Merging sources

Whatever tools produce the rows, a merged timeline should have, for every event:

ColumnExample
utc2026-09-14T15:43:12.120Z
sourceusagestats daily/1789344000000
artifactACTIVITY_RESUMED
subjectcom.whatsapp/.Conversation
detailFree text: message text, URL, file name
original_value56592120 (offset)
toolALEAPP 3.x module usagestats

Keep the original value and source path: they let anyone reproduce the conversion. A spreadsheet works for a few thousand rows; for more, load the CSVs into SQLite or Timesketch, which provides search, tagging and collaborative review.

# Merge TSV exports into one SQLite table for querying
sqlite3 timeline.db <<'SQL'
CREATE TABLE ev(utc TEXT, source TEXT, artifact TEXT, subject TEXT, detail TEXT, original_value TEXT, tool TEXT);
.mode tabs
.import usage.tsv ev
.import messages.tsv ev
.import browser.tsv ev
SQL
sqlite3 timeline.db "SELECT * FROM ev WHERE utc BETWEEN '2026-09-14T15:30' AND '2026-09-14T16:00' ORDER BY utc;"

Deduplicate without losing corroboration

The same real-world action appears in several sources: a WhatsApp message sent at 17:43 local time exists in msgstore.db, maybe in the notification history of the recipient's phone, and is framed by usagestats and Digital Wellbeing foreground events and a battery +top transition. Do not delete the duplicates; group them. An event supported by three independent writers is stronger than one, and small offsets between them (milliseconds to seconds) are normal because each component timestamps at a different moment.

Pivot from anchors

Timelines are too large to read linearly. Start from an anchor and expand:

  • An install. The it time of a suspicious package in packages.xml. Look back for the download in browser history and the Settings screens in usagestats; look forward for accessibility grants, first network activity and permission use. See the malware triage guide.
  • A message. A key message's send time. Look at what was on screen before and after, which notifications arrived, whether the phone was moving (location sources), whether media was created.
  • A location. A Timeline visit or a photo with GPS. Look for Wi-Fi joins, Bluetooth connections, app use and communications in the same window. See the location guide.
  • A power event. A boot or shutdown near the incident. Explain gaps and look for a factory reset or a forced reboot.

Work in windows (minutes around the anchor, then hours, then days), and write down each hypothesis and the rows that support or contradict it.

Converting local times in practice

Local-time sources need the zone rules for the date of the event, not today's offset. Python's zoneinfo applies daylight saving history correctly:

from datetime import datetime
from zoneinfo import ZoneInfo

tz = ZoneInfo("Europe/Paris")            # from persist.sys.timezone
# logcat line "09-14 17:43:12.120" from a bugreport dated 2026
local = datetime(2026, 9, 14, 17, 43, 12, 120000, tzinfo=tz)
print(local.astimezone(ZoneInfo("UTC")).isoformat())   # 2026-09-14T15:43:12.120000+00:00

Ambiguous and missing local times exist around daylight saving changes: a local 02:30 on the night clocks go back happens twice. If an event falls in such a window, say so and give both possible UTC values unless another source resolves it.

Worked example: an install as anchor

A suspicious package is found during triage. Its it value in packages.xml converts to 2026-09-14 15:43:12 UTC. The merged timeline around it, in UTC:

UTCSourceEvent
15:40:03Chrome History (visits)Visit to a file-sharing page, transition typed
15:41:47Chrome History (downloads)APK download completed to /sdcard/Download/
15:42:10usagestatsPackage installer activity resumed
15:43:12packages.xmlPackage first installed, installer package installer, initiator Chrome
15:44:30usagestatsSettings accessibility screen resumed
15:45:02settings_secure.xmlFile modified; accessibility service now enabled (from current state and file time)
15:47:55appopsFirst fine location access by the package

Each row cites its source and can be reproduced from the raw artifact. The sequence takes seven minutes and shows a deliberate, hands-on installation and configuration. Adding the lock state (a KEYGUARD_HIDDEN at 15:39) and the owner's usual pattern (the phone was normally unused at that hour) turns it into a strong narrative.

Pattern of life as context

Before calling an event unusual, establish what usual looks like: first and last use of the day, typical apps by hour, charging habits. The pattern-of-life guide shows how to derive it from usage sources. A deviation on the day of interest (the phone unused at the time it is always used, or used at 03:00 for the first time in weeks) is often the most persuasive finding in the report.

Common mistakes

  • Mixing seconds and milliseconds. MMS and Telegram use seconds among millisecond-based neighbours.
  • Forgetting the usagestats base. Offsets decoded without the file name base land in 1970.
  • Treating local as UTC. Logcat, battery anchors, EXIF and some dumps are local.
  • Ignoring clock changes. A manual clock change shifts every later local record until it is corrected.
  • Over-trusting inferred events. Timeline "visits", ALEAPP labels and parsed event names are interpretations; the raw values are the evidence.
  • Losing provenance. A row without its source path and original value cannot be defended.

Reporting

  • State the reference time source, measured device offset, configured time zone and whether automatic time was on.
  • Present times in UTC, with local time alongside where it helps the reader, and name the zone.
  • Cite the source file and original value for every event relied on.
  • List tool names and versions, and the manual queries used to validate key rows.
  • Show corroborating sources for critical events, and explain discrepancies.

Checklist

  • Record clock offset, time zone and automatic time settings at acquisition.
  • Run ALEAPP on the full file system extraction; add Plaso and manual parsers for gaps.
  • Convert every source to UTC with a documented method.
  • Merge into one table with source, original value and tool columns.
  • Group duplicates as corroboration; do not discard them.
  • Pivot from anchors and compare with the device's usual pattern.
  • Validate every reported event against the raw artifact.