System.DateTime representa una fecha y una hora del día, del año 1 al año 9999, con una precisión de 100 nanosegundos (un "tick"). System.TimeSpan representa una duración: la diferencia entre dos valores DateTime. Los dos son tipos de valor inmutables (structs), así que cada operación devuelve un valor nuevo.
Salida:
2026-09-24 00:00:00
2026-09-24 14:30:00
14:30:05.250
2026 9 24
14:30
Thursday
267
2026-09-24 00:00
14:30:00
Todos los ejemplos de esta página imprimen las fechas con un string de formato explícito. El ToString() por defecto sigue la cultura actual (9/24/2026 2:30:00 PM en Estados Unidos, 24.09.2026 14:30:00 en Alemania), así que su salida depende de la máquina. Los códigos de formato están en formato de DateTime.
Una fecha no válida lanza una excepción: new DateTime(2026, 2, 30) lanza una ArgumentOutOfRangeException, igual que el mes 13 o la hora 24.
Now, UtcNow y Today
Tres propiedades static leen el reloj:
Salida de ejemplo:
Now: 2026-09-24 18:20:41 (Local)
UtcNow: 2026-09-24 16:20:41 (Utc)
Today: 2026-09-24 00:00:00
En este ejemplo, la zona horaria local va dos horas por delante de UTC, así que las dos primeras líneas se diferencian en dos horas; en una máquina configurada en UTC coinciden. La propiedad Kind indica si un valor es Local, Utc o Unspecified (el valor por defecto de las fechas que construyes tú). Usa DateTime.UtcNow para todo lo que guardes, registres, compares o envíes a otro sistema: no salta cuando empieza o termina el horario de verano, y significa el mismo momento en todos los servidores. Convierte a hora local solo al mostrarle un valor a una persona.
Para medir cuánto tarda un código, usa System.Diagnostics.Stopwatch en lugar de restar dos valores de DateTime.Now; tiene una resolución mucho más fina y no le afectan los ajustes del reloj.
Sumar y restar tiempo
AddDays, AddHours, AddMinutes, AddSeconds, AddMonths y AddYears devuelven un DateTime nuevo. Pasa un número negativo para ir hacia atrás. Como DateTime es inmutable, hay que asignar el resultado:
Salida:
2026-01-31
2026-02-03 09:00
2026-01-30 21:00
2026-02-28
2027-01-31
10:30
29
True
AddMonths se ajusta al último día del mes cuando el día no existe: 31 de enero más un mes es 28 de febrero (o 29 en un año bisiesto), no 3 de marzo. Por eso sumar un mes dos veces y sumar dos meses pueden dar fechas distintas.
Restar fechas: TimeSpan
Restar un DateTime de otro da un TimeSpan:
Salida:
3.20:30:00
Days: 3, Hours: 20, Minutes: 30
TotalDays: 3.85
TotalHours: 92.5
TotalMinutes: 5550
Nights: 4
Esta es la parte de la API en la que más se equivoca la gente. Days, Hours, Minutes y Seconds son los componentes del intervalo (3 días, 20 horas, 30 minutos). TotalDays, TotalHours y TotalMinutes son la duración completa en una unidad, como double. "¿Cuántas horas se quedó el huésped?" es TotalHours (92,5), no Hours (20).
La última línea muestra una cuestión relacionada: pasaron 3,85 días, pero el huésped se quedó 4 noches. Comparar las partes .Date cuenta días del calendario, que es lo que suelen querer la facturación y las pantallas de "días que faltan".
Crear y dar formato a valores TimeSpan
Salida:
02:15:00
01:30:00
1.12:00:00
True
03:45:00
True
02:15
36h 0m
00:00:00
TimeSpan admite +, -, comparaciones, Duration() (valor absoluto) y Negate(). Los formatos propios como @"hh\:mm" necesitan una barra invertida antes de los caracteres literales, y hh ahí muestra solo el componente de horas (de 0 a 23), así que para duraciones de más de un día, construye el texto a partir de TotalHours como en la penúltima línea.
Comparar fechas
DateTime admite ==, !=, <, >, <= y >=, además de CompareTo y DateTime.Compare. Para comparar solo la fecha e ignorar la hora, compara las propiedades .Date:
Salida:
True
True
1
True
2026-09-01
Para "¿está esta marca de tiempo dentro del 30 de septiembre?", compara con el principio del día siguiente usando <, como arriba. Escribir check <= end excluiría todo lo posterior a la medianoche del último día, porque end es 2026-09-30 00:00:00.
Las comparaciones solo miran los ticks, no Kind: un valor Local y un valor Utc que se imprimen igual se consideran iguales aunque sean momentos distintos. Otro motivo para guardar las horas en UTC.
Día de la semana e inicio de la semana
DayOfWeek es un enum que va de Sunday (0) a Saturday (6). La aritmética sobre él encuentra los días laborables y los límites de la semana:
Salida:
Thursday
4
Weekend: False
Week starts 2026-09-21 (Monday)
Next Friday: 2026-09-25
2026-09-01 to 2026-09-30
Los nombres de los días que imprime DayOfWeek.ToString() siempre están en inglés. Para un nombre localizado, da formato a la fecha con "dddd" y una cultura.
Parsear fechas desde strings
Cuando conoces el formato de la entrada, usa ParseExact o TryParseExact con CultureInfo.InvariantCulture. El string de formato usa los mismos códigos que el formato de salida:
Salida:
2026-09-24 00:00
2026-09-24 18:05
'2026-02-28' -> Saturday, February 28
'2026-02-30' -> invalid
'28.02.2026' -> invalid
'' -> invalid
2026-02-28
2026-09-24 10:00 Utc
DateTime.Parse(text) sin formato intenta adivinar usando la cultura actual. "03/04/2026" es 4 de marzo en una máquina estadounidense y 3 de abril en una británica, y una fecha que se parsea bien en tu portátil puede lanzar una FormatException en un servidor. Reserva Parse para la entrada que escribe un usuario local; usa ParseExact con la cultura invariante para archivos, API y bases de datos. ParseExact lanza FormatException cuando el texto no coincide; TryParseExact devuelve false en su lugar.
Calcular una edad
Restar fechas de nacimiento y dividir entre 365 falla cerca de los cumpleaños y en los años bisiestos. Compara los años y después corrige si el cumpleaños de este año todavía no ha llegado:
Salida:
36
35
18
70 days to go
DateTimeOffset
Un DateTime no registra en qué zona horaria está, más allá del impreciso indicador Kind. DateTimeOffset guarda el valor junto con su diferencia respecto a UTC, así que siempre identifica un momento exacto:
Salida:
2026-09-24 14:00 +02:00
2026-09-24 12:00
2026-09-24 12:30
00:30:00
21:00 +09:00
Usa DateTimeOffset (o valores DateTime en UTC) para las marcas de tiempo: cuándo se hizo un pedido, cuándo se envió un mensaje. Las bases de datos y los serializadores JSON lo manejan bien. Para convertir entre zonas horarias con nombre y sus reglas de horario de verano, usa TimeZoneInfo.ConvertTime; los ID de zona cambian según el sistema operativo en las versiones antiguas de .NET ("Europe/Paris" en Linux, "Romance Standard Time" en Windows), y .NET 6 y posteriores aceptan los dos.
DateOnly y TimeOnly (.NET 6 y posteriores)
Muchos valores son una fecha sin hora (un cumpleaños, una fecha de vencimiento) o una hora sin fecha (el horario de apertura). .NET 6 añadió dos tipos para ellos:
// .NET 6 and later
DateOnly birthday = new DateOnly(1990, 9, 24);
DateOnly due = DateOnly.FromDateTime(DateTime.Today).AddDays(14);
int daysLeft = due.DayNumber - DateOnly.FromDateTime(DateTime.Today).DayNumber;
TimeOnly opens = new TimeOnly(9, 0);
TimeOnly closes = new TimeOnly(17, 30);
bool isOpen = TimeOnly.FromDateTime(DateTime.Now).IsBetween(opens, closes);
Eliminan una clase de bugs en los que una hora o una zona horaria que se cuela desplaza una fecha un día. El código antiguo, y el que apunta a .NET Framework o Unity, usa DateTime con la hora a medianoche.
Errores comunes
- Descartar el resultado de
AddDays.DateTimees inmutable; asigna el valor devuelto. - Usar
Hoursen lugar deTotalHours. Componentes frente a duración total. - Guardar
DateTime.Now. Guarda UTC y convierte para mostrar. - Llamar a
ToString()sin formato en logs, archivos o pruebas, donde la salida depende de la cultura de la máquina. - Parsear datos de máquina con
DateTime.Parse. UsaParseExacty la cultura invariante. - Confundir
mmyMMen los strings de formato (minutos y meses). Consulta formato de DateTime.
Preguntas frecuentes
¿Qué diferencia hay entre DateTime.Now y DateTime.UtcNow?
DateTime.Now es la hora actual en la zona horaria local del ordenador, con Kind puesto a Local. DateTime.UtcNow es la hora actual en UTC, con Kind puesto a Utc, y además es más rápido porque se salta la conversión de zona horaria. Guarda y compara las marcas de tiempo en UTC, y convierte a hora local solo para mostrarlas.
¿Cómo obtengo la diferencia entre dos fechas en C#?
Réstalas: TimeSpan gap = end - start;. Después lee gap.TotalDays, gap.TotalHours o gap.TotalMinutes para la duración completa como double, o gap.Days para la parte de días enteros. Para meses o años del calendario no hay ninguna propiedad incorporada, porque los meses tienen longitudes distintas; compara tú los campos del año y del mes.
¿Qué diferencia hay entre TimeSpan.Hours y TotalHours?
Hours es solo el componente de horas, de 0 a 23, una vez descontados los días enteros. TotalHours es toda la duración expresada en horas, como double. Para un intervalo de 1 día y 3 horas, Hours es 3 y TotalHours es 27. Usar Hours donde se quería TotalHours es un bug muy común.
¿Cómo parseo un string de fecha en C#?
Cuando conoces el formato, usa DateTime.ParseExact(text, "yyyy-MM-dd", CultureInfo.InvariantCulture), o DateTime.TryParseExact para obtener false en lugar de una FormatException ante una entrada incorrecta. DateTime.Parse adivina el formato a partir de la cultura actual, así que 03/04/2026 significa 4 de marzo en Estados Unidos y 3 de abril en el Reino Unido.
¿Por qué AddDays no cambia mi DateTime?
DateTime es un tipo de valor inmutable. AddDays, AddHours y los demás métodos devuelven un DateTime nuevo y dejan el original sin cambios, así que tienes que asignar el resultado: due = due.AddDays(7);.
¿Cuándo debo usar DateTimeOffset en lugar de DateTime?
Usa DateTimeOffset para las marcas de tiempo que deben identificar un momento exacto, como cuándo se hizo un pedido o se escribió una entrada de log, sobre todo si los datos se mueven entre servidores y zonas horarias. Guarda la diferencia con UTC junto con el valor. DateTime está bien para marcas de tiempo solo en UTC y para fechas sin una zona horaria con significado.