The .NET Framework provides two ways to sample the current time: DateTime.Now and Diagnostics.Stopwatch.GetTimestamp(). DateTime.Now is accurate—it tells you the current time—but it only has 16 millisecond precision. Stopwatch.GetTimestamp() is as precise as your CPU instruction counter and can distinguish between nanoseconds, but it is not accurate at all—it contains no information about what the absolute time is.
The DateTime struct is just a wrapper for a signed 64 bit integer which represents the number of 100-nanosecond periods since midnight of January 1st of the year one. DateTime, therefore, is capable of representing a time with 100 nanosecond precision.
DateTimePrecise.Now returns a DateTime struct that supplements DateTime.Now with information from the Stopwatch, giving you a current time that has 16 millisecond accuracy and 100 nanosecond precision. It will also smooth out discontinuities in the system clock time, like when your computer synchronizes with an internet time server.
Features of DateTimePrecise
- Continuous and strictly increasing, even when the system clock time is changed, so long as the system clock time is changed by less than the resynchronization period.
- Thread-safe and lock-free. Uses internal immutable states to ensure consistency. Atomic operations are not even needed, because DateTimePrecise is tolerant of read-write instruction re-ordering and memory caching.
- Will asymptotically approach the system clock time, but damped, so that it is stable and doesn't accelerate suddenly.
The following table shows how much time it takes, in seconds, to call each time sampling method on Windows Server 2003 x64 Standard Edition, .NET Framework 2.0, Intel Xeon Woodcrest. There are a few interesting surprises in this table.
| Stopwatch.GetTimestamp() |
0.000000184 |
| DateTime.Now |
0.000000226 |
| DateTime.UtcNow |
0.000000010 |
| DateTime.UtcNow.ToLocalTime() |
0.000000221 |
| DateTimePrecise.Now |
0.000000482 |
| DateTimePrecise.UtcNow |
0.000000248 |
| DateTimePrecise.UtcNow.ToLocalTime() |
0.000000455 |
Using the code
DateTimePrecise is as easy to use as DateTime.Now, except that DateTimePrecise.Now is an instance method instead of a static method, so you have to first instantiate a DateTimePrecise.
DateTimePrecise dateTimePrecise = new DateTimePrecise(10);
DateTime timeNow = dateTimePrecise.Now;
Here is the source code, in C#.
Collapse
using System;
using System.Diagnostics;
namespace JamesBrock
{
public class DateTimePrecise
{
public DateTimePrecise(long synchronizePeriodSeconds)
{
Stopwatch = Stopwatch.StartNew();
this.Stopwatch.Start();
DateTime t = DateTime.UtcNow;
_immutable = new DateTimePreciseSafeImmutable(t, t, Stopwatch.ElapsedTicks, Stopwatch.Frequency);
_synchronizePeriodSeconds = synchronizePeriodSeconds;
_synchronizePeriodStopwatchTicks = synchronizePeriodSeconds * Stopwatch.Frequency;
_synchronizePeriodClockTicks = synchronizePeriodSeconds * _clockTickFrequency;
}
public DateTime UtcNow
{
get
{
long s = this.Stopwatch.ElapsedTicks;
DateTimePreciseSafeImmutable immutable = _immutable;
if (s < immutable._s_observed + _synchronizePeriodStopwatchTicks)
{
checked
{
return immutable._t_base.AddTicks(((s - immutable._s_observed) * _clockTickFrequency) / (immutable._stopWatchFrequency));
}
}
else
{
DateTime t = DateTime.UtcNow;
DateTime _t_base_new = immutable._t_base.AddTicks(((s - immutable._s_observed) * _clockTickFrequency) / (immutable._stopWatchFrequency));
long measuredStopwatchFrequency = ((s - immutable._s_observed) * _clockTickFrequency) / (t.Ticks - immutable._t_observed.Ticks);
DateTimePreciseSafeImmutable immutableNew;
if (s - immutable._s_observed < _synchronizePeriodStopwatchTicks)
{
immutableNew = new DateTimePreciseSafeImmutable(
t,
_t_base_new,
s,
(measuredStopwatchFrequency +
(
(_synchronizePeriodSeconds * measuredStopwatchFrequency * _clockTickFrequency)
/
(t.Ticks + _synchronizePeriodClockTicks - _t_base_new.Ticks)
)
) / 2
);
}
else
{
immutableNew = new DateTimePreciseSafeImmutable(
t,
_t_base_new,
s,
measuredStopwatchFrequency
);
}
_immutable = immutableNew;
return _t_base_new;
}
}
}
public DateTime Now
{
get
{
return this.UtcNow.ToLocalTime();
}
}
public Stopwatch Stopwatch;
private long _synchronizePeriodStopwatchTicks;
private long _synchronizePeriodSeconds;
private long _synchronizePeriodClockTicks;
private const long _clockTickFrequency = 10000000;
private DateTimePreciseSafeImmutable _immutable;
}
internal sealed class DateTimePreciseSafeImmutable
{
internal DateTimePreciseSafeImmutable(DateTime t_observed, DateTime t_base, long s_observed, long stopWatchFrequency)
{
_t_observed = t_observed;
_t_base = t_base;
_s_observed = s_observed;
_stopWatchFrequency = stopWatchFrequency;
}
internal readonly DateTime _t_observed;
internal readonly DateTime _t_base;
internal readonly long _s_observed;
internal readonly long _stopWatchFrequency;
}
}