Introduction
I thought this would be easy. It wasn't particularly. Hopefully, this article will make life easier for others who want to perform the same task.
Background
Sometimes, things just take longer than you think they're going to. For instance, when I wanted to write code to calculate the age of files in my program, I thought that it would be a pretty straight forward affair. Obviously it's a matter of getting the current time and subtracting the creation time of the file. However, file times and the system time are in different formats which need to be converted into compatible formats so that the subtraction can take place.
Unfortunately, (as far as I can tell) there is no pre-existing solution in VC++6. If there were a GetFileAge
function somewhere in Windows that took the file creation time and the current time as arguments and output the current age of the file, then I would have saved several hours this weekend and this article would be superfluous.
So, without further ado, here, in all its gory details, is my account of how to write code to calculate the age of a file...
The code for this article has been re-written almost entirely for the third edition and a second solution to the problem has been added to the article for the fourth edition. I have rewritten the code to make use of the CTime
and CTimeSpan
classes. This is possibly a nicer version because the age calculation occurs in a single line. The code is of a similar length to that of the third edition but I have put loads of comments in it which should, hopefully, make it nice and easy to understand.
The job in hand involves the FILETIME
structure which is designed to contain useful chronological information about files and a SYSTEMTIME
structure which is designed to hold the current time.
The MSDN library page for 'SYSTEMFILE' states:
Remarks
It is not recommended that you add and subtract values from the SYSTEMTIME
structure to obtain relative times. Instead, you should
-Convert the SYSTEMTIME structure to a FILETIME structure.
-Copy the resulting FILETIME structure to a ULARGE_INTEGER structure.
-Use normal 64-bit arithmetic on the ULARGE_INTEGER value.
The FILETIME
and SYSTEMTIME
structures are quite different. Perhaps there is an interesting story as to why they are so different but I don't know what that is.
Again from the 'SYSTEMFILE
' library page...
The SYSTEMTIME structure represents a date and time using individual
members for the month, day, year, weekday, hour, minute, second, and
millisecond.
typedef struct _SYSTEMTIME {
WORD wYear;
WORD wMonth;
WORD wDayOfWeek;
WORD wDay;
WORD wHour;
WORD wMinute;
WORD wSecond;
WORD wMilliseconds;
} SYSTEMTIME, *PSYSTEMTIME;
... where wYear
represents the current year, etc.
From the MSDN library page for 'FILETIME
...
The FILETIME structure is a 64-bit value representing the number of
100-nanosecond intervals since January 1, 1601 (UTC).
typedef struct _FILETIME {
DWORD dwLowDateTime;
DWORD dwHighDateTime;
} FILETIME, *PFILETIME;
The CTime
class has constructors which accept both FILETIME
and SYSTEMTIME
structures. This is handy when one of the times we are dealing with is the system time on the computer and the other is the creation time of a file. However, I didn't need the SYSTEMTIME
constructor because CTime
has a method - 'GetCurrentTime
' which loads the system time into the object.
One CTime
object can be subtracted from another using the '-' operator, i.e. exactly what we want to do when we are calculating an age. The result of this calculation is a CTimeSpan
object.
Using the Code
Code from the Fourth Edition
Here is my most recent version of the code...
My program sends the results of the conversion I'm doing to a dialog called 'CEditNodeDlg
'.
WIN32_FIND_DATA FindFileData;
HANDLE hFind;
hFind = FindFirstFile(strPath, &FindFileData);
const FILETIME ftFile = FindFileData.ftCreationTime;
CTime ctFile = ftFile;
CTime ctNow = ctNow.GetCurrentTime();
CTimeSpan tsAge;
tsAge = ctNow - ctFile;
FILETIME ftAge;
ULARGE_INTEGER ulAgeInSeconds;
ulAgeInSeconds.QuadPart = tsAge.GetTotalSeconds() * 1E7;
ftAge.dwLowDateTime=ulAgeInSeconds.LowPart;
ftAge.dwHighDateTime = ulAgeInSeconds.HighPart;
SYSTEMTIME stAge;
FileTimeToSystemTime(&ftAge,&stAge);
strYears.Format("%d", stAge.wYear-1601);
strMonths.Format("%d",stAge.wMonth-1);
strDays.Format("%d",stAge.wDay-1);
CEditNodeDlg dlg;
Code from the Third Edition
WIN32_FIND_DATA FindFileData;
HANDLE hFind;
hFind = FindFirstFile(strPath, &FindFileData);
CString strYears, strMonths, strDays;
SYSTEMTIME stNow;
GetSystemTime(&stNow);
const FILETIME ftFile = FindFileData.ftCreationTime;
FILETIME ftNow;
SystemTimeToFileTime (& stNow, &ftNow);
ULARGE_INTEGER ulNowTime, ulFileTime, ulAge;
ulNowTime.HighPart = ftNow.dwHighDateTime;
ulNowTime.LowPart = ftNow.dwLowDateTime;
ulFileTime.HighPart = ftFile.dwHighDateTime;
ulFileTime.LowPart = ftFile.dwLowDateTime;
ulAge.HighPart = ulNowTime.HighPart - ulFileTime.HighPart;
ulAge.LowPart = ulNowTime.LowPart - ulFileTime.LowPart;
FILETIME ftAge;
SYSTEMTIME stAge;
ftAge.dwHighDateTime = ulAge.HighPart;
ftAge.dwLowDateTime = ulAge.LowPart;
FileTimeToSystemTime(&ftAge,&stAge);
strYears.Format("%d", stAge.wYear-1601);
strMonths.Format("%d",stAge.wMonth-1);
strDays.Format("%d",stAge.wDay-1);
CEditNodeDlg dlg;
Points of Interest
The autocomplete feature on my editor doesn't list the 'HighPart
' and 'LowPart
' members for the ULARGE_INTEGER
structure. Initially, I thought that they weren't available to me and I wrote some tortured code that avoided using them. Then I discovered that if I wrote the members in manually, they worked fine.
Thanks
Reading the article 'Date and Time in C++' by RK_2000 (here on CodeProject) was a big help in producing the third edition of this article.
I am very grateful to DavidCrow for his help and encouragement.
History
- First edition 23rd February, 2008
- Second edition 25th February, 2008
I have updated the article to provide more information about one of the problems I found in completing this programing task - i.e. that I couldn't access the 'HighPart
' and 'LowPart
' members of the 'ULARGE_INTEGER
' structure. - Third edition 25th February, 2008
I found that I could access the HighPart
and LowPart
members of ULARGE_INTEGER
structures, after all and have rewritten the code almost entirely. There is no longer any dodgy cast of (FILETIME *
) onto &ULARGE_INTEGERS
.
I have also changed many of the variable names I used. Hopefully, the new ones will make understanding the code a bit easier. - Fourth edition 26th February, 2008
I have included a new solution to the problem. This new solution may be preferable. I have left the old solution so you can choose. I have paid more attention to comments in the code. - Fifth edition 9th March, 2008
Some tidying up done