Introduction
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 a 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.
DateTime.Now
is fine for time-stamping events that happen infrequently, and Stopwatch.GetTimestamp()
works well for measuring how long something lasts. But what if we have some event that happens several thousand times per second, and we want to time-stamp these events in a meaningful way, so that we can look at them later and see what time they occurred, and also in what order? The .NET Framework libraries have no solution.
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 a 100 nanosecond precision, so we can conveniently use the DateTime
struct for a high-resolution time-stamp.
DateTimePrecise.Now
returns a DateTime
struct that supplements DateTime.Now
with information from the Stopwatch
, giving you a current time that has a 16 millisecond accuracy and a 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. Creates no background threads. 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.
- Error / second ~= (0.032 seconds / synchronization period) + (Stopwatch error * synchronization period).
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#:
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)
{
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));
_immutable = new DateTimePreciseSafeImmutable(
t,
t_base_new,
s,
((s - immutable._s_observed) * _clockTickFrequency * 2)
/
(t.Ticks - immutable._t_observed.Ticks + t.Ticks +
t.Ticks - t_base_new.Ticks - immutable._t_observed.Ticks)
);
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;
}
}
History
- 10th April, 2008: Initial post