Trabajo avanzado con fechas y horas en C#: de la teoría a problemas reales
Trabajar con fechas y horas es una de las tareas más comunes y, al mismo tiempo, más engañosas en el desarrollo. A nivel básico, simplemente creamos DateTime.Now y lo mostramos en la consola. Pero cuando el proyecto supera los límites de juguete, aparecen complicaciones: el cambio al horario de verano, diferentes zonas horarias, alta precisión en los cálculos o la necesidad de trabajar con fechas sin zona horaria. En este artículo, analizaremos técnicas avanzadas para trabajar con fechas y horas en C# (.NET) que te ayudarán a escribir código confiable y predecible.
1. DateTime, DateTimeOffset y TimeSpan: eligiendo la herramienta correcta
Muchos desarrolladores usan DateTime por defecto, sin pensar en sus limitaciones. A nivel avanzado, es importante entender la diferencia entre los tres tipos principales.
DateTime
Almacena fecha y hora. Su principal problema es la ambigüedad. La propiedad Kind (Local, Utc, Unspecified) no siempre se establece explícitamente, lo que provoca errores en la serialización o comparación.
DateTimeOffset
Almacena fecha, hora y desplazamiento respecto a UTC. Este es el tipo recomendado para almacenar momentos en el tiempo, ya que define de manera inequívoca un punto en la línea de tiempo. Por ejemplo, 2025-03-15T14:00:00+03:00 es siempre un momento específico, independientemente de la zona horaria del usuario.
TimeSpan
Representa un intervalo de tiempo, no un momento específico. Se usa para aritmética: diferencia entre fechas, tiempos de espera, duración de operaciones.
// Ejemplo: DateTime vs DateTimeOffsetDateTime localNow = DateTime.Now; // Kind = LocalDateTime utcNow = DateTime.UtcNow; // Kind = Utc
DateTimeOffset preciseNow = DateTimeOffset.Now; // Siempre con indicación de desplazamiento
// Comparación: DateTimeOffset es más inequívocoConsole.WriteLine(preciseNow.Offset); // +03:00 o -05:00, etc.Consejo: Para proyectos nuevos, usa DateTimeOffset para almacenar momentos en el tiempo, TimeSpan para intervalos, y DateTime solo cuando sea realmente necesario (por ejemplo, para compatibilidad con código antiguo).
2. Análisis y formato: cómo evitar FormatException
Analizar fechas a partir de cadenas es una fuente de muchos errores, especialmente si las cadenas provienen de usuarios o de diferentes culturas.
Análisis dependiente de la cultura
Usa CultureInfo.InvariantCulture para el intercambio interno de datos (por ejemplo, JSON, API). Para la entrada del usuario, ten en cuenta la cultura del usuario.
// Análisis teniendo en cuenta la culturastring dateString = "15.03.2025";var ruCulture = new CultureInfo("ru-RU");DateTime parsedDate = DateTime.Parse(dateString, ruCulture);
// Análisis invariable (recomendado para API)string isoString = "2025-03-15T14:00:00Z";DateTimeOffset parsedIso = DateTimeOffset.Parse(isoString, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind);TryParseExact para mayor confiabilidad
Si se conoce el formato de la cadena, usa TryParseExact con la especificación explícita del formato. Esto elimina excepciones inesperadas.
string input = "2025-03-15 14:30";string format = "yyyy-MM-dd HH:mm";if (DateTime.TryParseExact(input, format, CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime result)){ Console.WriteLine($"Éxito: {result}");}else{ Console.WriteLine("Error de análisis");}Formato personalizado
Los especificadores estándar (d, D, t, T) son buenos, pero para un control preciso, usa cadenas de formato personalizadas.
DateTime now = DateTime.Now;Console.WriteLine(now.ToString("yyyy-MM-dd HH:mm:ss.fff")); // 2025-03-15 14:30:45.123Console.WriteLine(now.ToString("dd MMMM yyyy, dddd")); // 15 de marzo de 2025, sábado