Fechas con datetime en Python
En esta lección
Casi cualquier aplicación trabaja con fechas: la fecha de un pedido, el día de un examen, la hora de una reserva o la edad de un usuario. Guardarlas como texto ("15/06/2026") parece fácil, pero con texto no puedes sumar días, comparar ni saber qué día de la semana cae. Python trae el módulo datetime, que entiende el calendario de verdad: años bisiestos, meses de 28, 30 o 31 días y zonas horarias. En esta lección aprenderás a crear, operar, comparar y formatear fechas.
Los tipos del módulo datetime
El módulo tiene varias clases. Estas son las que usarás:
| Clase | Guarda | Ejemplo |
|---|---|---|
date | Una fecha (año, mes, día) | 2026-06-15 |
time | Una hora del día | 09:30:00 |
datetime | Fecha y hora juntas | 2026-06-15 09:30:00 |
timedelta | Una duración o diferencia | 3 days, 2:00:00 |
Cuidado: el módulo y la clase se llaman igual. Con
import datetimetendrías que escribirdatetime.datetime.now(). Por eso lo habitual esfrom datetime import date, datetime, timedelta, como en esta lección.
date: solo la fecha
from datetime import date
examen = date(2026, 6, 15) # año, mes, día
print(examen) # 2026-06-15
print(examen.year, examen.month, examen.day) # 2026 6 15
print(examen.weekday()) # 0 (lunes=0 … domingo=6)
print(examen.isoweekday()) # 1 (lunes=1 … domingo=7)
Los meses van del 1 al 12, como en el calendario (en JavaScript empiezan en 0; aquí no). Si la fecha no existe, Python protesta al crearla:
date(2026, 2, 30) # ValueError: day is out of range for month
time y datetime
from datetime import time, datetime
apertura = time(9, 30)
print(apertura) # 09:30:00
cita = datetime(2026, 3, 7, 18, 45) # año, mes, día, hora, minuto
print(cita) # 2026-03-07 18:45:00
print(cita.date(), cita.time()) # 2026-03-07 18:45:00
print(datetime.combine(examen, apertura)) # 2026-06-15 09:30:00
time suelto se usa poco; lo normal es trabajar con date o con datetime.
La fecha y la hora actuales
from datetime import date, datetime
hoy = date.today() # la fecha de hoy
ahora = datetime.now() # la fecha y la hora de este instante
print(hoy) # por ejemplo: 2026-09-30
print(ahora) # por ejemplo: 2026-09-30 17:42:08.512394
Los resultados dependen del momento en que ejecutes el programa. Por eso, en los ejemplos de esta lección usamos fechas fijas: así puedes comprobar la salida.
Sumar y restar con timedelta
Un timedelta representa una duración. Sumándolo o restándolo a una fecha obtienes otra fecha, y Python se encarga de cambiar de mes o de año cuando toca:
from datetime import date, timedelta
examen = date(2026, 6, 15)
print(examen + timedelta(days=30)) # 2026-07-15
print(examen - timedelta(weeks=2)) # 2026-06-01
print(date(2026, 1, 31) + timedelta(days=30)) # 2026-03-02
Al restar dos fechas obtienes un timedelta. Su atributo .days da el número de días:
falta = date(2026, 12, 25) - date(2026, 9, 30)
print(falta) # 86 days, 0:00:00
print(falta.days) # 86
Con datetime la diferencia incluye horas y minutos. Para pasarla a una sola unidad, usa total_seconds():
duracion = datetime(2026, 3, 7, 20, 15) - datetime(2026, 3, 7, 18, 45)
print(duracion) # 1:30:00
print(duracion.total_seconds() / 60) # 90.0 minutos
print(timedelta(hours=36)) # 1 day, 12:00:00
print(timedelta(days=1, hours=3, minutes=20) * 2) # 2 days, 6:40:00
Consejo:
timedeltaaceptaweeks,days,hours,minutesyseconds, pero no meses ni años, porque no duran siempre lo mismo. Para cambiar de mes o de año usafecha.replace(year=..., month=...).
Comparar y ordenar fechas
Las fechas se comparan con los operadores de siempre: una fecha es “menor” si es anterior.
print(date(2026, 6, 15) < date(2026, 9, 1)) # True
fechas = [date(2026, 5, 3), date(2025, 12, 1), date(2026, 1, 20)]
print(max(fechas)) # 2026-05-03 (la más reciente)
for f in sorted(fechas):
print(f)
# 2025-12-01
# 2026-01-20
# 2026-05-03
Así puedes saber si un pedido está caducado (fecha_limite < date.today()) o filtrar las reservas de este mes. Lo que no puedes es comparar un date con un datetime: da TypeError: can't compare datetime.datetime to datetime.date. Convierte antes con .date().
De fecha a texto y de texto a fecha
strftime: dar formato
strftime (string format time) convierte una fecha en texto con el formato que quieras, usando códigos que empiezan por %:
cita = datetime(2026, 3, 7, 18, 45, 5)
print(cita.strftime("%d/%m/%Y")) # 07/03/2026
print(cita.strftime("%H:%M")) # 18:45
print(cita.strftime("%d-%m-%y %H:%M:%S")) # 07-03-26 18:45:05
print(cita.strftime("%Y%m%d_%H%M%S")) # 20260307_184505 (útil para nombres de fichero)
print(f"Cita: {cita:%d/%m/%Y}") # Cita: 07/03/2026
El último ejemplo muestra que los códigos también funcionan dentro de un f-string, después de los dos puntos.
| Código | Significado | Ejemplo |
|---|---|---|
%d | Día con dos cifras | 07 |
%m | Mes con dos cifras | 03 |
%Y | Año con cuatro cifras | 2026 |
%y | Año con dos cifras | 26 |
%H | Hora (00-23) | 18 |
%I | Hora (01-12) | 06 |
%p | AM o PM | PM |
%M | Minutos | 45 |
%S | Segundos | 05 |
%j | Día del año | 066 |
%A / %a | Día de la semana / abreviado | Saturday / Sat |
%B / %b | Mes / abreviado | March / Mar |
%% | Un signo % | % |
Cuidado:
%mes el mes y%Mson los minutos. Confundirlos es el error más típico.
strptime: leer un texto
strptime (string parse time) hace lo contrario: convierte un texto en datetime. Le indicas el formato exacto en que viene:
reserva = datetime.strptime("25/12/2026 20:30", "%d/%m/%Y %H:%M")
print(reserva) # 2026-12-25 20:30:00
dia = datetime.strptime("15/06/2026", "%d/%m/%Y").date()
print(dia) # 2026-06-15
Si el texto no encaja con el formato, obtienes ValueError: time data '2026-06-15' does not match format '%d/%m/%Y'. Captúralo con try cuando el texto venga del usuario (lo viste en excepciones).
Formato ISO: isoformat y fromisoformat
El formato ISO 8601 (AAAA-MM-DD) es el estándar para guardar fechas en ficheros, bases de datos y APIs: no es ambiguo y se ordena bien como texto.
print(date.fromisoformat("2026-06-15")) # 2026-06-15
print(datetime.fromisoformat("2026-06-15T09:30:00")) # 2026-06-15 09:30:00
print(cita.isoformat()) # 2026-03-07T18:45:05
Como json no sabe guardar fechas (TypeError: Object of type date is not JSON serializable), lo habitual es guardarlas con isoformat() y recuperarlas con fromisoformat(). Lo tienes en ficheros y JSON.
Dos cálculos muy habituales
Calcular una edad
Restar años no basta: si todavía no ha llegado el cumpleaños de este año, hay que quitar uno. Comparar tuplas (mes, día) lo resuelve de forma elegante:
def calcular_edad(nacimiento, hoy=None):
if hoy is None:
hoy = date.today()
edad = hoy.year - nacimiento.year
if (hoy.month, hoy.day) < (nacimiento.month, nacimiento.day):
edad -= 1 # aún no ha cumplido este año
return edad
nacimiento = date(2004, 11, 20)
print(calcular_edad(nacimiento, date(2026, 9, 30))) # 21
print(calcular_edad(nacimiento, date(2026, 11, 20))) # 22
El parámetro opcional hoy permite probar la función con fechas fijas; en tu programa la llamarás solo con calcular_edad(nacimiento).
Días hasta una fecha
def dias_hasta(objetivo, hoy=None):
if hoy is None:
hoy = date.today()
return (objetivo - hoy).days
print(dias_hasta(date(2026, 12, 25), date(2026, 9, 30))) # 86
print(dias_hasta(date(2026, 9, 1), date(2026, 9, 30))) # -29 (ya pasó)
Un resultado negativo indica que la fecha ya ha pasado.
Nombres de meses y días en español
%A y %B escriben los nombres en inglés (dependen del locale del sistema, que cambia de un ordenador a otro y es poco fiable). La solución más sencilla y portable es tener tus propias listas:
MESES = ["enero", "febrero", "marzo", "abril", "mayo", "junio", "julio",
"agosto", "septiembre", "octubre", "noviembre", "diciembre"]
DIAS = ["lunes", "martes", "miércoles", "jueves", "viernes", "sábado", "domingo"]
def fecha_larga(f):
return f"{DIAS[f.weekday()]}, {f.day} de {MESES[f.month - 1]} de {f.year}"
print(fecha_larga(date(2026, 3, 7))) # sábado, 7 de marzo de 2026
print(fecha_larga(date(2026, 10, 12))) # lunes, 12 de octubre de 2026
Fíjate en f.month - 1: los meses van de 1 a 12, pero los índices de la lista, de 0 a 11. En cambio weekday() ya empieza en 0, así que encaja directamente.
Zonas horarias con zoneinfo
Los datetime que has creado hasta ahora son ingenuos (naive): no saben en qué zona horaria están. Si tu aplicación tiene usuarios en varios países, necesitas fechas conscientes (aware), con zona. Desde Python 3.9 se usa el módulo zoneinfo:
from datetime import datetime
from zoneinfo import ZoneInfo
madrid = ZoneInfo("Europe/Madrid")
reunion = datetime(2026, 7, 10, 18, 0, tzinfo=madrid)
print(reunion) # 2026-07-10 18:00:00+02:00
print(reunion.astimezone(ZoneInfo("America/Mexico_City"))) # 2026-07-10 10:00:00-06:00
print(reunion.astimezone(ZoneInfo("UTC"))) # 2026-07-10 16:00:00+00:00
invierno = datetime(2026, 1, 10, 18, 0, tzinfo=madrid)
print(invierno) # 2026-01-10 18:00:00+01:00
print(reunion.tzname(), invierno.tzname()) # CEST CET
ahora_en_madrid = datetime.now(madrid)
astimezone convierte el mismo instante a otra zona. Observa que zoneinfo aplica automáticamente el horario de verano: Madrid está a +2 horas de UTC en julio y a +1 en enero.
Consejo: en Windows puede faltar la base de datos de zonas horarias. Si
ZoneInfo("Europe/Madrid")daZoneInfoNotFoundError, instala el paquetetzdatacon pip.
No mezcles ingenuas y conscientes: compararlas da TypeError: can't compare offset-naive and offset-aware datetimes. Una buena práctica en aplicaciones web es guardar todo en UTC y convertir a la zona del usuario solo al mostrarlo.
Pausas y cronómetros con time
El módulo time (distinto de la clase time de datetime) tiene dos funciones muy útiles:
import time
for i in range(3, 0, -1):
print(i)
time.sleep(1) # espera 1 segundo
print("¡Ya!")
inicio = time.perf_counter()
total = sum(range(5_000_000))
fin = time.perf_counter()
print(f"Tardó {fin - inicio:.3f} s") # por ejemplo: Tardó 0.059 s
time.sleep(segundos)detiene el programa ese tiempo (acepta decimales, como0.5).time.perf_counter()es un cronómetro de alta precisión. Su valor suelto no significa nada; solo sirve la diferencia entre dos llamadas. Lo usaste en el decorador de funciones avanzadas.
Errores frecuentes
- Confundir
%m(mes) con%M(minutos) enstrftimeystrptime. import datetimey luegodatetime.now(): daAttributeError, porquedatetimees el módulo. Usafrom datetime import datetimeodatetime.datetime.now().- Guardar fechas como texto
"15/06/2026"y compararlas: como texto,"02/01/2027" < "15/06/2026"esTrue. Convierte adateo usa ISO. - Calcular la edad restando solo años: falla si aún no ha llegado el cumpleaños.
- Sumar meses con
timedelta(days=30): no todos los meses tienen 30 días. - Mezclar
datecondatetime, o fechas con y sin zona horaria, al comparar. - Salidas fijas de “hoy” en tus pruebas: pasa la fecha como parámetro para que el resultado sea predecible.
Resumen
| Tarea | Código |
|---|---|
| Crear una fecha | date(2026, 6, 15) |
| Fecha y hora | datetime(2026, 6, 15, 9, 30) |
| Hoy / ahora | date.today() / datetime.now() |
| Sumar días | fecha + timedelta(days=7) |
| Días entre fechas | (fecha2 - fecha1).days |
| Fecha a texto | fecha.strftime("%d/%m/%Y") |
| Texto a fecha | datetime.strptime(texto, "%d/%m/%Y") |
| Formato ISO | fecha.isoformat() / date.fromisoformat(texto) |
| Zona horaria | datetime.now(ZoneInfo("Europe/Madrid")) |
| Convertir de zona | dt.astimezone(ZoneInfo("UTC")) |
| Esperar | time.sleep(1) |
| Medir tiempo | time.perf_counter() |
Con datetime y la biblioteca estándar resuelves casi todo lo relacionado con fechas. Para tareas más avanzadas existen paquetes externos, que aprenderás a instalar en pip y entornos virtuales.
Pon a prueba lo que has aprendido
¿Te ha quedado claro? Márcala y verás tu progreso en el explorador.