Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / Win32

How To Calculate the Age of a File

3.29/5 (10 votes)
9 Mar 2008CPOL4 min read 1  
Using time and file functions

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'.

C#
//Here, useful information about the file in question (identified 
//by its path and file name 'strPath') is gathered when FindFirstFile is
//executed and this is stored in the structure 'FindFileData'.

    WIN32_FIND_DATA FindFileData;
    HANDLE hFind;
    hFind = FindFirstFile(strPath, &FindFileData);

//The creation time of the file in question is acquired in a FILETIME
//structure 

    const FILETIME  ftFile = FindFileData.ftCreationTime;

//Then the ftFile structure is copied to a CTime object. Another 
//CTime object is created and assigned the current time by calling 
//the 'GetCurrentTime' member function.

    CTime ctFile = ftFile;
    CTime ctNow = ctNow.GetCurrentTime();

//The result of using the '-' operator on CTime objects is a
//CTimeSpan object. Hence a CTimeSpan object 'tsAge' is created
//for the result
    
    CTimeSpan tsAge;
    tsAge = ctNow - ctFile;

//Now because I want the results in years, months and days (E.g.
//one year, three months and no days - not fifteen months) I
//am converting the data back to a FILETIME structure. This 
//involves an intermediate conversion to ULARGE_INTEGER.

    FILETIME ftAge;
    ULARGE_INTEGER ulAgeInSeconds;

//The 64bit number of LARGE_INTEGER and ULARGE_INTEGER can be
//accessed in a single action using the QuadPart member.
//    Since FILETIME deals in units of 100 nanoseconds and 
//CTimeSpan deals in seconds. The CTimeSpan value has to 
//be multiplied by 1E7.
//    I got a warning about possible data loss for this line.
//I assume that the compiler is referring to the fact that 
//any non-integer part of the right hand side is going to be
//lost. Not a problem here.

    ulAgeInSeconds.QuadPart = tsAge.GetTotalSeconds() * 1E7;

//Unlike ULARGE_INTEGER, FILETIME doesn't have an equivalent
//of the 64 bit QuadPart member. Instead it has two 32 bit
//members 'LowPart' and 'HighPart' which can be assigned to 
//using the corresponding ULARGE_INTEGER members.
//    I got into some trouble due to the fact that the
//autocomplete list for ULARGE_INTEGER in the editor I
//was using didn't list 'LowPart' or 'HighPart'. I wasted
//some time writing some tortured code which worked without
//accessing these members before I found out that I could 
//just write them in manually and they would be fine.
    
    ftAge.dwLowDateTime=ulAgeInSeconds.LowPart;
    ftAge.dwHighDateTime = ulAgeInSeconds.HighPart;
    
//One last conversion to SYSTEMTIME
    
    SYSTEMTIME stAge;
    FileTimeToSystemTime(&ftAge,&stAge);

//Note the arithmetic adjustments below, needed to give the
//correct answer. The subtraction of '1601' is required for the 
//year value because FILETIME only goes back to 1601, whereas
//SYSTEMTIME goes back to Jesus' birthday. The other subtractions
//are just boundary corrections.

    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

C#
//Here, useful information about the file in question (identified 
//by it's path and file name 'strPath') is gathered when FindFirstFile is
//executed and this is stored in the structure 'FindFileData'.

    WIN32_FIND_DATA FindFileData;
    HANDLE hFind;
    hFind = FindFirstFile(strPath, &FindFileData);
    CString strYears, strMonths, strDays;

    SYSTEMTIME stNow; 
    
//The current time is acquired and stored in SYSTEMTIME format

    GetSystemTime(&stNow);
    
//The creation time of the file in question is acquired in a FILETIME
//structure then converted into a SYSTEMTIME structure

    const FILETIME  ftFile = FindFileData.ftCreationTime;

//A reference to the current time in FILETIME format is created

    FILETIME ftNow;
    SystemTimeToFileTime (& stNow, &ftNow);

//The contents of the FILETIME versions of the current time
//and the creation time of the file are now stored in 
//ULARGE_INTEGER structures

    ULARGE_INTEGER ulNowTime, ulFileTime, ulAge;
    ulNowTime.HighPart = ftNow.dwHighDateTime;
    ulNowTime.LowPart = ftNow.dwLowDateTime;
    ulFileTime.HighPart = ftFile.dwHighDateTime;
    ulFileTime.LowPart = ftFile.dwLowDateTime;

//The difference between the current time and the creation time
//of the file can now be created

    ulAge.HighPart = ulNowTime.HighPart - ulFileTime.HighPart;
    ulAge.LowPart = ulNowTime.LowPart - ulFileTime.LowPart;
   
//I got into some trouble due to the fact that the
//autocomplete list for ULARGE_INTEGER in the editor I
//was using didn't list 'LowPart' or 'HighPart'. I wasted
//some time writing some tortured code which worked without
//accessing these members before I found out that I could 
//just write them in manually and they would be fine.
    
 
    FILETIME ftAge;
    SYSTEMTIME stAge;

//The ULARGE_INTEGER data can then be copied back to a FILETIME
//structure, this can then be converted to SYSTEMTIME
//which is the convenient format we want our data to be in....

    ftAge.dwHighDateTime = ulAge.HighPart;
    ftAge.dwLowDateTime = ulAge.LowPart;

    FileTimeToSystemTime(&ftAge,&stAge);

//Note the arithmetic adjustments below, needed to give the
//correct answer. The subtraction of '1601' is required for the 
//year value because FILETIME only goes back to 1601, whereas
//SYSTEMTIME goes back to Jesus' birthday. The other subtractions
//are just boundary corrections.

    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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)