csharp-manejo-fechas-horas

Handling Dates and Times in C# (DateTime, TimeSpan, and More)

  • 5 min

In C#, we have several tools for modeling time. At first, everyone uses DateTime, but as we progress we discover the world is more complex (time zones, durations, dates without time…).

So C# provides a pleeethora of Classes and methods aimed at managing entities related to time. In summary:

SituationRecommended TypeWhy
General Use / Web AppsDateTimeOffsetUnambiguous, includes time zone.
Legacy / In-memory onlyDateTime (UTC)Compatible with old libraries.
Measuring elapsed timeTimeSpanDate mathematics.
Birthdays / AnniversariesDateOnlyWe don’t need time or time zone.
Alarm clock / SchedulesTimeOnlyNot tied to a specific day.

Before delving into the different classes, let’s review the topic of dates in computers.

What is time for a computer? (Ticks)

For C#, a date is not “October 12th”. It’s a number (for your computer, everything is numbers, even letters).

In .NET, time is measured in Ticks.

A Tick is the smallest unit of time the system can represent. It equals 100 nanoseconds (yes, nanoseconds. That’s how precise it is).

When we create a date, internally C# stores a 64-bit integer (long) that counts how many Ticks have passed since January 1, year 0001 at 00:00:00.

The Common Form DateTime

The DateTime structure is the most common way to represent an instant in time. It combines date and time.

// Create a specific date (Year, Month, Day, Hour, Minute, Second)
DateTime releaseDate = new DateTime(2025, 12, 25, 10, 30, 00);

// Get the current date
DateTime now = DateTime.Now;

Copied!

The Context Problem (DateTimeKind)

Here comes the important point. A DateTime has a property called Kind that indicates what that date represents:

  1. Utc: Coordinated Universal Time (the global standard, “Zulu time”).
  2. Local: The time of the computer where the code runs (with its time zone and daylight saving).
  3. Unspecified: We only know the date and time, but not from where or which time zone.

Always save and process in UTC (DateTime.UtcNow). Convert to Local only to display it to the end user.

Do not save dates to the database using DateTime.Now (Local). If your server is in Spain and your user is in Mexico, you’ll have a monumental mess.

The Modern Form DateTimeOffset

If you are making a web or cloud application, forget DateTime. Use the improved DateTimeOffset.

The problem with DateTime is that it’s ambiguous. If it’s 10:00, is that 10:00 in London or Tokyo?

DateTimeOffset stores two things:

  1. The exact instant (UTC).
  2. The offset (Offset) from UTC for the local zone.
DateTimeOffset exactMoment = DateTimeOffset.Now;
// Prints something like: 15/08/2023 10:30:00 +02:00
Copied!

With this structure, we know the event occurred at 10:30 local time, and that locality is 2 hours ahead of the Greenwich meridian. It’s an absolute and unambiguous representation of time.

Microsoft officially recommends using DateTimeOffset as the default type for modern application development, unless you have a very specific reason to use DateTime.

The Duration TimeSpan

While DateTime and DateTimeOffset are points on the timeline (a “when?”), TimeSpan is a length of time (a “how long?”).

It represents an interval: For example 2 hours, 5 minutes, and 10 seconds.

We can get a TimeSpan by subtracting two DateTimes:

DateTime start = new DateTime(2023, 1, 1);
DateTime end = new DateTime(2023, 1, 10);

TimeSpan duration = end - start;

Console.WriteLine(duration.TotalDays); // 9.0
Console.WriteLine(duration.TotalHours); // 216.0

Copied!

We can also create them manually to add time to a date:

TimeSpan halfHour = TimeSpan.FromMinutes(30);
DateTime newDate = DateTime.Now + halfHour; // Advance 30 mins

Copied!

DateOnly and TimeOnly

For years, if we wanted to save a birth date (without time) we used DateTime and set the time to 00:00:00. Or if we wanted to save an alarm time (without date), we used an arbitrary DateTime or a TimeSpan.

It was a mess waste of memory and semantically incorrect.

Since .NET 6, we have DateOnly and TimeOnly.

  • DateOnly: Stores only day, month, and year. Uses less memory (internally uses a 32-bit integer, not 64). Ideal for birthdays or holidays.
  • TimeOnly: Stores only the clock time. Ideal for store opening hours or recurring alarms.
DateOnly birthday = new DateOnly(1990, 5, 20);
TimeOnly opening = new TimeOnly(09, 00);

Copied!

Formatting and Parsing

To display a date to the user, we use the .ToString() method with format strings.

Dates depend on Culture (CultureInfo). The format in Spain (dd/MM/yyyy) is not the same as in the USA (MM/dd/yyyy).

DateTime date = new DateTime(2023, 12, 31, 23, 59, 0);

// Standard formats
Console.WriteLine(date.ToString("d")); // Short date (31/12/2023)
Console.WriteLine(date.ToString("t")); // Short time (23:59)
Console.WriteLine(date.ToString("O")); // ISO 8601 (Universal format for JSON/APIs)

// Custom formats
Console.WriteLine(date.ToString("yyyy-MM-dd HH:mm")); // 2023-12-31 23:59
Copied!

Parsing (Text to Date)

The reverse path is delicate. Converting text to date can fail if the format is not as expected.

string text = "31/12/2023";

// Unsafe way (Throws exception if it fails)
DateTime date1 = DateTime.Parse(text); 

// Safe way (TryParse pattern)
if (DateTime.TryParse(text, out DateTime safeDate))
{
    Console.WriteLine("Valid date: " + safeDate);
}
else
{
    Console.WriteLine("The text is not a valid date");
}
Copied!