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

前言, FILETIME, SYSTEMTIME, COleDateTime以及UTC简析:

FILETIME和SYSTEMTIME是Windows API提供给我们的两个存储文件时间和系统时间的重要数据结构, COleDateTime则是在OLE Automation中使用的对时间数据的封装, 因其方便性也经常被混合使用. 然而三者的混合使用却在UTC这个概念上很容易出错. 笔者希望通过这篇文章对FILETIME, SYSTEMTIME, COleDateTime三者在UTC这个点上的联系与区分进行整理总结, 并给出两句容易记忆的总结语概括之.

FILETIME, SYSTEMTIME, COleDateTime的概念(UTC):

根据MSDN的官方文档, 这三者的定义分别如下:

FILETIME: 
        Contains a 64-bit value representing 
        the number of 100-nanosecond intervals 
        since January 1, 1601 (UTC).
SYSTEMTIME: 
        Specifies a date and time, using individual 
        members for the month, day, year, weekday, 
        hour, minute, second, and millisecond. The 
        time is either in coordinated universal time 
        (UTC) or local time, depending on the function 
        that is being called.
COleDateTime:
        Encapsulates the DATE data type that is used 
        in OLE automation.

FILETIME, SYSTEMTIME, COleDateTime这三者的定义相信看了以后应该也很明白直接, 其中有UTC这个概念在里面, 这里稍微解释一下UTC时间, UTC是协调世界时(Universal Time Coordinated),这么说吧, 我们把整个地球分为二十四时区,每个时区都有自己的本地时间(Local Time), 比如北京时间是UTC +8, 美国加拿大的太平洋时间是UTC -8。为了统一起见,我们需要或者说世界需要使用一个统一的时间,称为通用协调时(UTC, Universal Time Coordinated), 并且UTC与英国伦敦的本地时相同。PS: 以前学过的格林威治平均时(GMT)现在已经不作为标准时间了.

另外注意FILETIME永远是基于UTC的时间, SYSTEMTIME则可能是是本地时间, 这个要根据不同的API函数决定,  然后COleDateTime是时间数据封装的类, 独立于UTC的, 没有说和UTC有特定的关系, 给一个初始化数据就可以初始化一个COleDateTime类, 可以比较方便的对时间进行格式化的输出之类的. 下面的例子很快就可以看到这三者的一些用法, 笔者下面也会提到, 不用死记他们的概念, 笔者总结了两句话以概括之.

FILETIME, SYSTEMTIME, COleDateTime的相互转换和UTC的关系:

很多人可能会纠结于UTC这个概念并且觉得看不清楚这么多时间结构之间的关系, 但是笔者认为其实也没有必要搞清楚其中的细节, 只需要注意和记住三个时间概念:

第一, 系统时间(System Time);

第二, 文件时间(File Time);

第三, 本地时间(Local Time). 这个其实就是电脑右下角显示的时间.

然后记住两句话:

第一, 遇到系统时间System Time的时候, 时间转换关系一定是, System Time + H = 其他时间(File Time OR Local Time);

第二, 遇到本地时间Local Time的时候, 时间转换关系一定是, Local Time = 其他时间(System Time OR File Time) + H. 

这里的H是指时区时差, 比如北京时间就是 UTC +8, 那么H = 8, 太平洋时间是UTC -8, 那么H = -8. 那么我们就来验证一下上面这两句话是不是对的:

首先利用COleDateTime对FILETIME和SYSTEMTIME进行分装和格式化输出, 方便我们观察结果, 代码如下:

CString GetReadableDateTimeString(const FILETIME & ftDateTime)
{
	COleDateTime oleDateTime(ftDateTime);
	return oleDateTime.Format(_T("%Y-%m-%d, %H:%M:%S"));
}

CString GetReadableDateTimeString(const SYSTEMTIME & stDateTime)
{
	COleDateTime oleDateTime(stDateTime);
	return oleDateTime.Format(_T("%Y-%m-%d, %H:%M:%S"));
}

第1个我们要看的是System Time和Local Time的关系, 代码如下:这里我是写了一个MFC的Dialog应用, m_listOutput是一个list control, 用来看输出的, 代替printf或者cout的功能. 可以在Figure 1中看到, 对应的红字1的输出, 由于我测试的时候时区设置成了UTC -8, 那么可以看出SYSTEM Time 和Local Time的关系符合我的两句总结.

int iIndex = 0;
	SYSTEMTIME stDateTimeSystemTime;
	SYSTEMTIME stDateTimeLocalTime;

	// Local Time 是指在电脑右下角显示的本地时间
	// (1) Local Time = System Time + H
	::GetSystemTime(&stDateTimeSystemTime);
	m_listOutput.InsertString(iIndex++, _T("GetSystemTime = ") +
		this->GetReadableDateTimeString(stDateTimeSystemTime));

	::GetLocalTime(&stDateTimeLocalTime);
	m_listOutput.InsertString(iIndex++, _T("GetLocalTime = ") +
	this->GetReadableDateTimeString(stDateTimeLocalTime));

第2个我们看看SYSTEMTIME和FILETIME的关系, 代码如下, 转换关系也在注释里面了, 结果请看Figure 1里面的红字2对应的输出, 同样符合我的两句总结:

//
	//                   BOOL WINAPI SystemTimeToFileTime(
	//                       _In_   const SYSTEMTIME *lpSystemTime,
	//                       _Out_  LPFILETIME lpFileTime);
	//    SYSTEMTIME   < ------------------------------------------- > FILETIME
	//                   BOOL WINAPI FileTimeToSystemTime(
	//                       _In_   const FILETIME *lpFileTime,
	//                       _Out_  LPSYSTEMTIME lpSystemTime);
	// FILETIME = SYSTEMTIME + H
	FILETIME   ftDateTime;
	::SystemTimeToFileTime(&stDateTimeSystemTime, &ftDateTime);
	m_listOutput.InsertString(iIndex++,
		_T("SystemTimeToFileTime: SystemTime = ") +
		this->GetReadableDateTimeString(stDateTimeSystemTime) +
		_T(", FileTime = ") +
		this->GetReadableDateTimeString(ftDateTime));

	// SYSTEMTIME = FILETIME - H
	SYSTEMTIME stConverted;
	::FileTimeToSystemTime(&ftDateTime, &stConverted);
	m_listOutput.InsertString(iIndex++,
		_T("FileTimeToSystemTime: FileTime = ") +
		this->GetReadableDateTimeString(ftDateTime) +
		_T(", SystemTime = ") +
		this->GetReadableDateTimeString(stConverted));
Windows Time Structures Experimental Results

Figure 1. Windows Time Structures Experimental Results by Sigmainfy

做完这两个实验以后, 笔者还写了三个不同的测试代码, 第3个是从COleDateTime得到当前时间并转换为SYSTEMTIME和FILETIME, 然而当这三者混合到一起使用的时候有一个非常容易出错的地方, 笔者的本意是得到当前的Local Time也就是电脑右下角显示的时间, 并且把这个时间存在SYSTEMTTIME结构或者FILETIME结构中(实际应用中很多需要这样, 比如文件创建时间就是当前的时间), 也就是说得到的SYSTEMTIME和FILETIME如果进行格式化转换输出应当得到的是当前系统的本地时间. 但是结果却不是这么简单, 可以看红字3, 于是笔者又写了第4个测试代码, 进行进一步的转换和验证, 得到了期望的SYSTEMTIME和FILETIME. 第5个实验目的是用两种不同的方法得到正确的FILETIME结构, 并且这个结构代表当前本地时间. 既然这里已经引出了不少Windows同的系统函数SystemTimeToFileTime等, 笔者想请大家先自己查阅API然后看能不能发现我说的问题(第3个实验), 并且用两种不同的方法得到当前系统本地之间对应的FILETIME结构(第5个实验). 我很快会在后续文章里面给出实验代码和步骤. 这里权当做一个练习, 先让大家思考一下.

结束语:

这篇博文讨论了SYSTEMTIME, FILETIME, COleDateTime与UTC之间的关系和相互转换的基础知识, 并且给出了转换关系的两句关键总结:

第一, 遇到系统时间System Time的时候, 时间转换关系一定是, System Time + H = 其他时间(File Time OR Local Time);

第二, 遇到本地时间Local Time的时候, 时间转换关系一定是, Local Time = 其他时间(System Time OR File Time) + H.

Written on February 1, 2014