Building a DateTimeService to Generate Unique DateTime Values

Monday, 20 February 2023
dotnet C# DateTimeService
Edit This Page

Introduction

In the 'TimeWarp Architecture', we use a DateTimeService class to timestamp Domain Events, which is essential for maintaining the integrity of our application's data. Generating unique DateTime values is critical for applications that require consistent ordering. The DateTimeService generates unique DateTime values by incrementing the Ticks value until an unused tick is found.

We will also demonstrate how to test this class to ensure that it works as expected.

Warning

This approach only ensures uniqueness within a single machine. If your application needs to generate unique DateTime values across multiple machines, you may need a different solution.

Building the DateTimeService Class

The DateTimeService class will implement the IDateTimeService interface, which defines two methods: UtcNow and NextUtcNow. The UtcNow method simply returns the current UTC time, while the NextUtcNow method generates the next unique DateTime value by incrementing the last value used until an unused tick is found.

Here is the implementation of the DateTimeService class:

namespace TimeWarp.Architecture.Services;

public class DateTimeService : IDateTimeService
{
  // A private field to store the last value used
  private long LastValueUsed = DateTime.UtcNow.Ticks;

  public DateTime UtcNow => DateTime.UtcNow;

  /// <summary>
  /// Get the next unique DateTime closest to now
  /// </summary>
  /// <remarks>
  /// This will move forward in time (barely) until if finds an unused tick
  /// </remarks>
  public DateTime NextUtcNow()
  {
    long result;
    long ticksNow = DateTime.UtcNow.Ticks;

    // Do this loop until result >= ticksNow
    do
    {
      result = Interlocked.Increment(ref LastValueUsed);

      if (result >= ticksNow)
        return new DateTime(ticks: result);

      ticksNow = LastValueUsed;
    } while (Interlocked.CompareExchange(ref LastValueUsed, ticksNow, result) != result);

    return new DateTime(ticks: result);
  }
}

In the NextUtcNow method, we use the Interlocked.Increment method to increment the LastValueUsed field and return the new value. We then check if this value is greater than or equal to the current UTC time, and if so, return a new DateTime value using the result ticks value. Otherwise, we update the ticksNow variable with the value of LastValueUsed and repeat the loop until an unused tick is found.

Testing the DateTimeService Class

To test the NextUtcNow method of the DateTimeService class, we need to ensure that it generates unique DateTime values that are always increasing. We can achieve this by creating multiple threads and having each thread call the NextUtcNow method multiple times. We can then combine the generated DateTime values from all threads into a single array and check that there are no duplicates and that the values are in increasing order.

Here is the implementation of the test:

namespace DateTimeService_;

public class NextUtcNow_Returns
{

  public void No_Duplicates()
  {
    // Arrange
    var dateTimeService = new DateTimeService();

    // Act
    var trigger = new ManualResetEvent(false);
    Task<List<DateTime>>[] tasks =
      Enumerable.Range(1, 10)
      .Select(x => Task.Run(() => GetDates(trigger)))
      .ToArray();

    Thread.Sleep(1000);
    trigger.Set();

    Task.WaitAll(tasks);
    DateTime[] allDates = tasks.SelectMany(x => x.Result).ToArray();
    long ticksDifference = Math.Abs(allDates[^1].Ticks - DateTime.UtcNow.Ticks);

    IGrouping<DateTime, DateTime>[] counts =
      allDates.GroupBy(x => x)
      .Where(x => x.Count() > 1)
      .ToArray();

    // Assert
    counts.Length.Should().Be(0);

    // Local Functions
    List<DateTime> GetDates(ManualResetEvent trigger)
    {
      const int NumberOfDates = 100_000;
      var result = new List<DateTime>(NumberOfDates);
      trigger.WaitOne();
      for (int i = 0; i < NumberOfDates; i++)
      {
        result.Add(dateTimeService.NextUtcNow());
      }
      return result;
    }
  }
}

In this test, we create an instance of the DateTimeService class and use Task.Run to create 10 tasks that call the NextUtcNow method 100,000 times each. We then wait for a second to ensure that all tasks are running, and then allow them to generate DateTime values by calling the Set method of a ManualResetEvent object.

Once the tasks have finished generating DateTime values, we combine all of the values into a single array and check that there are no duplicates using LINQ's GroupBy method. We then use FluentAssertions to assert that the length of the array of IGrouping<DateTime, DateTime> where there is more than one instance of a particular DateTime is equal to zero.

Credit

I want to give a big shoutout to my close friend, Peter Morris. He has been an incredible help to me in both my personal and professional life.

In particular, I want to thank Peter for his contributions to the DateTimeService class featured in this post. He was the "driver" and wrote the vast majority of this while I was the "navigator."

Conclusion

In this post, we have built a DateTimeService class that generates unique DateTime values by incrementing the Ticks value until an unused tick is found. We have also demonstrated how to test this class to ensure that it works as expected, even when called concurrently by multiple threads.

By using the DateTimeService class in your software project, you can be sure that each generated DateTime value is unique and that there are no duplicates. This can be particularly useful in applications that require unique time-stamped values, such as financial transactions, event logging, or messaging.

By tapping into the superpower of pair programming, you and your coding partner can work together to improve the quality of your code and collaboration. Consider harnessing the power of pair programming to supercharge your productivity and code quality in your next project!