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.

By 4 min read

Crack open any USN record, any MFT entry, any registry hive, any LNK file, any Prefetch trace, any jump list, and the timestamps you find are all the same shape: a 64-bit integer that reads as nonsense without conversion. That format is FILETIME. Learn it once, read every Windows artefact for the rest of your career.

The definition

A FILETIME is a 64-bit unsigned integer counting 100-nanosecond intervals since 1601-01-01 00:00:00 UTC. That is the whole specification.

The choice of 1601 is not arbitrary — it is the start of the 400-year Gregorian cycle that contains the present, which lets calendar arithmetic skip the leap-year edge cases that Unix time has to handle. Microsoft documents the type in the FILETIME structure reference.

A worked example: FILETIME 133593600000000000 is 2024-08-09 12:00:00 UTC. Verify your converter against that value before you trust it on case data.

Converting to Unix time

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, which 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, where the standard library handles the calendar arithmetic for you:

unix_seconds = filetime / 10_000_000 - 11_644_473_600

# 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, where you need BigInt to preserve 64-bit precision:

const unixMs = Number((filetime - 116444736000000000n) / 10000n);
const date = new Date(unixMs);

In SQL (Postgres), useful when you have a column of raw FILETIME values dumped from a Plaso export:

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 NTFS bothered to record, and $STANDARD_INFORMATION updates are driven by syscalls rather than by the system clock. But the cases where it does matter — correlating a USN record with a packet capture, lining up a Sysmon event 11 against a DataExtend — you want to keep the value in 100-ns ticks until the very 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")

Python's datetime covers everything except the last digit. Most analysts truncate to milliseconds without losing meaningful precision.

Variants you will run into

Windows ships several timestamp encodings and they leak across artefacts in ways that catch new analysts out:

FormatWhere it appearsEncoding
FILETIME$MFT, $UsnJrnl, registry, EVTX64-bit LE, 100-ns ticks since 1601-01-01 UTC
SYSTEMTIMEEVTX rendered output, some COM APIs8× 16-bit fields (year, month, day, ...)
TIME_T (32-bit)Older registry keys, pagefile headersUnix seconds since 1970, 32-bit
DOSTIMEFAT (occasionally leaks into NTFS metadata copied from FAT)Packed 16-bit date + 16-bit time, local time

On disk, FILETIME is little-endian — the LSB comes first. xxd shows bytes left-to-right; flip the byte order before interpretation if you are reading by hand. Tools handle this automatically.

Gotchas that have cost real time

Endianness in raw dumps. Hex viewers display in memory order; the conversion arithmetic assumes you have the integer value. Bytes need to be reversed first.

The zero sentinel. FILETIME = 0 is technically 1601-01-01 00:00:00 UTC. In practice Windows uses it to mean "never set". Treat zero as null at the presentation layer or you will publish reports that claim a file was last accessed in 1601. The same trap applies to certain $FILE_NAME timestamps NTFS leaves unset.

Signed vs unsigned. Some Windows internals (and some parsers built on top of them) treat FILETIME as signed int64. Values past roughly 30828 AD wrap into negatives, which obviously does not matter in practice — but a deliberately tampered value can land in that range and break a buggy converter. Stay unsigned.

Local vs UTC. FILETIME is always UTC. If a tool displays local time, it converted on output. For DFIR you almost always want UTC at the storage layer — convert to local zones only when rendering for a stakeholder. Cross-host correlation across time zones falls apart otherwise.

$STANDARD_INFORMATION vs $FILE_NAME. Both live in every $MFT entry, both store FILETIME M/A/C/B times. The SI values are user-space writable; the FN copies update less often and are much harder to change. Comparing the two is the classic timestomping detection, and the timestomping post walks through exactly how the USN journal complements that comparison.

Verification table

If you write your own converter — and at some point everyone does, because the language they are stuck with does not handle the conversion natively — these values are the ones I check against:

FILETIMEUTC date
01601-01-01 00:00:00 (or null, by convention)
1164447360000000001970-01-01 00:00:00
1329235200000000002022-02-23 16:00:00
1335936000000000002024-08-09 12:00:00

If your converter produces those four, it produces the right thing for everything else.

Further reading

  • Microsoft Learn — FILETIME structure and the related FileTimeToSystemTime function.
  • Joachim Metz's libfwnt — the reference C implementation for FILETIME conversion that almost every libyal parser uses.
  • SANS DFIR — Windows time rules cheat sheet covers how $STANDARD_INFORMATION and $FILE_NAME timestamps update under different operations, which is the practical complement to the FILETIME format itself.