If you have done backend work in C#, you have probably seen the DateTime versus DateTimeOffset debate. Teams split into camps, PR comments get long, and somehow the "right" answer depends on who you ask.

But for Unix timestamps, the answer is clear: use DateTimeOffset. It carries the offset explicitly, which removes the ambiguity that makes DateTime painful.

What a Unix Timestamp Looks Like in C#

A Unix timestamp is just a number:

long unixSeconds = 1719820800;
long unixMillis = 1719820800000;

The question is what type you convert it into.

DateTimeOffset Is the Right Default

.NET has had built-in Unix conversion methods on DateTimeOffset since .NET 4.6 and .NET Core:

long seconds = 1719820800;

DateTimeOffset dto = DateTimeOffset.FromUnixTimeSeconds(seconds);
Console.WriteLine(dto); // 7/1/2024 12:00:00 AM +00:00

The +00:00 is the important part. It tells you exactly what offset the value represents. No guessing, no hidden assumptions.

For milliseconds:

long millis = 1719820800000;
DateTimeOffset dto = DateTimeOffset.FromUnixTimeMilliseconds(millis);

Going the other direction is just as clean:

DateTimeOffset now = DateTimeOffset.UtcNow;

long seconds = now.ToUnixTimeSeconds();
long millis = now.ToUnixTimeMilliseconds();

These methods exist specifically so you do not have to write (long)(now - epoch).TotalSeconds anymore.

The DateTime Footgun

Before DateTimeOffset.FromUnixTimeSeconds() existed, the standard approach was:

static readonly DateTime UnixEpoch =
    new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);

DateTime dateTime = UnixEpoch.AddSeconds(1719820800);

This works, but it depends on DateTimeKind. If your epoch constant is Utc, the result is UTC. If someone changes it to Unspecified or Local, your dates shift by the local offset. And DateTime does not carry enough information in its type to prevent that.

DateTime.SpecifyKind() can label the value after the fact, but that is like putting a sticker on a box instead of packing it correctly from the start.

Displaying in Local Time

When you need to show a value to a user, convert the DateTimeOffset to the target timezone:

DateTimeOffset utcTime = DateTimeOffset.FromUnixTimeSeconds(1719820800);

TimeZoneInfo tz = TimeZoneInfo.FindSystemTimeZoneById("Asia/Tokyo");
DateTimeOffset localTime = TimeZoneInfo.ConvertTime(utcTime, tz);

Console.WriteLine(localTime.ToString("yyyy-MM-dd HH:mm:ss zzz"));
// 2024-07-01 09:00:00 +09:00

The offset is explicit in the output. With DateTime, you would have to check DateTime.Kind to know what you are looking at — and nobody remembers to do that.

Parsing a JSON API Response

Here is a pattern I use when consuming an API that returns Unix seconds:

public class ApiResponse
{
    [JsonPropertyName("created_at")]
    public long CreatedAt { get; set; }

    public DateTimeOffset CreatedAtDate =>
        DateTimeOffset.FromUnixTimeSeconds(CreatedAt);
}

var response = JsonSerializer.Deserialize<ApiResponse>(json);
Console.WriteLine(response.CreatedAtDate.ToString("yyyy-MM-dd HH:mm:ss"));
// 2024-07-01 00:00:00

If the API sends milliseconds, swap in FromUnixTimeMilliseconds. The consuming code stays the same.

Common Pitfalls

Messing Up the Kind Property

DateTime utcNow = DateTime.UtcNow;      // Kind = Utc
DateTime localNow = DateTime.Now;        // Kind = Local
DateTime unknown = new DateTime(2024, 7, 1); // Kind = Unspecified

Pass an Unspecified DateTime through a JSON serializer and the receiver has no idea what timezone you meant. That is exactly the problem DateTimeOffset solves.

Confusing Seconds and Milliseconds

long fromApi = 1719820800000; // Actually milliseconds

// Wrong
DateTimeOffset dto = DateTimeOffset.FromUnixTimeSeconds(fromApi);
// Result: year 56468

// Right
DateTimeOffset dto = DateTimeOffset.FromUnixTimeMilliseconds(fromApi);

Count the digits. 10 means seconds, 13 means milliseconds. This check saves you in every language.

JSON Serializer Behavior

System.Text.Json serializes DateTimeOffset as ISO 8601 by default. If the other end expects a raw Unix timestamp, add a custom converter:

public class UnixTimestampConverter : JsonConverter<DateTimeOffset>
{
    public override DateTimeOffset Read(
        ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        return DateTimeOffset.FromUnixTimeSeconds(reader.GetInt64());
    }

    public override void Write(
        Utf8JsonWriter writer, DateTimeOffset value, JsonSerializerOptions options)
    {
        writer.WriteNumberValue(value.ToUnixTimeSeconds());
    }
}

Database Mappings

In Entity Framework, DateTimeOffset maps to timestamp with time zone in PostgreSQL and datetimeoffset in SQL Server. DateTime maps to types that may or may not preserve offset information. If timezone matters — and it usually does — DateTimeOffset is safer at every layer.

When DateTime Still Makes Sense

DateTime is fine for values that do not represent an absolute moment on the timeline. A birthday, a recurring meeting time, or a holiday do not need a UTC anchor.

DateTime birthday = new DateTime(1990, 5, 15);

For those cases, skip the Unix timestamp entirely and store the components directly.

Conclusion

For Unix timestamps in C#, the pattern is short:

// In
DateTimeOffset dto = DateTimeOffset.FromUnixTimeSeconds(seconds);

// Out
long seconds = dto.ToUnixTimeSeconds();

Use DateTimeOffset for transport, storage, and business logic. Keep DateTime for the few cases where timezone truly does not matter. Your future self and your code reviewers will thank you.

A Quick Unit Test Pattern

If you are working with timestamps in a .NET project, add a unit test that verifies your conversion round-trips correctly. It is cheap insurance:

[Fact]
public void UnixTimestamp_RoundTrips()
{
    long original = 1719820800;
    var dto = DateTimeOffset.FromUnixTimeSeconds(original);
    long result = dto.ToUnixTimeSeconds();

    Assert.Equal(original, result);
}

I add a similar test whenever a service consumes or produces Unix timestamps. It catches seconds-vs-milliseconds mistakes early, before they reach production.

For quick timestamp checks without opening a console app, the FastUnix Timestamp Converter is always handy.