Why this matters
If you've ever opened a Chrome history database, an iOS backup file, or a Windows registry hive in a hex editor and tried to make sense of the date fields, you've already met this problem. The dates are stored as integers, but those integers don't match the Unix timestamps you expect. The number is too big, or it represents a date in the wrong century, or it's offset by some inscrutable amount.
The reason is that nearly every operating system and major application uses its own epoch. Here's a tour of the ones you're most likely to encounter, ordered by how often I run into them in practice.
Chrome / Chromium / Edge: microseconds since 1601
If you crack open Chrome's History SQLite file (located at ~/Library/Application Support/Google/Chrome/Default/History on macOS, or the equivalent path on Windows and Linux), you'll find columns named last_visit_time, visit_time, creation_time, and last_modified. They look like enormous 17-digit numbers.
They're microseconds since 1601-01-01 00:00:00 UTC. The same epoch as Windows FILETIME, but in microseconds instead of 100-nanosecond intervals.
Conversion:
unix_seconds = (chrome_value / 1_000_000) - 11_644_473_600
Same format applies to the Chrome cookies database (Cookies SQLite file, columns creation_utc, expires_utc, last_access_utc). And to Chrome's Web Data, Login Data, and most other SQLite stores Chromium maintains.
Forensics quick-query for inspecting Chrome history without extracting the file:
SELECT url, title,
datetime(last_visit_time / 1000000 - 11644473600, 'unixepoch') AS visited
FROM urls
ORDER BY last_visit_time DESC
LIMIT 20;
The Chrome/WebKit converter handles this format directly if you need to spot-check individual values.
iOS / macOS Cocoa: seconds since 2001
Apple's Cocoa framework — the API that everything iOS and macOS is built on — uses a different epoch entirely: January 1, 2001, 00:00:00 UTC. The type is NSTimeInterval, a 64-bit double representing seconds (with fractional precision).
Where you'll find it:
- Core Data SQLite stores — columns named
Z-prefixed (Apple's convention for managed object storage) often have date columns asREALvalues - Binary plist files — the type marker
0x33followed by 8 bytes is an NSDate - iOS app backups — both iTunes/Finder backups and iOS device extracts contain plists everywhere
- Health.app exports — workout and sleep timestamps
- Notes.app data — note creation and modification dates
- iMessage chat.db — message timestamps
Conversion:
unix_seconds = cocoa_seconds + 978307200
Worth noting: iMessage's chat.db timestamps were originally Cocoa seconds (since 2001), but starting in macOS Sierra (2016) they switched to Cocoa nanoseconds in the same column. A trick to tell which one a row uses: if the value is around 800 million, it's seconds; if it's around 800 quadrillion, it's nanoseconds. Same epoch in both cases, different units. Annoying.
Cocoa quick-query for iMessage:
-- For Sierra+ messages (nanoseconds since 2001):
SELECT text, datetime(date/1000000000 + 978307200, 'unixepoch') AS sent
FROM message ORDER BY date DESC LIMIT 10;
Windows event logs and registry: FILETIME (100ns since 1601)
Most Windows internals — event logs, NTFS filesystem timestamps, registry keys, AD attributes — use FILETIME: 100-nanosecond intervals since 1601-01-01 00:00:00 UTC. The values are typically 18-digit numbers, stored as 64-bit unsigned integers.
This is the same format as the LDAP/Active Directory format — same epoch, same units. Active Directory inherited it from Windows.
Conversion:
unix_seconds = (filetime / 10_000_000) - 11_644_473_600
In PowerShell, the built-in helper is handy:
# FILETIME to readable date
[DateTime]::FromFileTimeUtc(133829280000000000)
# → Wednesday, January 1, 2025 12:00:00 AM
For the registry, most timestamps are stored as REG_QWORD (8-byte unsigned int) FILETIME values. Some are REG_BINARY with 8 bytes you have to decode manually.
Excel: days since 1900-01-00 (with a bug)
If you ever export an Excel file to CSV and the date columns come out as 5-digit numbers like 45658 instead of "Jan 1, 2025", you're looking at Excel's OADate format. Days since 1900-01-00 (a non-existent reference day Excel uses), with fractional days for time-of-day.
Excel famously preserves a bug from Lotus 1-2-3: it treats 1900 as a leap year, even though it's not. For any date past March 1, 1900, all serial numbers are off by one from a correct calendar. This has been preserved deliberately for backwards compatibility for 40 years.
Conversion (works for any date after 1900-03-01, which is essentially everything):
unix_seconds = (excel_serial - 25569) * 86400
If you're parsing Excel files programmatically (with openpyxl, ExcelJS, etc.), most libraries handle this for you. The conversion is visible in the Excel OADate converter if you need to spot-check.
SQLite: julianday or text — your choice
SQLite's built-in date functions store dates as either text (in ISO 8601 format) or as Julian Day numbers. The julianday() function returns days since 4713 BC Jan 1 UTC noon, which is the canonical Julian Day. The fractional part represents time-of-day.
Conversion:
unix_seconds = (julian_day - 2440587.5) * 86400
This rarely comes up in forensic work — most applications using SQLite store dates as ISO 8601 text or as Unix timestamps. But it's possible for a database to use julianday() output directly, and the values look like 7-digit decimals (around 2.46 million for current dates). If you see those, it's a JD value.
GPS receivers: weeks + second-of-week since 1980-01-06
If you pull data from a GPS receiver or drone telemetry log, you'll often see two timestamps stamped together: a "GPS week" (integer, currently around 2347) and a "second of week" (0 to 604799). This represents seconds since the GPS epoch, but expressed in week-and-offset form because that's how GPS satellites broadcast it.
Conversion (also see the GPS time converter for the leap-second handling):
gps_seconds = week * 604800 + second_of_week
unix_seconds = gps_seconds + 315964800 - 18 # current leap seconds
The cheat sheet
Here's the lookup table I keep open when doing this kind of work:
| Format | Epoch | Unit | Current value (May 2026) |
|---|---|---|---|
| Unix | 1970-01-01 | seconds | 1.78 billion (10 digits) |
| Unix ms | 1970-01-01 | ms | 1.78 trillion (13 digits) |
| NTP | 1900-01-01 | seconds | 3.99 billion (10 digits) |
| FILETIME / LDAP | 1601-01-01 | 100ns | 1.34 × 10¹⁷ (18 digits) |
| Chrome / WebKit | 1601-01-01 | microseconds | 1.34 × 10¹⁶ (17 digits) |
| .NET ticks | 0001-01-01 | 100ns | 6.39 × 10¹⁷ (18 digits) |
| Cocoa | 2001-01-01 | seconds | 800 million (9 digits) |
| Excel OADate | 1900-01-00 | days | 45,700 (5 digits) |
| Julian Day | 4713 BC Jan 1 noon | days | 2,461,000 (7 digits) |
| GPS seconds | 1980-01-06 | seconds | 1.46 billion (10 digits) |
Notice that Unix seconds and GPS seconds have the same digit count, and FILETIME and .NET ticks both have 18. The number of digits is a good first filter; the date you get out is the proof. If converting as one format gives you a plausible date and converting as another gives you 1812 or 2099, the plausible one is right.
For interactive conversion of any of these, the relevant specialty converter on epochtime.tools handles it: LDAP, Chrome/WebKit, .NET ticks, Cocoa, Excel, Julian Day, GPS, NTP.
Published April 29, 2026. Tagged: forensics, specialty-formats, reverse-engineering.