1 Where location data lives
| Source | What it holds | Notes |
|---|---|---|
| iOS location caches | Recent fixes with time, latitude, longitude, accuracy, sometimes speed and course | SQLite databases of the system location services (e.g. the Cache.sqlite family under routined); Core Data style tables with Z prefixes and Cocoa timestamps. Names and retention change between iOS versions. |
| iOS significant locations | Places the phone visits often, with visit times | Learned by the system; protected, usually only in full file-system acquisitions |
| Google location history | Timeline of positions and visits | Older exports via Google Takeout (Records.json with latitudeE7); newer versions keep the timeline on the device |
| App databases | Check-ins, ride and fitness tracks, shared locations in chats, map searches | Each app has its own schema and epoch |
| Media metadata | GPS coordinates in photo and video files | Depends on camera settings; often removed when files are shared |
| Network records | Cell towers and Wi-Fi networks seen | Positions are approximate and must be resolved through a tower or Wi-Fi database |
The sample on this page is a small location cache modelled on the Core Data style: a ZLOCATION
table with one row per fix, recorded during a morning jog around a lake in a public park.
2 A location cache in hex
Each row has ZTIMESTAMP, ZLATITUDE, ZLONGITUDE,
ZHORIZONTALACCURACY (metres), ZSPEED and ZSOURCE. The coordinates are
8-byte floats (serial type 7). Cell 1 has ZTIMESTAMP 810369000.5: far too small for Unix time today,
and exactly right for Cocoa time, seconds since 2001-01-01 00:00 UTC.
Look at the accuracy column: the table declares it REAL, but most cells store it with serial type
1 (a 1-byte integer). SQLite saves space by storing a REAL without a fractional part as an integer. The values
are the same; only the bytes differ. Cell 13 stores 1400 in two bytes, a 1,400 m radius from a cell
tower fix.
The same rows as a query (builder output)
Try this: select the ZLATITUDE of cell 1 and read its 8 bytes as a big-endian IEEE 754
double (any online or Python converter: struct.unpack('>d', bytes.fromhex('…'))). Then check how
many seconds lie between two fixes.
3 Epochs and time zones
A timestamp is a number of units since an epoch. The number alone does not tell you which epoch. Decide by the source (the platform and the app), then check that the result is plausible.
| Format | Epoch and unit | Typical value in 2026 | Where |
|---|---|---|---|
| Unix time | 1970-01-01, seconds | 1.79 × 109 (10 digits) | many apps, Linux |
| Unix milliseconds | 1970-01-01, ms | 1.79 × 1012 (13 digits) | most Android databases, Java |
| Cocoa / Mac absolute time | 2001-01-01, seconds (often with fraction) | 8.1 × 108 (9 digits) | iOS and macOS |
| WebKit / Chrome time | 1601-01-01, microseconds | 1.34 × 1016 (17 digits) | Chrome history, Chromium-based apps |
Timestamp converter
All four formats count in UTC. The phone's time zone is not part of the value. When you report local times, state the zone and remember daylight saving time: the same UTC value is 07:30 in London in September and 06:30 in December. Some apps store local time without a zone, or keep the zone in a separate column; find out before you convert. Device clocks can also be wrong, so compare with an independent time source if timing is critical.
4 Every point is a circle
A fix is not a dot. It is a circle: "the device was probably within r metres of this point". GPS fixes outdoors reach a few metres. Positions computed from Wi-Fi networks are tens of metres off. Cell tower positions can be off by a kilometre or more. Mixed into one track, the coarse fixes create jumps and detours that never happened.
The track above is drawn from the bytes of the sample page: no map service, so you only see the geometry. Fix 13, the cell fix, has a 1,400 m radius; its circle covers the whole park. Fix 8 (Wi-Fi, ±65 m) pulls the track off the path at the western end. Untick "accuracy circles" and the track looks precise, which it is not.
Takeout-style exports
Google's older location history exports store coordinates as integers in units of 10−7 degrees
(latitudeE7, longitudeE7), with ISO 8601 timestamps in UTC. Divide by 10,000,000 to get
degrees. The squares on the map are these records: the walk to the park before the jog.
Takeout-style JSON (sample)
Try this: convert the first Takeout record to degrees. How far is it from the start of the jog, and how many minutes passed between them? Does the walking speed make sense?
5 Try it yourself
Answer from the hex view, the converter and the Takeout sample above. Numbers can be typed in decimal or hex
(0x…).
Convert the ZTIMESTAMP of the last fix (cell 16) to Unix seconds.
Select ZTIMESTAMP in cell 16 to see its value, then add the offset between the
two epochs (section 2).
The 8 bytes 41 C8 26 A2 BC C0 00 00, read as a big-endian double, are
810,370,425.5 Cocoa seconds.
810,370,425.5 + 978,307,200 = 1,788,677,625.5 → 2026-09-06 06:53:45.5 UTC.
How many seconds lie between fix 1 and fix 2?
Subtract the two ZTIMESTAMP values. Both are Cocoa seconds, so the epoch does not
matter for a difference.
Fix 1: 41 C8 26 9F F4 40 00 00 = 810,369,000.5. Fix 2:
41 C8 26 A0 23 C0 00 00 = 810,369,095.5.
Difference: 95 seconds. All fixes in the table follow the same 95-second rhythm.
Fix 8 (Wi-Fi) has no speed. Which serial type byte does its ZSPEED have?
Cell 8's record header has 8 bytes: its own length, then one serial type per column
(Z_PK, ZTIMESTAMP, ZLATITUDE, ZLONGITUDE,
ZHORIZONTALACCURACY, ZSPEED, ZSOURCE).
Cell 8 starts 26 08 08 00 07 07 07 01 01 15: payload 38, rowid 8, then the
record header 08 | 00 07 07 07 01 01 15.
ZSPEED is the 6th serial type: 01, a 1-byte signed integer. Its value byte
is FF = −1, a "no value" marker, not a speed. In cell 1 the same column has type 07
(an 8-byte float, 2.8 m/s).
What is the latitude of the first Takeout record, in degrees?
Open the Takeout-style JSON in section 4. latitudeE7 is in units of
10−7 degrees.
"latitudeE7": 515134000 ÷ 10,000,000 = 51.5134°. The longitude
−1,588,000 gives −0.1588°.
These 46 bytes are cell 1, the first fix (file offset 0x7D2). Its ZTIMESTAMP is the
big-endian double 41 C8 26 9F F4 40 00 00 at 0x7DC. Edit the bytes and watch cell 1
in the decoded page below:
- Change the 4th timestamp byte
9F(at0x7DF) toA0: the time moves by 512 seconds. Reset and change the 3rd byte26(at0x7DE) to27instead. How far does it move now? Each byte to the left is worth 256 times more. - Reset, then change the 6th timestamp byte
40(at0x7E1) to00. The half second disappears, and the decoder now also offers a Unix reading. Why can a whole number of seconds not tell you its epoch? - Set
ZHORIZONTALACCURACY, the single byte05at0x7F4, toFF. Serial type 1 is signed: the radius becomes −1, which is not a radius at all.