Introduction
Here, I present a possible solution (or at least hopefully a pretty good polyfill) for those who want the precision of the new GetSystemTimePreciseAsFileTime
API but have OS versions that do not support it. The GetSystemTimePreciseAsFileTime
API is only available in Windows 8 or later or Windows Server 2012 or later. Many people still use Windows 7 and this will probably remain true for several years.
Background
Calling DateTime.UtcNow
is a useful way to obtain the current date and time in C#. It returns a DateTime struct
which is a 64-bit value that stores "ticks". A tick is a 100-nanosecond interal, or 0.1 microseconds. This is a nice level of precision, but unfortunately the resolution is limited to the Windows system timer resolution, which on most systems cannot get better than 5000 ticks or half a millisecond, and is closer to 15ms in practice on most machines I've observed. (Use NtQueryTimerResolution
to see your system's timer resolution.)
Say your app creates 'widgets' that have a date/time applied to them. You care about sorting these widgets by date/time, but you come to realize that if you create several widgets in a short period of time, then they all appear to have the same date/time. Maybe you can apply a monotonically increasing 'sequence number' to each widget, but somehow this seems unsatisfactory. The 64-bit tick count clearly provides enough space to store everything we need, yet we can only see up to 0.02% of it (assuming: 1 tick / min system res = 0.0001ms / 0.5ms = 0.0002).
Of course, QueryPerformanceCounter
has long provided a high-resolution way to measure elapsed time, and .NET's Stopwatch
class uses it (where available). Note that QPC allows at least 10% of the ticks value to be used (0.0001ms / 0.001ms), as it provides no worse than 1-microsecond resolution. I highly recommend reading Acquiring high-resolution time stamps on MSDN which provides you everything you ever wanted to know about QPC and how to use it. QueryPerformanceCounter
, though, does not give a date/time, it gives a high-resolution monotonically-increasing value that can be used to calculate a time span. So why don't we combine QueryPerformanceCounter
with the API calls that use the Windows system timer, and create a high-resolution DateTime
value?
This is the essence of my code, and while others have implemented similar concepts on the surface, my code not only provides a close approximation to real time but it also ensures monotonically-increasing values (for calls on the same thread).
Using the Code
The static
class DateTimeHighResWin7
provides two public
properties, UtcNow
and Now
, which function similarly to DateTime
's like-named properties but provide higher resolution. It's as easy as that. You can even combine DateTimeHighResWin7
with Sebastian Krysmanski's HighResolutionDateTime to provide the full .NET high-resolution date/time experience for old and new OSes alike.
var now = HighResolutionDateTime.IsAvailable ?
HighResolutionDateTime.UtcNow : DateTimeHighResWin7.UtcNow;
The code for DateTimeHighResWin7
is short enough to reproduce in full below.
public static class DateTimeHighResWin7
{
public static DateTime UtcNow
{
get
{
return MakeHighResolution(DateTime.UtcNow);
}
}
public static DateTime Now
{
get
{
return MakeHighResolution(DateTime.Now);
}
}
static DateTimeHighResWin7()
{
TicksAdjustNormal = TimeSpan.TicksPerSecond * 1.0 / Stopwatch.Frequency;
TicksAdjustSlowdown = 1.0;
Unreliable = !Stopwatch.IsHighResolution ||
TicksAdjustNormal < 1.414;
}
private static readonly double TicksAdjustNormal;
private static readonly double TicksAdjustSlowdown;
private static readonly bool Unreliable;
[ThreadStatic]
private static long LastTicks;
[ThreadStatic]
private static long LastTS;
[ThreadStatic]
private static bool AdjustTicks;
[ThreadStatic]
private static long LastSystemTicks;
private static DateTime MakeHighResolution(DateTime dt)
{
long ts = Stopwatch.GetTimestamp();
if (Unreliable) return dt;
bool systemTicksChanged = false;
if (dt.Ticks != LastSystemTicks)
{
LastSystemTicks = dt.Ticks;
systemTicksChanged = true;
}
if (LastTS == 0L)
{
LastTicks = dt.Ticks;
LastTS = ts;
AdjustTicks = false;
}
else
{
long newTicks = LastTicks + (long)((ts - LastTS) *
(AdjustTicks ? TicksAdjustSlowdown : TicksAdjustNormal));
if (dt.Ticks >= newTicks)
{
LastTicks = dt.Ticks;
LastTS = ts;
AdjustTicks = false;
}
else
{
if (systemTicksChanged)
{
LastTicks = newTicks;
LastTS = ts;
AdjustTicks = true;
}
dt = new DateTime(newTicks);
}
}
return dt;
}
}
Points of Interest
Bundled in the source code is a console app that runs tests and does timing comparisons between DateTimeHighResWin7
and DateTime
(and GetSystemTimePreciseAsFileTime
where available). I believe the code and the comments speak for themselves. Please study them, run your own tests, and I welcome any feedback you may have.
History
- July 21, 2015 -- Code first published
- July 24, 2015 -- Use "
Unreliable
" flag