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
Comments
- Anonymous
September 18, 2011
This (FILETIME*)&utcFT is not recommendable because on 64-bit machine it can causer misalignment.