▷ Manejar fechas en Python con `datetime` — La guía sin sufrir 2026

Manejar fechas en Python con `datetime` — La guía sin sufrir

Las fechas son uno de esos temas que parecen sencillos hasta que los tocas en serio. ¿Cuántos días faltan para Navidad? Fácil. ¿Qué hora es ahora en Tokio? Ya cambia la cosa. ¿Cómo parseo "2026-04-19T15:30:00+00:00" y lo guardo bien? Ahí mucha gente se atasca.

La buena noticia es que Python trae el módulo datetime en la librería estándar y, con dos o tres conceptos claros, resuelves el 95% de los casos sin sufrir. En esta entrada te enseño lo que importa de verdad: crear fechas, sumar y restar días, parsear y formatear strings, manejar zonas horarias correctamente y los errores típicos que te muerden en producción.

Lo importante: cuatro tipos que tienes que conocer

El módulo datetime te da cuatro tipos. No son intercambiables — cada uno representa algo distinto.

TipoPara qué
dateUna fecha sin hora: 2026-04-19
timeUna hora sin fecha: 15:30:00
datetimeFecha + hora: 2026-04-19 15:30:00
timedeltaUna duración: “3 días y 4 horas”

El que más vas a usar es datetime. El que más útil es por sorprendente: timedelta.

from datetime import date, time, datetime, timedelta

Sí, los nombres son confusos: el módulo se llama datetime y dentro hay un tipo que también se llama datetime. Es lo que hay.

Crear fechas y horas

Lo más básico — la “fecha de hoy”, una hora puntual, un instante en el tiempo:

from datetime import date, datetime

# Fecha de hoy
hoy = date.today()
# date(2026, 4, 19)

# Ahora mismo (fecha + hora)
ahora = datetime.now()
# datetime(2026, 4, 19, 15, 30, 12, 482103)

# Crear una fecha específica
nacimiento = date(1995, 12, 15)
estreno = datetime(2010, 7, 16, 22, 0)

Cada componente lo puedes sacar como atributo:

print(ahora.year)     # 2026
print(ahora.month)    # 4
print(ahora.day)      # 19
print(ahora.hour)     # 15
print(ahora.weekday())  # 6 → domingo (0=lunes, 6=domingo)

💡 year, no año/anio — porque ano en español significa otra cosa. Convención del proyecto que acaba ahorrándote disgustos al revisar el código a las 3am.

Sumar y restar — timedelta

Aquí brilla datetime. Si quieres “dentro de 7 días”, “hace 3 horas”, “cuántos días faltan” — todo gira alrededor de timedelta.

from datetime import date, datetime, timedelta

hoy = date.today()
en_una_semana = hoy + timedelta(days=7)
ayer = hoy - timedelta(days=1)

print(en_una_semana - hoy)   # 7 days, 0:00:00

Restar dos fechas te da un timedelta. Y un timedelta se puede sumar/restar a cualquier date o datetime.

fecha_inicio = date(2026, 1, 1)
fecha_fin = date(2026, 4, 19)

duracion = fecha_fin - fecha_inicio
print(duracion.days)   # 108

timedelta admite muchos parámetros:

timedelta(days=2, hours=3, minutes=30, seconds=15)

Ojo: timedelta no admite months= ni years= — un mes no son siempre 30 días, y un año no son siempre 365. Si necesitas “dentro de 1 mes”, usa dateutil.relativedelta (librería externa, pip install python-dateutil).

📥 Llévate el cheatsheet de Python (gratis)

PDF de 6 páginas con lo esencial: tipos, condicionales, bucles, estructuras de datos, funciones y los errores que más vas a cometer. Para tener al lado mientras programas.

Sin spam. Te apuntas a la lista, descargas el cheatsheet y recibes contenido de Python cada semana.

Parsear strings → fechas

Lo más típico cuando vienes de un CSV, JSON o input del usuario.

Si el formato es ISO (YYYY-MM-DD)

from datetime import date, datetime

texto = "2026-04-19"
fecha = date.fromisoformat(texto)
# date(2026, 4, 19)

texto2 = "2026-04-19T15:30:00"
dt = datetime.fromisoformat(texto2)
# datetime(2026, 4, 19, 15, 30)

fromisoformat() acepta el formato ISO 8601 (con o sin la T, con o sin segundos). Es el más limpio.

Si el formato es otro

Usa strptime (string parse time):

texto = "19/04/2026 15:30"
dt = datetime.strptime(texto, "%d/%m/%Y %H:%M")
# datetime(2026, 4, 19, 15, 30)

Los códigos importantes para strptime y strftime:

CódigoSignifica
%YAño 4 cifras (2026)
%yAño 2 cifras (26)
%mMes 2 cifras (01-12)
%dDía 2 cifras (01-31)
%HHora 24h (00-23)
%IHora 12h (01-12)
%MMinutos
%SSegundos
%pAM/PM
%ADía de la semana completo (lunes, monday)
%BMes completo (abril, april)

💡 Truco para no aprenderlos de memoria: tienes una chuleta visual genial en strftime.org. Lo abres una vez, lo bookmarkeas, sales del paso.

Formatear fechas → strings

Lo opuesto: tienes una datetime y la quieres mostrar bonita.

from datetime import datetime

ahora = datetime.now()

# ISO (estándar)
print(ahora.isoformat())
# 2026-04-19T15:30:12.482103

# Personalizado con strftime
print(ahora.strftime("%d/%m/%Y %H:%M"))
# 19/04/2026 15:30

print(ahora.strftime("%A %d de %B de %Y"))
# Sunday 19 of April of 2026

# En español (necesita locale configurado)
import locale
locale.setlocale(locale.LC_TIME, "es_ES.UTF-8")
print(ahora.strftime("%A %d de %B"))
# domingo 19 de abril

Tip-friki: desde Python 3.6 puedes usar f-strings con format spec: f"{ahora:%d/%m/%Y}". Más limpio que ahora.strftime("%d/%m/%Y").

Zonas horarias — el tema que rompe scripts

datetime tiene un concepto crítico: las fechas pueden ser naive (sin zona horaria) o aware (con zona horaria). Y mezclarlas es el origen de la mitad de los bugs de fechas en producción.

from datetime import datetime, timezone, timedelta

naive = datetime.now()              # sin zona, "ahora aquí, sin saber dónde es aquí"
aware = datetime.now(timezone.utc)  # con zona, "ahora en UTC"

print(naive.tzinfo)   # None
print(aware.tzinfo)   # datetime.timezone.utc

# Esto explota:
naive - aware
# TypeError: can't subtract offset-naive and offset-aware datetimes

Regla de oro: trabaja siempre en UTC en el código y solo convierte a la zona local al mostrar al usuario.

from datetime import datetime
from zoneinfo import ZoneInfo  # Python 3.9+

# Ahora en UTC
ahora_utc = datetime.now(ZoneInfo("UTC"))

# Convertir a zona horaria de Madrid
ahora_madrid = ahora_utc.astimezone(ZoneInfo("Europe/Madrid"))

zoneinfo viene de serie en Python 3.9+. Antes tenías que usar pytz (librería externa). Si estás en versión moderna, usa zoneinfo, no pytz.

Casos reales típicos

“¿Cuántos días faltan para Navidad?”

from datetime import date

hoy = date.today()
navidad = date(hoy.year, 12, 25)
if navidad < hoy:
    navidad = date(hoy.year + 1, 12, 25)

faltan = (navidad - hoy).days
print(f"Faltan {faltan} días para Navidad.")

“¿Cuántos años tiene alguien?”

from datetime import date

def calcular_edad(fecha_nacimiento: date) -> int:
    hoy = date.today()
    edad = hoy.year - fecha_nacimiento.year
    # Ajuste si aún no ha cumplido este año
    if (hoy.month, hoy.day) < (fecha_nacimiento.month, fecha_nacimiento.day):
        edad -= 1
    return edad

print(calcular_edad(date(1995, 12, 15)))

Procesar timestamps de un log

from datetime import datetime

linea = "2026-04-19 15:30:12 INFO usuario logueado"
fecha_str = linea[:19]
fecha = datetime.strptime(fecha_str, "%Y-%m-%d %H:%M:%S")

# Filtrar logs de hace menos de 1 hora
from datetime import timedelta
hace_una_hora = datetime.now() - timedelta(hours=1)
if fecha > hace_una_hora:
    print("Log reciente:", linea)

Iterar día a día entre dos fechas

from datetime import date, timedelta

inicio = date(2026, 4, 1)
fin = date(2026, 4, 7)

actual = inicio
while actual <= fin:
    print(actual.isoformat())
    actual += timedelta(days=1)

Errores típicos al manejar fechas

# 1. Mezclar naive y aware
naive = datetime.now()
aware = datetime.now(timezone.utc)
naive - aware    # ❌ TypeError

# 2. Asumir que un mes son 30 días
fecha + timedelta(days=30)   # NO es "dentro de un mes". Usa relativedelta.

# 3. Confundir strftime y strptime
datetime.strftime("19/04/2026", "%d/%m/%Y")    # ❌ strftime es para FORMATEAR (de objeto a string)
datetime.strptime("19/04/2026", "%d/%m/%Y")    # ✓ strptime es para PARSEAR (de string a objeto)

# Truco mnemotécnico: strpTime = parseTime, strfTime = formatTime.

# 4. Usar datetime.now() para timestamps que se guardan
# Mejor:
datetime.now(timezone.utc)   # con zona, evita problemas al cargar de otra zona

# 5. Comparar string en vez de fecha
"2026-04-19" > "2026-13-01"   # alfabéticamente sí, pero da igual
date.fromisoformat("2026-04-19") > date.fromisoformat("2026-12-31")   # ✓ comparación real

# 6. timedelta(hours=24) vs timedelta(days=1)
# Son lo mismo. Pero "un día" en horario civil con cambios de hora no son siempre 24h.
# Si vas en serio con DST, usa zonas y cuidado con sumar timedeltas largos.

Resumen

OperaciónSintaxis
Hoy / ahoradate.today(), datetime.now()
Crear fechadate(2026, 4, 19)
Sumar / restarfecha + timedelta(days=N)
Días entre fechas(fin - inicio).days
Parsear ISOdate.fromisoformat(), datetime.fromisoformat()
Parsear formato customdatetime.strptime(s, formato)
Formatear a stringdt.strftime(formato) o f"{dt:%d/%m/%Y}"
Zona horariaZoneInfo("Europe/Madrid") (Python 3.9+)
Convertir zonadt.astimezone(ZoneInfo(...))
Diferencia de fechas reales (meses, años)dateutil.relativedelta

¿Te ha valido esto?

Si te ha resultado útil, llévate el cheatsheet de Python en PDF — 6 páginas con tipos, condicionales, bucles, estructuras de datos, funciones y los errores típicos. Para tener al lado mientras programas. Gratis.

Sin spam. Email + cheatsheet + un correo por semana con tutoriales nuevos.

Tu siguiente paso

Si has llegado hasta aquí, las fechas en Python ya no son terreno minado. La próxima vez que alguien te pase un timestamp en string raro o que te toque calcular “el primer lunes del mes que viene”, sabes con qué herramientas atacar el problema.

Si quieres aprender Python desde la base hasta proyectos reales que persisten datos con fechas (logs, eventos, calendarios, reservas), en El Pythonista lo enseño paso a paso.

Un abrazo,
Oscar

¿Quieres aprender Python en orden, no a saltos?

Esto que has leído es solo una pieza. En El Pythonista lo verás todo encadenado: 11 módulos, 37+ horas de vídeo, 734 actividades y un proyecto real (MovieTracker) que crece contigo desde la primera variable hasta el deploy a producción.

Ver el curso completo →

37+ horas · 734 actividades · Proyecto real · Acceso de por vida · 14 días de garantía

Compartir

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Publicar un comentario