Ir al contenido principal

Trabajando con fechas y horas

El manejo de fechas y horas es una necesidad fundamental en la mayoría de aplicaciones de software. Ya sea para registrar cuándo ocurrió un evento, calcular períodos de tiempo, programar tareas futuras, o simplemente mostrar la fecha actual al usuario, necesitamos herramientas robustas y precisas para trabajar con información temporal.

C# proporciona varios tipos especializados para el trabajo con fechas y horas, siendo los más importantes DateTime, DateOnly, TimeOnly, TimeSpan y DateTimeOffset. Cada uno está diseñado para casos de uso específicos, desde fechas simples hasta marcas de tiempo precisas con información de zona horaria.

En este artículo aprenderemos a crear, manipular y formatear fechas y horas, realizar cálculos temporales, y manejar las complejidades que surgen al trabajar con diferentes formatos, zonas horarias y culturas.

Tipos principales para fechas y horas

C# ofrece varios tipos para manejar información temporal, cada uno optimizado para diferentes escenarios:

Tipo Descripción Uso recomendado
DateTime Fecha y hora completas Marcas de tiempo generales
DateOnly Solo fecha (sin hora) Fechas de nacimiento, eventos
TimeOnly Solo hora (sin fecha) Horarios, duraciones del día
TimeSpan Intervalo de tiempo Duraciones, diferencias temporales
DateTimeOffset Fecha/hora con zona horaria Aplicaciones globales

Trabajando con DateTime

DateTime es el tipo más utilizado para representar fechas y horas. Puede representar valores desde el año 1 hasta el año 9999 con precisión de 100 nanosegundos.

Creación de objetos DateTime

using System;

class Program
{
    static void Main()
    {
        // Diferentes formas de crear un DateTime
        
        // Fecha y hora actuales
        DateTime ahora = DateTime.Now;
        DateTime ahoraUtc = DateTime.UtcNow;
        DateTime hoy = DateTime.Today; // Solo fecha, hora a 00:00:00
        
        // Constructores específicos
        DateTime fechaEspecifica = new DateTime(2024, 12, 25); // 25 de diciembre de 2024
        DateTime fechaConHora = new DateTime(2024, 12, 25, 9, 30, 0); // 25/12/2024 9:30:00
        DateTime fechaCompleta = new DateTime(2024, 12, 25, 9, 30, 45, 500); // Con milisegundos
        
        // Parsing desde strings
        DateTime fechaParseada = DateTime.Parse("2024-03-15 14:30:00");
        
        Console.WriteLine($"Ahora: {ahora}");
        Console.WriteLine($"Ahora UTC: {ahoraUtc}");
        Console.WriteLine($"Hoy: {hoy}");
        Console.WriteLine($"Fecha específica: {fechaEspecifica}");
        Console.WriteLine($"Fecha con hora: {fechaConHora}");
        Console.WriteLine($"Fecha parseada: {fechaParseada}");
    }
}

Propiedades principales de DateTime

using System;

class Program
{
    static void Main()
    {
        DateTime fecha = new DateTime(2024, 6, 15, 14, 30, 45);
        
        // Componentes individuales
        Console.WriteLine($"Año: {fecha.Year}");
        Console.WriteLine($"Mes: {fecha.Month}");
        Console.WriteLine($"Día: {fecha.Day}");
        Console.WriteLine($"Hora: {fecha.Hour}");
        Console.WriteLine($"Minuto: {fecha.Minute}");
        Console.WriteLine($"Segundo: {fecha.Second}");
        Console.WriteLine($"Milisegundo: {fecha.Millisecond}");
        
        // Información adicional
        Console.WriteLine($"Día de la semana: {fecha.DayOfWeek}");
        Console.WriteLine($"Día del año: {fecha.DayOfYear}");
        
        // Solo fecha o solo hora
        Console.WriteLine($"Solo fecha: {fecha.Date}");
        Console.WriteLine($"Solo hora: {fecha.TimeOfDay}");
        
        // Verificaciones
        Console.WriteLine($"¿Es año bisiesto? {DateTime.IsLeapYear(fecha.Year)}");
    }
}

Operaciones con fechas

Suma y resta de tiempo

using System;

class Program
{
    static void Main()
    {
        DateTime fechaBase = new DateTime(2024, 6, 15, 10, 0, 0);
        
        // Sumar diferentes unidades de tiempo
        DateTime sumarDias = fechaBase.AddDays(7);
        DateTime sumarHoras = fechaBase.AddHours(3.5);
        DateTime sumarMinutos = fechaBase.AddMinutes(45);
        DateTime sumarMeses = fechaBase.AddMonths(2);
        DateTime sumarAños = fechaBase.AddYears(1);
        
        Console.WriteLine($"Fecha base: {fechaBase}");
        Console.WriteLine($"+ 7 días: {sumarDias}");
        Console.WriteLine($"+ 3.5 horas: {sumarHoras}");
        Console.WriteLine($"+ 45 minutos: {sumarMinutos}");
        Console.WriteLine($"+ 2 meses: {sumarMeses}");
        Console.WriteLine($"+ 1 año: {sumarAños}");
        
        // También podemos restar (números negativos)
        DateTime restarDias = fechaBase.AddDays(-10);
        Console.WriteLine($"- 10 días: {restarDias}");
        
        // Calcular diferencias entre fechas
        DateTime fecha1 = new DateTime(2024, 1, 1);
        DateTime fecha2 = new DateTime(2024, 12, 31);
        TimeSpan diferencia = fecha2 - fecha1;
        
        Console.WriteLine($"\nDiferencia entre {fecha2:yyyy-MM-dd} y {fecha1:yyyy-MM-dd}:");
        Console.WriteLine($"Días: {diferencia.Days}");
        Console.WriteLine($"Horas totales: {diferencia.TotalHours:F0}");
        Console.WriteLine($"Minutos totales: {diferencia.TotalMinutes:F0}");
    }
}

Comparaciones de fechas

using System;

class Program
{
    static void Main()
    {
        DateTime fecha1 = new DateTime(2024, 6, 15);
        DateTime fecha2 = new DateTime(2024, 6, 20);
        DateTime fecha3 = new DateTime(2024, 6, 15);
        
        // Operadores de comparación
        Console.WriteLine($"fecha1 < fecha2: {fecha1 < fecha2}");
        Console.WriteLine($"fecha1 > fecha2: {fecha1 > fecha2}");
        Console.WriteLine($"fecha1 == fecha3: {fecha1 == fecha3}");
        
        // Métodos de comparación
        Console.WriteLine($"fecha1.CompareTo(fecha2): {fecha1.CompareTo(fecha2)}");
        
        // Encontrar la fecha más reciente o más antigua
        DateTime masReciente = (fecha1 > fecha2) ? fecha1 : fecha2;
        DateTime masAntigua = (fecha1 < fecha2) ? fecha1 : fecha2;
        
        Console.WriteLine($"Más reciente: {masReciente:yyyy-MM-dd}");
        Console.WriteLine($"Más antigua: {masAntigua:yyyy-MM-dd}");
        
        // Verificar si una fecha está en un rango
        DateTime fechaPrueba = new DateTime(2024, 6, 18);
        bool estaEnRango = fechaPrueba >= fecha1 && fechaPrueba <= fecha2;
        Console.WriteLine($"¿{fechaPrueba:yyyy-MM-dd} está entre las otras fechas? {estaEnRango}");
    }
}

Formato de fechas y horas

Formatos estándar

using System;
using System.Globalization;

class Program
{
    static void Main()
    {
        DateTime fecha = new DateTime(2024, 6, 15, 14, 30, 45);
        
        // Formatos estándar más comunes
        Console.WriteLine($"Formato corto de fecha (d): {fecha:d}");
        Console.WriteLine($"Formato largo de fecha (D): {fecha:D}");
        Console.WriteLine($"Formato corto de hora (t): {fecha:t}");
        Console.WriteLine($"Formato largo de hora (T): {fecha:T}");
        Console.WriteLine($"Formato completo (F): {fecha:F}");
        Console.WriteLine($"Formato general (G): {fecha:G}");
        Console.WriteLine($"ISO 8601 (s): {fecha:s}");
        Console.WriteLine($"Universal (u): {fecha:u}");
        
        // Formatos personalizados
        Console.WriteLine($"\nFormatos personalizados:");
        Console.WriteLine($"dd/MM/yyyy: {fecha:dd/MM/yyyy}");
        Console.WriteLine($"yyyy-MM-dd HH:mm:ss: {fecha:yyyy-MM-dd HH:mm:ss}");
        Console.WriteLine($"dddd, dd 'de' MMMM 'de' yyyy: {fecha:dddd, dd 'de' MMMM 'de' yyyy}");
        Console.WriteLine($"HH:mm: {fecha:HH:mm}");
    }
}

Formatos específicos por cultura

using System;
using System.Globalization;

class Program
{
    static void Main()
    {
        DateTime fecha = new DateTime(2024, 6, 15, 14, 30, 45);
        
        // Diferentes culturas
        CultureInfo culturaEspañola = new CultureInfo("es-ES");
        CultureInfo culturaInglesa = new CultureInfo("en-US");
        CultureInfo culturaFrancesa = new CultureInfo("fr-FR");
        
        Console.WriteLine($"Español: {fecha.ToString("F", culturaEspañola)}");
        Console.WriteLine($"Inglés (US): {fecha.ToString("F", culturaInglesa)}");
        Console.WriteLine($"Francés: {fecha.ToString("F", culturaFrancesa)}");
        
        // Nombres de días y meses en diferentes idiomas
        Console.WriteLine($"\nNombres de días:");
        Console.WriteLine($"Español: {fecha.ToString("dddd", culturaEspañola)}");
        Console.WriteLine($"Inglés: {fecha.ToString("dddd", culturaInglesa)}");
        Console.WriteLine($"Francés: {fecha.ToString("dddd", culturaFrancesa)}");
        
        Console.WriteLine($"\nNombres de meses:");
        Console.WriteLine($"Español: {fecha.ToString("MMMM", culturaEspañola)}");
        Console.WriteLine($"Inglés: {fecha.ToString("MMMM", culturaInglesa)}");
        Console.WriteLine($"Francés: {fecha.ToString("MMMM", culturaFrancesa)}");
    }
}

Parsing y conversión de strings

Conversión segura de strings a DateTime

using System;
using System.Globalization;

class Program
{
    static void Main()
    {
        // Strings de ejemplo con diferentes formatos
        string[] fechasString = {
            "2024-06-15",
            "15/06/2024",
            "June 15, 2024",
            "15 de junio de 2024",
            "2024-06-15 14:30:00",
            "formato_invalido"
        };
        
        foreach (string fechaStr in fechasString)
        {
            Console.WriteLine($"\nIntentando parsear: '{fechaStr}'");
            
            // Método Parse (puede lanzar excepción)
            try
            {
                DateTime fechaParseada = DateTime.Parse(fechaStr);
                Console.WriteLine($"Parse exitoso: {fechaParseada}");
            }
            catch (FormatException)
            {
                Console.WriteLine("Parse falló con Parse()");
            }
            
            // Método TryParse (más seguro)
            if (DateTime.TryParse(fechaStr, out DateTime fechaSafe))
            {
                Console.WriteLine($"TryParse exitoso: {fechaSafe}");
            }
            else
            {
                Console.WriteLine("TryParse falló");
            }
        }
        
        // ParseExact para formatos específicos
        string fechaPersonalizada = "15-06-2024 14:30";
        string formato = "dd-MM-yyyy HH:mm";
        
        if (DateTime.TryParseExact(fechaPersonalizada, formato, null, 
            DateTimeStyles.None, out DateTime fechaExacta))
        {
            Console.WriteLine($"\nParseExact exitoso: {fechaExacta}");
        }
    }
}

Trabajando con TimeSpan

TimeSpan representa un intervalo de tiempo y es útil para medir duraciones y realizar cálculos temporales:

using System;

class Program
{
    static void Main()
    {
        // Diferentes formas de crear TimeSpan
        TimeSpan duracion1 = new TimeSpan(2, 30, 0); // 2 horas, 30 minutos
        TimeSpan duracion2 = TimeSpan.FromHours(1.5); // 1.5 horas
        TimeSpan duracion3 = TimeSpan.FromMinutes(90); // 90 minutos
        TimeSpan duracion4 = TimeSpan.FromDays(7); // 7 días
        
        Console.WriteLine($"Duración 1: {duracion1}");
        Console.WriteLine($"Duración 2: {duracion2}");
        Console.WriteLine($"Duración 3: {duracion3}");
        Console.WriteLine($"Duración 4: {duracion4}");
        
        // Propiedades de TimeSpan
        Console.WriteLine($"\nDetalles de duración1 ({duracion1}):");
        Console.WriteLine($"Días: {duracion1.Days}");
        Console.WriteLine($"Horas: {duracion1.Hours}");
        Console.WriteLine($"Minutos: {duracion1.Minutes}");
        Console.WriteLine($"Total en horas: {duracion1.TotalHours}");
        Console.WriteLine($"Total en minutos: {duracion1.TotalMinutes}");
        
        // Operaciones con TimeSpan
        TimeSpan suma = duracion1 + duracion2;
        TimeSpan resta = duracion4 - duracion1;
        
        Console.WriteLine($"\nSuma de duraciones: {suma}");
        Console.WriteLine($"Resta de duraciones: {resta}");
        
        // Ejemplo práctico: calcular tiempo trabajado
        DateTime inicioTrabajo = new DateTime(2024, 6, 15, 9, 0, 0);
        DateTime finTrabajo = new DateTime(2024, 6, 15, 17, 30, 0);
        TimeSpan tiempoTrabajado = finTrabajo - inicioTrabajo;
        
        Console.WriteLine($"\nTiempo trabajado: {tiempoTrabajado}");
        Console.WriteLine($"Horas trabajadas: {tiempoTrabajado.TotalHours:F1}");
    }
}

DateOnly y TimeOnly (.NET 6+)

Los tipos DateOnly y TimeOnly introducidos en .NET 6 proporcionan representaciones más específicas:

using System;

class Program
{
    static void Main()
    {
        // DateOnly - solo fechas
        DateOnly fechaSolo = new DateOnly(2024, 6, 15);
        DateOnly hoy = DateOnly.FromDateTime(DateTime.Now);
        
        Console.WriteLine($"Fecha específica: {fechaSolo}");
        Console.WriteLine($"Hoy: {hoy}");
        
        // Operaciones con DateOnly
        DateOnly enUnaSeamana = fechaSolo.AddDays(7);
        DateOnly enUnMes = fechaSolo.AddMonths(1);
        
        Console.WriteLine($"En una semana: {enUnaSeamana}");
        Console.WriteLine($"En un mes: {enUnMes}");
        
        // TimeOnly - solo horas
        TimeOnly horaSolo = new TimeOnly(14, 30, 0);
        TimeOnly ahoraHora = TimeOnly.FromDateTime(DateTime.Now);
        
        Console.WriteLine($"\nHora específica: {horaSolo}");
        Console.WriteLine($"Hora actual: {ahoraHora}");
        
        // Operaciones con TimeOnly
        TimeOnly enDosHoras = horaSolo.AddHours(2);
        TimeOnly en30Minutos = horaSolo.AddMinutes(30);
        
        Console.WriteLine($"En dos horas: {enDosHoras}");
        Console.WriteLine($"En 30 minutos: {en30Minutos}");
        
        // Combinar DateOnly y TimeOnly
        DateTime fechaCompleta = fechaSolo.ToDateTime(horaSolo);
        Console.WriteLine($"\nFecha completa combinada: {fechaCompleta}");
    }
}

Ejemplo práctico: calculadora de edad

using System;

class CalculadoraEdad
{
    public static void CalcularEdadCompleta(DateTime fechaNacimiento)
    {
        DateTime hoy = DateTime.Today;
        
        // Calcular años
        int años = hoy.Year - fechaNacimiento.Year;
        if (hoy.DayOfYear < fechaNacimiento.DayOfYear)
            años--;
        
        // Calcular próximo cumpleaños
        DateTime proximoCumpleaños = new DateTime(hoy.Year, fechaNacimiento.Month, fechaNacimiento.Day);
        if (proximoCumpleaños < hoy)
            proximoCumpleaños = proximoCumpleaños.AddYears(1);
        
        TimeSpan tiempoParaCumpleaños = proximoCumpleaños - hoy;
        
        // Calcular días vividos
        TimeSpan diasVividos = hoy - fechaNacimiento;
        
        Console.WriteLine($"Fecha de nacimiento: {fechaNacimiento:dd/MM/yyyy}");
        Console.WriteLine($"Fecha actual: {hoy:dd/MM/yyyy}");
        Console.WriteLine($"Edad: {años} años");
        Console.WriteLine($"Días vividos: {diasVividos.Days:N0}");
        Console.WriteLine($"Próximo cumpleaños: {proximoCumpleaños:dd/MM/yyyy}");
        Console.WriteLine($"Días para el próximo cumpleaños: {tiempoParaCumpleaños.Days}");
        
        // Información adicional
        int semanas = (int)(diasVividos.Days / 7);
        int mesesAprox = años * 12 + (hoy.Month - fechaNacimiento.Month);
        
        Console.WriteLine($"Semanas vividas aproximadamente: {semanas:N0}");
        Console.WriteLine($"Meses vividos aproximadamente: {mesesAprox}");
    }
}

class Program
{
    static void Main()
    {
        // Ejemplo de uso
        DateTime fechaNacimiento = new DateTime(1990, 3, 15);
        CalculadoraEdad.CalcularEdadCompleta(fechaNacimiento);
        
        Console.WriteLine("\n" + new string('-', 50));
        
        // Probar con otra fecha
        DateTime otraFecha = new DateTime(2000, 12, 25);
        CalculadoraEdad.CalcularEdadCompleta(otraFecha);
    }
}

Ejemplo práctico: sistema de horarios

using System;
using System.Collections.Generic;

class SistemaHorarios
{
    private Dictionary<DayOfWeek, (TimeOnly inicio, TimeOnly fin)> horarios;
    
    public SistemaHorarios()
    {
        horarios = new Dictionary<DayOfWeek, (TimeOnly, TimeOnly)>();
    }
    
    public void EstablecerHorario(DayOfWeek dia, TimeOnly inicio, TimeOnly fin)
    {
        horarios[dia] = (inicio, fin);
    }
    
    public bool EstaAbierto(DateTime momento)
    {
        DayOfWeek dia = momento.DayOfWeek;
        TimeOnly hora = TimeOnly.FromDateTime(momento);
        
        if (!horarios.ContainsKey(dia))
            return false;
        
        var (inicio, fin) = horarios[dia];
        return hora >= inicio && hora <= fin;
    }
    
    public void MostrarHorarios()
    {
        Console.WriteLine("Horarios de atención:");
        string[] nombresDeias = { "Domingo", "Lunes", "Martes", "Miércoles", 
                                  "Jueves", "Viernes", "Sábado" };
        
        for (int i = 0; i < 7; i++)
        {
            DayOfWeek dia = (DayOfWeek)i;
            Console.Write($"{nombresDeias[i]}: ");
            
            if (horarios.ContainsKey(dia))
            {
                var (inicio, fin) = horarios[dia];
                Console.WriteLine($"{inicio:HH:mm} - {fin:HH:mm}");
            }
            else
            {
                Console.WriteLine("Cerrado");
            }
        }
    }
    
    public TimeSpan TiempoParaApertura(DateTime momento)
    {
        if (EstaAbierto(momento))
            return TimeSpan.Zero;
        
        // Buscar el próximo día de apertura
        DateTime proximaApertura = momento.Date.AddDays(1);
        
        for (int i = 1; i <= 7; i++)
        {
            DateTime diaConsiderado = momento.Date.AddDays(i);
            DayOfWeek dia = diaConsiderado.DayOfWeek;
            
            if (horarios.ContainsKey(dia))
            {
                var (inicio, fin) = horarios[dia];
                proximaApertura = diaConsiderado.Add(inicio.ToTimeSpan());
                break;
            }
        }
        
        return proximaApertura - momento;
    }
}

class Program
{
    static void Main()
    {
        SistemaHorarios tienda = new SistemaHorarios();
        
        // Establecer horarios de lunes a viernes
        for (DayOfWeek dia = DayOfWeek.Monday; dia <= DayOfWeek.Friday; dia++)
        {
            tienda.EstablecerHorario(dia, new TimeOnly(9, 0), new TimeOnly(18, 0));
        }
        
        // Sábado horario reducido
        tienda.EstablecerHorario(DayOfWeek.Saturday, new TimeOnly(10, 0), new TimeOnly(14, 0));
        
        tienda.MostrarHorarios();
        
        // Probar diferentes momentos
        DateTime[] momentosPrueba = {
            new DateTime(2024, 6, 17, 10, 30, 0), // Lunes 10:30
            new DateTime(2024, 6, 22, 15, 0, 0),  // Sábado 15:00
            new DateTime(2024, 6, 23, 12, 0, 0)   // Domingo 12:00
        };
        
        Console.WriteLine("\nVerificaciones de apertura:");
        foreach (DateTime momento in momentosPrueba)
        {
            bool abierto = tienda.EstaAbierto(momento);
            Console.WriteLine($"{momento:dddd dd/MM/yyyy HH:mm}: {(abierto ? "ABIERTO" : "CERRADO")}");
            
            if (!abierto)
            {
                TimeSpan tiempoParaApertura = tienda.TiempoParaApertura(momento);
                Console.WriteLine($"  Abre en: {tiempoParaApertura.Days} días, {tiempoParaApertura.Hours} horas, {tiempoParaApertura.Minutes} minutos");
            }
        }
    }
}

Resumen

El trabajo con fechas y horas en C# es fundamental para la mayoría de aplicaciones. Hemos explorado los tipos principales como DateTime, DateOnly, TimeOnly y TimeSpan, cada uno optimizado para diferentes necesidades. Aprendimos a crear, manipular, comparar y formatear fechas y horas, así como a realizar cálculos temporales y manejar diferentes culturas y formatos.

Los tipos de fecha y hora en C# proporcionan una base sólida para manejar información temporal de manera precisa y eficiente. Desde simples cálculos de edad hasta sistemas complejos de horarios, estas herramientas nos permiten crear aplicaciones que manejan el tiempo de forma robusta y confiable. En el siguiente artículo profundizaremos en las operaciones avanzadas con cadenas, complementando nuestro conocimiento de los tipos de datos fundamentales en C#.