CodyTheDev
CodyTheDev

Reputation: 3

C# TimeSpan seconds and milliseconds are out of sync

I am working on updates to packet simulation software that our team maintains and uses to test other applications we work on. Two of the fields that these simulated packets hold are unix time (seconds) and nanoseconds. This is the time elapsed in seconds and nanoseconds since 1, 1, 1970. We are using c# for this particular app and grab the elapsed time using a TimeSpan object as such.

TimeSpan ts = (DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);

packet.SetTimeSeconds(Convert.ToUInt64(ts.Totalseconds));

packet.SetTimeNanoseconds(Convert.ToUInt64(1e6 * ts.Milliseconds));

When the packets are captured at its destination and the contents are displayed it appears that the seconds value is lagging behind the nanoseconds value by a considerable amount. The nanoseconds value will tick up to the threshold, roll over, and start ticking up again but the seconds value takes a few packets to roll over. I have checked to see if somehow the seconds value is updated after being set elsewhere in the code but it isn't. The remaining packet values are set after the snippet above and then the packet is sent to the destination using a socket.

I understand that the time for one tick to take place is 16ms and the update could fall between two packets. This could cause them to be out of sync momentarily. In that case I would expect for them to not match for one packet at most due to the tick rate. In my situation this mismatch happens for 3 or 4 packets before the seconds value is updated when producing data every 100 ms.

It is important to note that due to the work environment I legally can not put full source code online so don't ask. Also I have checked all logic after time is set in the packet to make sure nothing is being overwritten. My main question is if this is just an issue with DateTime.UtcNow or potentially getting accurate elapsed time with TimeSpan. This is not a make or break issue as this is just a simulator, but it would be nice to have accurate simulated time in our test packets.

EDIT:

For added information, the issue is experienced even when I do not utilize my packet structure and send the values to a receiver. I have written the following loop to test TimeSpan/DateTime values without the potential for other logic to get in the way.

for (int i = 0; i < 150; i++)
{

     TimeSpan temp = (DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);

     Console.WriteLine("The seconds elapsed is: " + Convert.ToUInt64(temp.TotalSeconds) + " The nanoseconds portion: " + Convert.ToUInt64(1e6 * temp.Milliseconds);

}

Keep in mind that the folowing loop will print out several instances of the same values because the processor can iterate through the loop several times between clock ticks (each tick takes 16ms). If you run the loop you will see that the nanoseconds value ticks up as expected up to 999000000 then rolls over to start over when a new second is reached. When you see that roll over check the seconds value and you will notice that is has not updated. I would post a printout but my machine with internet connectivity has no IDE.

Solution:

As the accepted answer below points out, I was utilizing the convert.ToUInt64 to cast the TotalSeconds value (double) to a Ulong. This was done to meet our in house built library functions. This cast rounds the seconds value which makes the value appear to be inaccurate. I ended up using the Math.Floor(double) function to remove the fractal portion of the seconds before the cast and everything remained in sync. My updated code is listed below.

TimeSpan ts = (DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);

packet.SetTimeSeconds(Convert.ToUInt64(Math.Floor(ts.Totalseconds)));

packet.SetTimeNanoseconds(Convert.ToUInt64(1e6 * ts.Milliseconds));

Upvotes: 0

Views: 1341

Answers (1)

Vikhram
Vikhram

Reputation: 4394

I don't see any inconsistencies. Below is a TimeSpan object that I got from your code.

TimeSpan.TicksPerMillisecond    : 10,000
TimeSpan.TicksPerDay            : 864,000,000,000

Ticks               : 15511159080437769         1551115908043.7769  milliseconds

Days                : 17952                     1551052800000       milliseconds
Hours               : 17                             61200000       milliseconds
Minutes             : 31                              1860000       milliseconds
Seconds             : 48                                48000       milliseconds
Milliseconds        : 43                                   43       milliseconds
Sum                 :                           1551115908043       milliseconds (NOTE: we don't have microsecond or nanosecond value bearing properties)

TotalDays           : 17952.730417173341        15511159080437769.0 / TimeSpan.TicksPerDay
TotalHours          : 430865.53001216019        15511159080437769.0 / TimeSpan.TicksPerHour
TotalMinutes        : 25851931.800729614        15511159080437769.0 / TimeSpan.TicksPerMinute
TotalSeconds        : 1551115908.0437768        15511159080437769.0 / TimeSpan.TicksPerSecond
TotalMilliseconds   : 1551115908043.7769        15511159080437769.0 / TimeSpan.TicksPerMillisecond

As you can see, all the values are calculated based on the TimeSpan.Ticks level. You can calculate

UPDATE:

Use the below code to get the correct value for just the nano-second portion or total time span in nano seconds

public static class TimeSpanExtension {
    const decimal TicksPerNanosecond = TimeSpan.TicksPerMillisecond / 1000000m;
    public static decimal GetTotalNanoSeconds(this TimeSpan ts) => ts.Ticks / TicksPerNanosecond;
    public static decimal GetMicroAndNanoSeconds(this TimeSpan ts) => ts.Ticks % TimeSpan.TicksPerMillisecond / TicksPerNanosecond;
}

UPDATE 2:

From your code updates in the question, I notice that you are using Convert.ToUInt64 to round the number of seconds, causing you to get confused.

Console.WriteLine("The seconds elapsed is: " + Convert.ToUInt64(temp.TotalSeconds) +
                    " The nanoseconds portion: " + Convert.ToUInt64(1e6 * temp.Milliseconds));

Instead of the above code, you should use the below code to see that there is no inconsistency. You can see the TotalSeconds rolling over along with the Milliseconds, but Convert.ToUInt64(temp.TotalSeconds) will not rollover

// C# 6.0 string interpolation syntax
Console.WriteLine($"{temp.TotalSeconds}, {Convert.ToUInt64(temp.TotalSeconds)}, {temp.Milliseconds,000}");

// similar statement without string interpolation
Console.WriteLine(temp.TotalSeconds + ", " + Convert.ToUInt64(temp.TotalSeconds) + ", " + temp.Milliseconds);

Upvotes: 2

Related Questions