Question

DateTime vs DateTimeOffset in C#: Differences, Usage, and Best Practices

csharp.netdatetimetimezonedatetimeoffset

Question

In C#, what is the difference between DateTime and DateTimeOffset, and when should each one be used?

At the moment, we handle .NET dates in a time-zone-aware way by following a standard approach:

  • When creating a DateTime, we use UTC, such as DateTime.UtcNow
  • When displaying a value, we convert it from UTC to the user's local time

For example:

DateTime createdAt = DateTime.UtcNow;

This approach works well, but DateTimeOffset appears to store both the date/time value and its UTC offset together. I want to understand how that differs from DateTime, what problem it solves, and in which situations DateTimeOffset is the better choice.

Short Answer

By the end of this page, you will understand how DateTime and DateTimeOffset represent time differently in C#, why UTC alone is often not the full story, and how to choose the right type for storage, APIs, logging, and user-facing features.

Concept

DateTime and DateTimeOffset both represent dates and times in .NET, but they model different ideas.

DateTime

A DateTime stores a date and time, plus a Kind value:

  • Utc
  • Local
  • Unspecified

The important limitation is that DateTime does not reliably preserve the original offset from UTC. It may represent a UTC value or a local machine value, but by itself it is often ambiguous.

For example, this value:

DateTime meeting = new DateTime(2026, 3, 15, 9, 0, 0);

What does 9:00 AM mean here?

  • 9:00 AM UTC?
  • 9:00 AM on the server's local time?
  • 9:00 AM in the user's time zone?

If Kind is Unspecified, you cannot know from the value alone.

DateTimeOffset

A DateTimeOffset stores:

  • a date and time
  • the offset from UTC at that moment

Example:

DateTimeOffset appointment = new DateTimeOffset(2026, 3, 15, 9, 0, 0, TimeSpan.FromHours(-5));

This means:

  • local clock time is 2026-03-15 09:00:00
  • offset is -05:00
  • UTC time is 2026-03-15 14:00:00Z

That makes the value much less ambiguous.

Why this matters

In real programs, time-related bugs often happen because developers mix up:

  • a calendar date and clock time
  • a UTC instant
  • a local time in some region
  • a time-zone rule set
  • an offset from UTC

DateTimeOffset is usually better when you want to represent a specific instant in time and keep the original offset that came with it.

Important distinction: offset is not the same as time zone

A DateTimeOffset knows the offset, such as +02:00, but it does not know the actual time zone, such as Europe/Berlin or America/New_York.

That means it cannot answer questions like:

  • "Will this region switch to daylight saving time next month?"
  • "What is the offset for this city on a future date?"

For those questions, you need a time zone, usually represented with TimeZoneInfo.

Practical guideline

In many applications:

  • Use DateTimeOffset for timestamps, events, logs, API payloads, and database values that represent real moments in time.
  • Use DateTime carefully when you specifically need UTC values, legacy compatibility, or APIs that require it.
  • Use TimeZoneInfo when working with regional time-zone rules.

Mental Model

Think of time values like mailing a package.

DateTime

DateTime is like writing:

  • "Delivered at 9:00 AM"

That tells you the clock time, but not necessarily where that clock is.

If someone else reads it later, they may ask:

  • Which time zone?
  • Was that local time or UTC?
  • Which machine created it?

DateTimeOffset

DateTimeOffset is like writing:

  • "Delivered at 9:00 AM, UTC-05:00"

Now the value includes enough context to identify the exact moment.

Time zone

A time zone is like the city and its rules:

  • New York
  • London
  • Tokyo

That matters because cities may change offsets during daylight saving time.

So:

  • DateTime = a clock reading, sometimes ambiguous
  • DateTimeOffset = a clock reading plus its UTC offset
  • TimeZoneInfo = the rulebook for a geographic region
Take Quiz

Syntax and Examples

Basic syntax

DateTime

DateTime utcNow = DateTime.UtcNow;
DateTime localNow = DateTime.Now;
DateTime unspecified = new DateTime(2026, 3, 15, 9, 0, 0);

DateTimeOffset

DateTimeOffset nowWithOffset = DateTimeOffset.Now;
DateTimeOffset utcOffsetNow = DateTimeOffset.UtcNow;
DateTimeOffset custom = new DateTimeOffset(2026, 3, 15, 9, 0, 0, TimeSpan.FromHours(-5));

Example: comparing the two

DateTime dt = DateTime.UtcNow;
DateTimeOffset dto = DateTimeOffset.UtcNow;

Console.WriteLine(dt);
Console.WriteLine(dto);

DateTime.UtcNow gives a UTC date/time value.

DateTimeOffset.UtcNow gives the same instant, but as a DateTimeOffset with offset +00:00.

Example: preserving an incoming offset

Imagine an API sends this timestamp:

string input = "2026-03-15T09:00:00-05:00";
DateTimeOffset parsed = DateTimeOffset.Parse(input);

Console.WriteLine(parsed);        // 03/15/2026 09:00:00 -05:00
Console.WriteLine(parsed.UtcDateTime); // 03/15/2026 14:00:00

This keeps the original offset. That is often useful for logs, integrations, and audit data.

Example: converting for display

DateTimeOffset stored = DateTimeOffset.UtcNow;
TimeZoneInfo userZone = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
DateTimeOffset userTime = TimeZoneInfo.ConvertTime(stored, userZone);

Console.WriteLine(userTime);

This converts the instant into the user's time zone for display.

Example: when DateTime is acceptable

DateTime createdUtc = DateTime.UtcNow;

if (createdUtc.Kind != DateTimeKind.Utc)
{
    throw new InvalidOperationException("Expected UTC time.");
}

If your codebase strictly stores and passes UTC DateTime values only, DateTime can work well. The risk is that other code may accidentally create Local or Unspecified values.

Step by Step Execution

Consider this code:

DateTimeOffset orderPlaced = new DateTimeOffset(2026, 3, 15, 9, 0, 0, TimeSpan.FromHours(-5));
DateTimeOffset utcValue = orderPlaced.ToUniversalTime();

Console.WriteLine(orderPlaced);
Console.WriteLine(utcValue);

Step by step

1. Create the original value

DateTimeOffset orderPlaced = new DateTimeOffset(2026, 3, 15, 9, 0, 0, TimeSpan.FromHours(-5));

This creates a timestamp with:

  • local date/time: 2026-03-15 09:00:00
  • offset: -05:00

This represents a real instant.

2. Convert it to UTC

DateTimeOffset utcValue = orderPlaced.ToUniversalTime();

Because the original offset is -05:00, UTC is 5 hours ahead.

So:

  • original: 2026-03-15 09:00:00 -05:00
  • UTC: 2026-03-15 14:00:00 +00:00

3. Print both values

Console.WriteLine(orderPlaced);
Console.WriteLine(utcValue);

The first line shows the original clock time and offset.

The second line shows the same instant in UTC.

Key idea

The two values look different, but they point to the same moment in time.

That is the main strength of DateTimeOffset: it preserves enough information to convert safely between local representations and UTC.

Compare with ambiguous DateTime

DateTime ambiguous = new DateTime(2026, 3, 15, 9, 0, 0);
Console.WriteLine(ambiguous.Kind);

This usually prints:

Unspecified

Now you have 9:00 AM, but no reliable meaning attached to it.

Real World Use Cases

When DateTimeOffset is a strong choice

API timestamps

If your API receives or returns timestamps like:

2026-03-15T09:00:00-05:00

DateTimeOffset preserves the exact instant and the provided offset.

Logging and auditing

For logs, audit records, and event histories, you usually want:

  • the exact moment something happened
  • a representation that is safe across servers and regions

DateTimeOffset is a good fit.

Database records

When storing creation times, update times, payment times, or webhook timestamps, DateTimeOffset reduces ambiguity.

Distributed systems

If one service runs in UTC, another in Europe, and another in the US, DateTimeOffset helps preserve the real timestamp consistently.

When DateTime is still common

Internal UTC-only systems

If your application enforces a rule such as:

  • all stored times are UTC
  • all values must have Kind == Utc

then DateTime can still be perfectly valid.

Legacy libraries and existing codebases

Some APIs, ORMs, and older code use DateTime extensively. In those cases, consistency and careful validation matter.

Date-only or clock-only concepts

If you are really dealing with a date on a calendar rather than a moment in time, neither DateTime nor DateTimeOffset may be ideal. For example:

  • birthday
  • due date
  • store opening hour

Those are conceptually different from timestamps.

Take Quiz

Real Codebase Usage

In real projects, developers often combine these types with a few common patterns.

1. Store timestamps as UTC or DateTimeOffset

Typical examples:

  • CreatedAt
  • UpdatedAt
  • ProcessedAt
  • LastLoginAt

Many teams prefer DateTimeOffset because it is harder to misinterpret.

public class AuditEntry
{
    public DateTimeOffset CreatedAt { get; set; } = DateTimeOffset.UtcNow;
}

2. Use guard clauses for validation

If your method expects UTC DateTime, validate that immediately.

public void Save(DateTime timestamp)
{
    if (timestamp.Kind != DateTimeKind.Utc)
    {
        throw new ArgumentException("timestamp must be UTC", nameof(timestamp));
    }

    // Save value
}

This avoids hidden bugs later.

3. Convert only at the edges

A common pattern is:

  • store in UTC or DateTimeOffset
  • convert for UI display near the frontend or presentation layer

This keeps business logic predictable.

4. Parse external timestamps as DateTimeOffset

External systems often send ISO 8601 values with offsets. Parse them into DateTimeOffset first.

DateTimeOffset receivedAt = DateTimeOffset.Parse("2026-03-15T09:00:00-05:00");

5. Keep time-zone logic separate

If a feature depends on named time zones, use TimeZoneInfo explicitly rather than assuming an offset is enough.

TimeZoneInfo zone = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
DateTimeOffset localTime = TimeZoneInfo.ConvertTime(DateTimeOffset.UtcNow, zone);

6. Use early returns to reject invalid assumptions

public static DateTimeOffset NormalizeIncoming(DateTimeOffset input)
{
    if (input.Offset == TimeSpan.Zero)
    {
        return input;
    }

    return input.ToUniversalTime();
}

This makes intent clear and avoids complicated nested logic.

Common Mistakes

1. Assuming DateTime always knows the time zone

Broken example:

DateTime dt = new DateTime(2026, 3, 15, 9, 0, 0);
Console.WriteLine(dt.ToUniversalTime());

Problem:

  • This DateTime has Kind == Unspecified
  • Conversion may use assumptions based on the local machine

Avoid it by:

  • using DateTime.UtcNow for UTC values
  • using DateTimeOffset when offset matters

2. Confusing offset with time zone

Broken assumption:

  • -05:00 means New York

Problem:

  • many regions can share the same offset at one moment
  • the same region may use different offsets during daylight saving time

Avoid it by using TimeZoneInfo for real zone rules.

3. Mixing UTC and local values without noticing

Broken example:

DateTime created = DateTime.Now;
DateTime expires = DateTime.UtcNow.AddHours(1);

bool expired = created > expires;

Problem:

  • one value is local
  • one is UTC
  • comparison can be logically wrong

Avoid it by standardizing on one representation.

4. Losing offset information when converting to DateTime

DateTimeOffset dto = DateTimeOffset.Parse("2026-03-15T09:00:00-05:00");
DateTime dt = dto.DateTime;

Problem:

  • dt now contains the clock time without preserving the offset context

Prefer:

DateTime utc = dto.UtcDateTime;

if you need a UTC DateTime.

5. Using local server time in backend systems

Broken example:

var timestamp = DateTime.Now;

Problem:

  • server location may differ across environments
  • containers and cloud servers may not match user expectations

Prefer:

var timestamp = DateTimeOffset.UtcNow;

or DateTime.UtcNow if your system is strictly UTC-only.

6. Thinking DateTimeOffset solves all time-zone problems

It does not store a named time zone. It only stores an offset for that moment.

If you need recurring event logic like "every day at 9 AM in Paris," you also need a real time zone.

Comparisons

ConceptDateTimeDateTimeOffset
Stores date and timeYesYes
Stores UTC offsetNo, not reliably as part of the valueYes
Can represent an exact instant clearlySometimesYes, usually more clearly
Can be ambiguousYesMuch less often
Good for UTC-only workflowsYesYes
Good for external timestamps with offsetsLess idealExcellent
Stores named time zoneNoNo

DateTime.UtcNow vs DateTimeOffset.UtcNow

ExpressionResult
DateTime.UtcNowCurrent UTC time as DateTime
DateTimeOffset.UtcNowCurrent UTC time as DateTimeOffset with +00:00

DateTime vs TimeZoneInfo

TypePurpose
DateTimeStores a date/time value
DateTimeOffsetStores a date/time plus UTC offset
TimeZoneInfoProvides time-zone rules and conversions

Which should you choose?

  • Choose DateTimeOffset for most timestamps that move between systems.
  • Choose DateTime when your codebase is deliberately UTC-only or an API requires it.
  • Choose TimeZoneInfo when the business rule depends on an actual region and daylight saving behavior.
Take Quiz

Cheat Sheet

Quick rules

  • Use DateTimeOffset for timestamps in APIs, logs, events, and databases.
  • Use DateTime.UtcNow or DateTimeOffset.UtcNow for backend-generated current timestamps.
  • Avoid DateTime.Now in server-side code unless local server time is intentionally required.
  • Do not assume an offset is the same as a time zone.
  • Convert for display at the edge of the system.

Common syntax

DateTime utc = DateTime.UtcNow;
DateTimeOffset dtoUtc = DateTimeOffset.UtcNow;
DateTimeOffset dtoLocal = DateTimeOffset.Now;
DateTimeOffset parsed = DateTimeOffset.Parse("2026-03-15T09:00:00-05:00");
DateTimeOffset asUtc = parsed.ToUniversalTime();
DateTime utcFromDto = parsed.UtcDateTime;

Watch out for

  • DateTimeKind.Unspecified
  • mixing local and UTC values
  • dropping offset information accidentally
  • using offset where a real time zone is required

Safe defaults

  • Persist timestamps as DateTimeOffset or UTC values
  • Validate incoming DateTime.Kind if using DateTime
  • Use TimeZoneInfo for user-region conversions

Best mental shortcut

  • DateTime = date/time value
  • DateTimeOffset = date/time value + UTC offset
  • TimeZoneInfo = time-zone rules

FAQ

What is the main difference between DateTime and DateTimeOffset in C#?

DateTime stores a date and time, but it may be ambiguous about its time context. DateTimeOffset stores a date and time together with a UTC offset, which makes the represented instant clearer.

Should I use DateTimeOffset instead of DateTime?

For most timestamps that are saved, transmitted, or compared across systems, yes. DateTimeOffset is often the safer default.

Is DateTimeOffset the same as a time zone?

No. It stores an offset like +01:00, not a named zone like Europe/Paris. It does not contain daylight saving rules.

If I already store everything in UTC, is DateTime enough?

Yes, it can be enough if your codebase consistently uses UTC and validates that assumption. The main risk is accidental use of local or unspecified values.

Why is DateTimeKind.Unspecified dangerous?

Because the value looks valid, but its meaning is unclear. Conversions may apply machine-local assumptions that are not what you intended.

Should I use DateTime.Now on the server?

Usually no. Prefer DateTime.UtcNow or DateTimeOffset.UtcNow unless you explicitly need the server's local time.

Can DateTimeOffset handle daylight saving time correctly?

It can preserve the correct offset for a specific instant, but it does not know future or regional daylight saving rules by itself. For that, use TimeZoneInfo.

What should I use for recurring events like "9 AM every Monday in London"?

You need more than a timestamp. Use a local time concept plus a named time zone with TimeZoneInfo or another time-zone-aware approach.

Take Quiz

Mini Project

Description

Build a small C# console app that records events using DateTimeOffset, stores them in UTC, and displays them in a chosen time zone. This demonstrates why preserving a clear instant in time is safer than relying on ambiguous local DateTime values.

Goal

Create and display timestamped events using DateTimeOffset, then convert them to UTC and to a user's time zone for output.

Requirements

  • Create at least two event records with DateTimeOffset timestamps.
  • Store or normalize the timestamps as UTC.
  • Convert the UTC timestamps to a selected time zone for display.
  • Print the original value, the UTC value, and the converted display value.
  • Use TimeZoneInfo for the display conversion.
Take Quiz

Keep learning

Related questions

View more