Trabajar con fechas y horas es una de esas tareas que parece sencilla hasta que te topas con zonas horarias, horario de verano, diferencias entre fechas o conversiones a distintos formatos. Python incluye el módulo datetime en su librería estándar, que cubre la mayoría de casos. Para operaciones más avanzadas con zonas horarias, la librería zoneinfo (incluida desde Python 3.9) y dateutil son los complementos habituales.
Los tipos principales del módulo datetime
from datetime import datetime, date, time, timedelta
# date: solo fecha (año, mes, día) — sin hora
hoy = date.today()
fecha = date(2026, 10, 7)
print(hoy) # 2026-10-07
print(fecha) # 2026-10-07
# time: solo hora (hora, minuto, segundo, microsegundo)
hora = time(14, 30, 0)
print(hora) # 14:30:00
# datetime: fecha + hora juntas
ahora = datetime.now() # hora local del sistema
ahora_utc = datetime.utcnow() # hora en UTC (sin info de zona horaria)
dt = datetime(2026, 10, 7, 14, 30, 0)
print(ahora) # 2026-10-07 14:30:00.123456
print(dt) # 2026-10-07 14:30:00
# timedelta: duración (diferencia entre dos momentos)
diferencia = timedelta(days=7, hours=3, minutes=30)
print(diferencia) # 7 days, 3:30:00
print(diferencia.days) # 7
print(diferencia.total_seconds()) # 621000.0
Crear fechas y horas
from datetime import datetime, date
# Desde componentes individuales
dt = datetime(year=2026, month=10, day=7, hour=14, minute=30, second=0)
# También como posicionales:
dt = datetime(2026, 10, 7, 14, 30, 0)
# Momento actual
ahora = datetime.now() # hora local
print(ahora.year) # 2026
print(ahora.month) # 10
print(ahora.day) # 7
print(ahora.hour) # 14
print(ahora.weekday()) # 0=lunes … 6=domingo
print(ahora.isoweekday()) # 1=lunes … 7=domingo
# Inicio y fin del día
from datetime import time
inicio_dia = datetime.combine(date.today(), time.min) # 00:00:00.000000
fin_dia = datetime.combine(date.today(), time.max) # 23:59:59.999999
# Desde timestamp Unix (segundos desde 1970-01-01 UTC)
dt = datetime.fromtimestamp(1728345600)
print(dt) # 2024-10-07 (aproximadamente)
# El timestamp Unix actual
import time as time_module
ts = time_module.time() # float con microsegundos
print(ts) # 1759000000.123456
Formatear fechas: strftime
strftime convierte un objeto datetime en un string con el formato que elijas.
from datetime import datetime
ahora = datetime(2026, 10, 7, 14, 30, 45)
# Formatos más usados
print(ahora.strftime("%Y-%m-%d")) # 2026-10-07
print(ahora.strftime("%d/%m/%Y")) # 07/10/2026
print(ahora.strftime("%d de %B de %Y")) # 07 de octubre de 2026 (en inglés por defecto)
print(ahora.strftime("%H:%M:%S")) # 14:30:45
print(ahora.strftime("%Y-%m-%dT%H:%M:%S")) # 2026-10-07T14:30:45 (formato ISO 8601)
print(ahora.isoformat()) # 2026-10-07T14:30:45 (equivalente)
# Referencia de los códigos más importantes:
# %Y año con 4 dígitos (2026)
# %y año con 2 dígitos (26)
# %m mes con cero (10)
# %d día con cero (07)
# %B nombre del mes (October)
# %b nombre abreviado (Oct)
# %A nombre del día (Wednesday)
# %a nombre abreviado (Wed)
# %H hora 24h con cero (14)
# %I hora 12h con cero (02)
# %M minutos con cero (30)
# %S segundos con cero (45)
# %p AM / PM (PM)
# %Z nombre zona horaria (UTC, EST…)
# %j día del año (280)
# %W semana del año (40)
Nombres de mes y día en español
import locale
from datetime import datetime
# Cambiar el locale para obtener nombres en español
# En Linux/Mac:
locale.setlocale(locale.LC_TIME, "es_ES.UTF-8")
# En Windows:
# locale.setlocale(locale.LC_TIME, "Spanish_Spain.1252")
ahora = datetime(2026, 10, 7, 14, 30)
print(ahora.strftime("%A, %d de %B de %Y"))
# miércoles, 07 de octubre de 2026
# Alternativa sin depender del locale del sistema:
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_en_espanol(dt: datetime) -> str:
return f"{DIAS[dt.weekday()]}, {dt.day} de {MESES[dt.month]} de {dt.year}"
print(fecha_en_espanol(ahora))
# miércoles, 7 de octubre de 2026
Parsear fechas: strptime
strptime convierte un string en un objeto datetime. Necesitas especificar el formato exactamente como aparece en el string.
from datetime import datetime
# strptime(string, formato) → datetime
dt = datetime.strptime("2026-10-07", "%Y-%m-%d")
print(dt) # 2026-10-07 00:00:00
dt = datetime.strptime("07/10/2026 14:30", "%d/%m/%Y %H:%M")
print(dt) # 2026-10-07 14:30:00
# ISO 8601 (el más habitual en APIs):
dt = datetime.fromisoformat("2026-10-07T14:30:00")
print(dt) # 2026-10-07 14:30:00
# Con zona horaria en el string (Python 3.11+):
dt = datetime.fromisoformat("2026-10-07T14:30:00+05:00")
print(dt) # 2026-10-07 14:30:00+05:00
# Error habitual: el formato no coincide con el string
try:
datetime.strptime("7/10/2026", "%d/%m/%Y") # OK: 7 sin cero funciona
datetime.strptime("10-07-2026", "%Y-%m-%d") # Error: año no es 10
except ValueError as e:
print(f"Error de formato: {e}")
Parsear formatos desconocidos con dateutil
# pip install python-dateutil
# dateutil.parser.parse detecta el formato automáticamente
from dateutil import parser
fechas = [
"October 7, 2026",
"7 Oct 2026",
"2026-10-07",
"07/10/2026",
"Wed, 07 Oct 2026 14:30:00 GMT",
"2026-10-07T14:30:00Z",
]
for texto in fechas:
dt = parser.parse(texto)
print(f"{texto!r:40} → {dt}")
# Útil cuando los datos vienen de fuentes externas con formatos variados
# Advertencia: en formatos ambiguos como "01/02/03",
# dateutil asume MM/DD/YY (comportamiento americano)
# Usa dayfirst=True para forzar DD/MM/YY
dt = parser.parse("01/02/2026", dayfirst=True) # 1 de febrero
dt = parser.parse("01/02/2026", dayfirst=False) # 2 de enero (por defecto)
Aritmética con fechas
from datetime import datetime, date, timedelta
hoy = date.today()
ahora = datetime.now()
# Sumar y restar timedelta
manana = hoy + timedelta(days=1)
ayer = hoy - timedelta(days=1)
hace_una_hora = ahora - timedelta(hours=1)
en_dos_semanas = hoy + timedelta(weeks=2)
en_90_dias = hoy + timedelta(days=90)
print(manana) # 2026-10-08
print(en_dos_semanas) # 2026-10-21
# Diferencia entre dos fechas: resultado es timedelta
fecha1 = date(2026, 1, 1)
fecha2 = date(2026, 10, 7)
diferencia = fecha2 - fecha1
print(diferencia.days) # 279 días
# Diferencia entre dos datetime
dt1 = datetime(2026, 10, 7, 9, 0, 0)
dt2 = datetime(2026, 10, 7, 14, 30, 0)
delta = dt2 - dt1
print(delta) # 5:30:00
print(delta.total_seconds()) # 19800.0
print(delta.seconds // 3600) # 5 horas
print((delta.seconds % 3600) // 60) # 30 minutos
# Comparar fechas
print(fecha2 > fecha1) # True
print(dt1 == dt2) # False
Sumar meses y años con dateutil
# timedelta no soporta sumar meses directamente (los meses tienen distinta duración)
# dateutil.relativedelta sí lo hace correctamente
from dateutil.relativedelta import relativedelta
from datetime import date
hoy = date(2026, 10, 7)
en_un_mes = hoy + relativedelta(months=1) # 2026-11-07
en_un_anio = hoy + relativedelta(years=1) # 2027-10-07
hace_3_meses = hoy - relativedelta(months=3) # 2026-07-07
# Caso especial: fin de mes
fin_enero = date(2026, 1, 31) + relativedelta(months=1)
print(fin_enero) # 2026-02-28 (ajusta al último día del mes)
# Diferencia en meses entre dos fechas
fecha_inicio = date(2025, 3, 15)
fecha_fin = date(2026, 10, 7)
delta = relativedelta(fecha_fin, fecha_inicio)
print(f"{delta.years} años, {delta.months} meses, {delta.days} días")
# 1 años, 6 meses, 22 días
Zonas horarias
Un datetime sin zona horaria se llama naive (ingenuo). Un datetime con zona horaria se llama aware (consciente). Mezclar los dos tipos produce errores. En aplicaciones que manejan usuarios en distintos países, siempre trabaja con datetimes aware.
from datetime import datetime, timezone
from zoneinfo import ZoneInfo # incluido desde Python 3.9
# Datetime naive (sin zona horaria) — evitar en producción
naive = datetime.now()
print(naive.tzinfo) # None
# Datetime aware en UTC
utc_ahora = datetime.now(timezone.utc)
print(utc_ahora) # 2026-10-07 19:30:00+00:00
print(utc_ahora.tzinfo) # UTC
# Datetime aware en una zona horaria específica
bogota = ZoneInfo("America/Bogota")
madrid = ZoneInfo("Europe/Madrid")
tokio = ZoneInfo("Asia/Tokyo")
ahora_bogota = datetime.now(bogota)
ahora_madrid = datetime.now(madrid)
print(ahora_bogota) # 2026-10-07 14:30:00-05:00
print(ahora_madrid) # 2026-10-07 21:30:00+02:00
# Convertir entre zonas horarias
ahora_utc = datetime.now(timezone.utc)
en_bogota = ahora_utc.astimezone(bogota)
en_tokio = ahora_utc.astimezone(tokio)
print(en_bogota) # 2026-10-07 14:30:00-05:00
print(en_tokio) # 2026-10-08 04:30:00+09:00
# Es el mismo instante, representado en distintas zonas horarias
# Buenas prácticas con zonas horarias
# 1. Guardar siempre en UTC en la base de datos
def guardar_evento(nombre: str, cuando: datetime) -> dict:
# Convertir a UTC antes de guardar
cuando_utc = cuando.astimezone(timezone.utc)
return {
"nombre": nombre,
"timestamp": cuando_utc.isoformat(), # "2026-10-07T19:30:00+00:00"
}
# 2. Mostrar al usuario en su zona horaria
def mostrar_al_usuario(timestamp_utc: str, zona_usuario: str) -> str:
dt_utc = datetime.fromisoformat(timestamp_utc)
zona = ZoneInfo(zona_usuario)
dt_local = dt_utc.astimezone(zona)
return dt_local.strftime("%d/%m/%Y %H:%M")
hora_mostrar = mostrar_al_usuario("2026-10-07T19:30:00+00:00", "America/Bogota")
print(hora_mostrar) # 07/10/2026 14:30
# 3. Nunca comparar naive con aware
naive = datetime(2026, 10, 7, 14, 30)
aware = datetime(2026, 10, 7, 14, 30, tzinfo=timezone.utc)
try:
resultado = naive < aware # TypeError
except TypeError as e:
print(f"Error: {e}")
# can't compare offset-naive and offset-aware datetimes
Casos prácticos habituales
Calcular la edad de una persona
from datetime import date
from dateutil.relativedelta import relativedelta
def calcular_edad(fecha_nacimiento: date) -> int:
hoy = date.today()
delta = relativedelta(hoy, fecha_nacimiento)
return delta.years
def dias_para_cumpleanios(fecha_nacimiento: date) -> int:
hoy = date.today()
proximo = fecha_nacimiento.replace(year=hoy.year)
if proximo < hoy:
proximo = proximo.replace(year=hoy.year + 1)
return (proximo - hoy).days
nacimiento = date(1990, 10, 7)
print(calcular_edad(nacimiento)) # 36
print(dias_para_cumpleanios(nacimiento)) # 0 (hoy es su cumpleaños)
Primer y último día del mes
import calendar
from datetime import date
def primer_dia_mes(anio: int, mes: int) -> date:
return date(anio, mes, 1)
def ultimo_dia_mes(anio: int, mes: int) -> date:
ultimo = calendar.monthrange(anio, mes)[1] # (día_semana_inicio, total_días)
return date(anio, mes, ultimo)
def dias_del_mes_actual():
hoy = date.today()
return (
primer_dia_mes(hoy.year, hoy.month),
ultimo_dia_mes(hoy.year, hoy.month)
)
inicio, fin = dias_del_mes_actual()
print(inicio) # 2026-10-01
print(fin) # 2026-10-31
Medir el tiempo de ejecución de un bloque de código
import time
from datetime import datetime
# Con time.perf_counter (más preciso para medir rendimiento)
inicio = time.perf_counter()
# ... código que quieres medir ...
import math
resultado = sum(math.sqrt(i) for i in range(1_000_000))
fin = time.perf_counter()
print(f"Tiempo: {fin - inicio:.4f} segundos") # Tiempo: 0.0821 segundos
# Con contextmanager reutilizable
from contextlib import contextmanager
@contextmanager
def medir_tiempo(etiqueta: str = ""):
inicio = time.perf_counter()
yield
fin = time.perf_counter()
print(f"{etiqueta}: {(fin - inicio) * 1000:.2f} ms")
with medir_tiempo("consulta SQL"):
time.sleep(0.05) # simular consulta
# consulta SQL: 50.12 ms
Agrupar registros por fecha (logs, ventas, etc.)
from datetime import datetime
from collections import defaultdict
registros = [
{"fecha": "2026-10-07T09:15:00", "importe": 120.0},
{"fecha": "2026-10-07T14:30:00", "importe": 85.5},
{"fecha": "2026-10-08T10:00:00", "importe": 200.0},
{"fecha": "2026-10-08T16:45:00", "importe": 45.0},
{"fecha": "2026-10-09T11:00:00", "importe": 300.0},
]
ventas_por_dia = defaultdict(float)
for r in registros:
dt = datetime.fromisoformat(r["fecha"])
clave = dt.date() # solo la parte de fecha
ventas_por_dia[clave] += r["importe"]
for dia, total in sorted(ventas_por_dia.items()):
print(f"{dia}: {total:.2f} €")
# 2026-10-07: 205.50 €
# 2026-10-08: 245.00 €
# 2026-10-09: 300.00 €
Resumen
- El módulo
datetimeincluye cuatro tipos principales:date(solo fecha),time(solo hora),datetime(fecha + hora) ytimedelta(duración). Para la mayoría de operaciones cotidianas no necesitas instalar nada. strftimeconvierte un datetime en string usando códigos de formato (%Y,%m,%d…).strptimehace el proceso inverso: convierte un string en datetime dado su formato. Para ISO 8601, usa directamentedatetime.fromisoformat().- La aritmética con fechas usa
timedeltapara días, horas, minutos y segundos. Para sumar meses o años de forma correcta (respetando meses de distinta duración), usadateutil.relativedelta. - Distingue entre datetimes naive (sin zona horaria) y aware (con zona horaria). En aplicaciones con usuarios en distintos países, guarda siempre en UTC y convierte a la zona del usuario solo para mostrar. El módulo
zoneinfo(Python 3.9+) gestiona zonas horarias sin instalar nada extra. dateutil.parser.parsedetecta automáticamente el formato de un string de fecha, lo que es útil cuando los datos vienen de fuentes externas con formatos inconsistentes.
No hay comentarios todavía. Sé el primero en compartir tu opinión.