user1473508
user1473508

Reputation: 195

Convert datetime back to Windows 64-bit FILETIME

I would like to create network timestamps in NT format.

I've been able to convert them to readable time with this function:

NetworkStamp = "\xc0\x65\x31\x50\xde\x09\xd2\x01"

def GetTime(NetworkStamp):
    Ftime = int(struct.unpack('<Q',NetworkStamp)[0])
    Epoch = divmod(Ftime - 116444736000000000, 10000000)
    Actualtime = datetime.datetime.fromtimestamp(Epoch[0])
    return Actualtime, Actualtime.strftime('%Y-%m-%d %H:%M:%S')

print GetTime(NetworkStamp)

Output:

(datetime.datetime(2016, 9, 8, 11, 35, 57), '2016-09-08 11:35:57')

Now I'd like to do the opposite, converting '2016/09/08 11:35:57' sec to this format:

 "\xc0\x65\x31\x50\xde\x09\xd2\x01"

Upvotes: 3

Views: 3509

Answers (3)

Paul
Paul

Reputation: 6911

A basic conversion:

from datetime import datetime
from calendar import timegm

EPOCH_AS_FILETIME = 116444736000000000  # January 1, 1970 as MS file time
HUNDREDS_OF_NANOSECONDS = 10000000

def dt_to_filetime(dt):
    return EPOCH_AS_FILETIME + (timegm(dt.timetuple()) * HUNDREDS_OF_NANOSECONDS)

The answer is based on this Gist I found: https://gist.github.com/Mostafa-Hamdy-Elgiar/9714475f1b3bc224ea063af81566d873

The Gist adds support for timezones and conversion in the other direction.

Upvotes: -1

martineau
martineau

Reputation: 123491

    Your code that converts the Window's FILETIME value into a datetime.datetime isn't as accurate as it could be—it's truncating any fractional seconds there might have been (because it ignores the remainder of the divmod() result). This isn't noticeable in the readable string your code creates since it only shows whole seconds.
    Even if the fractional seconds are included, you can't do exactly what you want because the Windows FILETIME structure has values in intervals of 100-nanosecond (.1 microsecond), but Python's datetime only supports accuracy to whole microseconds. So best that's possible to do is approximate the original value due to this loss of information involved even doing the most accurate possible conversion.

Here's code, for both Python 2 and 3, demonstrating this using the NetworkStamp test value in your question:

import datetime
import struct
import time

WINDOWS_TICKS = int(1/10**-7)  # 10,000,000 (100 nanoseconds or .1 microseconds)
WINDOWS_EPOCH = datetime.datetime.strptime('1601-01-01 00:00:00',
                                           '%Y-%m-%d %H:%M:%S')
POSIX_EPOCH = datetime.datetime.strptime('1970-01-01 00:00:00',
                                         '%Y-%m-%d %H:%M:%S')
EPOCH_DIFF = (POSIX_EPOCH - WINDOWS_EPOCH).total_seconds()  # 11644473600.0
WINDOWS_TICKS_TO_POSIX_EPOCH = EPOCH_DIFF * WINDOWS_TICKS  # 116444736000000000.0

def get_time(filetime):
    """Convert windows filetime winticks to python datetime.datetime."""
    winticks = struct.unpack('<Q', filetime)[0]
    microsecs = (winticks - WINDOWS_TICKS_TO_POSIX_EPOCH) / WINDOWS_TICKS
    return datetime.datetime.fromtimestamp(microsecs)

def convert_back(timestamp_string):
    """Convert a timestamp in Y=M=D H:M:S.f format into a windows filetime."""
    dt = datetime.datetime.strptime(timestamp_string, '%Y-%m-%d %H:%M:%S.%f')
    posix_secs = int(time.mktime(dt.timetuple()))
    winticks = (posix_secs + int(EPOCH_DIFF)) * WINDOWS_TICKS
    return winticks

def int_to_bytes(n, minlen=0):  # helper function
    """ int/long to bytes (little-endian byte order).
        Note: built-in int.to_bytes() method could be used in Python 3.
    """
    nbits = n.bit_length() + (1 if n < 0 else 0)  # plus one for any sign bit
    nbytes = (nbits+7) // 8  # number of whole bytes
    ba = bytearray()
    for _ in range(nbytes):
        ba.append(n & 0xff)
        n >>= 8
    if minlen > 0 and len(ba) < minlen:  # zero pad?
        ba.extend([0] * (minlen-len(ba)))
    return ba  # with low bytes first

def hexbytes(s):  # formatting helper function
    """Convert string to string of hex character values."""
    ba = bytearray(s)
    return ''.join('\\x{:02x}'.format(b) for b in ba)

win_timestamp = b'\xc0\x65\x31\x50\xde\x09\xd2\x01'
print('win timestamp: b"{}"'.format(hexbytes(win_timestamp)))
dtime = get_time(win_timestamp)
readable = dtime.strftime('%Y-%m-%d %H:%M:%S.%f')  # includes fractional secs
print('datetime repr: "{}"'.format(readable))

winticks = convert_back(readable)  # includes fractional secs
new_timestamp = int_to_bytes(winticks)
print('new timestamp: b"{}"'.format(hexbytes(new_timestamp)))

Output:

win timestamp: b"\xc0\x65\x31\x50\xde\x09\xd2\x01"
datetime repr: "2016-09-08 07:35:57.996998"
new timestamp: b"\x80\x44\x99\x4f\xde\x09\xd2\x01"

Upvotes: 5

ShadowRanger
ShadowRanger

Reputation: 155506

If you understand how to perform the conversion in one direction, doing it in reverse is basically using the inverse of each method in reverse order. Just look at the documentation for the modules/classes you're using:

  1. strftime has a strptime counterpart
  2. fromtimestamp is matched by timestamp (if you're on pre-3.3 Python, timestamp doesn't exist, but you could define FILETIME_epoch = datetime.datetime(1601, 1, 1) - datetime.timedelta(seconds=time.altzone if time.daylight else time.timezone) outside the function to precompute a datetime that represents the FILETIME epoch for your timezone, then use int((mydatetime - FILETIME_epoch).total_seconds()) to get int seconds since FILETIME epoch directly, without manually adjusting for difference between FILETIME and Unix epoches separately)
  3. divmod (which you don't really need, since you only use the quotient, not the remainder, you could just do Epoch = (Ftime - 116444736000000000) // 10000000 and avoid indexing later) is trivially reversible (just multiply and add, with the add being unnecessary if you use my trick to convert to FILETIME epoch seconds directly from #2)
  4. struct.unpack is matched by struct.pack

I'm not providing the exact code because you really should learn to use these things yourself (and read the docs when necessary); I'm guessing your forward code was written without understanding what it is doing, because if you understood it, the reverse should have been obvious; every step has an inverse documented on the same page.

Upvotes: 3

Related Questions