Date/Time Formats and Conversions

In C/Windows land, there are a handful of different formats for storing date and time values. Unfortunately, you often need to convert between them to get something useful done, this also means it is important to know the properties of each format. Here is a cheat sheet for the standard date/time formats with some basic information about each.

 

Container

Domain

Format / Notes

Min Date

Resolution

SYSTEMTIME

Win32

Struct (16 bytes)

January 1, 1601

1 millisecond

FILETIME

Win32

Offset value (64bit unsigned int)

January 1, 1601

100 nanoseconds

VT_DATE

Win32 / OLE Automation

Offset value (64bit signed float), 0.0 is December 30, 1899.

January 1, 100 (1)

500 milliseconds (2)

FAT/MS-DOS

Win32

Struct (values packed into two 16bit ints)

January 1, 1980

2 seconds

time_t

CRT

Offset value (unsigned int)

January 1, 1970

1 second

tm

CRT

Struct (36 bytes (3))

January 1, 1900

1 second

 

Conversions

Windows Times

SYSTEMTIME

FILETIME

VT_DATE

FAT/MS-DOS

SYSTEMTIME

 

SystemTimeToFileTime

SystemTimeToVariantTime

 

FILETIME

FileTimeToSystemTime

 

 

FileTimeToDosDateTime

VT_DATE

VariantTimeToSystemTime

 

 

VariantTimeToDosDateTime

FAT/MS-DOS

 

DosDateTimeToFileTime

DosDateTimeToVariantTime

 

 

CRT Times

time_t

tm

time_t

 

gmtime

tm

mktime

 

Converting between SYSTEMTIME and time_t

In bridging between the CRT and Windows API sets, I've occasionally needed to convert a time_t value into something more Windows friendly. The below helper functions do just that (4).

 

void SystemTimeToTime_t(SYSTEMTIME *systemTime, time_t *dosTime)

    {

  LARGE_INTEGER jan1970FT = {0};

    jan1970FT.QuadPart = 116444736000000000I64; // january 1st 1970

    LARGE_INTEGER utcFT = {0};

    SystemTimeToFileTime(systemTime, (FILETIME*)&utcFT);

 

    unsigned __int64 utcDosTime = (utcFT.QuadPart - jan1970FT.QuadPart)/10000000;

 

    *dosTime = (time_t)utcDosTime;

    }

 

void Time_tToSystemTime(time_t dosTime, SYSTEMTIME *systemTime)

    {

    LARGE_INTEGER jan1970FT = {0};

    jan1970FT.QuadPart = 116444736000000000I64; // january 1st 1970

    LARGE_INTEGER utcFT = {0};

    utcFT.QuadPart = ((unsigned __int64)dosTime)*10000000 + jan1970FT.QuadPart;

 

    FileTimeToSystemTime((FILETIME*)&utcFT, systemTime);

    }

 

Note: we use a hack by interpreting a FILETIME struct as a LARGE_INTEGER since the layouts match (both are designed to hold 64bits of data). The "nice" way would be to use the initial format as a temp and then convert from one to the other by copying the "low" and "high" DWORDs individually. I will also leave it as a reader exercise to do any necessary parameter validation or error checking from the Win32 API calls.

Footnotes

(1) In reality the year can go way into BC values, but for practical reasons some subsystems capped it at the year 100.

(2) The 500 ms resolution is according to https://msdn2.microsoft.com/en-us/library/aa393691.aspx, but since this is an IEEE floating point value, your resolution will physically be bounded by precision as it relates to your date: the further away from 0.0 you go, the less precision you will have, but for normal dates, having a precision of 1 millisecond is physically possible.

(3) The struct consists of 9 ints, so the size will scale depending on your environment.

(4) RtlTimeToSecondsSince1970() may be what you need instead, but it is currently only available for driver projects.

 

Edit:

Here are a couple of "net" friendly apis when dealing with various network protocols:

WinHttpTimeToSystemTime, WinHttpTimeFromSystemTime