Las fechas son de lo más traicionero en cualquier lenguaje: zonas horarias, cambios de hora, formatos que cambian con el idioma. C# tiene tipos distintos para cada caso, y elegir bien cuál usar evita la mayoría de los errores. Todas las fechas de esta página son fijas, para que veas exactamente las mismas salidas.
También te puede interesar
Qué tipo usar
.NET tiene varios tipos para el tiempo, y elegir el adecuado ahorra la mitad de los problemas:
| Tipo | Guarda | Para |
|---|---|---|
DateOnly | una fecha sin hora | cumpleaños, vencimientos, festivos |
TimeOnly | una hora sin fecha | horarios de apertura, alarmas |
DateTime | fecha y hora, sin zona horaria clara | cuentas locales y código antiguo |
DateTimeOffset | fecha, hora y la diferencia con UTC | un instante exacto: registros, eventos, API |
TimeSpan | una duración | el resultado de restar dos fechas |
var cita = new DateTime(2026, 10, 25, 9, 30, 0);
var dia = new DateOnly(2026, 10, 25);
var hora = new TimeOnly(9, 30);
var conZona = new DateTimeOffset(2026, 10, 25, 9, 30, 0, TimeSpan.FromHours(-6));
Console.WriteLine(cita.ToString("yyyy-MM-dd HH:mm"));
Console.WriteLine(dia.ToString("yyyy-MM-dd"));
Console.WriteLine(hora.ToString("HH:mm"));
Console.WriteLine(conZona.ToString("yyyy-MM-dd HH:mm zzz"));
Console.WriteLine(conZona.UtcDateTime.ToString("yyyy-MM-dd HH:mm"));salida2026-10-25 09:30 2026-10-25 09:30 2026-10-25 09:30 -06:00 2026-10-25 15:30
DateTimeOffset sabe que las 9:30 en una zona UTC-6 son las 15:30 en UTC. Un DateTime no lo sabe: su propiedad Kind dice si es local, UTC o «sin especificar», y casi siempre es lo último. Para guardar cuándo pasó algo, usa DateTimeOffset; para la fecha de un calendario, DateOnly.
Mostrar fechas
using System.Globalization;
var fecha = new DateTime(2026, 10, 25, 9, 5, 0);
var es = new CultureInfo("es-ES");
var us = new CultureInfo("en-US");
Console.WriteLine(fecha.ToString("dddd, d 'de' MMMM 'de' yyyy", es));
Console.WriteLine(fecha.ToString("d", es));
Console.WriteLine(fecha.ToString("d", us));
Console.WriteLine(fecha.ToString("o", CultureInfo.InvariantCulture));
Console.WriteLine(fecha.ToString("yyyy-mm-dd"));salidadomingo, 25 de octubre de 2026 25/10/2026 10/25/2026 2026-10-25T09:05:00.0000000 2026-05-25
Los nombres de días y meses, y el orden día/mes, dependen de la cultura. El formato o es ISO 8601, el que hay que usar en archivos y API. La última línea es una trampa clásica: mm en minúscula son minutos, no el mes, y por eso sale 2026-05-25. El mes es MM.
Sumar, restar y contar
var inicio = new DateOnly(2026, 1, 31);
Console.WriteLine(inicio.AddMonths(1).ToString("yyyy-MM-dd"));
Console.WriteLine(inicio.AddDays(30).DayOfWeek);
var entrega = new DateTime(2026, 12, 24, 18, 0, 0);
var ahora = new DateTime(2026, 10, 25, 9, 30, 0);
TimeSpan falta = entrega - ahora;
Console.WriteLine($"Faltan {falta.Days} días y {falta.Hours} horas");
Console.WriteLine($"En total, {falta.TotalHours:F1} horas");
int diasLaborables = 0;
for (var d = new DateOnly(2026, 11, 1); d <= new DateOnly(2026, 11, 30); d = d.AddDays(1))
{
if (d.DayOfWeek is not (DayOfWeek.Saturday or DayOfWeek.Sunday)) diasLaborables++;
}
Console.WriteLine($"Noviembre de 2026 tiene {diasLaborables} días laborables");salida2026-02-28 Monday Faltan 60 días y 8 horas En total, 1448.5 horas Noviembre de 2026 tiene 21 días laborables
DayOfWeek es un enum, y por eso se escribe en inglés (Monday); para el nombre en español, fecha.ToString("dddd", new CultureInfo("es-ES")). Sumar un mes al 31 de enero da el último día de febrero, no un 31 de febrero inexistente. Restar dos fechas da un TimeSpan: Days y Hours son las partes, y TotalHours, la duración entera en horas, con decimales.
Leer fechas de un texto
using System.Globalization;
var fecha = DateTime.ParseExact("25/10/2026 09:30", "dd/MM/yyyy HH:mm", CultureInfo.InvariantCulture);
Console.WriteLine(fecha.ToString("yyyy-MM-dd HH:mm"));
string[] entradas = ["2026-02-29", "2026-03-01", "ayer"];
foreach (var e in entradas)
{
bool ok = DateOnly.TryParseExact(e, "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.None, out var d);
Console.WriteLine(ok ? $"{e}: {d.DayOfWeek}" : $"{e}: no es una fecha válida");
}salida2026-10-25 09:30 2026-02-29: no es una fecha válida 2026-03-01: Sunday ayer: no es una fecha válida
ParseExact y TryParseExact exigen el formato que les digas, sin adivinar. Es lo que quieres con datos de un archivo o un formulario: DateTime.Parse("03/04/2026") da el 3 de abril o el 4 de marzo según el idioma del sistema. 2026 no es bisiesto, así que el 29 de febrero se rechaza.
Zonas horarias
var instante = new DateTimeOffset(2026, 10, 25, 15, 0, 0, TimeSpan.Zero);
foreach (var id in new[] { "America/Mexico_City", "America/Bogota", "Europe/Madrid", "America/Argentina/Buenos_Aires" })
{
var zona = TimeZoneInfo.FindSystemTimeZoneById(id);
var local = TimeZoneInfo.ConvertTime(instante, zona);
Console.WriteLine($"{id,-32} {local:yyyy-MM-dd HH:mm zzz}");
}
// La noche del cambio de hora en Madrid: el 25 de octubre de 2026 a las 3:00 vuelven a ser las 2:00
var madrid = TimeZoneInfo.FindSystemTimeZoneById("Europe/Madrid");
Console.WriteLine(madrid.IsAmbiguousTime(new DateTime(2026, 10, 25, 2, 30, 0)));salidaAmerica/Mexico_City 2026-10-25 09:00 -06:00 America/Bogota 2026-10-25 10:00 -05:00 Europe/Madrid 2026-10-25 16:00 +01:00 America/Argentina/Buenos_Aires 2026-10-25 12:00 -03:00 True
Desde .NET 6 los identificadores de la IANA (Europe/Madrid) funcionan también en Windows, que usa los suyos (Romance Standard Time). ConvertTime aplica el horario de verano de cada sitio: el mismo instante es otra hora en cada ciudad. Y el 25 de octubre de 2026, día del cambio de hora en España, las 2:30 de la madrugada ocurren dos veces: por eso son una hora ambigua.
DateTime.Now da un resultado distinto cada vez y no se puede probar el día de Año Nuevo sin esperar a que llegue. Desde .NET 8, recibe un TimeProvider: en producción pasas TimeProvider.System, y en las pruebas, un reloj falso que puedes adelantar.using Microsoft.Extensions.Time.Testing;
var reloj = new FakeTimeProvider(new DateTimeOffset(2026, 12, 31, 23, 59, 0, TimeSpan.Zero));
Console.WriteLine(Saludo(reloj));
reloj.Advance(TimeSpan.FromMinutes(2));
Console.WriteLine(Saludo(reloj));
static string Saludo(TimeProvider reloj) =>
reloj.GetUtcNow().Month == 1 && reloj.GetUtcNow().Day == 1 ? "¡Feliz año nuevo!" : "Todavía no";salidaTodavía no ¡Feliz año nuevo!
Todo el código de esta página se compiló con el .NET SDK 10.0.403 (C# 14) y se ejecutó con .NET 10.0.14 en un contenedor limpio antes de publicarla; las salidas y los mensajes del compilador están copiados de esa ejecución.