Updated on

DateTime.Now returns the current time as our machine’s clock reads it. DateTime.UtcNow returns the same instant expressed in UTC. On a machine two hours ahead of UTC the two values differ by two hours, and both describe the same moment.

The short recommendation: store UtcNow, display Now. Everything else in this article is the reasoning behind that sentence, and the traps that show up when a value crosses a machine, a database, or a daylight saving boundary.

To download the source code for this article, you can visit our GitHub repository.

Let’s start.

What Is the Difference Between DateTime.Now and DateTime.UtcNow?

DateTime.Now returns the current date and time of the machine running the code, expressed in that machine’s local time. DateTime.UtcNow returns the same instant expressed in Coordinated Universal Time. On a machine two hours ahead of UTC, the two values are two hours apart and both are correct.

The difference that matters in code is not the number, it is the flag that travels with it. Now returns a value whose Kind is Local; UtcNow returns one whose Kind is Utc. Every later conversion reads that flag to decide whether to shift the value or leave it alone.

Now is also the more expensive of the two. It reads the same underlying UTC clock and then applies the local time zone’s offset, including whichever daylight saving rule was in force at that instant, so it does strictly more work than UtcNow.

The practical consequence: UTC is the safe default for anything stored, compared, or sent across a boundary.

UTC is a universal format to represent date and time as an alternative to local time. Also known as the GMT+00 timezone.

Let’s see an example to better understand the difference:

var now = DateTime.Now;
var utcNow = DateTime.UtcNow;

Console.WriteLine($"Local Now: {now}");
Console.WriteLine($"UTC Now: {utcNow}");

Now, let’s run the app:

Local Now: 8/30/2026 11:52:52 AM
UTC Now: 8/30/2026 9:52:52 AM

The first message on-screen varies by our local time. Each system’s local time depends on the timezone assigned. In most common cases we leave it as the default, a value that fits the geographical area where we reside (in our case UTC+2), but we may also set it to a custom value if needed. Both values here are printed with the default ToString(); we cover formatting the value once we have it separately, along with the arithmetic available on DateTime values.

The second message is in the UTC format. It will present the current date and time in UTC, or in the GMT+00 timezone. We can always consider UTC as the base for calculating all the other time zones, like a timezone-neutral format:

One instant shown three ways: UTC 09:52:52 with Kind Utc, local 11:52:52 with Kind Local, and 11:52:52 with Kind Unspecified and no offset.

Here we can see that the local time of the computer is 2 hours ahead of UTC. The first two values are the same moment written two ways. The third is the same digits with the flag missing, which is the case most code actually meets.

Which One Should We Use, DateTime.Now or DateTime.UtcNow?

Use DateTime.UtcNow for every value the application keeps: database columns, log timestamps, audit trails, cache expiry, token lifetimes, and anything that crosses a process boundary. UTC has no offset to lose and no daylight saving transitions, so two values recorded an hour apart really are an hour apart.

Use DateTime.Now only at the edge, where a person reads the result. Convert from the stored UTC value at render time, using the time zone that belongs to the reader rather than the one that happens to belong to the server.

The rule survives contact with real deployments because machines move. Code that stores DateTime.Now produces one history on a developer’s laptop and a different one on a server configured for UTC, from the same source and with no error anywhere.

Is this material useful to you? Consider subscribing and get ASP.NET Core Web API Best Practices eBook for FREE!

When the offset genuinely has to be preserved alongside the instant rather than discarded, DateTimeOffset is the better type for the job.

For the cases where the offset itself is data rather than noise, we cover DateTimeOffset and how it compares with DateTime separately.

SituationWhat to callWhy
Writing a value to a databaseDateTime.UtcNowNo offset to lose, and no gap or repeat at a DST transition
Stamping a log or audit entryDateTime.UtcNowEntries stay orderable across servers in different zones
Returning a time in an API responseDateTime.UtcNowThe consumer's time zone is not the server's to guess
Comparing two stored instantsDateTime.UtcNow valuesTwo Local values captured in different zones are not comparable
Displaying a time to a personDateTime.Now, or convert the stored UTC valueThe one place local time is the correct answer
Measuring how long something tookNeither, use StopwatchThe wall clock can jump; an elapsed-time clock cannot
Keeping the offset alongside the instantDateTimeOffsetRecords the moment and the offset it was captured at
Getting the time inside testable codeTimeProvider.GetUtcNow()A static property cannot be substituted in a test

Two of those rows have articles of their own: Stopwatch, for measuring elapsed time, and DateOnly and TimeOnly, when only one half of the value matters.

How Do We Tell Whether a DateTime Is UTC or Local?

Read the Kind property. DateTime.Kind returns a DateTimeKind value that is Local, Utc, or Unspecified, and it is the only thing inside the struct recording which of the three a given value was meant to be.

Unspecified is both the common case and the dangerous one. It is what a DateTime carries after being parsed from a string with no zone in it, read back from a database column that stores no offset, or constructed with new DateTime(2022, 1, 9). The value looks complete and the flag says nothing at all.

The flag is also not part of the value. Two DateTime values with identical ticks and different kinds compare as equal, and most serializers either drop the flag on the way out or invent one on the way back in.

So Kind answers the question reliably only for a value that never left the process. Anywhere else, the answer has to come from a convention the code establishes and documents.

The parsing case is the one we meet most often, so it is worth knowing exactly what parsing a string into a DateTime gives us back, which is where Unspecified values come from.

When developing applications, we could receive some DateTime values from external resources we can’t control. We might not know the format used before the transfer.

Let’s say we receive a value as 8/30/2026 9:52:52 AM. Without knowing much about the sender, this value could be anything – it could be coming from a server in UTC+02, from a UTC-03, or any other time zone as well. In this case, the helpful tool we have is DateTime.Kind property.

The DateTime.Kind property indicates if a DateTime value is represented as Local, UTC, or none. This property returns a value of the DateTimeKind enum from C#, and its possible values are Local, Utc, or Unspecified.

Let’s see an example of how to check a date’s format:

var now = DateTime.Now;
var utcNow = DateTime.UtcNow;

Console.WriteLine(now.Kind);
Console.WriteLine(utcNow.Kind);

This should display the Local, then the Utc kind.

How Do We Convert Between Local Time and UTC?

ToUniversalTime() converts a local value to UTC, and ToLocalTime() converts a UTC value back to local. Both return a new DateTime rather than modifying the original, both set the Kind of the result, and both apply the daylight saving rule that was in force at the instant being converted.

Each method checks Kind first and does nothing when the value already matches. Calling ToLocalTime() on a value that is already Local hands it straight back, which is why calling either method twice is harmless.

Unspecified is where this turns sharp, because the two methods disagree about it deliberately. Microsoft’s documentation states that ToLocalTime() treats an unflagged value as if it were UTC, while ToUniversalTime() treats the same value as if it were local. Hand one unflagged DateTime to both and the results move in opposite directions, with no warning and no exception.

var now = DateTime.Now;
var utcNow = DateTime.UtcNow;

if (utcNow.Kind == DateTimeKind.Utc)
{
    var oldKind = utcNow.Kind;
    var utcToLocal = utcNow.ToLocalTime();
    var newKind = utcToLocal.Kind;

    Console.WriteLine($"Converted {utcNow} from {oldKind} to {newKind}: {utcToLocal}");
}
if (now.Kind == DateTimeKind.Local)
{
    var oldKind = now.Kind;
    var localToUtc = now.ToUniversalTime();
    var newKind = localToUtc.Kind;

    Console.WriteLine($"Converted {now} from {oldKind} to {newKind}: {localToUtc}");
}

The first if block will convert the UTC value to the local format and show a corresponding message on how the conversion was made.

Is this material useful to you? Consider subscribing and get ASP.NET Core Web API Best Practices eBook for FREE!

Reversely, the second if block will convert the local value to a universal one, and the message will give more details.

The if guards in this example are what keep it correct. Without them, an Unspecified value would be converted by both methods, in opposite directions, and the table below spells out exactly what each method does for each Kind.

Kind of the source valueToLocalTime() doesToUniversalTime() does
UtcConverts to local timeNothing, returns the value unchanged
LocalNothing, returns the value unchangedConverts to UTC
UnspecifiedAssumes the value is UTC, then convertsAssumes the value is local, then converts

Either method always sets the Kind of its result: ToLocalTime() returns Local and ToUniversalTime() returns Utc, so calling either one twice is safe.

How Do We Get the Current Time in Testable Code?

DateTime.UtcNow is a static property reading the machine clock, so any method calling it directly cannot be tested at a chosen moment. Every test that needs “an hour from now” ends up either sleeping or comparing against a tolerance.

.NET 8 added TimeProvider for exactly this. It is an abstract class with a System instance that reads the real clock and a GetUtcNow() method returning a DateTimeOffset. Code takes a TimeProvider as a constructor parameter instead of calling DateTime.UtcNow, production passes TimeProvider.System, and a test passes a fake set to whatever instant the test needs.

TimeProvider also abstracts timers and delays, which matters for anything that polls, retries, or expires on a schedule.

On a target older than .NET 8, we do not have to write our own abstraction: the Microsoft.Bcl.TimeProvider package supplies the same type down to .NET Framework 4.6.2 and .NET Standard 2.0, so the pattern is identical everywhere.

We cover this in depth in Testing Time-Dependent Code With TimeProvider in .NET, including the fake provider and the timer support.

For the pre-TimeProvider approach and the shape of a hand-written clock abstraction, see How to Overwrite DateTime.Now During Testing in .NET.

Conclusion

In this article, we’ve learned how to use universal and local DateTime formats, what is the difference between them, and also how to check if a DateTime value was created in the universal format or a local format.

Tested with .NET 10.0.10.