Dominar el tiempo con la biblioteca estándar de Python: datetime al máximo

Serie 03 – Operaciones de fecha/hora, zonas horarias y conversiones de formato en un solo lugar

El tiempo no es simplemente una "cadena de texto". Los días cambian, los meses se rotan, el horario de verano entra en juego y cada región tiene su propio estándar. Por eso, al trabajar con fechas y horas es crucial distinguir entre la representación textual y el valor numérico que se puede calcular, y también incluir la zona horaria en el manejo.

El laboratorio mágico de un relojero

En este artículo, centramos la explicación en el módulo datetime y cubrimos:

  • Crear la fecha y hora actuales
  • Calcular periodos (timedelta)
  • Formatear y analizar cadenas (strftime, strptime)
  • Gestionar zonas horarias (timezone, zoneinfo)
  • Puntos de atención frecuentes y patrones confiables

1. ¿Qué ofrece datetime?

El módulo datetime incluye varios tipos que, aunque parecidos, cumplen roles distintos.

  • date : cuando solo necesitas año‑mes‑día
  • time : cuando solo necesitas hora:minuto:segundo
  • datetime : fecha + hora (el más usado)
  • timedelta : la diferencia (periodo) entre dos instantes
  • timezone : zona horaria con desplazamiento fijo (ej.: UTC+9)

A partir de Python 3.9, la biblioteca estándar incorpora zoneinfo, lo que facilita el manejo de zonas horarias locales (ej.: Asia/Tokyo).


2. Obtener el "ahora": desde naive hasta aware

2.1 ¿Qué son naive y aware?

Los objetos datetime se clasifican en dos grupos.

  • naive datetime: sin información de zona horaria
  • aware datetime: con información de zona horaria (tzinfo)

Mezclar ambos en cálculos o comparaciones puede provocar errores o resultados inesperados.

2.2 Valor por defecto recomendado: comenzar con UTC aware

Unificar la base de cálculo y almacenamiento en UTC suele simplificar mucho las cosas.

from datetime import datetime, timezone

utc_now = datetime.now(timezone.utc)  # aware (UTC)
print(utc_now)

Si necesitas la hora local, conviértela en la fase de presentación.

from datetime import datetime, timezone
from zoneinfo import ZoneInfo

utc_now = datetime.now(timezone.utc)
tokyo_now = utc_now.astimezone(ZoneInfo("Asia/Tokyo"))

print(utc_now)
print(tokyo_now)

3. Cálculo de fechas/hora: el papel central de timedelta

3.1 Sumar y restar

from datetime import datetime, timedelta, timezone

now = datetime.now(timezone.utc)
print(now + timedelta(days=3))
print(now - timedelta(hours=2))

3.2 Diferencia entre dos instantes

from datetime import datetime, timezone

a = datetime(2026, 1, 1, tzinfo=timezone.utc)
b = datetime(2026, 1, 10, tzinfo=timezone.utc)

delta = b - a
print(delta.days)            # 9
print(delta.total_seconds()) # 777600.0

3.3 Sensibilidad al usar timedelta para "periodos"

timedelta opera en días, segundos y microsegundos. Expresiones como "un mes después" se aproximan con timedelta(days=30), pero para el calendario real (el mismo día del mes siguiente) se necesita otra aproximación, normalmente con dateutil u otras librerías externas.


4. Formato y análisis: strftime / strptime

4.1 datetime → cadena (strftime)

from datetime import datetime, timezone

dt = datetime(2026, 1, 30, 14, 5, 0, tzinfo=timezone.utc)
print(dt.strftime("%Y-%m-%d %H:%M:%S %z"))

Patrones comunes:

  • %Y-%m-%d : 2026-01-30
  • %H:%M:%S : 14:05:00
  • %z : +0000 (desplazamiento UTC)

4.2 Cadena → datetime (strptime)

from datetime import datetime

s = "2026-01-30 14:05:00"
dt = datetime.strptime(s, "%Y-%m-%d %H:%M:%S")
print(dt)

El dt resultante es naive. Si quieres fijar la zona horaria, añade tzinfo.

from datetime import datetime, timezone

s = "2026-01-30 14:05:00"
dt = datetime.strptime(s, "%Y-%m-%d %H:%M:%S").replace(tzinfo=timezone.utc)
print(dt)

replace(tzinfo=...) no convierte el valor de tiempo, solo le etiqueta la zona horaria. Para convertir, usa astimezone() (ver siguiente sección).


5. Zonas horarias: diferenciar timezone y zoneinfo

5.1 Desplazamiento fijo → timezone

Ej.: UTC, UTC+9 con desplazamiento constante.

from datetime import datetime, timezone, timedelta

kst_fixed = timezone(timedelta(hours=9))
dt = datetime(2026, 1, 30, 12, 0, tzinfo=kst_fixed)
print(dt)

5.2 Zona horaria local → zoneinfo

Para zonas con horario de verano, zoneinfo es la opción adecuada.

from datetime import datetime, timezone
from zoneinfo import ZoneInfo

utc_now = datetime.now(timezone.utc)
ny_now = utc_now.astimezone(ZoneInfo("America/New_York"))
print(ny_now)

5.3 Diferencia entre replace(tzinfo=...) y astimezone(...)

  • replace(tzinfo=...): mantiene el valor de tiempo, solo cambia la etiqueta de zona
  • astimezone(...): convierte el mismo instante a la hora local de otra zona

Conocer esta distinción reduce muchos errores de zona horaria.


6. Puntos de atención frecuentes

6.1 Mezcla de naive/aware

  • Para cálculos internos, usar aware (UTC) es más seguro.
  • Para entradas externas, normaliza la zona horaria al momento de recibirla.

6.2 "Hora local" varía según el entorno

datetime.now() sigue la configuración local del entorno de ejecución. En contenedores o servidores, la hora local puede ser UTC o no. Usar datetime.now(timezone.utc) evita ambigüedades.

6.3 Inconsistencias en el análisis de cadenas

strptime falla si el formato difiere en un solo carácter. Si se reciben múltiples formatos, se necesita pre‑procesar o probar secuencialmente. Lo ideal es restringir el formato de entrada.


7. Tres patrones comunes

7.1 Guardar en formato ISO 8601

Para una representación estándar, isoformat() es útil.

from datetime import datetime, timezone

dt = datetime.now(timezone.utc)
print(dt.isoformat())  # ej.: 2026-01-30T05:12:34.567890+00:00

7.2 Nombrar archivos con la fecha de hoy

from datetime import datetime

stamp = datetime.now().strftime("%Y%m%d")
filename = f"report_{stamp}.json"
print(filename)

7.3 Calcular tiempo restante hasta una fecha objetivo

from datetime import datetime, timezone

target = datetime(2026, 2, 1, 0, 0, tzinfo=timezone.utc)
now = datetime.now(timezone.utc)

remaining = target - now
print(remaining)
print(remaining.total_seconds())

8. Conclusión

datetime va más allá de crear cadenas de fecha; convierte el tiempo en un valor que se puede manipular aritméticamente. Incluir zonas horarias (timezone, zoneinfo) garantiza que el código sea estable, independientemente del entorno de ejecución.

En la próxima entrega, abordaremos el módulo random por separado, cubriendo generación de números aleatorios, muestreo, mezcla, semillas y la generación segura con secrets.


Enlaces relacionados:


Ver series anteriores