Saltar al contenido
fechas.py · devschool

Fechas con datetime en Python

Lección 18 de 19 · 10 min de lectura · Actualizado el

En esta lección
  1. Los tipos del módulo datetime
  2. La fecha y la hora actuales
  3. Sumar y restar con timedelta
  4. Comparar y ordenar fechas
  5. De fecha a texto y de texto a fecha
  6. Dos cálculos muy habituales
  7. Nombres de meses y días en español
  8. Zonas horarias con zoneinfo
  9. Pausas y cronómetros con time
  10. Errores frecuentes
  11. Resumen

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:

ClaseGuardaEjemplo
dateUna fecha (año, mes, día)2026-06-15
timeUna hora del día09:30:00
datetimeFecha y hora juntas2026-06-15 09:30:00
timedeltaUna duración o diferencia3 days, 2:00:00

Cuidado: el módulo y la clase se llaman igual. Con import datetime tendrías que escribir datetime.datetime.now(). Por eso lo habitual es from 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: timedelta acepta weeks, days, hours, minutes y seconds, pero no meses ni años, porque no duran siempre lo mismo. Para cambiar de mes o de año usa fecha.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ódigoSignificadoEjemplo
%dDía con dos cifras07
%mMes con dos cifras03
%YAño con cuatro cifras2026
%yAño con dos cifras26
%HHora (00-23)18
%IHora (01-12)06
%pAM o PMPM
%MMinutos45
%SSegundos05
%jDía del año066
%A / %aDía de la semana / abreviadoSaturday / Sat
%B / %bMes / abreviadoMarch / Mar
%%Un signo %%

Cuidado: %m es el mes y %M son 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") da ZoneInfoNotFoundError, instala el paquete tzdata con 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, como 0.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) en strftime y strptime.
  • import datetime y luego datetime.now(): da AttributeError, porque datetime es el módulo. Usa from datetime import datetime o datetime.datetime.now().
  • Guardar fechas como texto "15/06/2026" y compararlas: como texto, "02/01/2027" < "15/06/2026" es True. Convierte a date o 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 date con datetime, 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

TareaCódigo
Crear una fechadate(2026, 6, 15)
Fecha y horadatetime(2026, 6, 15, 9, 30)
Hoy / ahoradate.today() / datetime.now()
Sumar díasfecha + timedelta(days=7)
Días entre fechas(fecha2 - fecha1).days
Fecha a textofecha.strftime("%d/%m/%Y")
Texto a fechadatetime.strptime(texto, "%d/%m/%Y")
Formato ISOfecha.isoformat() / date.fromisoformat(texto)
Zona horariadatetime.now(ZoneInfo("Europe/Madrid"))
Convertir de zonadt.astimezone(ZoneInfo("UTC"))
Esperartime.sleep(1)
Medir tiempotime.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

[Python] ¿Qué imprime este código?
from datetime import date, timedelta

inicio = date(2026, 2, 20)
fin = inicio + timedelta(days=10)
print(fin)

[Python] ¿Qué imprime este código?
from datetime import datetime

d = datetime(2026, 7, 5, 9, 8)
print(d.strftime("%d/%M/%Y"))

[Python] Tienes el texto "25/12/2026" escrito por el usuario. ¿Cómo lo conviertes en una fecha?

[Python] ¿Qué imprime este código?
from datetime import date

print((date(2027, 1, 1) - date(2026, 12, 25)).days)

¿Te ha quedado claro? Márcala y verás tu progreso en el explorador.