FILETIME, SYSTEMTIME, COleDateTime和UTC的关系以及如何相互转换(二)

在前一篇文章《FILETIME, SYSTEMTIME, COleDateTime和UTC的关系以及如何相互转换(一)》中, 笔者遗留了三个不同的测试实验让大家思考, 如Figure 1所示, 第3个是从COleDateTime得到当前时间并转换为SYSTEMTIME和FILETIME, 当这三者混合到一起使用的时候有一个非常容易出错的地方: 笔者的本意是得到当前的Local Time也就是电脑右下角显示的时间, 并且把这个时间存储成SYSTEMTTIME结构以及FILETIME结构, 实际应用中很多需要这样, 比如把当前的本地时间设置成文件的创建时间或者最近更新时间, 也就是说得到的SYSTEMTIME和FILETIME如果进行格式化转换输出应当得到的是和当前系统的本地时间一样的时间.

SYSTEMTIME stDateTimeForOle;
	COleDateTime oleDateTime(COleDateTime::GetCurrentTime());
	oleDateTime.GetAsSystemTime(stDateTimeForOle);
	m_listOutput.InsertString(iIndex++, _T("Ole.GetAsSystemTime = ") +
		this->GetReadableDateTimeString(stDateTimeForOle));

	FILETIME ftWithDelta;
	::SystemTimeToFileTime(&stDateTimeForOle, &ftWithDelta);
	m_listOutput.InsertString(iIndex++,
		_T("OLE: SystemTimeToFileTime: SystemTime = ") +
		this->GetReadableDateTimeString(stDateTimeForOle) +
		_T(", FileTime = ") +
		this->GetReadableDateTimeString(ftWithDelta));

如上面的代码所示, 这是笔者的初步尝试, 输出结果可以看Figure 1的红字3, 笔者这么做是因为从COleDateTime到FILETIME只能通过中间步骤调用COleDateTime的GetAsSystemTime方法, 先得到SYSTEMTIME结构, 然后通过SystemTimeToFileTime这个API函数进一步转化成FILETIME结构, 但是在这个转化过程中, 正如上一篇文章说的, 里面有个UTC时差的概念, 当使用SystemTimeToFileTime进行转换的时候, 根据前文总结的第一点: 第一, 遇到系统时间System Time的时候, 时间转换关系一定是, System Time + H = 其他时间(File Time OR Local Time); 那么我们得到的FILETIME结构其实是在当前的本地时间上有一个H的差值, 输出结果红字3也验证了这一点, 这个就是前文我留下的”坑”, 不知道大家有没有自己试出来过.

那么为了达到笔者的目的, 得到与当前的系统本地之间一致的FILETIME结构, 于是笔者又写了第4个测试代码, 进行进一步的转换和验证, 代码如下:

FILETIME ftCorrect;
	::LocalFileTimeToFileTime(&ftWithDelta, &ftCorrect);
	m_listOutput.InsertString(iIndex++,
		_T("LocalFileTimeToFileTime: ftWithDelta = ") +
		this->GetReadableDateTimeString(ftWithDelta) +
		_T(", ftCorrect = ") +
		this->GetReadableDateTimeString(ftCorrect));

	FILETIME ftWithDelta2;
	::FileTimeToLocalFileTime(&ftCorrect, &ftWithDelta2);
	m_listOutput.InsertString(iIndex++,
		_T("FileTimeToLocalFileTime: ftWithDelta2 = ") +
		this->GetReadableDateTimeString(ftWithDelta2) +
		_T(", ftCorrect = ") +
		this->GetReadableDateTimeString(ftCorrect));

这里面使用到了LocalFileTimeToFileTime这个系统函数, 根据前文的总结: 第二, 遇到本地时间Local Time的时候, 时间转换关系一定是, Local Time = 其他时间(System Time OR File Time) + H. 这里有Local那么得到的FILETIME正好抵消前面SystemTimeToFileTime的”时差”,于是我们就得到了正确的结果, 见Figure 1中的红字4.

看了上面的一系列实验代码, 笔者在想一定要通过COleDateTime这个方法来得到正确的对应当前系统本地时间的FILETIME结构吗? 仔细回顾前面的代码, 别忘了我们还有GetSystemTime和GetLocalTime这两个系统函数呢, 于是我们可以用两种不同的方法得到正确的记过, 也就是我们的第5个实验, 代码如下:

m_listOutput.InsertString(iIndex++, _T("Get file time current way 1: ") +
    this->GetCurrentFileTimeWayOne());

m_listOutput.InsertString(iIndex++, _T("Get file time current way 2: ") +
    this->GetCurrentFileTimeWayTwo());

CString GetCurrentFileTimeWayOne(void)
{
	SYSTEMTIME stDateTime;
	FILETIME   ftDateTime;
	FILETIME   ftDateTimeRet;

	::GetLocalTime(&stDateTime); // current local time
	::SystemTimeToFileTime(&stDateTime, &ftDateTime);
	// set back!
	::LocalFileTimeToFileTime(&ftDateTime, &ftDateTimeRet); // the correct result
	return this->GetReadableDateTimeString(ftDateTimeRet);
}

CString GetCurrentFileTimeWayTwo(void)
{
	COleDateTime oleDatetime(COleDateTime::GetCurrentTime());
	SYSTEMTIME stDateTime;
	FILETIME   ftDateTime;
	FILETIME   ftDateTimeRet;

	oleDatetime.GetAsSystemTime(stDateTime);
	::SystemTimeToFileTime(&stDateTime, &ftDateTime);
	::LocalFileTimeToFileTime(&ftDateTime, &ftDateTimeRet); // the correct result
	return this->GetReadableDateTimeString(ftDateTimeRet);
}

实验结果可以看Figure 1中的红字5, 可以看到两种方法均可以得到和当前系统的本地时间一致的FILETIME结构.
Windows Time Structures Experimental Results

Figure 1. Windows Time Structures Experimental Results by Sigmainfy

Written on February 3, 2014