Open any USN journal record, any MFT entry, any registry hive, and the timestamps you find are all the same shape: a 64-bit integer that means nothing without a conversion. That format is FILETIME, and once you have a feel for it, every other Windows time format makes sense.
What FILETIME is
A FILETIME is a 64-bit unsigned integer. Its value is the number of 100-nanosecond intervals since 1601-01-01 00:00:00 UTC. That's it.
The choice of 1601 isn't arbitrary — it's the start of the 400-year Gregorian cycle that contains today, which lets calendar arithmetic stay consistent without leap-year edge cases. Microsoft documents the type in the FILETIME structure reference.
A small example: the FILETIME 133593600000000000 corresponds to 2024-08-09 12:00:00 UTC.
Converting to Unix time
The conversion is two arithmetic steps:
- Divide by 10,000,000 to go from 100-ns ticks to seconds.
- Subtract 11,644,473,600 — the number of seconds between 1601-01-01 and 1970-01-01.
In pseudocode:
unix_seconds = filetime / 10_000_000 - 11_644_473_600
In Rust (this is exactly what usnrs::Entry::unix_timestamp does):
pub fn unix_timestamp(&self) -> i64 {
(self.timestamp as i64) / 10_000_000 - 11_644_473_600
}
In Python:
unix_seconds = filetime / 10_000_000 - 11_644_473_600
# or with subsecond precision:
from datetime import datetime, timezone, timedelta
EPOCH = datetime(1601, 1, 1, tzinfo=timezone.utc)
dt = EPOCH + timedelta(microseconds=filetime / 10)
In JavaScript:
// filetime is a BigInt to keep 64-bit precision
const unixMs = Number((filetime - 116444736000000000n) / 10000n);
const date = new Date(unixMs);
In SQL (Postgres):
SELECT TIMESTAMP '1601-01-01 00:00:00' + (filetime / 10000000) * INTERVAL '1 second';
Sub-second precision
The full 100-ns resolution rarely matters for forensics — disk timestamps are quantised to whatever the OS bothered to record, and $STANDARD_INFORMATION is updated by syscall, not by clock. But for the cases where it does matter (correlating with packet captures, for example), keep the value in 100-ns ticks until the last step:
import datetime as dt
EPOCH = dt.datetime(1601, 1, 1, tzinfo=dt.timezone.utc)
ticks_100ns = 133593612345678901
microseconds = ticks_100ns // 10
nanoseconds_remainder = (ticks_100ns % 10) * 100
timestamp = EPOCH + dt.timedelta(microseconds=microseconds)
print(timestamp, f"+{nanoseconds_remainder}ns")
The microsecond resolution Python's datetime provides covers everything except the last digit of ticks_100ns. Most analysts truncate to milliseconds without losing meaningful precision.
Variants you'll meet
Several Windows artefacts use slightly different encodings of the same idea:
| Format | Where it appears | Encoding |
|---|---|---|
FILETIME | $MFT, $UsnJrnl, registry, EVTX | 64-bit LE, 100-ns ticks since 1601-01-01 UTC |
SYSTEMTIME | EVTX, some COM APIs | 8× 16-bit fields (year, month, day…) |
TIME_T (32-bit) | Older registry keys | Unix seconds since 1970, 32-bit |
DOSTIME | FAT (sometimes leaks into NTFS metadata) | Packed 16-bit date + 16-bit time, local time |
When parsing a binary structure, the byte order is little-endian — the LSB of the FILETIME comes first.
Common pitfalls
- Endianness in raw dumps.
xxdshows bytes left-to-right; FILETIME bytes need to be reversed before interpretation. Tools and parsers handle this automatically; manual analysis usually means flipping the byte order first. - Zero value.
FILETIME = 0is a real value (1601-01-01) but also a sentinel for "never set". Treat it as null when displaying. - Two's complement weirdness. Some implementations (including some Windows internal ones) treat
FILETIMEas signedint64, which means values past ~30828 AD wrap into negatives. Use unsigned when in doubt. - Local vs UTC. FILETIME is always UTC. If a tool shows you a local time, it converted on the way out. For DFIR you almost always want UTC for correlation across hosts.
$STANDARD_INFORMATIONvs$FILE_NAMEtimestamps. Both live in$MFT, both are FILETIME, both record M/A/C/B times.$FILE_NAME's copies update less often, which is why timestomping detection compares the two. Brian Carrier's notes and the Mandiant timestomp paper (now ranked behind X-Ways notes) are the standard references.
A quick conversion table for verification
If you write your own converter, these values are handy to check it:
| FILETIME | UTC date |
|---|---|
0 | 1601-01-01 00:00:00 |
116444736000000000 | 1970-01-01 00:00:00 |
132923520000000000 | 2022-02-23 16:00:00 |
133593600000000000 | 2024-08-09 12:00:00 |
If your converter produces those, it produces the right thing.