27

I have code running in a loop and it's saving state based on the current time. Sometimes this can be just milliseconds apart, but for some reason it seems that DateTime.Now will always return values of at least 10 ms apart even if it's only 2 or 3 ms later. This presents a major problem since the state i'm saving depends on the time it was saved (e.g. recording something)

My test code that returns each value 10 ms apart:

public static void Main()
{
    var dt1 = DateTime.Now;
    System.Threading.Thread.Sleep(2);
    var dt2 = DateTime.Now;

    // On my machine the values will be at least 10 ms apart
    Console.WriteLine("First: {0}, Second: {1}", dt1.Millisecond, dt2.Millisecond);
}

Is there another solution on how to get the accurate current time up to the millisecond ?

Someone suggested to look at the Stopwatch class. Although the Stopwatch class is very accurate it does not tell me the current time, something i need in order to save the state of my program.

7 Answers7

42

Curiously, your code works perfectly fine on my quad core under Win7, generating values exactly 2 ms apart almost every time.

So I've done a more thorough test. Here's my example output for Thread.Sleep(1). The code prints the number of ms between consecutive calls to DateTime.UtcNow in a loop:

sleep 1

Each row contains 100 characters, and thus represents 100ms of time on a "clean run". So this screen covers roughly 2 seconds. The longest preemption was 4ms; moreover, there was a period lasting around 1 second when every iteration took exactly 1 ms. That's almost real-time OS quality!1 :)

So I tried again, with Thread.Sleep(2) this time:

sleep 2

Again, almost perfect results. This time each row is 200ms long, and there's a run almost 3 seconds long where the gap was never anything other than exactly 2ms.

Naturally, the next thing to see is the actual resolution of DateTime.UtcNow on my machine. Here's a run with no sleeping at all; a . is printed if UtcNow didn't change at all:

no sleep

Finally, while investigating a strange case of timestamps being 15ms apart on the same machine that produced the above results, I've run into the following curious occurrences:

enter image description here enter image description here

There is a function in the Windows API called timeBeginPeriod, which applications can use to temporarily increase the timer frequency, so this is presumably what happened here. Detailed documentation of the timer resolution is available via the Hardware Dev Center Archive, specifically Timer-Resolution.docx (a Word file).

Conclusions:

  • DateTime.UtcNow can have a much higher resolution than 15ms
  • Thread.Sleep(1) can sleep for exactly 1ms
  • On my machine, UtcNow grows grow by exactly 1ms at a time (give or take a rounding error - Reflector shows that there's a division in UtcNow).
  • It is possible for the process to switch into a low-res mode, when everything is 15.6ms-based, and a high-res mode, with 1ms slices, on the fly.

Here's the code:

static void Main(string[] args)
{
    Console.BufferWidth = Console.WindowWidth = 100;
    Console.WindowHeight = 20;
    long lastticks = 0;
    while (true)
    {
        long diff = DateTime.UtcNow.Ticks - lastticks;
        if (diff == 0)
            Console.Write(".");
        else
            switch (diff)
            {
                case 10000: case 10001: case 10002: Console.ForegroundColor=ConsoleColor.Red; Console.Write("1"); break;
                case 20000: case 20001: case 20002: Console.ForegroundColor=ConsoleColor.Green; Console.Write("2"); break;
                case 30000: case 30001: case 30002: Console.ForegroundColor=ConsoleColor.Yellow; Console.Write("3"); break;
                default: Console.Write("[{0:0.###}]", diff / 10000.0); break;
            }
        Console.ForegroundColor = ConsoleColor.Gray;
        lastticks += diff;
    }
}

It turns out there exists an undocumented function which can alter the timer resolution. I haven't investigated the details, but I thought I'd post a link here: NtSetTimerResolution.

1Of course I made extra certain that the OS was as idle as possible, and there are four fairly powerful CPU cores at its disposal. If I load all four cores to 100% the picture changes completely, with long preemptions everywhere.

Roman Starkov
  • 52,420
  • 33
  • 225
  • 300
  • 5
    Those last 2 sentences are rather important :-) – nos Feb 10 '11 at 22:44
  • 3
    There exists documented functions that alters timer resolution as well. Those methods are [timePeriodBegin](http://msdn.microsoft.com/en-us/library/windows/desktop/dd757624%28v=vs.85%29.aspx) and [timePeriodEnd](http://msdn.microsoft.com/en-us/library/windows/desktop/dd757626%28v=vs.85%29.aspx) – Chuu Jan 17 '14 at 22:58
10

The problem with DateTime when dealing with milliseconds isn't due to the DateTime class at all, but rather, has to do with CPU ticks and thread slices. Essentially, when an operation is paused by the scheduler to allow other threads to execute, it must wait at a minimum of 1 time slice before resuming which is around 15ms on modern Windows OSes. Therefore, any attempt to pause for less than this 15ms precision will lead to unexpected results.

Roman Starkov
  • 52,420
  • 33
  • 225
  • 300
Chris
  • 26,212
  • 23
  • 116
  • 214
  • 4
    Erm, the problem is because DateTime.Now has a resolution of 10 milliseconds... See http://msdn.microsoft.com/en-us/library/system.datetime.now.aspx – configurator Feb 16 '09 at 03:41
  • 8
    And where do you think that 10 millisecond restriction comes from? Hint: It's not arbitrary. – Chris Feb 18 '09 at 18:22
2

IF you take a snap shot of the current time before you do anything, you can just add the stopwatch to the time you stored, no?

x0n
  • 47,695
  • 5
  • 84
  • 110
  • Interesting thought. It's highly performance critical code and I'm not sure using performance counters (Stopwatch internal API) is a good idea. I'll check that out. –  Nov 21 '08 at 02:02
  • I found in my benchmark (of a larger system) that there's no noticeable difference between using DateTime.UtcNow or adding a Stopwatch value to a common base time in a million calls. – ygoe Feb 09 '14 at 15:11
1

You should ask yourself if you really need accurate time, or just close enough time plus an increasing integer.

You can do good things by getting now() just after a wait event such as a mutex, select, poll, WaitFor*, etc, and then adding a serial number to that, perhaps in the nanosecond range or wherever there is room.

You can also use the rdtsc machine instruction (some libraries provide an API wrapper for this, not sure about doing this in C# or Java) to get cheap time from the CPU and combine that with time from now(). The problem with rdtsc is that on systems with speed scaling you can never be quite sure what its going to do. It also wraps around fairly quickly.

Zan Lynx
  • 49,393
  • 7
  • 74
  • 125
  • My problem is that I need to replay the saved state. So if i save two states 5 ms apart I need to be able to replay them 5 ms apart. Just knowing the order is not enough. –  Nov 21 '08 at 02:06
  • I don't know the problem you are trying to solve, but you may be trying too hard. Just getting the order right within 10 ms granularity may be enough. – Zan Lynx Nov 21 '08 at 03:37
  • Or maybe sounds like a task which should be hardware-accelerated or implemented in a realtime environment. – binki Feb 20 '14 at 20:03
0

All that I used to accomplish this task 100% accurately is a timer control, and a label.

The code does not require much explanation, fairly simple. Global Variables:

int timer = 0;

This is the tick event:

private void timeOfDay_Tick(object sender, EventArgs e)
    {

        timeOfDay.Enabled = false;
        timer++;

        if (timer <= 1)
        {
            timeOfDay.Interval = 1000;
            timeOfDay.Enabled = true;             
            lblTime.Text = "Time: " + DateTime.Now.ToString("h:mm:ss tt");                  
            timer = 0;
        }


}

Here is the form load:

private void DriverAssignment_Load(object sender, EventArgs e)
    {


        timeOfDay.Interval= 1;
        timeOfDay.Enabled = true;


}
Hesein Burg
  • 329
  • 2
  • 12
0

Answering the second part of your question regarding a more precise API, the comment from AnotherUser lead me to this solution that in my scenario overcomes the DateTime.Now precision issue:

static FileTime time;        

public static DateTime Now()
{
    GetSystemTimePreciseAsFileTime(out time);
    var newTime = (ulong)time.dwHighDateTime << (8 * 4) | time.dwLowDateTime;
    var newTimeSigned = Convert.ToInt64(newTime);
    return new DateTime(newTimeSigned).AddYears(1600).ToLocalTime();
}        

public struct FileTime
{
    public uint dwLowDateTime;
    public uint dwHighDateTime;
}

[DllImport("Kernel32.dll")]
public static extern void GetSystemTimePreciseAsFileTime(out FileTime lpSystemTimeAsFileTime);

In my own benchmarks, iterating 1M, it returns on an average 3 ticks vs DateTime.Now 2 ticks.

Why 1600 is out of my jurisdiction, but I use it to get the correct year.

EDIT: This is still an issue on win10. Anybody interested can run this peace of evidence:

void Main()
{
   for (int i = 0; i < 100; i++)
   {        
       Console.WriteLine(Now().ToString("yyyy-MM-dd HH:mm:ss.fffffff"));
       Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fffffff"));
       Console.WriteLine();
   }    
}
// include the code above
noontz
  • 986
  • 11
  • 19
-4

You could use DateTime.Now.Ticks, read the artical on MSDN

"A single tick represents one hundred nanoseconds or one ten-millionth of a second."

Rohan West
  • 9,084
  • 3
  • 35
  • 62
  • Looks promising, I'll check it out. –  Nov 21 '08 at 02:01
  • 7
    Where do you think the ticks are coming from? You will still be getting the delay in the datetime measurement, you just have the inaccurate measurement in ticks. All the ticks property returns is a tick representation of the datetime value. you will still have accuracy issues. – RhysC Nov 25 '09 at 02:36
  • The Problem is that the Ticks suggest a misleading Precision. The DateTime.Now Property, resp. its Ticks are updated only every 10ms as explained above. – Spoc Sep 22 '20 at 17:13