一.FILETIME和SYSTEMTIME格式
我遇到的第一种时间格式是在使用函数GetFileTime时返回的 FILETIME,这是一个64位(8字节)结构。在SDK文档中,关于这个结构是这样描述的“FILETIME结构的64位值,表示从1601年1月1 日开始的以100纳秒(一纳秒为十亿分之一秒)为间隔的值”。我不知道你——反正我的朋友和亲戚——在听到“100纳秒”时头都大了。
不过还好,Win32的另一种时间格式消除了这个问题,这个格式就是 SYSTEMTIME。它是一个包含有年、月、日、小时、分、秒、微秒这些成员的结构,而且还有一个API函数FileTimeToSystemTime 专门用来将FILETIEM格式转化成SYSTEMTIME格式。当然,假如你要知道是否为闰年、闰纪等这样的问题就只好自己手工转换了。
每次运行使用GetFileTime和FileTimeToSystemTime函数 的代码,我总是发现得出的时间比正确的时间少几个小时。为什么呢?思来想去,我终于明白了:在安装WINDOWS NT和95时,我曾经告诉系统我住在什么地区。而在win32下,FILETIME总是为格林尼治时间(UTC)。使用UTC,你就会发现实际时间是美国 新罕布什尔州的南雪(Nashua)只有2PM,而得到的时间却是格林尼治7PM。
如果让程序员对每个FILETIME都负责检查并修正,这太可怕了。这就是WIN32 提供FileTimeToLocalTime函数的原因。在调用FileTimeToSystemTime前先调用 FileTimeToLocalFileTime,可以非常方便地对FILETIME进行时区修正。
二.MS-DOS格式
第三种描绘时间的格式是一种老式的MS-DOS格式。在这种格式下,日期和时间被独立 存储在两个双字中。因为只有16位,所以年度值是相对于1980年的差值。而且,这种格式时间精度是2秒。如果你选择使用这种格式,那么函数 FileTimeToDosDateTime是不能缺少的。
我怎么会想到这种时间格式呢?真不好意思,当我在开发DEPENDS程序时,开始我并没有想到要使用SYSTEMTIME格式。所以,一直到我意识到这个问题之前,所有版本的DEPENDS都是将FILETIME转换成MS-DOS格式的。
三.PE文件中的时间戳
第四种时间格式在我们平常的API文档中是不会见到的。在WIN32可执行文件中, PE文件头部(IMAGE_FILE_HEADER)中有个DWORD字段TimeDateStamp。它是从格林尼治时间1970年1月1日夜里12: 00开始的秒数。TimeDateStamp由连接器(linker)设置,并且在PE文件的其它部分也有使用。
在这里,我要承认我以前犯的一个愚蠢的错误。在我的文章《Peering Inside the PE: A Tour of the Win32 Portable Executable File Format》 (MSJ March 1994)和书《Windows 95 System Programming Secrets》中,我将TimeDateStamp字段描述成从1969年12月31日4PM开始的秒数。我得到这个结果是因为我将 TimeDateStamp设置为0,然后运行DUMPBIN来看转换成什么时间。在这里我没有考虑到DUMPBIN已经帮我把时间转换成计算机当前时区 的时间(我的时区当然不会是格林尼治时区)了。所以,在这里我再次纠正这个错误。
TimeDateStamp这个字段在PE文件中相当有用。比如,当你在文件系统中改 变了文件的日期和时间后,TimeDateStamp却会保持不变。这样,当你想知道文件到底什么时候创建时,TimeDateStamp是比较准确(当 然前提是连接器正确设置了它)。TimeDateStamp的唯一需要点技术的地方是:怎样将它转变成我们比较熟悉的时间格式。
经过考虑,我是这样来解决这个问题的。FILETIME和TimeDateStamp 分别相对于某个时间点的值。如果我能用FILETIME来描述TimeDateStamp,我就可以使用上面提到的Win32时间函数来做我想做的任何事 情。所以我需要先将1970年1月1日表示成FILETIME格式,然后再将TimeDateStamp(以秒数为计数单位)转换成以100纳秒为计数单 位,最后将上面得到的两个值相加就可以得到我想要的FILETIME格式了。
为了将1970年1月1日转换成FILETIME,我首先定义了一个 SYSTEIMTIME结构,然后将1970年1月1日填空进去,接着我只要调用SystemTimeToFileTime便可以得到FILETIME格 式了。转换的结果我在DEPENDS代码用到:0x019DB1DED53E8000。将秒数转换成100纳秒比较简单:只要乘上10000000。当 然,结果肯定会超过32位,所以一定要将其中的被乘数强制成64位整数(VC中的_INT64)。当然,如果你想省点事,你完全可以使用C运行库中的 ctime函数。
四.MAC操作系统中的longdt时间
和FILETIME一样,这种时间也是个8字节数据格式,而且也是相对于一个时间点的秒数的计数。但不幸的是:这个时间点和FILETIME不同,它是1904年1月1日午夜12:00。