35

I'm using timestamps to temporally order concurrent changes in my program, and require that each timestamp of a change be unique. However, I've discovered that simply calling DateTime.Now is insufficient, as it will often return the same value if called in quick succession.

I have some thoughts, but nothing strikes me as the "best" solution to this. Is there a method I can write that will guarantee each successive call produces a unique DateTime?

Should I perhaps be using a different type for this, maybe a long int? DateTime has the obvious advantage of being easily interpretable as a real time, unlike, say, an incremental counter.

Update: Here's what I ended up coding as a simple compromise solution that still allows me to use DateTime as my temporal key, while ensuring uniqueness each time the method is called:

private static long _lastTime; // records the 64-bit tick value of the last time
private static object _timeLock = new object();

internal static DateTime GetCurrentTime() {
    lock ( _timeLock ) { // prevent concurrent access to ensure uniqueness
        DateTime result = DateTime.UtcNow;
        if ( result.Ticks <= _lastTime )
            result = new DateTime( _lastTime + 1 );
        _lastTime = result.Ticks;
        return result;
    }
}

Because each tick value is only one 10-millionth of a second, this method only introduces noticeable clock skew when called on the order of 10 million times per second (which, by the way, it is efficient enough to execute at), meaning it's perfectly acceptable for my purposes.

Here is some test code:

DateTime start = DateTime.UtcNow;
DateTime prev = Kernel.GetCurrentTime();
Debug.WriteLine( "Start time : " + start.TimeOfDay );
Debug.WriteLine( "Start value: " + prev.TimeOfDay );
for ( int i = 0; i < 10000000; i++ ) {
    var now = Kernel.GetCurrentTime();
    Debug.Assert( now > prev ); // no failures here!
    prev = now;
}
DateTime end = DateTime.UtcNow;
Debug.WriteLine( "End time:    " + end.TimeOfDay );
Debug.WriteLine( "End value:   " + prev.TimeOfDay );
Debug.WriteLine( "Skew:        " + ( prev - end ) );
Debug.WriteLine( "GetCurrentTime test completed in: " + ( end - start ) );

...and the results:

Start time:  15:44:07.3405024
Start value: 15:44:07.3405024
End time:    15:44:07.8355307
End value:   15:44:08.3417124
Skew:        00:00:00.5061817
GetCurrentTime test completed in: 00:00:00.4950283

So in other words, in half a second it generated 10 million unique timestamps, and the final result was only pushed ahead by half a second. In real-world applications the skew would be unnoticeable.

devios1
  • 36,899
  • 45
  • 162
  • 260
  • Is the application multi-threaded? Meaning can two different threads ask for an ID at the same time? – Chris Shain Apr 10 '11 at 00:50
  • Is there a reason you cannot use Guids? – juharr Apr 10 '11 at 01:40
  • 1
    I am using guids to represent unique identities. What I need here is temporal ordering... i.e. being able to look at two changes and tell which came first. Guids can't do that. Ideally I'd like to use some form of human-interpretable form of date, but I suppose an incrementing number would work in a pinch, perhaps combined with a DateTime for readability. – devios1 Apr 10 '11 at 01:45
  • 1
    Specify DateTimeKind.Utc when you call `new DateTime(_lastTime + 1, DateTimeKind.Utc);` – Lee Grissom Apr 04 '13 at 18:33

6 Answers6

78

One way to get a strictly ascending sequence of timestamps with no duplicates is the following code.

Compared to the other answers here this one has the following benefits:

  1. The values track closely with actual real-time values (except in extreme circumstances with very high request rates when they would get slightly ahead of real-time).
  2. It's lock free and should perform better that the solutions using lock statements.
  3. It guarantees ascending order (simply appending a looping a counter does not).

public class HiResDateTime
{
   private static long lastTimeStamp = DateTime.UtcNow.Ticks;
   public static long UtcNowTicks
   {
       get
       {
           long original, newValue;
           do
           {
               original = lastTimeStamp;
               long now = DateTime.UtcNow.Ticks;
               newValue = Math.Max(now, original + 1);
           } while (Interlocked.CompareExchange
                        (ref lastTimeStamp, newValue, original) != original);

           return newValue;
       }
   }
}

Also note the comment below that original = Interlocked.Read(ref lastTimestamp); should be used since 64-bit read operations are not atomic on 32-bit systems.

Ian Mercer
  • 38,490
  • 8
  • 97
  • 133
  • 3
    +1. I ran some unit tests against this code. It indeed works, and performs marginally faster than any other solution. The only downside is that it's not intuitive to the average reader. I'd expend more time explaining how it works to others than I'd prefer vs a simple lock (which is plenty fast). [Complexity/Performance tradeoff]. But again, I'd vote your solution up higher if I could. – Lee Grissom Apr 04 '13 at 18:58
  • I found it easy to understand, and well written, except for the variable names, which could be English words. Thank you very much! – Frank Hileman Apr 18 '13 at 22:39
  • @FrankHileman, updated variable names per your suggestion, thanks! – Ian Mercer Apr 18 '13 at 23:39
  • 1
    brilliant solution – AaronHS Apr 20 '16 at 23:55
  • 3
    Keep in mind that the system time can jump around a bit, i.e. when it gets an update from the time server. So, if the clock jumps backwards you will get a value that is different than the last value, but then when the clock catches up you might snag the same value you did before again and your algorithm won't know that. – Mike Marynowski Jul 08 '16 at 22:20
  • 1
    @MikeMarynowski Yes, but with the static `lastTimeStamp` it should always advance by at least 1 per call. So this would only apply on an app restart and only if the jump was larger than the time to restart the app. On a jump back it will continue to hand out incremental +1 values until the clock catches up (and it's unlikely you can request them fast enough that this wouldn't quickly get back to being close to actual clock time). – Ian Mercer Jul 09 '16 at 01:57
  • Ah sorry, you're totally correct...I apparently missed the math.max part when I was reading it over. Upvoted :) – Mike Marynowski Jul 09 '16 at 07:12
  • What happens when this code goes behind the load balancer? In theory this could generate two keys with same tick value. – Gurpreet Mar 21 '17 at 22:40
  • @Gurpreet This is *clearly* a single-server solution. If want a multi-server solution, create a single time source, use a database, or use some other technology that can guarantee unique timestamps within a cluster. – Ian Mercer Mar 22 '17 at 03:45
  • @Gurpreet you can also include the machine name(or anything else unique per process/machine) to the ticks and generate something unique across a cluster. – Mantzas May 31 '17 at 12:32
  • It should be `original = Interlocked.Read(ref lastTimestamp);` since 64-bit read operations are not atomic on 32-bit systems. – Anton Chaplygin Nov 01 '22 at 07:44
9

Er, the answer to your question is that "you can't," since if two operations occur at the same time (which they will in multi-core processors), they will have the same timestamp, no matter what precision you manage to gather.

That said, it sounds like what you want is some kind of auto-incrementing thread-safe counter. To implement this (presumably as a global service, perhaps in a static class), you would use the Interlocked.Increment method, and if you decided you needed more than int.MaxValue possible versions, also Interlocked.Read.

Domenic
  • 110,262
  • 41
  • 219
  • 271
7

DateTime.Now is only updated every 10-15ms.

Not a dupe per se, but this thread has some ideas on reducing duplicates/providing better timing resolution:

How to get timestamp of tick precision in .NET / C#?

That being said: timestamps are horrible keys for information; if things happen that fast you may want an index/counter that keeps the discrete order of items as they occur. There is no ambiguity there.

SharpC
  • 6,974
  • 4
  • 45
  • 40
Joe
  • 41,484
  • 20
  • 104
  • 125
4

I find that the most foolproof way is to combine a timestamp and an atomic counter. You already know the problem with the poor resolution of a timestamp. Using an atomic counter by itself also has the simple problem of requiring its state be stored if you are going to stop and start the application (otherwise the counter starts back at 0, causing duplicates).

If you were just going for a unique id, it would be as simple as concatenating the timestamp and counter value with a delimiter between. But because you want the values to always be in order, that will not suffice. Basically all you need to do is use the atomic counter value to add addition fixed width precision to your timestamp. I am a Java developer so I will not be able to provide C# sample code just yet, but the problem is the same in both domains. So just follow these general steps:

  1. You will need a method to provide you with counter values cycling from 0-99999. 100000 is the maximum number of values possible when concatenating a millisecond precision timestamp with a fixed width value in a 64 bit long. So you are basically assuming that you will never need more than 100000 ids within a single timestamp resolution (15ms or so). A static method, using the Interlocked class to provide atomic incrementing and resetting to 0 is the ideal way.
  2. Now to generate your id you simply concatenate your timestamp with your counter value padded to 5 characters. So if your timestamp was 13023991070123 and your counter was at 234 the id would be 1302399107012300234.

This strategy will work as long as you are not needing ids faster than 6666 per ms (assuming 15ms is your most granular resolution) and will always work without having to save any state across restarts of your application.

Justin Waugh
  • 3,975
  • 1
  • 21
  • 14
  • This does feel like the best compromise. I was thinking about something like this as a possible solution; thanks for going into detail. I think 100,000 changes per 15 ms is quite an acceptable limit. :) – devios1 Apr 10 '11 at 02:37
  • You could also represent the value as a string, rather than a 64-bit int, and simply concatenate an arbitrary-length counter onto the end of the string (perhaps with a delimiter like '.'), thus getting around the 6666 per ms "limitation". One could then simply cut this off the end to interpret the initial part as a date again for readability. – devios1 Apr 10 '11 at 16:44
  • Yes, but then you have to worry about ordering. `"123456789.12345" < "123456789.234"` if you compare naturally. Both parts have to be fixed width. Though you certainly could increase the width of the second part. I was just counting on the fact that the millis value for time won't increase in digits for a long time. In the year 2286 people will be furious that you did it. – Justin Waugh Apr 10 '11 at 16:48
  • Sorry I don't follow. You could just split the string on the '.' character and compare the two sections separately, or alternatively pad the second part with zeroes. What does this have to do with the year 2286? – devios1 Apr 10 '11 at 19:24
  • Sorry if that was unclear. Splitting the string and doing 2 comparisons seems irritating because then you can't take advantage of the natural order. I was just saying you would want to pad the second part with zeroes. And then the comment about the year 2286 was about the way I suggested doing it. I required that the number of digits in the timestamp not change, but in 2286 it will. – Justin Waugh Apr 11 '11 at 13:53
  • Alternately, you can append a Guid and it will do the same thing without having to worry about keeping a counter. It will be a great deal longer though. Maybe you could make an object that has both properties, and use the timestamp to sort. EDIT: Never Mind realized you want to ensure the ordering. You could still make the ID a separate property though, so you wouldn't need to compare by it unless the timestamps are equal. – Seth Micalizzi Aug 31 '12 at 13:32
  • This doesn't appear to guarantee ordering: If you request two values within the same millisecond window and the counter wraps over the end wouldn't they be in the wrong order? 123456789.99999 > 123456789.00001 – Ian Mercer Apr 04 '13 at 20:09
1

It can't be guaranteed to be unique, but is perhaps using ticks is granular enough?

A single tick represents one hundred nanoseconds or one ten-millionth of a second. There are 10,000 ticks in a millisecond.

Giovanni Galbo
  • 12,963
  • 13
  • 59
  • 78
  • 3
    Unfortunately it appears DateTime.Now is only updated every 15 ms roughly, rendering the Ticks property pointless in this scenario. – devios1 Apr 10 '11 at 01:00
  • 1
    There is an API technique involving GetTickCount() to get what you want. – Joshua Apr 10 '11 at 01:24
0

Not sure what you're trying to do entirely but maybe look into using Queues to handle sequentially process records.

mservidio
  • 12,817
  • 9
  • 58
  • 84