← Back to blog

Windows FILETIME explained — converting NTFS timestamps to something readable

FILETIME is the Windows timestamp format you'll meet in $MFT, $UsnJrnl, the registry, $Recycle.Bin and almost every Windows artefact. A short, complete reference: what it is, how to convert it, and the gotchas.

3 min read

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:

  1. Divide by 10,000,000 to go from 100-ns ticks to seconds.
  2. 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:

FormatWhere it appearsEncoding
FILETIME$MFT, $UsnJrnl, registry, EVTX64-bit LE, 100-ns ticks since 1601-01-01 UTC
SYSTEMTIMEEVTX, some COM APIs8× 16-bit fields (year, month, day…)
TIME_T (32-bit)Older registry keysUnix seconds since 1970, 32-bit
DOSTIMEFAT (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. xxd shows 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 = 0 is 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 FILETIME as signed int64, 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_INFORMATION vs $FILE_NAME timestamps. 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:

FILETIMEUTC date
01601-01-01 00:00:00
1164447360000000001970-01-01 00:00:00
1329235200000000002022-02-23 16:00:00
1335936000000000002024-08-09 12:00:00

If your converter produces those, it produces the right thing.