如何计算文件的年龄






3.29/5 (10投票s)
使用时间和文件函数
引言
我以为这会很容易。事实并非如此。希望这篇文章能让那些想执行相同任务的人的生活更轻松。
背景
有时候,事情就是比你想象的要花更长的时间。例如,当我想要编写代码来计算程序中文件的年龄时,我认为这将是一件相当直接的事情。显然,这是获取当前时间并减去文件创建时间的问题。然而,文件时间和系统时间格式不同,需要将它们转换为兼容的格式,以便进行减法运算。
不幸的是(据我所知),VC++6 中没有现成的解决方案。如果 Windows 中有某个函数 `GetFileAge`,它接收文件创建时间和当前时间作为参数并输出文件的当前年龄,那么我就能节省这个周末几个小时,而这篇文章也就没有必要了。
所以,废话不多说,这里将详细介绍我如何编写代码来计算文件年龄的整个过程……
这篇文章的代码已为第三版几乎全部重写,并在第四版中为该问题增加了一个第二个解决方案。我重写了代码以利用 `CTime` 和 `CTimeSpan` 类。这可能是一个更好的版本,因为年龄计算只在一行中完成。代码的长度与第三版相似,但我添加了大量的注释,希望这能让它变得清晰易懂。
这项工作涉及 `FILETIME` 结构,该结构用于包含有关文件的有用时间信息,以及 `SYSTEMTIME` 结构,该结构用于保存当前时间。
MSDN 库关于“SYSTEMFILE”的页面说明:
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.
`FILETIME` 和 `SYSTEMTIME` 结构差异很大。也许它们之所以如此不同有一个有趣的故事,但我不知道那是什么。
同样来自“`SYSTEMFILE`”库页面……
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;
……其中 `wYear` 代表当前年份等。
来自 MSDN 库关于“`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;
`CTime` 类有接受 `FILETIME` 和 `SYSTEMTIME` 结构的构造函数。当我们要处理的时间之一是计算机上的系统时间,而另一个是文件的创建时间时,这非常方便。但是,我不需要 `SYSTEMTIME` 构造函数,因为 `CTime` 有一个方法——`GetCurrentTime`,它可以将系统时间加载到对象中。
可以使用“-”运算符将一个 `CTime` 对象减去另一个 `CTime` 对象,即,这正是我们在计算年龄时想要做的事情。此计算的结果是一个 `CTimeSpan` 对象。
Using the Code
第四版代码
这是我最新的代码版本……
我的程序将我进行的转换结果发送到一个名为“`CEditNodeDlg`”的对话框。
//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;
第三版代码
//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;
关注点
我的编辑器上的自动完成功能没有列出 `ULARGE_INTEGER` 结构的“`HighPart`”和“`LowPart`”成员。最初,我以为它们对我不可用,然后我编写了一些复杂的代码来避免使用它们。后来我发现,如果手动输入这些成员,它们就能正常工作。
谢谢
阅读 RK_2000 在 CodeProject 上发表的文章“C++ 中的日期和时间”对我编写这篇文章的第三版帮助很大。
我非常感谢 DavidCrow 的帮助和鼓励。
历史
- 第一版 2008 年 2 月 23 日
- 第二版 2008 年 2 月 25 日
我已经更新了这篇文章,提供了更多关于我在完成这个编程任务时发现的一个问题的详细信息——即我无法访问 `ULARGE_INTEGER` 结构的“`HighPart`”和“`LowPart`”成员。 - 第三版 2008 年 2 月 25 日
我发现我毕竟可以访问 `ULARGE_INTEGER` 结构的 `HighPart` 和 `LowPart` 成员,并已几乎完全重写了代码。不再有对 `ULARGE_INTEGERS` 的(`FILETIME *`)的危险类型转换。
我还更改了许多使用的变量名。希望新的变量名能让代码更容易理解。 - 第四版 2008 年 2 月 26 日
我为该问题包含了一个新的解决方案。这个新解决方案可能更优。我保留了旧的解决方案,您可以自行选择。我更加关注代码中的注释。 - 第五版 2008 年 3 月 9 日
进行了一些清理工作