各种 Windows 函数返回的“当前”时间并不总是以每分钟 60 秒的速度递增。
有时 Windows 有意让时钟走得慢一点,或者快一点:
- 如果您的 PC 时钟当前落后于当前正确时间:稍微运行快赶上
- 如果您的 PC 时钟当前领先于当前的正确时间:稍微慢一点让正确的时间赶上这个时间
它会这样做而不是让你的时钟突然JUMP,现在你的日志文件有时间异常。
您可以使用GetSystemTimeAdjustment 查看Windows 当前是在更快 还是更慢 或以名义速率计时。例如,现在在我的电脑上:
-
系统时间调整:已禁用
- 每次更新增加标称 15,6250 毫秒
但如果您的时钟距离正确时间太远,Windows 只会BONK将您的时间调整到正确的时间。
时间可以快,慢,向前跳,向后跳。 QueryPerformanceCounter 不会
所以返回的时间是:
随着你的时钟跳来跳去,都可以快、慢或跳来跳去。而 QueryPerformanceCounter 永远不会加速或减速。它始终以 60 秒/分钟的准确速率递增。
那你关心什么
只要您的计算机时钟当前未经过SystemTimeAdjustment,则经过的时间间隔由:
- GetSytemTimeAsPreciseFileTime
- 查询性能计数器
两者都会:
- 保持同步
- 彼此相差不到十分之几微秒
- 具有 100ns (0.1 us) 的终极分辨率
要获得准确的时间测量,您应该使用这两个函数。问题,你关心哪一个任务:
奖金聊天
需要注意的是,GetLocalTime 和GetSystemTime 返回的SYSTEM_TIME 结构本质上仅限于毫秒精度。 GetSystemTime 无法为您提供亚毫秒级的分辨率,仅仅是因为返回类型无法为您提供微秒级:
struct SYSTEMTIME {
WORD wYear;
WORD wMonth;
WORD wDay;
WORD wHour;
WORD wMinute;
WORD wSecond;
WORD wMilliseconds; //<---- that's the limit
}
只有当您希望 Windows 将您的时间分解为年、月、日、小时、分钟、秒时,您才应该使用 SYSTEM_TIME。另一方面,FILE_TIME 结构的极限精度为 100ns (0.1 us)。
精确的时间函数
在我使用的语言中,我们使用DateTime,即Double 精度浮点数,其中:
- 整数部分表示自 1899 年 12 月 30 日以来的天数
- 小数部分代表一天 24 小时的一小部分
这是 COM、OLE、Delphi、VB、Excel、Lotus 123 使用的日期时间方案;并且类似于 SQL Server 使用的日期时间方案(尽管它们使用 1/1/1900 而不是 12/30/1899)
DateTime UtcNowPrecise()
{
const UInt64 OA_ZERO_TICKS = 94353120000000000; //12/30/1899 12:00am in ticks
const UInt64 TICKS_PER_DAY = 864000000000; //ticks per day
FILE_TIME ft;
GetSystemTimePreciseAsFileTime(out ft);
ULARGE_INTEGER dt; //needed to avoid alignment faults
dt.LowPart = ft.dwLowDateTime;
dt.HighPart = ft.dwHighDateTime;
return (dt.QuadPart - OA_ZERO_TICKS) / TICKS_PER_DAY;
}
DateTime NowPrecise()
{
const UInt64 OA_ZERO_TICKS = 94353120000000000; //12/30/1899 12:00am in ticks
const UInt64 TICKS_PER_DAY = 864000000000; //ticks per day
FILE_TIME ft;
GetSystemTimePreciseAsFileTime(out ft);
//Convert from UTC to local
FILE_TIME ftLocal;
if (!FileTimeToLocalFileTime(ft, ref ftLocal))
RaiseLastNativeError();
ULARGE_INTEGER dt; //needed to avoid alignment faults
dt.LowPart = ftLocal.dwLowDateTime;
dt.HighPart = ftLocal.dwHighDateTime;
return (dt.QuadPart - OA_ZERO_TICKS) / TICKS_PER_DAY;
}