Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

DateTimePrecise Class in C# -- An Improvement to DateTime.Now

0.00/5 (No votes)
10 Apr 2008 1  
This class combines the Windows system time with the high-resolution timer performance counter to return a current time that is both precise and accurate

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
{
    /// DateTimePrecise provides a way to get a DateTime that exhibits the
    /// relative precision of
    /// System.Diagnostics.Stopwatch, and the absolute accuracy of DateTime.Now.
    public class DateTimePrecise
    {
        /// Creates a new instance of DateTimePrecise.
        /// A large value of synchronizePeriodSeconds may cause arithmetic overthrow
        /// exceptions to be thrown. A small value may cause the time to be unstable.
        /// A good value is 10.
        /// synchronizePeriodSeconds = The number of seconds after which the
        /// DateTimePrecise will synchronize itself with the system clock.
        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;
        }

        /// Returns the current date and time, just like DateTime.UtcNow.
        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;
                }
            }
        }

        /// Returns the current date and time, just like DateTime.Now.
        public DateTime Now
        {
            get
            {
                return this.UtcNow.ToLocalTime();
            }
        }

        /// The internal System.Diagnostics.Stopwatch used by this instance.
        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

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here