En C#, tenemos varias herramientas para modelar el tiempo. Al principio todos usamos DateTime, pero a medida que avanzamos descubrimos que el mundo es más complejo (zonas horarias, duraciones, fechas sin hora…).
Así que C# dispone de una pleeeetora de Clases y métodos destinados a gestionar entidades relacionadas con el tiempo. En resumen
| Situación | Tipo Recomendado | Por qué |
|---|---|---|
| Uso general / Apps Web | DateTimeOffset | Inequívoco, incluye zona horaria. |
| Legado / Solo en memoria | DateTime (UTC) | Compatible con librerías antiguas. |
| Medir tiempo transcurrido | TimeSpan | Matemáticas de fechas. |
| Cumpleaños / Aniversarios | DateOnly | No necesitamos hora ni zona horaria. |
| Despertador / Horarios | TimeOnly | No está ligado a un día concreto. |
Antes de profundizar en las distintas clases, vamos a repasar el tema de fechas en los ordenadores.
¿Qué es el tiempo para un ordenador? (Ticks)
Para C#, una fecha no es “12 de Octubre”. Es un número (para tu ordenador todo son números, hasta las letras).
En .NET, el tiempo se mide en Ticks.
Un Tick es la unidad mínima de tiempo que el sistema puede representar. Equivale a 100 nanosegundos (sí, nanosegundos. Así de preciso es).
Cuando creamos una fecha, internamente C# almacena un número entero de 64 bits (long) que cuenta cuántos Ticks han pasado desde el 1 de enero del año 0001 a las 00:00:00.
La forma común DateTime
La estructura DateTime es la forma más común de representar un instante en el tiempo. Combina fecha y hora.
// Crear una fecha específica (Año, Mes, Día, Hora, Minuto, Segundo)
DateTime fechaLanzamiento = new DateTime(2025, 12, 25, 10, 30, 00);
// Obtener la fecha actual
DateTime ahora = DateTime.Now;
El problema del contexto (DateTimeKind)
Aquí viene el punto importante. Un DateTime tiene una propiedad llamada Kind que indica qué representa esa fecha:
Utc: Tiempo Universal Coordinado (el estándar global, “hora Zulú”).Local: La hora del ordenador donde se ejecuta el código (con su zona horaria y cambio de hora).Unspecified: Solo sabemos la fecha y hora, pero no de dónde ni qué zona.
Guardad y procesad siempre en UTC (DateTime.UtcNow). Convertid a Local solo para mostrarlo al usuario final.
No guardes las fechas en base de datos usando DateTime.Now (Local). Si tu servidor está en España y tu usuario en México, tendrás un lío monumental.
La forma moderna DateTimeOffset
Si estáis haciendo una aplicación web o en la nube, olvidad DateTime. Usad el mejorado DateTimeOffset.
El problema de DateTime es que es ambiguo. Si son las 10:00, ¿son las 10:00 en Londres o en Tokio?
DateTimeOffset almacena dos cosas:
- El instante exacto (UTC).
- El desplazamiento (Offset) respecto a UTC de la zona local.
DateTimeOffset momentoExacto = DateTimeOffset.Now;
// Imprime algo como: 15/08/2023 10:30:00 +02:00
Con esta estructura, sabemos que el evento ocurrió a las 10:30 hora local, y que esa localidad está a 2 horas de diferencia del meridiano de Greenwich. Es una representación absoluta e inequívoca del tiempo.
Microsoft recomienda oficialmente usar DateTimeOffset como el tipo por defecto para desarrollo de aplicaciones modernas, salvo que tengáis una razón muy específica para usar DateTime.
La duración TimeSpan
Mientras que DateTime y DateTimeOffset es un punto en la línea temporal (un “¿cuándo?”), TimeSpan es una longitud de tiempo (un “¿cuánto?”).
Representa un intervalo: Por ejemplo 2 horas, 5 minutos y 10 segundos.
Podemos obtener un TimeSpan restando dos DateTime:
DateTime inicio = new DateTime(2023, 1, 1);
DateTime fin = new DateTime(2023, 1, 10);
TimeSpan duracion = fin - inicio;
Console.WriteLine(duracion.TotalDays); // 9.0
Console.WriteLine(duracion.TotalHours); // 216.0
También podemos crearlos manualmente para sumar tiempo a una fecha:
TimeSpan mediaHora = TimeSpan.FromMinutes(30);
DateTime nuevaFecha = DateTime.Now + mediaHora; // Avanzamos 30 mins
DateOnly y TimeOnly
Durante años, si queríamos guardar una fecha de nacimiento (sin hora) usábamos DateTime y poníamos la hora a 00:00:00. O si queríamos guardar una hora de alarma (sin fecha), usábamos un DateTime arbitrario o un TimeSpan.
Era un guarrada desperdicio de memoria y semánticamente incorrecto.
Desde .NET 6, tenemos DateOnly y TimeOnly.
DateOnly: Almacena solo día, mes y año. Ocupa menos memoria (internamente usa un entero de 32 bits, no 64). Ideal para cumpleaños o festivos.TimeOnly: Almacena solo la hora del reloj. Ideal para horarios de apertura de tiendas o alarmas recurrentes.
DateOnly cumple = new DateOnly(1990, 5, 20);
TimeOnly apertura = new TimeOnly(09, 00);
Formateo y Parsing
Para mostrar una fecha al usuario, usamos el método .ToString() con cadenas de formato.
Las fechas dependen de la Cultura (CultureInfo). No es lo mismo el formato en España (dd/MM/yyyy) que en USA (MM/dd/yyyy).
DateTime fecha = new DateTime(2023, 12, 31, 23, 59, 0);
// Formatos estándar
Console.WriteLine(fecha.ToString("d")); // Fecha corta (31/12/2023)
Console.WriteLine(fecha.ToString("t")); // Hora corta (23:59)
Console.WriteLine(fecha.ToString("O")); // ISO 8601 (Formato universal para JSON/APIs)
// Formatos personalizados
Console.WriteLine(fecha.ToString("yyyy-MM-dd HH:mm")); // 2023-12-31 23:59
Parsing (Texto a Fecha)
El camino inverso es delicado. Convertir texto a fecha puede fallar si el formato no es el esperado.
string texto = "31/12/2023";
// Forma insegura (Lanza excepción si falla)
DateTime fecha1 = DateTime.Parse(texto);
// Forma segura (Patrón TryParse)
if (DateTime.TryParse(texto, out DateTime fechaSegura))
{
Console.WriteLine("Fecha válida: " + fechaSegura);
}
else
{
Console.WriteLine("El texto no es una fecha válida");
}
