C#
DateTime
precision
.NET
programming

C DateTime.Now precision

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

DateTime.Now in C# has a resolution of approximately 10-15 milliseconds on most Windows systems, despite the DateTime struct storing values with 100-nanosecond precision (ticks). This means two consecutive calls to DateTime.Now can return the same value even if real time has passed. For high-precision timing, use Stopwatch or DateTime.UtcNow (which avoids timezone conversion overhead).

DateTime Resolution vs Precision

The DateTime struct can represent time down to 100-nanosecond intervals (ticks), but DateTime.Now does not update that frequently:

csharp
1// These may all print the same time
2for (int i = 0; i < 5; i++)
3{
4    Console.WriteLine(DateTime.Now.ToString("HH:mm:ss.fffffff"));
5}
6// Output:
7// 14:30:25.1234560
8// 14:30:25.1234560
9// 14:30:25.1234560
10// 14:30:25.1234560
11// 14:30:25.1234560

The system clock updates at intervals determined by the OS timer resolution — typically 15.625ms on Windows (64 Hz).

Key Terminology

TermDefinition
ResolutionHow often the clock updates (~15ms for DateTime.Now)
PrecisionSmallest representable unit (100ns / 1 tick for DateTime)
AccuracyHow close the value is to the true time (depends on NTP sync)

DateTime.Now has high precision (100ns ticks) but low resolution (15ms updates). It is like a ruler marked in millimeters but only read with a magnifying glass every centimeter.

Demonstrating the Resolution Limit

csharp
1var timestamps = new List<DateTime>();
2
3for (int i = 0; i < 10000; i++)
4    timestamps.Add(DateTime.Now);
5
6var distinct = timestamps.Distinct().Count();
7Console.WriteLine($"10000 calls, {distinct} unique values");
8// Typical output: 10000 calls, ~5-20 unique values

Only a handful of distinct values appear because the clock does not update between calls.

DateTime.Now vs DateTime.UtcNow

DateTime.Now converts from UTC to local time on every call, adding overhead:

csharp
1var sw = Stopwatch.StartNew();
2for (int i = 0; i < 1_000_000; i++)
3    _ = DateTime.Now;
4sw.Stop();
5Console.WriteLine($"DateTime.Now: {sw.ElapsedMilliseconds}ms");
6
7sw.Restart();
8for (int i = 0; i < 1_000_000; i++)
9    _ = DateTime.UtcNow;
10sw.Stop();
11Console.WriteLine($"DateTime.UtcNow: {sw.ElapsedMilliseconds}ms");
12
13// DateTime.Now:    ~120ms
14// DateTime.UtcNow: ~30ms  (3-4x faster)

DateTime.UtcNow is faster because it skips timezone conversion. Both have the same ~15ms resolution.

High-Precision Timing with Stopwatch

For measuring elapsed time with microsecond or nanosecond precision, use Stopwatch:

csharp
1using System.Diagnostics;
2
3var sw = Stopwatch.StartNew();
4
5// Operation to measure
6Thread.Sleep(1);
7
8sw.Stop();
9
10Console.WriteLine($"Elapsed: {sw.Elapsed.TotalMilliseconds:F4}ms");
11Console.WriteLine($"Ticks: {sw.ElapsedTicks}");
12Console.WriteLine($"Frequency: {Stopwatch.Frequency} ticks/sec");
13Console.WriteLine($"High resolution: {Stopwatch.IsHighResolution}");
14
15// Elapsed: 1.2345ms
16// Frequency: 10000000 ticks/sec (on most modern systems)
17// High resolution: True

Stopwatch uses the hardware performance counter (QueryPerformanceCounter on Windows), which has nanosecond resolution.

Increasing System Timer Resolution

On Windows, you can increase the timer resolution to ~1ms using timeBeginPeriod:

csharp
1using System.Runtime.InteropServices;
2
3[DllImport("winmm.dll")]
4static extern uint timeBeginPeriod(uint period);
5
6[DllImport("winmm.dll")]
7static extern uint timeEndPeriod(uint period);
8
9// Increase resolution to 1ms
10timeBeginPeriod(1);
11
12// Now DateTime.Now updates approximately every 1ms
13var timestamps = new List<DateTime>();
14for (int i = 0; i < 10000; i++)
15    timestamps.Add(DateTime.Now);
16
17var distinct = timestamps.Distinct().Count();
18Console.WriteLine($"Distinct: {distinct}");  // Many more unique values
19
20// Restore default resolution
21timeEndPeriod(1);

Warning: Increasing timer resolution increases power consumption system-wide. Always restore the default when done.

.NET 8+ Improvements

Starting with .NET 8, TimeProvider offers a testable abstraction:

csharp
1// Production
2var provider = TimeProvider.System;
3DateTimeOffset now = provider.GetUtcNow();
4long timestamp = provider.GetTimestamp();
5
6// Testing with a fake clock
7var fakeProvider = new FakeTimeProvider(new DateTimeOffset(2024, 1, 1, 0, 0, 0, TimeSpan.Zero));
8DateTimeOffset fakeNow = fakeProvider.GetUtcNow();
9fakeProvider.Advance(TimeSpan.FromHours(1));  // Move time forward

Generating Unique Timestamps

If you need monotonically increasing timestamps (e.g., for ordering events):

csharp
1class UniqueTimestamp
2{
3    private long _lastTicks;
4
5    public DateTime GetUniqueNow()
6    {
7        long ticks;
8        do
9        {
10            ticks = DateTime.UtcNow.Ticks;
11            long last = Interlocked.Read(ref _lastTicks);
12            if (ticks <= last)
13                ticks = last + 1;
14        } while (Interlocked.CompareExchange(ref _lastTicks, ticks, Interlocked.Read(ref _lastTicks)) != Interlocked.Read(ref _lastTicks));
15
16        return new DateTime(ticks, DateTimeKind.Utc);
17    }
18}

Or simply use a counter suffix or Guid.NewGuid() for uniqueness.

Common Pitfalls

  • Using DateTime.Now for performance measurement: DateTime.Now has 15ms resolution, making it useless for sub-millisecond benchmarks. Use Stopwatch instead.
  • Assuming unique timestamps: Two events within the same 15ms window get the same DateTime.Now value. Do not use timestamps as unique identifiers.
  • DateTime.Now in tight loops: DateTime.Now performs a timezone conversion on every call. In hot loops, use DateTime.UtcNow and convert to local time once at display time.
  • Cross-platform differences: Linux and macOS have higher timer resolution (~1ms) than Windows (~15ms). Code that works on Linux may behave differently on Windows.
  • Thread.Sleep precision: Thread.Sleep(1) actually sleeps for ~15ms on Windows due to the same timer resolution. Use SpinWait or Stopwatch for sub-millisecond delays.

Summary

  • DateTime.Now has ~15ms resolution on Windows despite storing 100ns ticks
  • Use Stopwatch for high-precision elapsed time measurement
  • Use DateTime.UtcNow instead of DateTime.Now for better performance (no timezone conversion)
  • Do not rely on DateTime.Now for unique timestamps or sub-millisecond accuracy
  • On .NET 8+, use TimeProvider for testable time abstractions

Course illustration
Course illustration

All Rights Reserved.