Microsoft / Windows timestamp formats¶
FILETIME¶
A 64-bit count of 100-nanosecond intervals since 1601-01-01 00:00:00 UTC, defined in
MS-DTYP §2.3.3.
On disk it is two little-endian 32-bit halves; recombine as
(dwHighDateTime << 32) | dwLowDateTime. UTC by definition; leap seconds are not
counted. It underlies NTFS file times, the registry, the Windows Event Log, and Active
Directory Integer8 timestamps.
- Worked example:
116444736000000000= 1970-01-01 (the FILETIME→Unix anchor);132223104000000000= 2020-01-01. - Gotcha: the 1601 epoch and 100-ns unit make values ~18 digits — easily confused with Unix nanoseconds (1970 epoch). The 1601→1970 gap is 11 644 473 600 s.
OLE Automation date¶
An 8-byte IEEE-754 double: the integer part is days, the fraction is the time of
day, counted from the 1899-12-30 epoch. The authoritative description is in the
Win32 conversion API
VariantTimeToSystemTime:
"2.0 represents January 1, 1900 … 2.5 represents noon on January 1, 1900".
Why 1899-12-30, and the Excel 1900 leap-year bug¶
The epoch is 1899-12-30, not 1900-01-01, to stay aligned with Excel's day numbering
— and Excel's numbering contains a deliberate error. Excel
treats 1900 as a leap year
(it is not — 1900 is divisible by 100 but not 400; see calendars).
The phantom 1900-02-29 exists as serial 60. Excel inherited this from Lotus 1-2-3
for compatibility and has never fixed it. Offsetting the OLE epoch back to 1899-12-30
makes OLE 2.0 line up with Excel serial 2 = 1900-01-01 for all dates from 1900-03-01
onward.
The 1900 vs 1904 date system¶
Excel for the original Macintosh used a 1904 date system (epoch 1904-01-01) to dodge the 1900 issue; Windows Excel defaults to 1900. The two differ by 1462 days, so a workbook moved between systems shifts every date by that amount.
- Gotcha: as a
double, sub-second precision is limited (see precision). Always determine which date system a workbook uses before trusting a raw serial.
.NET DateTime.Ticks¶
A 64-bit signed count of 100-nanosecond ticks since 0001-01-01 00:00:00, proleptic
Gregorian (DateTime.MinValue), per the .NET BCL (ECMA-335). 10 000 ticks/ms;
10 000 000 ticks/s. Interpretation depends on the value's Kind (Utc, Local,
Unspecified); leap seconds are not included.
- Worked example:
630822816000000000= 2000-01-01. - Gotcha: different epoch (year 1) from FILETIME (1601) — the offset is
504911232000000000ticks. AKind=Unspecifiedvalue carries no zone.
SYSTEMTIME¶
A packed 16-byte struct of eight WORD fields (year, month, day-of-week, day, hour,
minute, second, milliseconds), defined in
minwinbase.h.
It is not an offset count, carries no zone tag (UTC via GetSystemTime, local
via GetLocalTime), and does not validate the date — invalid dates can be stored.
Active Directory / LDAP¶
AD stores pwdLastSet, lastLogonTimestamp, lastLogon, accountExpires,
badPasswordTime as Integer8 (signed 64-bit) values that are FILETIME
counts — "100 nanosecond intervals since January 1, 1601 (UTC)" per
MS-ADA3 §2.175 (pwdLastSet).
The same Interval syntax (2.5.5.16) also encodes durations (e.g. maxPwdAge)
as negative values — so read the attribute's semantics before assuming a value is an
absolute time. Other attributes (whenCreated, whenChanged) use GeneralizedTime
strings (YYYYMMDDHHMMSS.0Z, UTC).
lastLogonTimestamp lags reality
Per MS-ADA1 §2.352,
lastLogonTimestamp is replicated lazily — its update window is 14 days minus a
random fraction of 5 days. It can lag the real last logon by up to ~14 days; it is
not an accurate "last logon" time. The per-DC lastLogon is precise but not
replicated. Sentinels: pwdLastSet = 0 means "change at next logon";
accountExpires of 0 or 0x7FFFFFFFFFFFFFFF means "never".
FAT / DOS¶
A 32-bit packed value: the high 16 bits are the date word, the low 16 bits the time word, in LOCAL time with no offset, at 2-second resolution. Epoch 1980.
| Word | Bits 15–9 | Bits 8–5 | Bits 4–0 |
|---|---|---|---|
| Date (high 16) | year − 1980 (0–127 → 1980–2107) | month (1–12) | day (1–31) |
| Time (low 16) | hour (bits 15–11) | minute (bits 10–5) | second ÷ 2 (bits 4–0) |
Layout: DosDateTimeToFileTime, Microsoft FAT32 specification; local-time storage is confirmed by the File Times doc.
- Worked example:
2162688(0x00210000) = 1980-01-01 (the FAT epoch / minimum);1391422645(0x52EF6CB5) = 2021-07-15 13:37:42. - Gotcha: write times are quantized to even seconds — an odd-second FAT modify time is impossible. FAT creation time carries a separate 10-ms field; last-access is date-only. The local-time storage means timeline work needs the machine's then-current timezone/DST. See also RTC hardware — 1980-01-01 is the classic reset-clock artifact.
exFAT¶
exFAT uses the same packed timestamp layout as FAT (epoch 1980, 2-second base, plus
a 10-ms increment field), but adds a UtcOffset field (OffsetFromUtc, 15-minute
increments, with an OffsetValid bit) that FAT entirely lacks. When OffsetValid = 1
the timestamp can be normalised to UTC; otherwise it is local time like FAT. Spec:
exFAT specification §7.4.5–7.4.10.
NTFS — $STANDARD_INFORMATION vs $FILE_NAME¶
NTFS stores file times in UTC on disk as FILETIME values (File Times). Each MFT record holds two independent MACB quartets (Modified, Accessed, Created, MFT-changed):
$STANDARD_INFORMATION(SI) — the times Explorer shows; writable from user space viaSetFileTime.$FILE_NAME(FN) — set by the kernel on create/rename/move; not updatable through normal Win32 file-time APIs.
Timestomping indicator
Anti-forensic tools backdate SI times trivially, but FN times usually retain
the true creation/move time. SI earlier than FN, or SI sub-seconds of exactly
.0000000 while FN is precise, is a classic timestomping tell. (The on-disk MFT
attribute byte layout has no Microsoft open specification; the FILETIME unit/epoch
and UTC storage are from MS-DTYP and the File Times doc, while the SI/FN forensic
distinction is established forensic practice.)
ZIP / MS-DOS time¶
A ZIP entry's mandatory timestamp uses the exact FAT/DOS packed format (1980 epoch,
2-second, local time) — its only required time field. The
.ZIP APPNOTE v6.3.10
defines optional extra fields with better time: the Info-ZIP extended timestamp
(0x5455) carries 32-bit Unix time_t values in UTC (1-second), and the NTFS extra
field (0x000A) carries FILETIME values (100-ns, UTC). A single entry may carry all
three at differing precision and zone.
- Gotcha: the mandatory DOS time is local with no zone — compare against the
0x5455/0x000Aextra fields when present; the local and central-directory copies can disagree; and the Unix extra field faces the Year 2038 problem.
See also¶
- References — all specifications linked above.
- Precision, RTC hardware, Rollovers.